当前位置: 首页 > news >正文

RabbitMq的基础及springAmqp的使用

RabbitMq

官网:RabbitMQ: One broker to queue them all | RabbitMQ

什么是MQ?

mq就是消息队列,消息队列遵循这先入先出原则。一般用来解决应用解耦,异步消息,流量削峰等问题,实现高性能,高可用,可伸缩和最终一致性架构。

rabbitMq的四大核心

image20230424154420240.png

RabbitMq的安装

RabbitMQ是一个开源的遵循 AMQP协议实现的基于 Erlang语言编写,即需要先安装部署Erlang环境再安装RabbitMQ环境。

查看兼容关系:Erlang Version Requirements | RabbitMQ

百度云地址:

链接:百度网盘 请输入提取码 提取码:6666

本篇文章使用版本:3.8.8,liunx7-cenOs7

#在存放位置执行以下指令rpm -ivh erlang-21.3-1.el7.x86_64.rpm#安装socat yum install socat -y#安装mqrpm -ivh rabbitmq-server-3.8.8-1.el7.noarch.rpm 

启动

#开机自动启动chkconfig rabbitmq-server on
#启动服务
/sbin/service rabbitmq-serve start
#查看启动
/sbin/service rabbitmq-serve status
#停止服务
/sbin/service rabbitmq-serve stop

image20230424154409259.png

坑:执行以上指令无效,重新执行下面指令

systemctl start rabbitmq-server.service #启动
systemctl status rabbitmq-server.service#查看状态

安装可视化界面

#尽量停止服务,在安装
#安装可视化界面
rabbitmq-plugins enable rabbitmq_management

访问地址:http://ip:15672/

如果访问不了,查看防火墙是够关闭 systemctl stop firewalld 关闭防火墙,访问成功后走rabbitmq的基本指令

卸载MQ:

systemctl stop rabbitmq-server
yum list | grep rabbitmq
yum -y remove rabbitmq-server.noarch
yum list | grep erlang
yum -y remove erlang-*
rm -rf /usr/lib64/erlang 
rm -rf /var/lib/rabbitmq
rm -rf /usr/local/erlang
rm -rf /usr/local/rabbitmq

docker安装

docker pull rabbitmq:3-management
#运行
docker run \-e RABBITMQ_DEFAULT_USER=itcast \-e RABBITMQ_DEFAULT_PASS=123321 \--name mq \--hostname mq1 \-p 15672:15672 \   #网页访问端口-p 5672:5672 \     #mq连接端口-d \rabbitmq:3-management

rabbitMq基本指令

#查看用户
rabbitmqctl list_users
#添加用户
rabbitmqctl add_user admin 123456
#设置角色 (超级管理员)rabbitmqctl set_user_tags admin administrator
#设置权限 
rabbitmqctl set_permissions -p '/' admin '.*' '.*' '.*'

image20230424160438952.png

登录后也可以在此界面添加用户

image20230424160714982.png

对接java(入门)

image20230506093856370.png

创建一个maven工程:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"><modelVersion>4.0.0</modelVersion><groupId>com.chen</groupId><artifactId>mq</artifactId><version>1.0-SNAPSHOT</version><properties><rabbitmq.version>5.8.0</rabbitmq.version><common.version>2.6</common.version></properties><dependencies>
<!--         https://mvnrepository.com/artifact/com.rabbitmq/amqp-client --><dependency><groupId>com.rabbitmq</groupId><artifactId>amqp-client</artifactId><version>${rabbitmq.version}</version></dependency><dependency><groupId>commons-io</groupId><artifactId>commons-io</artifactId><version>${common.version}</version></dependency></dependencies><build><plugins><plugin><groupId>org.apache.maven.plugins</groupId><artifactId>maven-compiler-plugin</artifactId><configuration><source>8</source><target>8</target></configuration></plugin></plugins></build>
</project>

生产者:

