MQ的用法总结就是三点:限流削峰、应用解耦、异步处理
一、RabbitMQ安装
docker run -dit --name demorabbitmq -e RABBITMQ_DEFAULT_USER=admin -e RABBITMQ_DEFAULT_PASS=admin -p 15672:15672 -p 5672:5672 rabbitmq:management
docker ps查看运行状况
运行成功后,进入rabbitmq容器
rabbitmqctl list_users
rabbitmqctl add_user admin admin 创建账号admin,密码也是admin
rabbitmqctl set_user_tags admin administrator 设置用户角色
rabbitmqctl set_permissions -p "/" admin "*" "*" "*" 设置用户具有vhost中所有资源的配置、写、读权限
配置完成进入页面查看效果
docker 运行时已经配置了,安装完后可以直接登录,不用配置上面的东西
192.168.81.130:15672 用户名密码都是admin
二、RabbitMQ的六大核心模式
1、Hello World
生产者发送消息
在pom文件导入依赖
<dependencies> <dependency> <groupId>com.rabbitmq</groupId> <artifactId>amqp-client</artifactId> <version>5.8.0</version> </dependency> <dependency> <groupId>commons-io</groupId> <artifactId>commons-io</artifactId> <version>2.6</version> </dependency> </dependencies>
测试发送消息
查看结果
消费者接收消息
编写代码
测试结果
2、Work Queues
生产者大量发消息给队列,由多个工作线程接收消息,一个消息只能被处理一次,不能重复处理。轮询分发消息。
新建工具类,获取信道
工作线程代码
生产者代码
开启生产者和所有工作线程
消息应答
分为自动应答和手动应答,自动应答如果消费者突然挂掉会导致消息丢失
手动应答:
1、Channel.basicAck(用于肯定确认)
2、RabbitMQ已知道该消息并且成功处理,可以将其丢弃
3、Channel.basicNack(用于否定确认)
Channel.basicReject(用于否定确认),与Channel.basicNack相比少了一个参数multiple(批量应答),不处理该消息了,直接拒绝,可以将其丢弃了
multiple的true和false是不同的意思:
1、true表示批量应答channel上未应答的消息,比如channel上有传送tag的消息5,6,7,8,,当前tag是8,那么此时5-8的这些还未应答的消息就会被确认收到消息应答
2、false同上面相比只会应答tag=8的消息,5,6,7这三个消息依然不会被确认收到消息应答
手动应答的好处是可以批量应答并且减少网络拥堵,而且还可以防止消息丢失
模拟消费者在工作中宕机情况
消费者4设置较长的睡眠时间,用于停止,模拟宕机
消费者3睡眠1秒,模拟正常运行的工作线程
运行三个线程,发送AA,消费者3成功接收,发送BB,在消费者4睡眠期间关闭,消息成功回到消息队列被消费者3接收
持久化
1、队列持久化
生产者代码,在生成队列的方法中,第二个参数改为true,如果原来已经创建过这个队列了,则需要删除原来的
在RabbitMQ界面可以看到队列已经持久化了
2、消息持久化
生产者代码,在发送消息的方法中,第三个参数设置为
MessageProperties.PERSISTENT_TEXT_PLAIN
不公平分发
消费者代码,在处理消息的方法前加上,每个工作线程都要加上,不会轮询处理消息,不公平分发即能者多劳
预取值
只要值不为1就是预取值,值为多少就是预取值多少
发布确认
生产者将信道设置成confirm模式,所有在该信道上面发布的消息都将会被指派一个唯一的ID(从1开始),一旦消息被投递到所有匹配的队列之后,broker就会发送一个确认给生产者(包含消息的唯一ID),如果消息和队列是可持久化的,那么确认消息会在将消息写入磁盘之后发出,此外 broker也可以设置basic.ack 的multiple域,表示到这个序列号之前的所有消息都已经得到了处理。
1、单个发布确认
生产者代码,在生成信道后设置成confirm模式,每发送一条消息确认一次,耗时长,但是可以知道哪条消息确认,哪条异常
2、批量发布确认
生产者代码,在每发送一部分消息后确认,耗时较短,但是无法知道哪条消息异常
3、异步发布确认
生产者代码,在发送消息之前,准备监听器addConfirmListener,然后设置成功和失败两个回调函数,耗时短,可靠性高
三种确认机制耗时比较,可以看出异步确认耗时最短,而且可靠性高,建议使用
如何处理异步未确认信息?
首先需要一个数据结构用来存储未确认的消息,ConcurrentSkipListMap是一个很好的选择
然后在每发一条消息就把消息的序号和消息存放到该表中
最后在确认消息的回调函数中删除已确认的消息,剩下的就是未确认消息,注意批量确认和单个确认删除方式不一样
交换机
1、fanout交换机(扇出交换机)
交换机一定要先创建,如果在消费者中创建就先开启消费者线程,在生产者中创建就先开启生产者线程
本次测试使用生产者创建交换机,在发送消息时指定交换机名称即可,和路由key无关,设不设置消费者都能接收
消费者在接收消息前需要绑定队列和交换机,其他消费者线程代码一样,绑定同一个交换机即可,和路由key无关,路由key不一样也可以接收到
测试如下,两个消费者都能接收到消息
2、direct交换机(直接交换机)
直接交换机可以通过指定的路由key把消息发送到绑定了该交换机的该路由key队列中
生产者代码需要声明交换机类型
消费者代码需要在绑定交换机的同时指定路由key
测试结果
3、topic交换机(主题交换机)
topic交换机根据路由key来分发消息,可以同时结合扇出和直接交换机
路由key为一个单词列表,由点号分割,例如 " s1.s2.s3 " " *.s1.* " "s1.#"
*
可以代替一个单词#
可以代替零个或多个单词
生产者代码需要指定交换机类型,和发送的路由key
消费者代码需要绑定交换机,并且指定路由key
测试结果
死信队列
生产者代码,声明普通交换机和路由key,可以设置过期时间参数
普通消费者代码,声明普通队列,队列需要带上参数设置死信交换机和路由key,绑定普通交换机和路由key
死信消费者代码,声明死信队列,绑定死信交换机和路由key即可
死信的几种方式:
1、消息TTL过期:Time to Live ,即生存时间
模拟过期可以在发送消息时设置过期参数,先启动生产者和两个消费者,然后关闭普通消费者,重启生产者发送消息,时间到后死信消费者接收到消息
2、队列达到最大长度:
模拟可以在生成普通队列时设置队列长度,先启动生产者和两个消费者,然后关闭普通消费者,重启生产者发送消息,超过队列长度的消息被死信消费者接收
3、消息被拒绝:
模拟可以在消费者代码中,把接收消息改为手动提交(false),然后在接收消息的接口实现中拒绝消息,并且不让消息重回队列(false)
延迟队列
1、准备工作
在pom文件中添加依赖
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-amqp</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>1.2.73</version> </dependency> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> </dependency> <!-- swag依赖--> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger2</artifactId> <version>2.9.2</version> </dependency> <dependency> <groupId>io.springfox</groupId> <artifactId>springfox-swagger-ui</artifactId> <version>2.9.2</version> </dependency> <dependency> <groupId>org.springframework.amqp</groupId> <artifactId>spring-rabbit-test</artifactId> <scope>test</scope> </dependency>
编写配置类文件
swag配置类
如果swag报错,可以尝试降低springboot版本,或者在启动类上加上@EnableWebMvc注解
RabbitMQ配置类
如果存在队列,需要在RabbitMQ网站先删除,如果报错或者接收不到消息,查看配置是否出错
生产者代码
因为配置类里已经配置好了路由器和队列,这里只要发送消息即可
消费者代码
监听死信队列
上面这种方法有局限性,设置的TTL是固定的,如果需要设置其他的TTL还需要在配置类里再定义一条队列,这样很不方便,所以我们直接在配置类中定义一条没有设置TTL的队列
在生产者中定义TTL,注意时间的单位是ms