rabbitmq-plugins enable rabbitmq_management
进行安装overview→Totals :所有队列的阻塞情况
Ready:待消费的消息总数
Unacked:待应答的消息总数
Total:总数 Ready+Unacked
Publish:producter pub消息的速率。
Publisher confirm:broker确认pub消息的速率。
Deliver(manual ack):customer手动确认的速率。
Deliver( auto ack):customer自动确认的速率。
Consumer ack:customer正在确认的速率。
Redelivered:正在传递’redelivered’标志集的消息的速率。
Get (manual ack):响应basic.get而要求确认的消息的传输速率。
Get (auto ack):响应于basic.get而发送不需要确认的消息的速率。
Return:将basic.return发送给producter的速率。
Disk read:queue从磁盘读取消息的速率。
Disk write:queue从磁盘写入消息的速率。
整体角色的个数
Connections:client的tcp连接的总数。
Channels:通道的总数。
Exchange:交换器的总数。
Queues:队列的总数。
Consumers:消费者的总数。
Overview→Nodes
broker的属性
Name:broker名称
File descriptors:broker打开的文件描述符和限制。
Socket descriptors:broker管理的网络套接字数量和限制。当限制被耗尽时,RabbitMQ将停止接受新的网络连接。
Erlang processes:erlang启动的进程数。
Memory:当前broker占用的内存。
Disk space:当前broker占用的硬盘。
Uptime:当前broker持续运行的时长。
Info:集群的信息。
Reset stats:重启单节点或整个集群。
Overview->Export definitions
定义由用户,虚拟主机,权限,参数,交换,队列和绑定组成。 它们不包括队列的内容或集群名称。 独占队列不会被导出。
Overview->Import definitions
导入的定义将与当前定义合并。 如果在导入过程中发生错误,则所做的任何更改都不会回滚。
Virtual host:所属的虚拟主机。
Name:名称。
User name:使用的用户名。
State:当前的状态,running:运行中;idle:空闲。
SSL/TLS:是否使用ssl进行连接。
Protocol:使用的协议。
Channels:创建的channel的总数。
From client:每秒发出的数据包。
To client:每秒收到的数据包。
通道的属性
channel:名称。
Node:节点名称。
Virtual host:所属的虚拟主机。
User name:使用的用户名。
Mode:渠道保证模式。 可以是以下之一,或者不是:C: confirm。T:transactional(事务)。
State :当前的状态,running:运行中;idle:空闲。
Unconfirmed:待confirm的消息总数。
Prefetch:设置的prefetch的个数。
Unacker:待ack的消息总数。
publish:producter pub消息的速率。
confirm:producter confirm消息的速率。
deliver/get:consumer 获取消息的速率。
ack:consumer ack消息的速率。
Virtual host:所属的虚拟主机。
Name:名称。
Type:exchange type,
Features:功能。 可以是以下之一,或者不是:D: 持久化。T:Internal,存在改功能表示这个exchange不可以被client用来推送消息,仅用来进行exchange和exchange之间的绑定,否则可以推送消息也可以绑定。
Message rate in:消息进入的速率。
Message rate out:消息出去的速率。
Virtual host:所属的虚拟主机。
Name:名称。
Features:功能。 可以是以下之一,或者不是:D: 持久化。
State:当前的状态,running:运行中;idle:空闲。
Ready:待消费的消息总数。
Unacked:待应答的消息总数。
Total:总数 Ready+Unacked。
incoming:消息进入的速率。
deliver/get:消息获取的速率。
ack:消息应答的速率。
```
1.MQ(Message Queue,消息队列)是一种应用系统之间的通信方法。是通过读写出入队列的消息来通信(RPC则是通过直接调用彼此来通信的)。消息发送后可以立即返回,由消息系统来确保消息的可靠传递。消息发布者只管把消息发布到 MQ 中而不用管谁来取,消息使用者只管从 MQ 中取消息而不管是谁发布的。这样发布者和使用者都不用知道对方的存在。
2.AMQP,即Advanced Message Queuing Protocol,高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计。消息中间件主要用于组件之间的解耦,消息的发送者无需知道消息使用者的存在,反之亦然。
AMQP的主要特征是面向消息、队列、路由(包括点对点和发布/订阅)、可靠性、安全。
3.RabbitMQ是一个开源的AMQP实现,服务器端用Erlang语言编写,支持多种客户端,如:Python、Ruby、.NET、Java、JMS、C、PHP、ActionScript、XMPP、STOMP等,支持AJAX。用于在分布式系统中存储转发消息,在易用性、扩展性、高可用性等方面表现不俗。常见场景包括:业务解耦、最终一致性、广播、错峰流控等等
```
Message:消息,包含消息头(即附属的配置信息)和消息体(即消息的实体内容) Publisher:生产者,向交换机发布消息的主体 Exchange:交换机,用来接收生产者发送的消息并将这些消息路由给服务器中的队列 Binding:绑定,用于给Exchange和Queue建立关系,就是我们熟知的配对的红娘 Queue:消息队列,用来保存消息直到发送给消费者。它是消息的容器,也是消息的终点。一个消息可投入一个或多个队列。消息一直在队列里面,等待消费者连接到这个队列将其取走。 Connection:连接 Channel:通道,MQ与外部打交道都是通过Channel来的,发布消息、订阅队列还是接收消息,这些动作都是通Channel完成;简单来说就是消息通过Channel塞进队列或者流出队列 Consumer:消费者,从消息队列中获取消息的主体 Virtual Host: 虚拟主机,表示一批交换器、消息队列和相关对象。虚拟主机是共享相同的身份认证和加密环境的独立服务器域。每个 vhost 本质上就是一个 mini 版的 RabbitMQ 服务器,拥有自己的队列、交换器、绑定和权限机制。vhost 是 AMQP 概念的基础,必须在连接时指定,RabbitMQ 默认的 vhost 是 / Broker:消息队列服务器实体 Routing Key: 路由关键字,exchange根据这个关键字进行消息投递
AMQP 中消息的路由过程和 Java 开发者熟悉的 JMS 存在一些差别,AMQP 中增加了 Exchange 和 Binding 的角色。生产者把消息发布到 Exchange 上,消息最终到达队列并被消费者接收,而 Binding 决定交换器的消息应该发送到那个队列。
Exchange分发消息时根据类型的不同分发策略有区别,目前共四种类型:direct、fanout、topic、headers 。headers 匹配 AMQP 消息的 header 而不是路由键,此外 headers 交换器和 direct 交换器完全一致,但性能差很多,目前几乎用不到了,所以直接看另外三种类型:
这种是特殊的Direct Exchange,是rabbitmq内部默认的一个交换机。该交换机的name是空字符串,所有queue都默认binding 到该交换机上。所有binding到该交换机上的queue,routing-key都和queue的name一样
消息中的路由键(routing key)如果和 Binding 中的 binding key 一致, 交换器就将消息发到对应的队列中。路由键与队列名完全匹配,如果一个队列绑定到交换机要求路由键为“test”,则只转发 routing key 标记为“test”的消息,不会转发“test.test”,也不会转发“test.test1”等等。它是完全匹配、单播的模式。
每个发到 fanout 类型交换器的消息都会分到所有绑定的队列上去。fanout 交换器不处理路由键,只是简单的将队列绑定到交换器上,每个发送到交换器的消息都会被转发到与该交换器绑定的所有队列上。很像子网广播,每台子网内的主机都获得了一份复制的消息。fanout 类型转发消息是最快的。
通配符 | 说明 | 示例 |
---|---|---|
* | 匹配一个或多个内容 | bigdata. * 可以匹配到bigdata.spark或者 bigdata.hadoop.hive等 |
# | 匹配一个内容 | bigdata.# 只能匹配到bigdata.spark或者bigdata.hadoop |
topic 交换器通过模式匹配分配消息的路由键属性,将路由键和某个模式进行匹配,此时队列需要绑定到一个模式上。它将路由键和绑定键的字符串切分成单词,这些单词之间用点隔开。它同样也会识别两个通配符:符号“#”和符号“*”。#匹配0个或多个单词,*匹配不多不少一个单词
headers 也是根据规则匹配, 相较于 direct 和 topic 固定地使用 routing_key , headers 则是一个自定义匹配规则的类型.在队列与交换器绑定时, 会设定一组键值对规则, 消息中也包括一组键值对( headers 属性), 当这些键值对有一对, 或全部匹配时, 消息被投送到对应队列
每个Consumer可能需要一段时间才能处理完收到的数据。如果在这个过程中,Consumer出错了,异常退出了,而数据还没有处理完成,那么 非常不幸,这段数据就丢失了。因为我们采用no-ack的方式进行确认,也就是说,每次Consumer接到数据后,而不管是否处理完成,RabbitMQ Server会立即把这个Message标记为完成,然后从queue中删除了。 如果一个Consumer异常退出了,它处理的数据能够被另外的Consumer处理,这样数据在这种情况下就不会丢失了(注意是这种情况下)。 为了保证数据不被丢失,RabbitMQ支持消息确认机制,即acknowledgments。 为了保证数据能被正确处理而不仅仅是被Consumer收到,那么我们不能采用no-ack。而应该是在处理完数据后发ack。 在处理数据后发送的ack,就是告诉RabbitMQ数据已经被接收,处理完成,RabbitMQ可以去安全的删除它了。 如果Consumer退出了但是没有发送ack,那么RabbitMQ就会把这个Message发送到下一个Consumer。 这样就保证了在Consumer异常退出的情况下数据也不会丢失。
死信队列同其他的队列一样都是普通的队列。在RabbitMQ中并没有特定的“死信队列”类型,而是通过配置,将其实现。 当我们在创建一个业务的交换机和队列的时候,可以配置参数,指明另一个队列为当前队列的死信队列,在RabbitMQ中,死信队列(严格的说应该是死信交换机)被称为DLX Exchange。当消息“死掉”后,会被自动路由到DLX Exchange的queue中. 什么样的消息会进入死信队列? 1.消息的TTL过期(Time To Live)-存活时间已经过期 2.消费者对broker应答Nack,并且消息禁止重回队列。(basic.reject or basic.nack) 且带 requeue=false不重新入队参数或达到的retry重新入队的上限次数 3.Queue队列长度已达上限。队列满,queue的"x-max-length"参数) 应用场景: 重要的业务队列如果失败,就需要重新将消息用另一种业务逻辑处理;如果是正常的业务逻辑故意让消息中不合法的值失败,就不需要死信
#属性文件:org.springframework.boot.autoconfigure.amqp.RabbitProperties
#Config:
# base
spring.rabbitmq.host: 服务Host
spring.rabbitmq.port: 服务端口
spring.rabbitmq.username: 登陆用户名
spring.rabbitmq.password: 登陆密码
spring.rabbitmq.virtual-host: 连接到rabbitMQ的vhost
spring.rabbitmq.addresses: 指定client连接到的server的地址,多个以逗号分隔(优先取addresses,然后再取host)
spring.rabbitmq.requested-heartbeat: 指定心跳超时,单位秒,0为不指定;默认60s
spring.rabbitmq.publisher-confirms: 是否启用【发布确认】
spring.rabbitmq.publisher-returns: 是否启用【发布返回】
spring.rabbitmq.connection-timeout: 连接超时,单位毫秒,0表示无穷大,不超时
spring.rabbitmq.parsed-addresses:
# ssl
spring.rabbitmq.ssl.enabled: 是否支持ssl
spring.rabbitmq.ssl.key-store: 指定持有SSL certificate的key store的路径
spring.rabbitmq.ssl.key-store-password: 指定访问key store的密码
spring.rabbitmq.ssl.trust-store: 指定持有SSL certificates的Trust store
spring.rabbitmq.ssl.trust-store-password: 指定访问trust store的密码
spring.rabbitmq.ssl.algorithm: ssl使用的算法,例如,TLSv1.1
# cache
spring.rabbitmq.cache.channel.size: 缓存中保持的channel数量
spring.rabbitmq.cache.channel.checkout-timeout: 当缓存数量被设置时,从缓存中获取一个channel的超时时间,单位毫秒;如果为0,则总是创建一个新channel
spring.rabbitmq.cache.connection.size: 缓存的连接数,只有是CONNECTION模式时生效
spring.rabbitmq.cache.connection.mode: 连接工厂缓存模式:CHANNEL 和 CONNECTION
# listener
spring.rabbitmq.listener.simple.auto-startup: 是否启动时自动启动容器
spring.rabbitmq.listener.simple.acknowledge-mode: 表示消息确认方式,其有三种配置方式,分别是none、manual和auto;默认auto
spring.rabbitmq.listener.simple.concurrency: 最小的消费者数量
spring.rabbitmq.listener.simple.max-concurrency: 最大的消费者数量
spring.rabbitmq.listener.simple.prefetch: 指定一个请求能处理多少个消息,如果有事务的话,必须大于等于transaction数量.
spring.rabbitmq.listener.simple.transaction-size: 指定一个事务处理的消息数量,最好是小于等于prefetch的数量.
spring.rabbitmq.listener.simple.default-requeue-rejected: 决定被拒绝的消息是否重新入队;默认是true(与参数acknowledge-mode有关系)
spring.rabbitmq.listener.simple.idle-event-interval: 多少长时间发布空闲容器时间,单位毫秒
spring.rabbitmq.listener.simple.retry.enabled: 监听重试是否可用
spring.rabbitmq.listener.simple.retry.max-attempts: 最大重试次数
spring.rabbitmq.listener.simple.retry.initial-interval:第一次和第二次尝试发布或传递消息之间的间隔
spring.rabbitmq.listener.simple.retry.multiplier: 应用于上一重试间隔的乘数
spring.rabbitmq.listener.simple.retry.max-interval: 最大重试时间间隔
spring.rabbitmq.listener.simple.retry.stateless: 重试是有状态or无状态
# template
spring.rabbitmq.template.mandatory: 启用强制信息;默认false
spring.rabbitmq.template.receive-timeout: receive() 操作的超时时间
spring.rabbitmq.template.reply-timeout: sendAndReceive() 操作的超时时间
spring.rabbitmq.template.retry.enabled: 发送重试是否可用
spring.rabbitmq.template.retry.max-attempts: 最大重试次数
spring.rabbitmq.template.retry.initial-interval: 第一次和第二次尝试发布或传递消息之间的间隔
spring.rabbitmq.template.retry.multiplier: 应用于上一重试间隔的乘数
spring.rabbitmq.template.retry.max-interval: 最大重试时间间隔
org.springframework.boot spring-boot-starter-amqp
spring: application: name: upms-service main: allow-bean-definition-overriding: true #当遇到同样名字的时候,是否允许覆盖注册 rabbitmq: username: guest password: guest port: 5672 host: localhost
//配置test队列 @Configuration public class RabbitConfig { @Bean public Queue helloQueue() { return new Queue("test"); } } // 消费者监听test队列 @Component public class TestReceiver { @RabbitListener(queues = "test") public void receiveMessageTest(String test) throws InterruptedException { System.out.println("======处理信息======"); Thread.sleep(1000L); System.out.println("test Receiver : " + test+"===="+System.currentTimeMillis()); System.out.println("======处理信息结束======"); } } //生产者发送消息 @RestController public class MQController { @Autowired RabbitTemplate rabbitTemplate; @GetMapping("/send") public void send() { String context = "hello " + System.currentTimeMillis()+":"+Thread.currentThread().getName(); System.out.println("Sender : " + context); rabbitTemplate.convertAndSend("test", context); } }
/** * @描述* 说明: * 1. fanout路由又称为广播路由,会将收到的消息广播到消息对列上。当使用fanout交换器时,它会将消息广播到与该交换器绑定的所有队列上,有利于对单条消息做不同的反应。 * 2. fanout路由无需指定route key,即使指定了也会被忽略,只要队列与交换机绑定,那么发送到交换机上的消息就会被分发到消息接收者上。 * */ //配置fanout队列和交换机 @Slf4j @Configuration public class TestExchangeConfiguration { /** * 交换机名称 */ public static final String FANOUT_EXCHANGE_NAME = "fanout.exchange.name"; /** * 测试队列名称1 */ public static final String TEST_QUEUE1_NAME = "test.queue1.name"; /** * 测试队列名称1 */ public static final String TEST_QUEUE2_NAME = "test.queue2.name"; /** * 创建广播形式的交换机 * * @return 交换机实例 */ @Bean public FanoutExchange fanoutExchange() { log.info("【【【交换机实例创建成功】】】"); return new FanoutExchange(FANOUT_EXCHANGE_NAME); } /** * 测试队列一 * * @return 队列实例 */ @Bean public Queue queue1() { log.info("【【【测试队列一实例创建成功】】】"); return new Queue(TEST_QUEUE1_NAME); } /** * 测试队列二 * * @return 队列实例 */ @Bean public Queue queue2() { log.info("【【【测试队列二实例创建成功】】】"); return new Queue(TEST_QUEUE2_NAME); } /** * 绑定队列一到交换机 * * @return 绑定对象 */ @Bean public Binding bingQueue1ToExchange() { log.info("【【【绑定队列一到交换机成功】】】"); return BindingBuilder.bind(queue1()).to(fanoutExchange()); } /** * 绑定队列二到交换机 * * @return 绑定对象 */ @Bean public Binding bingQueue2ToExchange() { log.info("【【【绑定队列二到交换机成功】】】"); return BindingBuilder.bind(queue2()).to(fanoutExchange()); } } // 消费者监听队列 @Component @Slf4j public class TestReceiver { @RabbitHandler @RabbitListener(queues = "test.queue1.name") public void receiveMessage(String message) { log.info("消息接收者接收到来自【队列一】的消息,消息内容: {}", message); } @RabbitHandler @RabbitListener(queues = "test.queue2.name") public void receiveMessage1(String message) { log.info("消息接收者接收到来自【队列二】的消息,消息内容: {}", message); } } //发送消息到交换机,广播到所有队列 @RestController @Slf4j public class MQController { @Autowired RabbitTemplate rabbitTemplate; /** * 发送消息 * * @param message 消息内容 * 说明: routingKey可以指定也可以不指定,这里我们给一个空字符串"" */ @GetMapping("/send") public void sendMessage(@RequestParam String message) { log.info("【消息发送者】发送消息到fanout交换机,消息内容为: {}", message); rabbitTemplate.convertAndSend("fanout.exchange.name", "", message); } }
//配置fanout队列和交换机 @Slf4j @Configuration public class TestTopicConfiguration { /** * 交换机名称 */ public static final String FANOUT_EXCHANGE_NAME = "Topic.exchange.name"; /** * 测试队列名称1 */ public static final String TEST_QUEUE1_NAME = "Topic.queue1.name"; /** * 测试队列名称1 */ public static final String TEST_QUEUE2_NAME = "Topic.queue2.name"; /** * 创建广播形式的交换机 * @return 交换机实例 */ @Bean public TopicExchange topicExchange() { log.info("【【【Topic交换机实例创建成功】】】"); return new TopicExchange(FANOUT_EXCHANGE_NAME); } /** * 测试队列一 * @return 队列实例 */ @Bean public Queue queue1() { log.info("【【【Topic测试队列一实例创建成功】】】"); return new Queue(TEST_QUEUE1_NAME); /** * 测试队列二 * * @return 队列实例 */ @Bean public Queue queue2() { log.info("【【【Topic测试队列二实例创建成功】】】"); return new Queue(TEST_QUEUE2_NAME); } /** * 绑定队列一到交换机 * 配置该消息队列的 routingKey * Topic.* 匹配 第一个.后面的单词 代表一个 单词 * 比如 topic.asd 会被该消息队列接受 topic.asd.dsf不会被该消息队列接受 * @return 绑定对象 */ @Bean public Binding bingQueue1ToExchange() { log.info("【【【绑定队列一到交换机成功】】】"); return BindingBuilder.bind(queue1()).to(topicExchange()).with("Topic.*"); } /** * 绑定队列二到交换机 * 配置该消息队列的 routingKey * Topic.# 匹配 所有.后面的单词代表任意个单词 * 比如 topic.asd 会被该消息队列接受 topic.asd.dsf也会被该消息队列接受 * @return 绑定对象 */ @Bean public Binding bingQueue2ToExchange() { log.info("【【【绑定队列二到交换机成功】】】"); return BindingBuilder.bind(queue2()).to(topicExchange()).with("Topic.#"); } } /** * @描述 */ // 消费者监听队列 @Component @Slf4j public class TestReceiver { @RabbitHandler @RabbitListener(queues = "Topic.queue1.name") public void receiveMessage2(String message) { log.info("Topic消息接收者接收到来自【队列一】的消息,消息内容: {}", message); } @RabbitHandler @RabbitListener(queues = "Topic.queue2.name") public void receiveMessage3(String message) { log.info("Topic消息接收者接收到来自【队列二】的消息,消息内容: {}", message); } } /* * @描述 */ //发送消息到交换机 @RestController @Slf4j public class MQController { @Autowired RabbitTemplate rabbitTemplate; @GetMapping("send") public void sendTwo(String message){ log.info("【Topic消息发送者】发送消息到Topic交换机,消息内容为: {}", message); rabbitTemplate.convertAndSend("Topic.exchange.name","Topic.name",message); rabbitTemplate.convertAndSend("Topic.exchange.name","Topic.a",message); } }
/** * @描述* 说明: */ @Slf4j @Configuration public class TestHeadersConfiguration { /** * 交换机名称 */ public static final String HEADERS_EXCHANGE_NAME = "Headers.exchange.name"; /** * 测试队列名称1 */ public static final String TEST_QUEUE1_NAME = "Headers.queue1.name"; /** * 测试队列名称1 */ public static final String TEST_QUEUE2_NAME = "Headers.queue2.name"; /** * 创建广播形式的交换机 * * @return 交换机实例 */ @Bean public HeadersExchange headersExchange() { log.info("【Headers交换机实例创建成功】"); return new HeadersExchange(HEADERS_EXCHANGE_NAME); } /** * 测试队列一 * @return 队列实例 */ @Bean public Queue queue5() { log.info("【Headers测试队列一实例创建成功】"); return new Queue(TEST_QUEUE1_NAME); } /** * 测试队列二 * @return 队列实例 */ @Bean public Queue queue6() { log.info("【Headers测试队列二实例创建成功】"); return new Queue(TEST_QUEUE2_NAME); } @Bean public Binding bingQueue5ToExchange() { log.info("【Headers绑定队列一到交换机成功】"); HashMap<String, Object> header = new HashMap<>(); header.put("queue", "queue1"); header.put("bindType", "whereAll"); return BindingBuilder.bind(queue5()).to(headersExchange()).whereAll(header).match(); } @Bean public Binding bingQueue6ToExchange() { log.info("【Headers绑定队列二到交换机成功】"); HashMap<String, Object> header = new HashMap<>(); header.put("queue", "queue2"); header.put("bindType", "whereAny"); return BindingBuilder.bind(queue6()).to(headersExchange()).whereAny(header).match(); } /** * @描述 */ @Component @Slf4j public class TestReceiver { @RabbitListener(queues = "Headers.queue1.name") public void receiveMessage4(Message message) { try { log.info("Headers消息接收者接收到来自【队列一】的消息,消息内容: {}",new String(message.getBody(),message.getMessageProperties().getContentType())); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } } @RabbitListener(queues = "Headers.queue2.name") public void receiveMessage5(Message message) { try { log.info("Headers消息接收者接收到来自【队列二】的消息,消息内容: {}",new String(message.getBody(),message.getMessageProperties().getContentType())); } catch (UnsupportedEncodingException e) { e.printStackTrace(); } } } /** * @描述 */ @RestController @Slf4j public class MQController { @Autowired RabbitTemplate rabbitTemplate; @GetMapping("send3") public void send3(String message){ log.info("【Headers消息发送者】发送消息到Headers交换机,消息内容为: {}", message); MessageProperties messageProperties = new MessageProperties(); messageProperties.setDeliveryMode(MessageDeliveryMode.NON_PERSISTENT); messageProperties.setContentType("UTF-8"); messageProperties.setHeader("queue", "queue2"); messageProperties.setHeader("bindType", "whereAny"); Message message1 = new Message(message.getBytes(), messageProperties); rabbitTemplate.convertAndSend("Headers.exchange.name", null, message1); } }
/**
* 如果采用的是java的Serializable接口则生产者和消费者的包名必须一致*
*
*/
@Bean
public MessageConverter messageConverter() {
return new Jackson2JsonMessageConverter();
}
消息在传输过程中,可能会出现各种异常失败甚至宕机情况,为了保证消息传输的可靠性,需要进行持久化,也就是在 数据写在磁盘上。消息队列持久化包括三部分
发送时消息持久化。(Message包含body,body为我们需要发送的消息具体内容,一般以json字符串发送,消费端再解析;MessageProperties为Message的一些额外的属性,做一些扩展作用)
public void send(String message){ MessageProperties messageProperties = new MessageProperties(); //消息持久化 MessageDeliveryMode.PERSISTENT messageProperties.setDeliveryMode(MessageDeliveryMode.PERSISTENT); messageProperties.setContentType("UTF-8"); Message message1 = new Message(message.getBytes(), messageProperties); //发送 rabbitTemplate.convertAndSend("exchange.name", null, message1); }
@Bean public Queue helloQueue() { return new Queue("hello",true); } /** *name:队列名 *durable:是否持久化 默认为 true *exclusive: 排他队列, 默认 false 如果一个队列被声明为排他队列,该队列仅对首次声明它的连接可见,并在连接断开时自动删除。这里需要注意三点:其一,排他队列是基于连接可见的,同一连接的不同信道channel是可以同时访问同一个连接创建的排他队列的。其二,“首次”,如果一个连接已经声明了一个排他队列,其他连接是不允许建立同名的排他队列的,这个与普通队列不同。其三,即使该队列是持久化的,一旦连接关闭或者客户端退出,该排他队列都会被自动删除的。这种队列适用于只限于一个客户端发送读取消息的应用场景 * autoDelete: 自动删除, 默认 false 如果该队列没有任何订阅的消费者的话,该队列会被自动删除。这种队列适用于临时队列。 */ public Queue(String name, boolean durable, boolean exclusive, boolean autoDelete) { this(name, durable, exclusive, autoDelete, (Map)null); }
@Bean public DirectExchange helloExchange(){ return new DirectExchange("helloexchange",true,false); } /** name 交换机名称 * durable 是否持久化 默认true * autoDelete 当交换机没有绑定队列时会自动删除交换机 默认false */ public DirectExchange(String name, boolean durable, boolean autoDelete) { super(name, durable, autoDelete); }
NONE 可以称之为自动回调,即使无响应或者发生异常均会通知队列消费成功,会丢失数据。 AUTO 自动检测异常或者超时事件,如果发生则返回noack,消息自动回到队尾,但是这种方式可能出现消息体本身有问题,返回队尾其他队列也不能消费,造成队列阻塞。 MANUAL 手动回调,在程序中我们可以对消息异常记性捕获,如果出现消息体格式错误问题,手动回复ack,接着再次调用发送接口把消息推到队尾。
spring: rabbitmq: username: guest password: guest port: 5672 host: localhost publisher-confirms: true # 消息发送到交换机确认机制,是否确认回调 publisher-returns: true # 消息发送到交换机确认机制,是否返回回馈 listener: # 开启ACK direct: #NONE(默认):自动;AUTO:根据情况确认;MANUAL:手动确认 acknowledge-mode: manual simple: acknowledge-mode: manual
public class MsgSendConfirmCallBack implements RabbitTemplate.ConfirmCallback,RabbitTemplate.ReturnCallback { /** * 当消息发送到交换机(exchange)时,该方法被调用. * 1.如果消息没有到exchange,则 ack=false * 2.如果消息到达exchange,则 ack=true * @param correlationData 回调id * @param ack * @param cause */ @Override public void confirm(CorrelationData correlationData, boolean ack, String cause) { System.out.println("MsgSendConfirmCallBack, 回调id:" + correlationData); if (ack) { System.out.println("消息发送到exchange成功"); } else { System.err.println("消息发送到exchange失败"); } } /** * 当消息从交换机到队列失败时,该方法被调用。(若成功,则不调用) * 需要注意的是:该方法调用后,MsgSendConfirmCallBack中的confirm方法也会被调用,且ack = true * @param message * @param replyCode * @param replyText * @param exchange * @param routingKey */ @Override public void returnedMessage(Message message, int replyCode, String replyText, String exchange, String routingKey) { System.out.println("消息从交换机到队列失败] message:"+message); } } /** * @描述 发送消息 */ @Service @Slf4j public class sendMq{ @Autowired private RabbitTemplate rabbitTemplate; public void send() { String context = Thread.currentThread().getName()+"你好现在是 " + new Date() +""; // ID标识 这样在RabbitConfirmCallBack中【消息唯一标识】 就不为空了 CorrelationData date = new CorrelationData(UUID.randomUUID().toString()); // 生产者发送消息到exchange后没有绑定的queue时将消息退回 rabbitTemplate.setReturnCallback((message, replyCode, replyText, exchange, routingKey) -> { log.info("消息主体: {}", message); log.info("回复编码: {}", replyCode); log.info("回复内容: {}", replyText); log.info("交换器: {}", exchange); log.info("路由键: {}", routingKey); log.info(Thread.currentThread().getName()+"发送消息被退回" + exchange + routingKey); }); // 生产者发送消息confirm检测 this.rabbitTemplate.setConfirmCallback((correlationData, ack, cause) -> { log.info("消息唯一标识: {}", correlationData); log.info("确认状态: {}", ack); if (!ack) { log.info("造成原因: {}", cause); } else { System.out.println(Thread.currentThread().getName()); log.info("消息发送成功 "); } }); this.rabbitTemplate.convertAndSend("Direct.exchange.name", "Direct.queue1.name",context,date); } }
//消息接收者 @RabbitListener(queues = "Direct.queue1.name") @RabbitHandler public void receiveMessageTest1(String msg, CorrelationData correlationData, Channel channel, Message message) throws Exception { log.info("消息接收者接收到来自【Direct.exchange.name】的消息,消息内容: 【{}】,【{}】,【{}】,【{}】", message,correlationData,channel,message); //消息的标识,false只确认当前一个消息收到,true确认所有consumer获得的消息 channel.basicAck(message.getMessageProperties().getDeliveryTag(), false); //ack返回false,并重新回到队列,并重新发送到消息接收者 channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true); //丢弃这条消息 channel.basicNack(message.getMessageProperties().getDeliveryTag(), false,false); //拒绝消息 deliveryTag:该消息的index requeue:被拒绝的是否重新入队列 true 重新入队列 channel.basicReject(message.getMessageProperties().getDeliveryTag(), true); }
rabbitMQ有个方法channel.basicNack()能够让消息回到队列中,这样可以实现重试。但是这样没有明确重试次数,如果当前的消息一直重试的话,则后面的消息就会堆积起来,导致后面的消息无法消费。这是一个致命的缺点。因此这就需要设置重试次数来解决这种问题。下面提供几种解决方案。 1.使用redis或者mongo等第三方存储当前重试次数。 2.在header中添加重试次数,并且使用channel.basicPublish() 方法重新将消息发送出去后将重试次数加1。 3.使用spring-rabbit中自带的retry功能
1.监听的方法内部必须使用channel进行消息确认,包括消费成功或消费失败 2.如果不手动确认,也不抛出异常,消息不会自动重新推送(包括其他消费者),因为对于rabbitmq来说始终没有接收到消息消费是否成功的确认,并且Channel是在消费端有缓存的,没有断开连接 3.如果rabbitmq断开,连接后会自动重新推送(不管是网络问题还是宕机) 4.如果消费端应用重启,消息会自动重新推送 5.如果消费端处理消息的时候宕机,消息会自动推给其他的消费者 6.如果监听消息的方法抛出异常,消息会按照listener.retry的配置进行重发,但是重发次数完了之后还抛出异常的话,消息不会重发(也不会重发到其他消费者),只有应用重启后会重新推送。因为retry是消费端内部处理的,包括异常也是内部处理,对于rabbitmq是不知道的(可使用死信队列) 7.spring.rabbitmq.listener.retry配置的重发是在消费端应用内处理的,不是rabbitqq重发 8.可以配置MessageRecoverer对异常消息进行处理,此处理会在listener.retry次数尝试完并还是抛出异常的情况下才会调可以配置MessageRecoverer对异常消息进行处理,此处理会在listener.retry次数尝试完并还是抛出异常的情况下才会调用,默认有两个实现:
//RepublishMessageRecoverer:将消息重新发送到指定队列,需手动配置,如: @Bean public MessageRecoverer messageRecoverer(RabbitTemplate rabbitTemplate){ return new RepublishMessageRecoverer(rabbitTemplate, "exchangemsxferror", "routingkeymsxferror"); } //RejectAndDontRequeueRecoverer:如果不手动配置MessageRecoverer,会默认使用这个,实现仅仅是将异常打印抛出,源码如下: public class RejectAndDontRequeueRecoverer implements MessageRecoverer { protected Log logger = LogFactory.getLog(RejectAndDontRequeueRecoverer.class); @Override public void recover(Message message, Throwable cause) { if (this.logger.isWarnEnabled()) { this.logger.warn("Retries exhausted for message " + message, cause); } throw new ListenerExecutionFailedException("Retry Policy Exhausted", new AmqpRejectAndDontRequeueException(cause), message); } }
rabbitmq: username: guest password: guest port: 5672 host: localhost publisher-confirms: true # 消息发送到交换机确认机制,是否确认回调 publisher-returns: true # 消息发送到交换机确认机制,是否返回回馈 listener: # 开启ACK direct: acknowledge-mode: manual simple: acknowledge-mode: manual retry: enabled: true # 允许消息消费失败的重试 max-attempts: 6 # 消息最多消费次数6次 initial-interval: 1000ms # 消息多次消费的间隔1秒 max-interval: 1200000ms #重试最大时间间隔(单位毫秒) multiplier: 2 #应用于上一重试间隔的乘数 即重试时间为 上次重试时间*2 stateless: true default-requeue-rejected: false # 设置为false,会丢弃消息或者重新发布到死信队列
spring: rabbitmq: username: guest password: guest port: 5672 host: localhost publisher-confirms: true # 消息发送到交换机确认机制,是否确认回调 publisher-returns: true # 消息发送到交换机确认机制,是否返回回馈 listener: # 开启ACK direct: acknowledge-mode: manual simple: acknowledge-mode: manual retry: enabled: true # 是否支持重试 max-attempts: 6 initial-interval: 5000ms multiplier: 2 stateless: true default-requeue-rejected: false
import lombok.extern.slf4j.Slf4j; import org.springframework.amqp.core.Binding; import org.springframework.amqp.core.BindingBuilder; import org.springframework.amqp.core.DirectExchange; import org.springframework.amqp.core.Queue; import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter; import org.springframework.amqp.support.converter.MessageConverter; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.HashMap; import java.util.Map; /** * @描述 */ @Configuration @Slf4j public class TestDLXConfiguration { /** * 延时队列 * 发送到该队列的message会在一段时间后过期进入到delay_process_queue * 队列里所有的message都有统一的失效时间 */ public static String DELAY_QUEUE = "delay.queue"; /** * 业务交换机 */ public static String DELAY_EXCHANGE = "delay.queue.exchange"; /** * 实际消费队列 * message失效后进入的队列,也就是实际的消费队列 */ public static final String PROCESS_QUEUE = "process.queue"; /** * 处理的交换器 */ public static String PROCESS_EXCHANGE = "process.queue.exchange"; /** * 超时时间 */ public static Long QUEUE_EXPIRATION = 4000L; /** * 配置处理交换(死信交换机) * @return */ @Bean DirectExchange processExchange() { log.info("【DLX交换机实例创建成功】"); return new DirectExchange(PROCESS_EXCHANGE); } /** * 设置处理队列(死信队列) * @return */ @Bean public Queue processQueue() { log.info("【DLX测试队列一实例创建成功】"); return QueueBuilder .durable(PROCESS_QUEUE) .build(); } /** * 将DLX绑定到实际消费队列(绑定死信队列到死信交换机) * @return */ @Bean Binding processBinding() { return BindingBuilder .bind(processQueue()) .to(processExchange()) .with(PROCESS_QUEUE); } /** * 配置业务队列 * @return */ @Bean public Queue delayQueue() { //Map
arguments = new HashMap<>(2); //arguments.put("x-dead-letter-exchange",DIRCET_EXCHANGE_NAME); // arguments.put("x-dead-letter-routing-key",TEST_QUEUE1_NAME); // arguments.put("x-message-ttl", 4000L); // return new Queue(orderQueue,true,false,false,arguments); //构造者模式 return QueueBuilder.durable(DELAY_QUEUE) // DLX,dead letter发送到的exchange ,设置死信队列交换器到处理交换器 .withArgument("x-dead-letter-exchange", PROCESS_EXCHANGE) // dead letter携带的routing key,配置处理队列的路由key .withArgument("x-dead-letter-routing-key", PROCESS_QUEUE) // 设置过期时间 当配置此事件则为延迟死信队列 .withArgument("x-message-ttl", QUEUE_EXPIRATION) .build(); } /** * 配置业务交换机 * @return */ @Bean DirectExchange delayExchange() { return new DirectExchange(DELAY_EXCHANGE); } /** * 将delayQueue2绑定延时交换机中,routingKey为队列名称 * @return */ @Bean Binding delayBinding() { return BindingBuilder .bind(delayQueue()) .to(delayExchange()) .with(DELAY_QUEUE); } } //发送 rabbitTemplate.convertAndSend( DelayConfig.DELAY_EXCHANGE, // routingKey DelayConfig.DELAY_QUEUE, msg);
rabbitTemplate.convertAndSend("Direct.exchange.name", "Direct.queue1.name", msg, message -> { message.getMessageProperties().setDeliveryMode(MessageDeliveryMode.PERSISTENT); //此处设置延迟时间 单位毫秒 message.getMessageProperties().setExpiration("10000"); return message; });
/** * 声明业务队列 * @return Queue * 在声明业务队列时,创建了一个Map,并且put了两个值,这两个值就是死信队列的声明。 * x-dead-letter-exchange:死信交换机的名称 * x-dead-letter-routing-key:死信交换机的路由键,因为demo中两个交换机的类型都是direct的,因此路由键必须相同。 */ @Bean public Queue orderQueue() { Map<String,Object> arguments = new HashMap<>(2); // 绑定该队列到死信交换机 arguments.put("x-dead-letter-exchange",DIRCET_EXCHANGE_NAME); arguments.put("x-dead-letter-routing-key",TEST_QUEUE1_NAME); // 设置过期时间 arguments.put("x-message-ttl", 4000L); return new Queue(orderQueue,true,false,false,arguments); }
docker pull rabbitmq:management
## 多个容器之间使用“--link”连接,此属性不能少;
## Erlang Cookie值必须相同,也就是RABBITMQ_ERLANG_COOKIE参数的值
docker run -d --hostname rabbit1 --name myrabbit1 -p 15672:15672 -p 5672:5672 -e RABBITMQ_ERLANG_COOKIE='rabbitcookie' rabbitmq:management
docker run -d --hostname rabbit2 --name myrabbit2 -p 15673:15672 -p 5673:5672 --link myrabbit1:rabbit1 -e RABBITMQ_ERLANG_COOKIE='rabbitcookie' rabbitmq:management
docker run -d --hostname rabbit3 --name myrabbit3 -p 15674:15672 -p 5674:5672 --link myrabbit1:rabbit1 --link myrabbit2:rabbit2 -e RABBITMQ_ERLANG_COOKIE='rabbitcookie' rabbitmq:management
docker run -d --hostname rabbit1 --name myrabbit1 -v /usr/herdsric/rabbitmq/rabbitmq1/etc/rabbitmq:/etc/rabbitmq -v /usr/herdsric/rabbitmq/rabbitmq1/lib/rabbitmq:/var/lib/rabbitmq -v /usr/herdsric/rabbitmq/rabbitmq1/log/rabbitmq:/var/log/rabbitmq -p 15672:15672 -p 5672:5672 -e RABBITMQ_ERLANG_COOKIE='rabbitcookie' rabbitmq:management
docker run -d --hostname rabbit2 --name myrabbit2 -v /usr/herdsric/rabbitmq/rabbitmq2/etc/rabbitmq:/etc/rabbitmq -v /usr/herdsric/rabbitmq/rabbitmq2/lib/rabbitmq:/var/lib/rabbitmq -v /usr/herdsric/rabbitmq/rabbitmq2/log/rabbitmq:/var/log/rabbitmq -p 15673:15672 -p 5673:5672 --link myrabbit1:rabbit1 -e RABBITMQ_ERLANG_COOKIE='rabbitcookie' rabbitmq:management
docker run -d --hostname rabbit3 --name myrabbit3 -v /usr/herdsric/rabbitmq/rabbitmq3/etc/rabbitmq:/etc/rabbitmq -v /usr/herdsric/rabbitmq/rabbitmq3/lib/rabbitmq:/var/lib/rabbitmq -v /usr/herdsric/rabbitmq/rabbitmq3/log/rabbitmq:/var/log/rabbitmq -p 15674:15672 -p 5674:5672 --link myrabbit1:rabbit1 --link myrabbit2:rabbit2 -e RABBITMQ_ERLANG_COOKIE='rabbitcookie' rabbitmq:management
物理机复制文件到容器:docker cp 物理机目录 容器名称:容器目录
容器复制文件到物理机:docker cp 容器名称:容器目录 物理机目录
docker cp -a '容器id':/var/log/rabbitmq /usr/herdsric/rabbitmq/rabbitmq3/log/
docker cp -a '容器id'://etc/rabbitmq /usr/herdsric/rabbitmq/rabbitmq3/etc/
docker cp -a '容器id':/var/lib/rabbitmq /usr/herdsric/rabbitmq/rabbitmq3/lib/
docker exec -it myrabbit1 bash
rabbitmqctl stop_app
rabbitmqctl reset
rabbitmqctl start_app
exit
# 参数“--ram”表示设置为内存节点,忽略次参数默认为磁盘节点
docker exec -it myrabbit2 bash
rabbitmqctl stop_app
rabbitmqctl reset
rabbitmqctl join_cluster --ram rabbit@rabbit1
rabbitmqctl start_app
exit
docker exec -it myrabbit3 bash
rabbitmqctl stop_app
rabbitmqctl reset
rabbitmqctl join_cluster --ram rabbit@rabbit1
rabbitmqctl start_app
exit
启动了3个节点,1个磁盘节点和2个内存节点 .容器停止需先停止磁盘节点,再停止内存节点.即需先启动
–link 的依赖。关闭相反
sr/herdsric/rabbitmq/rabbitmq3/log/rabbitmq:/var/log/rabbitmq -p 15674:15672 -p 5674:5672 --link myrabbit1:rabbit1 --link myrabbit2:rabbit2 -e RABBITMQ_ERLANG_COOKIE=‘rabbitcookie’ rabbitmq:management
###### 复制容器内容到宿主机
物理机复制文件到容器:docker cp 物理机目录 容器名称:容器目录
容器复制文件到物理机:docker cp 容器名称:容器目录 物理机目录
docker cp -a ‘容器id’:/var/log/rabbitmq /usr/herdsric/rabbitmq/rabbitmq3/log/
docker cp -a ‘容器id’/etc/rabbitmq /usr/herdsric/rabbitmq/rabbitmq3/etc/
docker cp -a ‘容器id’:/var/lib/rabbitmq /usr/herdsric/rabbitmq/rabbitmq3/lib/
###### 加入RabbitMQ节点到集群
- 设置节点1:
```shell
docker exec -it myrabbit1 bash
rabbitmqctl stop_app
rabbitmqctl reset
rabbitmqctl start_app
exit
# 参数“--ram”表示设置为内存节点,忽略次参数默认为磁盘节点
docker exec -it myrabbit2 bash
rabbitmqctl stop_app
rabbitmqctl reset
rabbitmqctl join_cluster --ram rabbit@rabbit1
rabbitmqctl start_app
exit
docker exec -it myrabbit3 bash
rabbitmqctl stop_app
rabbitmqctl reset
rabbitmqctl join_cluster --ram rabbit@rabbit1
rabbitmqctl start_app
exit
启动了3个节点,1个磁盘节点和2个内存节点 .容器停止需先停止磁盘节点,再停止内存节点.即需先启动
–link 的依赖。关闭相反