package com.chen.rabbitmq.one;import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;/*** 生产者*/
public class production {private static final String MQ_KEY="holle";public static void main(String[] args)throws Exception {
//        创建rabbitmq的工厂ConnectionFactory factory = new ConnectionFactory();
//    连接地址ipfactory.setHost("172.17.18.162");
//        用户名factory.setUsername("admin");
//        密码factory.setPassword("123456");//        创建连接Connection connection = factory.newConnection();
//        创建通道Channel channel = connection.createChannel();
//   生产队列
//   参数一:队列名称
//   参数二:持久性(默认为false)
//   参数三:该队列是否可以有多个消费者,是否消息共享
//   参数四:是否自动删除
//   参数五:其他参数channel.queueDeclare(MQ_KEY,true,false,false,null);/*** 发送一个消费者* 1.发送到那个交换机* 2.路由的key值是哪个 本次是队列的名称* 3.其他参数* 4.发送消息的消息体*/channel.basicPublish("",MQ_KEY,null,"holle word".getBytes());System.out.println("消息发送成功!");}}

测试是否发送成功:

image20230506094013364.png

消费者:

package com.chen.rabbitmq.one;import com.rabbitmq.client.*;import java.io.IOException;public class Consumption {private static final String MQ_KEY="holle";public static void main(String[] args) throws Exception {ConnectionFactory factory = new ConnectionFactory();factory.setHost("172.17.18.162");factory.setUsername("admin");factory.setPassword("123456");
//        创建一个新的连接Connection connection = factory.newConnection();Channel channel = connection.createChannel();/*参数:1: 消费哪个队列2.消费成功之后是否要自动应答, true 带边自动应答 false 手动3.消费者未成功的回调4.消费者取录成功的回调*/channel.basicConsume(MQ_KEY, true,(DeliverCallback) (consumerTag, message) -> System.out.println(new String(message.getBody())),(CancelCallback) (consumerTag)-> System.out.println(consumerTag));}}

工作队列:

工作队列(又称任务队列)的主要思想是避免立即执行资源密集型任务,而不得不等待它完成。相反我们安排任务在之后执行。我们把任务封装为消息并将其发送到队列。在后台运行的工作进程将弹出任务并最终执行作业。当有多个工作线程时,这些工作线程将一起处理这些任务。

image20230506103939702.png

1.线程轮询

类似nginx的负载均衡(轮询),线1一次,线2一次。

image20230506111137320.png

工具类:

package com.chen.rabbitmq.tow.utils;import com.rabbitmq.client.Channel;
import com.rabbitmq.client.Connection;
import com.rabbitmq.client.ConnectionFactory;public class RabbitUtils {public static Channel rabbitConnection() throws Exception{ConnectionFactory factory = new ConnectionFactory();factory.setHost("172.17.18.162");factory.setUsername("admin");factory.setPassword("123456");
//        创建一个新的连接Connection connection = factory.newConnection();Channel channel = connection.createChannel();return channel;}
}
生产者:package com.chen.rabbitmq.tow.test;import com.chen.rabbitmq.tow.utils.RabbitUtils;
import com.rabbitmq.client.Channel;import java.util.Scanner;public class Production {private final static String MQ_KEY="word";
//    生产者public static void production() throws Exception{Channel channel = RabbitUtils.rabbitConnection();Scanner scanner = new Scanner(System.in);//生产队列channel.queueDeclare(MQ_KEY,true,false,false,null);while (scanner.hasNext()){String next = scanner.next();channel.basicPublish("",MQ_KEY,null,next.getBytes());System.out.println("消息发布成功-> "+next);}}public static void main(String[] args) throws Exception{production();}
}
消费者:package com.chen.rabbitmq.tow.test;import com.chen.rabbitmq.tow.utils.RabbitUtils;
import com.rabbitmq.client.CancelCallback;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;public class Consumption {private final static String MQ_KEY="word";//    消费者public static void consumption() throws Exception{
//        获取连接队列Channel channel = RabbitUtils.rabbitConnection();channel.basicConsume(MQ_KEY,true,(DeliverCallback)(consumerTag,message)->{System.out.println(new String(message.getBody()));},(CancelCallback)(tag)->{System.out.println(tag);System.out.println("中断了");});}public static void main(String[] args) throws Exception{consumption();}
}

image20230506111456297.png

idea开启两个线程。

消息应答

1.自动应答

RabbitMQ 是一个广泛使用的开源消息代理,它支持多种消息协议,例如 AMQP、MQTT、STOMP 等。在 RabbitMQ 中,自动应答(Automatic Acknowledgement,Auto-ack)是一种消息确认机制,用于标记消息是否已被成功接收和处理。了解自动应答的概念,对于构建可靠、高效的消息传递系统非常重要。

当消费者接收并处理来自 RabbitMQ 的消息时,通常会使用消息确认(acknowledgements)机制来告知 RabbitMQ 该消息已经成功处理。这样一来,RabbitMQ 就可以确保消息不会意外丢失。然而,这种确认过程可能会导致一定的延迟和额外开销。为了解决这个问题,RabbitMQ 提供了自动应答机制。

在自动应答模式下,消费者接收到消息后,RabbitMQ 会立即将该消息标记为已处理。这意味着消费者不需要显式地发送确认(ack)消息给 RabbitMQ。这种机制可以降低延迟,提高消息传递的速度,但是也存在一定的风险。因为消息一旦被发送出去,RabbitMQ 就认为它已经成功处理,而实际上消费者可能还没有完成对消息的处理。如果消费者在处理消息时发生故障,那么这个消息可能会丢失。

2.手动应答

方法:

Channel.basicAck (用于肯定确认)

RabbitMQ已知道该消息并且成功的处理消息,可以将其丢弃了

Channel.basicNack(用于否定确认)

Channel.basicReject (用于否定确认)

Channel.basicNack 相比少一个参数 不处理该消息了直接拒绝,可以将其丢弃了

Multiple

//源码
public void basicAck(long deliveryTag, boolean multiple) throws IOException {this.delegate.basicAck(deliveryTag, multiple);
}

multiple 的 true 和 false 代表不同意思:

  1. true 代表批量应答 channel 上未应答的消息

    比如说 channel 上有传送 tag 的消息 5,6,7,8 当前 tag 是 8 那么此时5-8 的这些还未应答的消息都会被确认收到消息应答

    image20230506145530499.png

    2.false 只会应答 tag=8 的消息 5,6,7 这三个消息依然不会被确认收到消息应答

    image20230506145536088.png

消息重新入队

为了解决消息丢失问题。

image20230506150713801.png

具体代码:

生产者:

package com.chen.rabbitmq.three;
import com.chen.rabbitmq.tow.utils.RabbitUtils;
import com.rabbitmq.client.Channel;
import java.nio.charset.StandardCharsets;
import java.util.Scanner;
public class Pro {private static final String MQ_KEY="mqkey";public static void pro() throws Exception{Channel channel = RabbitUtils.rabbitConnection();channel.queueDeclare(MQ_KEY,true,false,false,null);Scanner scanner = new Scanner(System.in);while (scanner.hasNext()){String next = scanner.next();channel.basicPublish("",MQ_KEY,null,scanner.next().getBytes());System.out.println("消息发布成功-> "+next);}}public static void main(String[] args) throws Exception {pro();}
}

消费者1:

package com.chen.rabbitmq.three;import com.chen.rabbitmq.tow.utils.RabbitUtils;
import com.rabbitmq.client.CancelCallback;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;//消费者1
public class Word1 {public static final String MQ_KEY="mqkey";public static void word() throws Exception{Channel channel = RabbitUtils.rabbitConnection();channel.basicConsume(MQ_KEY,false,(DeliverCallback) (consumerTag,message)->{
//             睡眠1stry {Thread.sleep(1*1000);System.out.println("Word1接收到消息->"+new String(message.getBody()));
//                 参数一:tag标记  参数二:是否批量channel.basicAck(message.getEnvelope().getDeliveryTag(),false);} catch (InterruptedException e) {e.printStackTrace();}},(CancelCallback) e->{System.out.println("消息中断"+e);} );}public static void main(String[] args) throws Exception {word();}}

消费者2:

package com.chen.rabbitmq.three;import com.chen.rabbitmq.tow.utils.RabbitUtils;
import com.rabbitmq.client.CancelCallback;
import com.rabbitmq.client.Channel;
import com.rabbitmq.client.DeliverCallback;//消费者1
public class Word2 {public static final String MQ_KEY="mqkey";public static void word() throws Exception{Channel channel = RabbitUtils.rabbitConnection();channel.basicConsume(MQ_KEY,false,(DeliverCallback) (consumerTag,message)->{
//             睡眠10stry {Thread.sleep(10*1000);System.out.println("Word2接收到消息->"+new String(message.getBody()));
//                 参数一:tag标记  参数二:是否批量channel.basicAck(message.getEnvelope().getDeliveryTag(),false);} catch (InterruptedException e) {e.printStackTrace();}},(CancelCallback) e->{System.out.println("消息中断"+e);} );}public static void main(String[] args) throws Exception{word();}}

image20230506160401052.png

经测试会发现,消费者1为第一个接收到消息,接下来当生产者在生产出一条消息,应到消费者2接收到消息,但是此时消费者2突然出现宕机,使用了应答机制,消息则会重新打到消费者1;

持久化设置

1.队列持久化

作用:当rabbitmq宕机后,重启队列依然存在

//创建队列时的第二个参数为设置持久化 
channel.queueDeclare(MQ_KEY,true,false,false,null);

image20230506161649268.png

2.消息持久化

作用:当rabbitmq宕机了重新启动,发送的消息依然存在。

下面的方法不是绝对的能保证消息的持久化

//生产者  private static final String MQ_KEY="mqkey";public static void pro() throws Exception{Channel channel = RabbitUtils.rabbitConnection();channel.queueDeclare(MQ_KEY,true,false,false,null);Scanner scanner = new Scanner(System.in);while (scanner.hasNext()){String next = scanner.next();
//MessageProperties.PERSISTENT_TEXT_PLAIN 消息持久化channel.basicPublish("",MQ_KEY,MessageProperties.PERSISTENT_TEXT_PLAIN,scanner.next().getBytes());System.out.println("消息发布成功-> "+next);}}

3.发布确认

完成以上两步还不足以持久化,要把发布确认加上。

//默认是不开启的
Channel channel = RabbitUtils.rabbitConnection();
channel.confirmSelect();//开启发布确认
发布确认的策略:

1.单个确认发布

这个发布确认是同步的,需等待确认一次在发布下一次,一手交钱一手交货原则

缺点:发布速度特别慢

//单个确认public static void one() throws Exception{Channel channel = RabbitUtils.rabbitConnection();//开启发布确认channel.confirmSelect();String uuid = UUID.randomUUID().toString();//创建队列channel.queueDeclare(uuid,true,false,false,null);//开始时间long begin = System.currentTimeMillis();for (Integer i = 0; i < COUNT; i++) {String message = i + "";channel.basicPublish("",uuid,null,message.getBytes());//发布确认boolean flag = channel.waitForConfirms();if(flag){System.out.println("消息确认成功!");}}long last = System.currentTimeMillis();System.out.println("耗时:"+(last-begin));}

2.批量确认发布

发布速度相对单个发布确认要快,但是当其中一条消息出现异常,将无法查找到那个消息丢失 。

 //批量public static void batch() throws Exception{Channel channel = RabbitUtils.rabbitConnection();String uuid = UUID.randomUUID().toString();//开启消息确认channel.confirmSelect();//创建队列channel.queueDeclare(uuid,true,false,false,null);//这个这个变量用记录发布值Integer messageCount=100;Integer record =0;//开始时间long begin = System.currentTimeMillis();for (Integer i = 0; i < COUNT; i++) {record++;String message=i+"";//发布消息channel.basicPublish("",uuid,null,message.getBytes());if(messageCount.equals(record)){channel.waitForConfirms();record=0;}}long last = System.currentTimeMillis();System.out.println("耗时"+(last-begin));}

3.异步确认发布(推荐使用)

异步确认虽然比上的两个代码复杂,但同时也解决了上面两种方式遗留下来的问题。

image20230511104153138.png

public static void asyn() throws Exception{Channel channel = RabbitUtils.rabbitConnection();//开启发布确认channel.confirmSelect();String uuid = UUID.randomUUID().toString();//创建队列channel.queueDeclare(uuid,true,false,false,null);//开始时间long begin = System.currentTimeMillis();//        创建一个线程的ListMap用于记录 ----》处理异步未确认的消息ConcurrentSkipListMap<Long, String> map = new ConcurrentSkipListMap<>();//        监听消息channel.addConfirmListener((deliveryTag, multiple)->{if(multiple){ConcurrentNavigableMap<Long, String> longStringConcurrentNavigableMap =map.headMap(deliveryTag);longStringConcurrentNavigableMap.clear();}else{map.remove(deliveryTag);}System.out.println("确认消息:"+deliveryTag);},(deliveryTag, multiple)->{String message = map.get(deliveryTag);System.out.println("发送失败的数据是:"+message+"未确认消息:"+deliveryTag+"-----失败");});for (Integer i = 0; i < COUNT; i++) {String message=""+i;channel.basicPublish("",uuid,null,message.getBytes());//获取信道的标识,存入消息map.put(channel.getNextPublishSeqNo(),message);}long last = System.currentTimeMillis();System.out.println("耗时:"+(last-begin));}

不公平分发原则(能者多劳原则)

在上面中的所有例子都是尊寻这轮询的规则去执行的,问题:当其中的一台服务响应特别慢时就会影响到整体的效率。

channel.basicQos(1);

//消费者
public static void word() throws Exception{Channel channel = RabbitUtils.rabbitConnection();//设置不公平分发channel.basicQos(1);channel.basicConsume(MQ_KEY,false,(DeliverCallback) (consumerTag,message)->{try {//模拟虚拟机延迟Thread.sleep(1*1000);System.out.println("Word2接收到消息->"+new String(message.getBody()));channel.basicAck(message.getEnvelope().getDeliveryTag(),false);} catch (InterruptedException e) {e.printStackTrace();}},(CancelCallback) e->{System.out.println("消息中断"+e);} );
}

也可以用来设置预期值!

//消费者1
public static void word2() throws Exception{Channel channel = RabbitUtils.rabbitConnection();//设置预期值channel.basicQos(3);channel.basicConsume(MQ_KEY,false,(DeliverCallback) (consumerTag,message)->{try {//模拟虚拟机延迟Thread.sleep(1*1000);System.out.println("Word2接收到消息->"+new String(message.getBody()));channel.basicAck(message.getEnvelope().getDeliveryTag(),false);} catch (InterruptedException e) {e.printStackTrace();}},(CancelCallback) e->{System.out.println("消息中断"+e);} );
}
//消费者2
public static void word2() throws Exception{Channel channel = RabbitUtils.rabbitConnection();//设置预期值channel.basicQos(5);  channel.basicConsume(MQ_KEY,false,(DeliverCallback) (consumerTag,message)->{try {//模拟虚拟机延迟Thread.sleep(10*1000);System.out.println("Word2接收到消息->"+new String(message.getBody()));channel.basicAck(message.getEnvelope().getDeliveryTag(),false);} catch (InterruptedException e) {e.printStackTrace();}},(CancelCallback) e->{System.out.println("消息中断"+e);} );
}

交换机

在RabbitMQ中,生产者发送消息不会直接将消息投递到队列中,而是先将消息投递到交换机中, 在由交换机转发到具体的队列, 队列再将消息以推送或者拉取方式给消费者进行消费

绑定(bindings)

与交换机产生关系,并且能有routekey控制发送消息给哪个队列。

fanout交换机(扇形)

扇形交换机是最基本的交换机类型,它所能做的事清非常简单广播消息。扇形交换机会把能接收到的消息全部发送给绑定在自己身上的队列。因为广播不需要'思考”,所以扇形交换机处理消息的速度也是所有的交换机类型里面最快的。

//消费者
public class Word {
//    交换机名称private static String EXCHANGE_NAME="logs";public static void main(String[] args) throws Exception {Channel channel = RabbitUtils.rabbitConnection();
//        声明一个交换机channel.exchangeDeclare(EXCHANGE_NAME,"fanout");
//        声明一个队列 临时队列String queue = channel.queueDeclare().getQueue();
//        绑定交换机与队列channel.queueBind(queue,EXCHANGE_NAME,"");System.out.println("等待消息~");//消费者取消消息时回调接口channel.basicConsume(queue,true, (consumerTag,message)->{System.out.println("word1控制台打印接收消息:"+new String(message.getBody(),"UTF-8"));},cancelCallback->{});}
}public class Word2 {
//    交换机名称private static String EXCHANGE_NAME="logs";public static void main(String[] args) throws Exception {Channel channel = RabbitUtils.rabbitConnection();
//        声明一个交换机channel.exchangeDeclare(EXCHANGE_NAME,"fanout");
//        声明一个队列 临时队列String queue = channel.queueDeclare().getQueue();
//        绑定交换机与队列channel.queueBind(queue,EXCHANGE_NAME,"");System.out.println("等待消息~");
//消费者取消消息时回调接口channel.basicConsume(queue,true, (consumerTag,message)->{System.out.println("word2控制台打印接收消息:"+new String(message.getBody(),"UTF-8"));},cancelCallback->{});}
}
//生产者
public class send {//    交换机名称private static String EXCHANGE_NAME="logs";public static void main(String[] args) throws Exception {Channel channel = RabbitUtils.rabbitConnection();Scanner scanner = new Scanner(System.in);while (scanner.hasNext()){String next = scanner.next();channel.basicPublish(EXCHANGE_NAME,"",null,next.getBytes("UTF-8"));System.out.println("生产者发送消息:"+next);}}
}

直连交换机: Direct exchange

直连交换机的路由算法非常简单: 将消息推送到binding key与该消息的routing key相同的队列。

代码几乎类型fanout交换机,只需要指定routerkey即可。

主题交换机: Topic exchange

发送到主题交换机的 消息不能有任意的 routing key, 必须是由点号分开的一串单词,这些单词可以是任意的,但通常是与消息相关的一些特征。

如以下是几个有效的routing key:

"stock.usd.nyse", "nyse.vmw", "quick.orange.rabb 代", routing key的单词可以 有很多,最大限制是255 bytes。

Topic 交换机的 逻辑与 direct 交换机有点 相似 使用特定路由键发送的消息 将被发送到所有使用匹配绑定键绑定的队列 ,然而 ,绑定键有两个特殊的情况:

*表示匹配任意一个单词

#表示匹配任意—个或多个单词

image20230525104709697.png

比如上图:

发送routerkey为:ws.orange.rabbit那么对应的就是Q1,Q2

发送routerkey为:lazy.orange.elephant那么对应的就是Q1,Q2

//消费者
public class word1 {private static final String EXCHANGE_NAME="topic_logs";private static final String QUEUE_NAME="Q1";public static void main(String[] args) throws Exception {Channel channel = RabbitUtils.rabbitConnection();
//        创建交换机channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);
//       创建队列channel.queueDeclare(QUEUE_NAME,true,true,false,null);
//        绑定队列channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"*.orange.*");
//        接收消息channel.basicConsume(QUEUE_NAME,true,(consumerTag,message)->{System.out.println("接收到的消息:"+new String(message.getBody()));},cancelCallback->{});System.out.println("等下消息~");}
}
public class word2 {private static final String EXCHANGE_NAME="topic_logs";private static final String QUEUE_NAME="Q2";public static void main(String[] args) throws Exception {Channel channel = RabbitUtils.rabbitConnection();
//        创建交换机channel.exchangeDeclare(EXCHANGE_NAME, BuiltinExchangeType.TOPIC);
//       创建队列channel.queueDeclare(QUEUE_NAME,true,true,false,null);
//        绑定队列channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"*.*.rabbit");channel.queueBind(QUEUE_NAME,EXCHANGE_NAME,"lazy.#");
//        接收消息channel.basicConsume(QUEUE_NAME,true,(consumerTag,message)->{System.out.println("接收到的消息:"+new String(message.getBody()));},cancelCallback->{});System.out.println("等下消息~");}
}
//生产者
public class send {private static final String EXCHANGE_NAME="topic_logs";public static void main(String[] args) throws Exception{Channel channel = RabbitUtils.rabbitConnection();Scanner scanner = new Scanner(System.in);while (true){System.out.println("请输入routerkey:");String key = scanner.next();System.out.println("请输入消息内容:");String message = scanner.next();channel.basicPublish(EXCHANGE_NAME,key,null,message.getBytes());}}
}

image20230525112943356.png

image20230525112959575.png

死信队列

顾名思义:无法被消费的消息,一般来说,producer将消息投递broker或者直接到queue里了,consumer(消费者)从queue取出消息进行消费,但某些时间由特定原因导致queue中的某些消息无法被消费,这样如果没有后续的处理,就变成了死信。

应用场景:为了确保订单业务的消息数据不丢失,需要使用到RabbitMQ的死信队列机制,当消息被消息时发生了异常,这是就将消息存到死信中,还比如说:用户商城下单成功,并且点击支付后在指定时间支付时自动失效。

image20230525143950674.png

消息TTL过期时间测试:

//生产者
public class send {private static final String NORMAL_EXCHANGE="normal_exchange";public static final String NORMAL_QUEUE="normal_queue";public static void main(String[] args) throws Exception {Channel channel = RabbitUtils.rabbitConnection();
//        设置死信时间AMQP.BasicProperties basicProperties =new AMQP.BasicProperties().builder().expiration("10000").build();for (int i = 0; i < 11; i++) {String msg="info"+i;channel.basicPublish(NORMAL_EXCHANGE,"zhangsan",basicProperties,msg.getBytes());}}
}
//消费者1
public class C1 {private static final String NORMAL_EXCHANGE="normal_exchange";private static final String DEAD_EXCHANGE="dead_exchange";public static final String NORMAL_QUEUE="normal_queue";public static final String DEAD_QUEUE="dead_queue";public static void main(String[] args) throws Exception {Channel channel = RabbitUtils.rabbitConnection();
//      创建c1交换机channel.exchangeDeclare(NORMAL_EXCHANGE, BuiltinExchangeType.DIRECT);channel.exchangeDeclare(DEAD_EXCHANGE, BuiltinExchangeType.TOPIC);
//       声明普通队列HashMap<String, Object>  map = new HashMap<>();
//        设置过期时间 10s  单位ms 这里有消费整去做控制
//        map.put("x-message-ttl",100000);
//        正常队列设置死信交换机map.put("x-dead-letter-exchange",DEAD_EXCHANGE);
//       设置死信的routerKeymap.put("x-dead-letter-routing-key","lisi");//    创建普通队列channel.queueDeclare(NORMAL_QUEUE,false,false,false,map);//创建死信队列channel.queueDeclare(DEAD_QUEUE,false,false,false,null);//        绑定channel.queueBind(NORMAL_QUEUE,NORMAL_EXCHANGE,"zhangsan");channel.queueBind(DEAD_QUEUE,DEAD_EXCHANGE,"lisi");channel.basicConsume(NORMAL_QUEUE,true,(consumerTag, message) -> {System.out.println("C1消息为:"+message.getBody());},cancelCallback->{});}
}
//消费者2
public class C2 {public static final String DEAD_QUEUE="dead_queue";private static final String DEAD_EXCHANGE="dead_exchange";public static void main(String[] args) throws Exception {Channel channel = RabbitUtils.rabbitConnection();channel.basicConsume(DEAD_QUEUE,true,(consumerTag,message)->{System.out.println("消息为:"+new String(message.getBody()));},cancelCallback->{});}
}

正常队列长度的限制:

根据c1做修改,测试报错先删除原来的队列与交换机

//设置正常队列长度的限制
map.put("x-max-length",6);

拒接消息:

添加手动应答拒接。

public class C1 {private static final String NORMAL_EXCHANGE="normal_exchange";private static final String DEAD_EXCHANGE="dead_exchange";public static final String NORMAL_QUEUE="normal_queue";public static final String DEAD_QUEUE="dead_queue";public static void main(String[] args) throws Exception {Channel channel = RabbitUtils.rabbitConnection();
//      创建c1交换机channel.exchangeDeclare(NORMAL_EXCHANGE, BuiltinExchangeType.DIRECT);channel.exchangeDeclare(DEAD_EXCHANGE, BuiltinExchangeType.TOPIC);
//       声明普通队列HashMap<String, Object>  map = new HashMap<>();
//        设置过期时间 10s  单位ms
//        map.put("x-message-ttl",100000);
//        正常队列设置死信交换机map.put("x-dead-letter-exchange",DEAD_EXCHANGE);
//       设置死信的routerKeymap.put("x-dead-letter-routing-key","lisi");
//        设置正常队列长度的限制
//        map.put("x-max-length",6);//    创建普通队列channel.queueDeclare(NORMAL_QUEUE,false,false,false,map);//创建死信队列channel.queueDeclare(DEAD_QUEUE,false,false,false,null);//        绑定channel.queueBind(NORMAL_QUEUE,NORMAL_EXCHANGE,"zhangsan");channel.queueBind(DEAD_QUEUE,DEAD_EXCHANGE,"lisi");channel.basicConsume(NORMAL_QUEUE,false,(consumerTag, message) -> {String msg = new String(message.getBody());System.out.println("C1消息为:"+msg);
//            拒接对应消息if(msg.equals("info2")){
//                deliveryTagchannel.basicReject(message.getEnvelope().getDeliveryTag(),false);}else{channel.basicAck(message.getEnvelope().getDeliveryTag(),false);}},cancelCallback->{});}
}

SpringAMQP

官网地址:Spring AMQP

Spring AMQP 是 Spring 框架中的一个模块,它提供了基于 AMQP(Advanced Message Queuing Protocol,高级消息队列协议)标准的抽象层,用于简化在 Spring 应用程序中使用消息队列的过程。Spring AMQP 不仅简化了与消息代理(如 RabbitMQ)的集成,还提供了一套高度可配置的模板类来生产、消费消息,并管理AMQP基础设施组件,如队(Queue)、交换机(Exchange)和绑定(Binding)。

使用

      <!--AMQP依赖,包含RabbitMQ--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-amqp</artifactId></dependency>

生产者

logging:pattern:dateformat: MM-dd HH:mm:ss:SSS
spring:rabbitmq:host: 38.6.217.70port: 5672username: itcastpassword: 123321virtual-host: /
package cn.itcast.mq.spring;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import javax.annotation.Resource;
@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringAmqpTest {@Resourceprivate RabbitTemplate rabbitTemplate;@Testpublic void testSendMessage2SimpleQueue() throws InterruptedException {// 1.发送消息String message = "Hello, Spring Amqp!";rabbitTemplate.convertAndSend("simple.queue", message);}
}

 

消费者

import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;
//监听机制
@Component
public class SpringRabbitListener {@RabbitListener(queues = "simple.queue")public void listenSimpleQueueMessage(String msg) {System.out.println("spring接收到的消息是:" + msg);}
}

预取限制

案例:将50条消息在一秒内分类交给两个消费者消费。

//生成者   
@Testpublic void testSendWordSimpleQueue() throws InterruptedException {// 1.发送消息String key ="simple.queue";String message = "Hello, Spring Amqp____";for (int i = 0; i < 49; i++) {rabbitTemplate.convertAndSend(key, message+i);Thread.sleep(20);}}
//消费者
@Component
public class SpringRabbitListener {@RabbitListener(queues = "simple.queue")public void listenSimpleQueueMessage(String msg) throws InterruptedException {System.out.println("spring接收到的消息是:" + msg+"___"+ LocalDateTime.now());Thread.sleep(20);}@RabbitListener(queues = "simple.queue")public void listenFanoutQueue1(String msg) throws InterruptedException {System.err.println("FanoutQueue1接收到的消息是:" + msg+"___"+ LocalDateTime.now());Thread.sleep(200); //模拟性能}
}

通过执行结果我们可以看出listenFanoutQueue1这个监听器执行的是奇数,而listenSimpleQueueMessage则是偶数。且时间超出了1秒。为什么呢?

因为在生产者发送到队列中时,消费者会预取消息,在默认情况下进行平分机制,在上面代码中我们可以看到我们使用了线程睡眠的方式模拟了性能,在平分的情况下,睡眠200的执行了25条,所以导致了超出了1s。 如何调整呢?

logging:pattern:dateformat: MM-dd HH:mm:ss:SSS
spring:rabbitmq:host: 38.6.217.70port: 5672username: itcastpassword: 123321virtual-host: /listener: #设置预取simple:prefetch: 1 #每次只取一条#这段配置的作用是在使用 RabbitMQ 的时候,配置消费者监听器的简单模式,并设置消息预取值为 1。这意味着每次只会从队列中取出一条消息进行处理,处理完后再去取下一条消息。这种方式可以保证消息的顺序处理。

发布与订阅

 

fanoutExchange

这种交换机需要进行绑定对应的队列,绑定对应的队列后,生产者将消息推送给交换机,交换机会将消息分别都发给绑定的消息队列。

实现

//消费者配置
import org.springframework.amqp.core.Binding;
import org.springframework.amqp.core.BindingBuilder;
import org.springframework.amqp.core.FanoutExchange;
import org.springframework.amqp.core.Queue;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class FanoutExchangeConfig {//    创建队列1  fanout.queue1@Beanpublic Queue queue1(){return new Queue("fanout.queue1");}
//    创建交换机 fanoutExchange@Beanpublic FanoutExchange fanoutExchange(){return new FanoutExchange("fanoutExchange");}
//   队列1绑定交换机@Beanpublic Binding bindingExchange1(){return BindingBuilder.bind(queue1()).to(fanoutExchange());}//    创建队列1  fanout.queue2@Beanpublic Queue queue2(){return new Queue("fanout.queue2");}//   队列2绑定交换机@Beanpublic Binding bindingExchange2(){return BindingBuilder.bind(queue2()).to(fanoutExchange());}
}

 

消费者

@Component
public class SpringRabbitListener {@RabbitListener(queues = "fanout.queue1")public void listenFanoutQueue1QueueMessage(String msg) throws InterruptedException {System.out.println("fanout.queue1接收到的消息是:" + msg+"___"+ LocalDateTime.now());}@RabbitListener(queues = "fanout.queue2")public void listenFanoutQueue2QueueMessage(String msg) throws InterruptedException {System.out.println("fanout.queue2接收到的消息是:" + msg+"___"+ LocalDateTime.now());}
}

生产者

    @Testpublic void testSendMessageFanoutQueue()  {// 1.发送消息String message = "Hello, testSendMessageFanoutQueue !";
//       交换机名称String exchange = "fanoutExchange";rabbitTemplate.convertAndSend(exchange,"",message);}
DirectExchange

这种交换机需要指定一个key进行发送,通过可以区别发送到那个队列,同时这些队列也可以绑定相同的key,那么也就是实现了fanout的效果。 

实现

//消费者
@Component
public class DirectExchangeListener {//    可以通过@bena的方式进注入,这里我们采用@RabbitListenner的方式@RabbitListener(bindings = @QueueBinding(value = @Queue(name = "direct.queue1"),//绑定的队列exchange = @Exchange(name = "direct.exchange", type = ExchangeTypes.DIRECT),//绑定的交换机key = {"red", "blue"} //绑定的key))public void listenDirectQueue1(String msg) throws InterruptedException {System.out.println("listenDirectQueue1接收到的消息是:" + msg);}@RabbitListener(bindings = @QueueBinding(value = @Queue(name = "direct.queue2"),//绑定的队列exchange = @Exchange(name = "direct.exchange", type = ExchangeTypes.DIRECT),//绑定的交换机key = {"red", "yellow"} //绑定的key))public void listenDirectQueue2(String msg) throws InterruptedException {System.out.println("listenDirectQueue2接收到的消息是:" + msg);}
}
//生产者@Testpublic void testSendMessageDirectQueue()  {String routingKey = "yellow";// 1.发送消息String message = "Hello, testSendMessageFanoutQueue !"+"__"+routingKey;
//       交换机名称String exchange = "direct.exchange";rabbitTemplate.convertAndSend(exchange,routingKey,message);}

 

TopicExchange

这种交换机其实和direct类型的交换机差不错,只不过它是使用通配符的方式。

使用

//消费者
@Component
public class TopicExchangeListener {@RabbitListener(bindings = @QueueBinding(value = @Queue(name = "topic.queue1"),exchange = @Exchange(name = "topic.exchange", type = ExchangeTypes.TOPIC),key = {"china.#"}))public void listenTopicQueue1(String msg) throws InterruptedException {System.out.println("topic.queue1接收到消息:" + msg);}@RabbitListener(bindings = @QueueBinding(value = @Queue(name = "topic.queue2"),exchange = @Exchange(name = "topic.exchange", type = ExchangeTypes.TOPIC),key = {"#.news"}))public void listenTopicQueue2(String msg) throws InterruptedException {System.out.println("topic.queue2接收到消息:" + msg);}
}
//生产者   
@Testpublic void testSendMessageTopicQueue()  {String routingKey = "news";// 1.发送消息String message = "Hello, testSendMessageTopicQueue !"+"__"+routingKey;
//       交换机名称String exchange = "topic.exchange";rabbitTemplate.convertAndSend(exchange,routingKey,message);}

消息转换器

例子:

//我们声明一个objQueue
@Beanpublic Queue objQueue(){return new Queue("obj.queue");}//发送消息@Testpublic void testSendMessageobjQueue()  {Map<String, Object> map = new HashMap<>();map.put("name","test");map.put("age",18);rabbitTemplate.convertAndSend("obj.queue",map);}

我们重rabbitmq的ui界面中我们可以发现消息是基于JDK完成的序列化。

缺点:这样不能很直接的看出消息的结果,并且占用大量内存,所以下面我们使用jdckson进行json序列化。

发送者

依赖:

<dependency><groupId>com.fasterxml.jackson.dataformat</groupId><artifactId>jackson-dataformat-xml</artifactId>
</dependency>

配置bean

//生产者配置
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter;@Beanpublic MessageConverter messageConverter() {return new Jackson2JsonMessageConverter();}

 

消费者

<dependency><groupId>com.fasterxml.jackson.dataformat</groupId><artifactId>jackson-dataformat-xml</artifactId>
</dependency>
//销售者配置
import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter;
import org.springframework.amqp.support.converter.MessageConverter;@Beanpublic MessageConverter messageConverter() {return new Jackson2JsonMessageConverter();}@RabbitListener(queues = "obj.queue")public void listenObjQueueMessage( Map<String, Object> msg) throws InterruptedException {System.out.println("obj.queue接收到的消息是:" + msg);}

后续会更新使用MQ做的具体案例:秒杀、订单业务处理等。 

相关文章:

RabbitMq的基础及springAmqp的使用

RabbitMq 官网:RabbitMQ: One broker to queue them all | RabbitMQ 什么是MQ&#xff1f; mq就是消息队列&#xff0c;消息队列遵循这先入先出原则。一般用来解决应用解耦&#xff0c;异步消息&#xff0c;流量削峰等问题&#xff0c;实现高性能&#xff0c;高可用&#xf…...

uniapp uniCloud云开发

uniCloud概述 uniCloud 是 DCloud 联合阿里云、腾讯云、支付宝云&#xff0c;为开发者提供的基于 serverless 模式和 js 编程的云开发平台。 uniCloud 的 web控制台地址&#xff1a;https://unicloud.dcloud.net.cn 文档&#xff1a;https://doc.dcloud.net.cn/uniCloud/ un…...

智能扫地机,让生活电器更加便民-NV040D扫地机语音方案

一、语音扫地机开发背景&#xff1a; 随着人工智能和物联网技术的飞速发展&#xff0c;智能家居设备已成为现代家庭不可或缺的一部分。其中&#xff0c;扫地机作为家庭清洁的重要工具&#xff0c;更是得到了广泛的关注和应用。 然而&#xff0c;传统的扫地机在功能和使用上仍存…...

【后端面试题】【中间件】【NoSQL】ElasticSearch索引机制和高性能的面试思路

Elasticsearch的索引机制 Elasticsearch使用的是倒排索引&#xff0c;所谓的倒排索引是相对于正排索引而言的。 在一般的文件系统中&#xff0c;索引是文档映射到关键字&#xff0c;而倒排索引则相反&#xff0c;是从关键字映射到文档。 如果没有倒排索引的话&#xff0c;想找…...

【漏洞复现】时空智友ERP updater.uploadStudioFile接口处存在任意文件上传

0x01 产品简介 时空智友ERP是一款基于云计算和大数据技术的企业资源计划管理系统。该系统旨在帮助企业实现数字化转型&#xff0c;提高运营效率、降低成本、增强决策能力和竞争力&#xff0c;时空智友ERP系统涵盖了企业的各个业务领域&#xff0c;包括财务管理、供应链管理、生…...

[leetcode hot 150]第五百三十题,二叉搜索树的最小绝对差

题目&#xff1a; 给你一个二叉搜索树的根节点 root &#xff0c;返回 树中任意两不同节点值之间的最小差值 。 差值是一个正数&#xff0c;其数值等于两值之差的绝对值。 解析&#xff1a; minDiffInBST 方法是主要方法。创建一个 ArrayList 来存储树的节点值。inorderTrave…...

【Docker】可视化平台Portainer

文章目录 Portainer的特点Portainer的安装步骤注意事项 Docker的可视化工具Portainer是一个轻量级的容器管理平台&#xff0c;它为用户提供了一个直观的图形界面来管理Docker环境。以下是关于Portainer的详细介绍和安装步骤&#xff1a; Portainer的特点 轻量级&#xff1a;P…...

MySQL高级-MVCC-原理分析(RR级别)

文章目录 1、RR隔离级别下&#xff0c;仅在事务中第一次执行快照读时生成ReadView&#xff0c;后续复用该ReadView2、总结 1、RR隔离级别下&#xff0c;仅在事务中第一次执行快照读时生成ReadView&#xff0c;后续复用该ReadView 而RR 是可重复读&#xff0c;在一个事务中&…...

压力测试Monkey命令参数和报告分析

目录 常用参数 -p <测试的包名列表> -v 显示日志详细程度 -s 伪随机数生成器的种子值 --throttle < 毫秒> --ignore-crashes 忽略崩溃 --ignore-timeouts 忽略超时 --monitor-native-crashes 监视本地崩溃代码 --ignore-security-exceptions 忽略安全异常 …...

C# Benchmark

创建控制台项目&#xff08;或修改现有项目的Main方法代码&#xff09;&#xff0c;Nget导入Benchmark0.13.12&#xff0c;创建测试类&#xff1a; public class StringBenchMark{int[] numbers;public StringBenchMark() {numbers Enumerable.Range(1, 20000).ToArray();}[Be…...

算法金 | 协方差、方差、标准差、协方差矩阵

大侠幸会&#xff0c;在下全网同名「算法金」 0 基础转 AI 上岸&#xff0c;多个算法赛 Top 「日更万日&#xff0c;让更多人享受智能乐趣」 抱个拳&#xff0c;送个礼 1. 方差 方差是统计学中用来度量一组数据分散程度的重要指标。它反映了数据点与其均值之间的偏离程度。在…...

FastAPI教程II

本文参考FastAPI教程https://fastapi.tiangolo.com/zh/tutorial Cookie参数 定义Cookie参数与定义Query和Path参数一样。 具体步骤如下&#xff1a; 导入Cookie&#xff1a;from fastapi import Cookie声明Cookie参数&#xff0c;声明Cookie参数的方式与声明Query和Path参数…...

Facebook的投流技巧有哪些?

相信大家都知道Facebook拥有着巨大的用户群体和高转化率&#xff0c;在国外社交推广中的影响不言而喻。但随着Facebook广告的竞争越来越激烈&#xff0c;在Facebook广告上获得高投资回报率也变得越来越困难。IPIDEA代理IP今天就教大家如何在Facebook上投放广告的技巧&#xff0…...

Spring Boot 中的微服务监控与管理

微服务的概述 微服务架构的优点和挑战 优点: 灵活性和可扩展性:微服务架构允许每个服务单独部署和扩展,这使得系统可以更灵活地适应不同的业务需求和负载变化。 使团队更加聚焦:每个微服务都有明确的职责,这使得开发团队可以更加聚焦,专注于开发他们的服务。 技术和框…...

【计算机网络】期末复习(1)模拟卷

一、选择题 1. 电路交换的三个阶段是建立连接、()和释放连接 A. Hello包探测 B. 通信 C. 二次握手 D. 总线连接 2. 一下哪个协议不属于C/S模式() A. SNMP…...

【软件工程中的演化模型及其优缺点】

文章目录 1. 增量模型什么是增量模型&#xff1f;优点缺点 2. 增量-迭代模型什么是增量-迭代模型&#xff1f;优点缺点 3. 螺旋模型什么是螺旋模型&#xff1f;优点缺点 1. 增量模型 什么是增量模型&#xff1f; 增量模型是一种逐步增加功能和特性的开发方法。项目被划分为多…...

Oracle 数据库详解:概念、结构、使用场景与常用命令

1. 引言 Oracle 数据库作为全球领先的关系型数据库管理系统&#xff08;RDBMS&#xff09;&#xff0c;在企业级应用中占据了重要地位。本文将详细介绍Oracle数据库的核心概念、架构、常用操作及其广泛的使用场景&#xff0c;旨在为读者提供全面而深入的理解。 2. Oracle 数据…...

FreeRTOS的裁剪与移植

文章目录 1 FreeRTOS裁剪与移植1.1 FreeRTOS基础1.1.1 RTOS与GPOS1.1.2 堆与栈1.1.3 FreeRTOS核心文件1.1.4 FreeRTOS语法 1.2 FreeRTOS移植和裁剪 1 FreeRTOS裁剪与移植 1.1 FreeRTOS基础 1.1.1 RTOS与GPOS ​ 实时操作系统&#xff08;RTOS&#xff09;&#xff1a;是指当…...

能求一个数字的字符数量的程序

目录 开头程序程序的流程图程序输入与打印的效果例1输入输出 例2输入输出 关于这个程序的一些实用内容结尾 开头 大家好&#xff0c;我叫这是我58&#xff0c;今天&#xff0c;我们先来看一下下面的程序。 程序 #define _CRT_SECURE_NO_WARNINGS 1 #include <stdio.h>…...

PTA-线性表实验(JAVA)

题目1&#xff1a;Josephus环的问题及算法 【实验内容】 编程实现如下功能&#xff1a; 题意说明&#xff1a;古代某法官要判决n个犯人的死刑&#xff0c;他有一条荒唐的法律&#xff0c;将犯人站成一个圆圈&#xff0c;从第start个犯人开始数起&#xff0c;每数到第distance的…...

LeetCode:494. 目标和

题目 给你一个非负整数数组 nums 和一个整数 target 。 向数组中的每个整数前添加 ‘’ 或 ‘-’ &#xff0c;然后串联起所有整数&#xff0c;可以构造一个 表达式 &#xff1a; 例如&#xff0c;nums [2, 1] &#xff0c;可以在 2 之前添加 ‘’ &#xff0c;在 1 之前添…...

HarmonyOS Next开发学习手册——选项卡 (Tabs)

当页面信息较多时&#xff0c;为了让用户能够聚焦于当前显示的内容&#xff0c;需要对页面内容进行分类&#xff0c;提高页面空间利用率。 Tabs 组件可以在一个页面内快速实现视图内容的切换&#xff0c;一方面提升查找信息的效率&#xff0c;另一方面精简用户单次获取到的信息…...

LeetCode2710.移除字符串中的尾随零

cpp class Solution { public:string removeTrailingZeros(string num) {int flag 0;string s num;int size num.length();for (int i num.length() - 1; i > 0; i--) {if (num[i] ! 0)break;if (num[i] 0) {size--;}}s.resize(size);return s;} };...

PPT录屏怎么录?PPT录屏,3种方法简单操作

在数字化时代&#xff0c;PPT已经成为我们日常工作、学习和生活中不可或缺的一部分。无论是商务报告、教学课件还是产品展示&#xff0c;PPT都能帮助我们更加生动、直观地传递信息。然而&#xff0c;有时候我们会面临PPT录屏怎么录的问题。这时&#xff0c;一个好的PPT录屏功能…...

HarmonyOS开发:应用完整性校验

简介 为了确保应用的完整性和来源可靠&#xff0c;OpenHarmony需要对应用进行签名和验签。 应用开发阶段&#xff1a; 开发者完成开发并生成安装包后&#xff0c;需要开发者对安装包进行签名&#xff0c;以证明安装包发布到设备的过程中没有被篡改。OpenHarmony的应用完整性校…...

【MySQL基础篇】SQL指令:DQL及DCL

1、DQL DQL - 介绍 DQL英文全称是Data Query Language(数据查询语言)&#xff0c;数据查询语言&#xff0c;用来查询数据表中的记录。&#xff08;在MySQL中应用是最为广泛的&#xff09; 查询关键字&#xff1a;SELECT DQL - 语法 SELECT 字段列表 FROM 表名列表 WHER…...

[C++][设计模式][适配器模式]详细讲解

目录 1.动机2.模式定义3.要点总结4.代码感受 1.动机 在软件系统中&#xff0c;由于应用环境的变化&#xff0c;常常需要将”一些现存的对象“放在新的环境中应用&#xff0c;但是新环境要求的接口是这些现存对象所不满足如何应对这些”迁移的变化“&#xff1f;如何既能利用现…...

8080时序驱动TFT显示屏 驱动IC GC9307

8080时序总共有控制线 CS片选线 DC(命令数据控制线) RD读控制线 WR写控制线 和N条数据线。 控制底层代码如下; 写读代码,读的代码反过来就行 inline void TFT8080WriteDat(unsigned char dat) {CS_L;//开始片选DC_H;//写数据 // RD_H;//禁止读WR_H;//禁止写WR_L;//写入…...

K8S 集群节点缩容

环境说明&#xff1a; 主机名IP地址CPU/内存角色K8S版本Docker版本k8s231192.168.99.2312C4Gmaster1.23.1720.10.24k8s232192.168.99.2322C4Gwoker1.23.1720.10.24k8s233&#xff08;需下线&#xff09;192.168.99.2332C4Gwoker1.23.1720.10.24 1. K8S 集群节点缩容 当集群中有…...

Web-HTML-事件

1 需求 2 语法 3 示例 4 参考资料 HTML 事件 | 菜鸟教程...

Installed Build Tools revision xxx is corrupted. Remove and install again 解决

1.在buildTools文件下找到对应的sdk版本&#xff0c;首先将版本对应目录下的d8.bat改名为dx.bat。 2.在lib文件下将d8.jar改名为dx.jar。 3.重新编译工程即可...

AI 与 Python 实战干货:基于深度学习的图像识别

《AI 与 Python 实战干货&#xff1a;基于深度学习的图像识别》 今天咱不啰嗦&#xff0c;直接上干货&#xff01; 在 AI 领域&#xff0c;特别是图像识别方面&#xff0c;Python 简直是一把利器。咱就以手写数字识别为例&#xff0c;来看看怎么用 Python 实现一个深度学习模…...

万字长文详解数据结构:树 | 第6章 | Java版大话数据结构 | 二叉树 | 哈夫曼树 | 二叉树遍历 | 构造二叉树 | LeetCode练习

&#x1f4cc;本篇分享的大话数据结构中&#x1f384;树&#x1f384;这一章的知识点&#xff0c;在此基础上&#xff0c;增加了练习题帮助大家理解一些重要的概念✅&#xff1b;同时&#xff0c;由于原文使用的C语言代码&#xff0c;不利于学习Java语言的同学实践&#xff0c;…...

NPOI入门指南:轻松操作Excel文件的.NET库

目录 引言 一、NPOI概述 二、NPOI的主要用途 三、安装NPOI库 四、NPOI基本使用 六、性能优化和内存管理 七、常见问题与解决方案 八、结论 附录 引言 Excel文件作为数据处理的重要工具&#xff0c;广泛应用于各种场景。然而&#xff0c;在没有安装Microsoft Office的…...

【高性能服务器】服务器概述

&#x1f525;博客主页&#xff1a; 我要成为C领域大神&#x1f3a5;系列专栏&#xff1a;【C核心编程】 【计算机网络】 【Linux编程】 【操作系统】 ❤️感谢大家点赞&#x1f44d;收藏⭐评论✍️ 本博客致力于知识分享&#xff0c;与更多的人进行学习交流 ​ 服务器概述 服…...

003 SSM框架整合

文章目录 整合web.xmlapplicationContext-dao.xmlapplicationContext-service.xmlspringmvc.xmldb.propertieslog4j.propertiespom.xml 测试sqlItemController.javaItemMapper.javaItem.javaItemExample.javaItemService.javaItemServiceImpl.javaItemMapper.xml 整合 将工程的…...

web刷题记录(7)

[HDCTF 2023]SearchMaster 打开环境&#xff0c;首先的提示信息就是告诉我们&#xff0c;可以用post传参的方式来传入参数data 首先考虑的还是rce&#xff0c;但是这里发现&#xff0c;不管输入那种命令&#xff0c;它都会直接显示在中间的那一小行里面&#xff0c;而实际的命令…...

【单片机毕业设计选题24037】-基于STM32的电力系统电力参数无线监控系统

系统功能: 系统上电后&#xff0c;OLED显示“欢迎使用电力监控系统请稍后”&#xff0c;两秒后显示“Waiting..”等待ESP8266初始化完成&#xff0c; ESP8266初始化成功后进入正常页面显示&#xff0c; 第一行显示电压值&#xff08;单位V&#xff09; 第二行显示电流值&am…...

Python使用彩虹表来尝试对MD5哈希进行破解

MD5是一种散列算法&#xff0c;它是不可逆的&#xff0c;无法直接解密。它的主要作用是将输入数据进行散列&#xff0c;生成一个固定长度的唯一哈希值。 然而&#xff0c;可以使用预先计算好的MD5哈希值的彩虹表&#xff08;Rainbow Table&#xff09;来尝试对MD5进行破解。彩…...

数据恢复篇: 如何在数据丢失后恢复照片

数据丢失的情况并不少见。如果您曾经遇到过图像丢失的情况&#xff0c;您可能想过照片恢复工具是如何工作的&#xff1f;可能会丢失多少数据图像&#xff1f;即使是断电也可能导致照片和媒体文件丢失。 话虽如此&#xff0c;如果你认为删除的照片无法恢复&#xff0c;那你就错…...

c++ 引用第三方库

文章目录 背景编写cmake代码里引用测试 背景 遇到一个c项目&#xff0c;想跑一些示例。了解下如何直接引用第三方库。 编写cmake 项目结构 myprojectincludexx.hmain.cppCMakeLists.txt CMakeLists.txt cmake_minimum_required(VERSION 3.28) project(velox_demo)set(CM…...

[数据集][目标检测]猪只状态吃喝睡站检测数据集VOC+YOLO格式530张4类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;530 标注数量(xml文件个数)&#xff1a;530 标注数量(txt文件个数)&#xff1a;530 标注类别…...

Redis中设置验证码

限制一分钟内最多发送5次&#xff0c;且每次有效时间是5分钟&#xff01; String 发送验证码(phoneNumber) {key "shortMsg:limit:" phoneNumber;// 设置过期时间为 1 分钟&#xff08;60 秒&#xff09;// 使⽤ NX&#xff0c;只在不存在 key 时才能设置成功bool…...

使用hadoop进行数据分析

Hadoop是一个开源框架&#xff0c;它允许分布式处理大数据集群上的大量数据。Hadoop由两个主要部分组成&#xff1a;HDFS&#xff08;Hadoop分布式文件系统&#xff09;和MapReduce。以下是使用Hadoop进行数据分析的基本步骤&#xff1a; 数据准备&#xff1a; 将数据存储在HDF…...

架构师篇-7、企业安全架构设计及实践

摘要&#xff1a; 认识企业安全架构企业安全案例分析及实践 内容&#xff1a; 为什么做企业安全架构怎么做好安全架构设计案例实践分析&随堂练 为什么要做企业安全架构 安全是麻烦制造者&#xff1f; 整天提安全需求增加开发工作增加运维要求增加不确定性延后业务上线…...

递归算法~快速排序、归并排序

递归排序是一种基于分治法的排序算法&#xff0c;最典型的例子就是快速排序和归并排序。这两种算法都利用递归将问题分解成更小的子问题&#xff0c;然后将子问题的解合并以得到原始问题的解。 1、快速排序&#xff08;Quick Sort&#xff09; 快速排序的基本思想是选择一个基…...

DarkGPT:基于GPT-4-200k设计的人工智能OSINT助手

关于DarkGPT DarkGPT是一款功能强大的人工智能安全助手&#xff0c;该工具基于GPT-4-200k设计并实现其功能&#xff0c;可以帮助广大研究人员针对泄露数据库进行安全分析和数据查询相关的OSINT操作。 工具要求 openai1.13.3 requests python-dotenv pydantic1.10.12 工具安装 …...

RAG 检索增强生成有效评估

我们将介绍RAG(检索增强生成)的评估工作流程 RAG工作流程的部分 数据集 这里是我们将要使用的LCEL (LangChain Expression Language)相关问题的数据集。 这个数据集是在LangSmith UI中使用csv上传创建的: https://smith.langchain.com/public/730d833b-74da-43e2-a614-4e2ca…...

Day38:LeedCode 1049. 最后一块石头的重量 II 494. 目标和 474.一和零

1049. 最后一块石头的重量 II 有一堆石头&#xff0c;用整数数组 stones 表示。其中 stones[i] 表示第 i 块石头的重量。 每一回合&#xff0c;从中选出任意两块石头&#xff0c;然后将它们一起粉碎。假设石头的重量分别为 x 和 y&#xff0c;且 x < y。那么粉碎的可能结果…...

sqlalchemy分页查询

sqlalchemy分页查询 在SQLAlchemy中,可以使用limit和offset方法实现分页查询 from sqlalchemy.orm import sessionmaker from sqlalchemy import create_engine from models import MyModel # 假设MyModel是你定义的模型# 连接数据库 engine = create_engine(sqlite:///myd…...

为PPT加密:如何设置和管理“打开密码”?

在保护演示文稿的内容时&#xff0c;给PPT文件设置“打开密码”是一个简单而有效的方法。今天一起来看看如何设置和管理PPT文件的“打开密码”吧&#xff01; 一、设置PPT“打开密码” 首先&#xff0c;打开需要加密的PPT文件&#xff0c;点击左上角的“文件”选项卡&#x…...

Spring Boot + liteflow 居然这么好用!实战

在我们的日常开发中&#xff0c;经常会遇到一些需要串行或并行处理的复杂业务流程。 那我们该如何利用Spring Boot结合liteflow规则引擎来简化我们的业务流程 先看一个实战案例&#xff01;&#xff01; 在电商场景下&#xff0c;当订单完成后&#xff0c;我们需要同时进行积…...

Python创建MySQL数据库

一、使用Docker部署本地MySQL数据库 docker run --restartalways -p 3307:3306 --name mysql -e MYSOL_ROOT_PASSWORDlms123456 -d mysql:8.0.25 参数解析: 用户名:root 密码:lms123456 端口:3307 二、在Pycharm开发工具中配置连接MySQL数据库 三、安装zdppy_mysql pip inst…...

docker -run hello-world超时

主要原因就是尝试拉取库的时候没有从阿里云镜像里拉&#xff0c;所以设置一下就好了 这里使用的是ubuntu系统&#xff08;命令行下逐行敲就行了&#xff09; sudo mkdir -p /etc/docker sudo tee /etc/docker/daemon.json <<-EOF {"registry-mirrors": [&quo…...

微软推出集成GPT-4o的文本转语音虚拟数字人服务

微软近日宣布&#xff0c;其全新的文本转语音虚拟数字人服务正式上线&#xff0c;并集成了GPT-4o技术。这一服务为用户提供了创建实时互动数字人的可能。通过先进的自然语言处理技术&#xff0c;数字人能够将文本转化为自然流畅的语音&#xff0c;并配以生动的虚拟形象&#xf…...

如何摆脱反爬虫机制?

在网站设计时&#xff0c;为了保证服务器的稳定运行&#xff0c;防止非法数据访问&#xff0c;通常会引入反爬虫机制。一般来说&#xff0c;网站的反爬虫机制包括以下几种&#xff1a; 1. CAPTCHA&#xff1a;网站可能会向用户显示CAPTCHA&#xff0c;要求他们在访问网站或执行…...

将生活与出行融合揽境凭什么可以做到?

中国消费者对SUV的钟爱与热衷,堪称市场中的一股强大潮流。他们对其的认可,不仅仅停留在功能性的满足,更是对品质、品味与生活态度的追求。SUV所代表的宽敞空间、卓越通过性和引人注目的外观,恰恰迎合了中国消费者对于舒适、实用与时尚并重的需求。从整个SUV市场来看,30万级…...

什么是访问控制漏洞

什么是AC Bugs&#xff1f; 实验室 Vertical privilege escalation 仅通过隐藏目录/判断参数来权限控制是不安全的&#xff08;爆破url/爬虫/robots.txt/Fuzz/jsfinder&#xff09; Unprotected functionality 访问robots.txt 得到隐藏目录&#xff0c;访问目录 &#xff0c;…...

自定义注解+AOP切面实现日志记录

自定义注解&#xff1a; Target(ElementType.METHOD)// 作用在方法上 Retention(RetentionPolicy.RUNTIME) Documented Inherited // 子类可以继承此注解 public interface OperationLog { } aop切面&#xff1a; Slf4j Aspect Component public class OperationAspect {Au…...

端口扫描利器--nmap

目录 普通扫描 几种指定目标的方法 TCP/UDP扫描 端口服务扫描 综合扫描 普通扫描 基于端口连接并响应(真实) ​ nmap -sn 网段(0/24)-sn 几种指定目标的方法 单个IP扫描 IP范围扫描 扫描文件里的IP 扫描网段,(排除某IP) 扫描网段(排除某清单IP) TCP/UDP扫描 -sS …...

搜维尔科技:Movella Xsens用于动画,CG,短视频制作案例

用户名称 广州百漫文化传播有限公司 应用场景 基于Xsens MVN Link 动作捕捉系统的动画制作、CG制作、短视频制作、快速动画MAYA插件、影视动漫实时合成预渲染。 现场照片 《西行纪》内容简介&#xff1a;在远古神明的年代&#xff0c;世间存在着天众、龙众、阿修罗等八部众…...

ES数据导出成csv文件

推荐使用es2csv 工具。 命令行实用程序&#xff0c;用Python编写&#xff0c;用于用Lucene查询语法或查询DSL语法查询Elasticsearch&#xff0c;并将结果作为文档导出到CSV文件中。该工具可以在多个索引中查询批量文档&#xff0c;并且只获取选定的字段&#xff0c;这减少了查…...