JMS
字段名称 | 含义 |
---|---|
JMSDestination | 字段包含了消息要发送到的目的地。 |
JMSDeliveryMode | 字段包含了消息在发送的时候指定的投递模式。 |
JMSMessageID | 该字段包含了服务器发送的每个消息的唯一标识。 |
JMSTimestamp | 该字段包含了消息封装完成要发往服务器的时间。不是真正向服务器发送的时间,因为真正的发送时间,可能会由于事务或客户端消息排队而延后。 |
JMSCorrelationID | 客户端使用该字段的值与另一条消息关联。一个典型的场景是使用该字段将响应消息与请求消息关联.JMSCorrelationID可以包含如下值: - 服务器规定的消息ID - 应用指定的字符串 - 服务器原生的byte[]值 |
JMSReplyTo | 该字段包含了在客户端发送消息的时候指定的Destination。即对该消息的响应应该发送到该字段指定的Destination。设置了该字段值的消息一般期望收到一个响应。 |
JMSRedelivered | 如果这个字段是true,则告知消费者应用这条消息已经发送过了,消费端应用应该小心别重复处理了。 |
JMSType | 消息发送的时候用于标识该消息的类型。具体有哪些类型,由JMS实现厂商决定。 |
JMSExpiration | 发送消息时,其到期时间将计算为send方法上指定的生存时间值与当前GMT值之和。 从send方法返回时,消息的JMSExpiration标头字段包含此值。 收到消息后,其JMSExpiration标头字段包含相同的值。 |
JMSPriority | JMS定义了一个十级优先级值,最低优先级为0,最高优先级为9。 此外,客户端应将优先级0-4视为正常优先级,将优先级5-9视为快速优先级。JMS不需要服务器严格执行消息的优先级排序; 但是,它应该尽力在普通消息之前传递加急消息。 |
架构体系
- jms client : 生产和消费基于消息的java程序
- jms producer: 创建消息jms
- jms consumer: 接收消息的jms
- jms message: 在jms客户之间传递的数据对象
- jms queue: 容器
- jms topic
对象模型
- ConectionFactory(连接工厂):用户用来创建到JMS提供者的连接的被管对象。JMS客户通过可移植的接口访问连接,这样当下层的实现改变时,代码不需要进行修改。管理员在JNDI名字空间中配置连接工厂,这样,JMS客户才能够查找到它们。根据消息类型的不同,用户将使用队列连接工厂,或者主题连接工厂。
- Connection(连接):连接代表了应用程序和消息服务器之间的通信链路。在获得了连接工厂后,就可以创建一个与JMS提供者的连接。根据不同的连接类型,连接允许用户创建会话,以发送和接收队列和主题到目标。
- Destination(目标):目标是一个包装了消息目标标识符的被管对象,消息目标是指消息发布和接收的地点,或者是队列,或者是主题。JMS管理员创建这些对象,然后用户通过JNDI发现它们。和连接工厂一样,管理员可以创建两种类型的目标,点对点模型的队列,以及发布者/订阅者模型的主题。
- Session(会话):表示一个单线程的上下文,用于发送和接收消息。由于会话是单线程的,所以消息是连续的,就是说消息是按照发送的顺序一个一个接收的。会话的好处是它支持事务。如果用户选择了事务支持,会话上下文将保存一组消息,直到事务被提交才发送这些消息。在提交事务之前,用户可以使用回滚操作取消这些消息。一个会话允许用户创建消息,生产者来发送消息,消费者来接收消息。
- MessageConsumer 接口(消息消费者):由会话创建的对象,用于接收发送到目标的消息。消费者可以同步地(阻塞模式),或(非阻塞)接收队列和主题类型的消息。
- MessageProducer 接口(消息生产者):由会话创建的对象,用于发送消息到目标。用户可以创建某个目标的发送者,也可以创建一个通用的发送者,在发送消息时指定目标
- Message 接口(消息):是在消费者和生产者之间传送的对象,也就是说从一个应用程序传送到另一个应用程序。一个
消息有三个主要部分:- 消息头(必须):包含用于识别和为消息寻找路由的操作设置。
- 一组消息属性(可选):包含额外的属性,支持其他提供者和用户的兼容。可以创建定制的字段和过滤器(消息选择器)。
- 一个消息体(可选):允许用户创建五种类型的消息(文本消息,映射消息,字节消息,流消息和对象消息)。
AMQP
概念
- Publisher:消息发送者,将消息发送到Exchange并指定RoutingKey,一遍queue可以接收到指定消息
- Consumer:消息消费者,从queue获取消息,一个Consumer可以订阅多个queue已从多个queue中接收消息
- Server:一个具体的MQ服务实例,也称Broker
- Exchange:交换器,接收Publish发送过来的消息,把消息转发到对应的Message Queue中
- Routing key:路由键,用于指定消息规则(Exchange将消息路由到queue中),通常需要和具体的Exchange类型,Binding的Routing key结合起来使用
- Bindings:制定了Exchange和Queue的绑定关系,Exchange根据消息的Routing key 和Bingding配置(绑定关系,Binding,Routing key 等)来决定把消息分派到哪些具体的queue中,这依赖于Exchange类型
- Message Queue实际存储消息的容器,并把消息传递给最终的Consumer
各中间件比较
RabbitMQ
RabbitMQ开始是用在电信业务的可靠通信的,也是少有的几款支持AMQP协议的产品之一。
优点:
1. 轻量级,快速,部署使用方便
2. 支持灵活的路由配置。RabbitMQ中,在生产者和队列之间有一个交换器模块。根据配置的路由规则,生产者发送的消息可以发送到不同的队列中。路由规则很灵活,还可以自己实现。
3. RabbitMQ的客户端支持大多数的编程语言。
缺点
1. 如果有大量消息堆积在队列中,性能会急剧下降(索引存在文件中,数据过大,会导致遍历整个文件)
2. RabbitMQ的性能在Kafka和RocketMQ中是最差的,每秒处理几万到几十万的消息。如果应用要求高的性能,不要选择RabbitMQ。
3. RabbitMQ是Erlang开发的,功能扩展和二次开发代价很高。
RocketMQ
RocketMQ是一个开源的消息队列,使用java实现。借鉴了Kafka的设计并做了很多改进。RocketMQ主要用于有序,事务,流计算,消息推送,日志流处理,binlog分发等场景。经过了历次的双11考验,性能,稳定性可可靠性没的说。
RocketMQ几乎具备了消息队列应该具备的所有特性和功能。
java开发,阅读源代码、扩展、二次开发很方便。
对电商领域的响应延迟做了很多优化。在大多数情况下,响应在毫秒级。如果应用很关注响应时间,可以使用RocketMQ
性能比RabbitMQ高一个数量级,每秒处理几十万的消息
缺点:
跟周边系统的整合和兼容不是很好
Kafka
Kafka的可靠性,稳定性和功能特性基本满足大多数的应用场景。
跟周边系统的兼容性是数一数二的,尤其是大数据和流计算领域,几乎所有相关的开源软件都支持Kafka。
Kafka高效,可伸缩,消息持久化。支持分区、副本和容错。
Kafka是Scala和Java开发的,对批处理和异步处理做了大量的设计,因此Kafka可以得到非常高的性能。它的异步消息的发送和接收是三个中最好的,但是跟RocketMQ拉不开数量级,每秒处理几十万的消息。
如果是异步消息,并且开启了压缩,Kafka最终可以达到每秒处理2000w消息的级别。
但是由于是异步的和批处理的,延迟也会高,不适合电商场景。
RabbitMQ
Exchange类型
- Fanout:会把所有发送到该交换器的消息路由到所有与该交换器绑定的队列中
- Direct:direct类型的交换器路由规则很简单,它会把消息路由到那些BindingKey和RoutingKey完全匹配的队列中
- Topic:topic类型的交换器在direct匹配规则上进行了扩展,也是将消息路由到BindingKey和RoutingKey相匹配的队列中;BindingKey和RoutingKey一样都是由"."分隔的字符串;BindingKey中可以存在两种特殊字符“”和“#”,用于模糊匹配,其中"“用于匹配一个单词,”#"用于匹配多个单词
数据存储
- 持久化消息:同时写入磁盘和内存中
- 非持久化消息:当内存不够用时,将数据写入磁盘
- rabbit_queue_index:索引使用顺序的段文件来存储,后缀为.idx,文件名从0开始累加,每个段文件中包含固定的segment_entry_count 条记录,默认值是16384。每个index从磁盘中读取消息的时候,至少要在内存中维护一个段文件,所以设置 queue_index_embed_msgs_below 值得时候要格外谨慎,一点点增大也可能会引起内存爆炸式增长。
- rabbit_msg_store:消息以键值对的形式存储到文件中,一个虚拟主机上的所有队列使用同一块存储,每个节点只有一
个。存储分为持久化存储(msg_store_persistent)和短暂存储(msg_store_transient)。持久化存储的内容在broker重启后不会丢失,短暂存储的内容在broker重启后丢失。
store使用文件来存储,后缀为.rdq,经过store处理的所有消息都会以追加的方式写入到该文件中,当该文件的大小超过指定的限制(file_size_limit)后,将会关闭该文件并创建一个新的文件以供新的消息写入。文件名从0开始进行累加。在进行消息的存储时,RabbitMQ会在ETS(Erlang TermStorage)表中记录消息在文件中的位置映射和文件的相关信息。
rabbit常用命令
# 前台启动Erlang VM和RabbitMQ
rabbitmq-server
# 后台启动
rabbitmq-server -detached
# 停止RabbitMQ和Erlang VM
rabbitmqctl stop
# 查看所有队列
rabbitmqctl list_queues
# 查看所有虚拟主机
rabbitmqctl list_vhosts
# 在Erlang VM运行的情况下启动RabbitMQ应用
rabbitmqctl start_app
rabbitmqctl stop_app
# 查看节点状态
rabbitmqctl status
# 查看所有可用的插件
rabbitmq-plugins list
# 启用插件
rabbitmq-plugins enable <plugin-name>
# 停用插件
rabbitmq-plugins disable <plugin-name>
# 添加用户
rabbitmqctl add_user username password
# 列出所有用户:
rabbitmqctl list_users
# 删除用户:
rabbitmqctl delete_user username
# 清除用户权限:
rabbitmqctl clear_permissions -p vhostpath username
# 列出用户权限:
rabbitmqctl list_user_permissions username
# 修改密码:
rabbitmqctl change_password username newpassword
# 设置用户权限:
rabbitmqctl set_permissions -p vhostpath username ".*" ".*" ".*"
# 创建虚拟主机:
rabbitmqctl add_vhost vhostpath
# 列出所以虚拟主机:
rabbitmqctl list_vhosts
# 列出虚拟主机上的所有权限:
rabbitmqctl list_permissions -p vhostpath
# 删除虚拟主机:
rabbitmqctl delete_vhost vhost vhostpath
# 移除所有数据,要在 rabbitmqctl stop_app 之后使用:
rabbitmqctl reset
用户标签和权限
Tag | Capabilities |
---|---|
(None) | 没有访问management插件的权限 |
management | 可以使用消息协议做任何操作的权限,加上: 1. 可以使用AMQP协议登录的虚拟主机的权限 2. 查看它们能登录的所有虚拟主机中所有队列、交换器和绑定的权限 3. 查看和关闭它们自己的通道和连接的权限<br4.>查看它们能访问的虚拟主机中的全局统计信息,包括其他用户的活动 |
policymaker | 所有management标签可以做的,加上: 1. 在它们能通过AMQP协议登录的虚拟主机上,查看、创建和删除策略以及虚拟主机参数的权限 |
monitoring | 所有management能做的,加上: 1. 列出所有的虚拟主机,包括列出不能使用消息协议访问的虚拟主机的权限 2. 查看其他用户连接和通道的权限 3. 查看节点级别的数据如内存使用和集群的权限 4. 查看真正的全局所有虚拟主机统计数据的权限 |
administrator | 所有policymaker和monitoring能做的,加上: 1. 创建删除虚拟主机的权限 2. 查看、创建和删除用户的权限 3. 查看、创建和删除权限的权限 4. 关闭其他用户连接的权限 |
使用
// 服务端
@Configuration
public class RabbitConfig {
@Bean
public ConnectionFactory connectionFactory() {
return new CachingConnectionFactory(URI.create("amqp://root:123456@192.168.187.128:5672/%2f"));
}
// rabbitTemplate
@Autowired
@Bean
public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
return new RabbitTemplate(connectionFactory);
}
// rabbitAdmin
@Autowired
@Bean
public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory){
return new RabbitAdmin(connectionFactory);
}
@Bean
public Queue queue() {
return QueueBuilder.nonDurable("queue.ex").build();
}
@Bean
public Exchange exchange() {
return new FanoutExchange("fanout.ex",false,false,null );
}
@Bean
@Autowired
public Binding binding(Queue queue,Exchange exchange) {
return BindingBuilder.bind(queue).to(exchange).with("key.ex").noargs();
}
}
客户端
// 拉模式
@Configuration
public class RabbitConfig {
@Bean
public ConnectionFactory connectionFactory() {
return new CachingConnectionFactory(URI.create("amqp://root:123456@192.168.187.128:5672/%2f"));
}
// rabbitTemplate
@Autowired
@Bean
public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) {
return new RabbitTemplate(connectionFactory);
}
// rabbitAdmin
@Autowired
@Bean
public RabbitAdmin rabbitAdmin(ConnectionFactory connectionFactory){
return new RabbitAdmin(connectionFactory);
}
@Bean
public Queue queue() {
return QueueBuilder.nonDurable("queue.ex").build();
}
}
// 监听模式
@Bean
@Autowired
public SimpleRabbitListenerContainerFactory simpleRabbitListenerContainerFactory(ConnectionFactory connectionFactory) {
SimpleRabbitListenerContainerFactory factory = new SimpleRabbitListenerContainerFactory();
factory.setConnectionFactory(connectionFactory);
factory.setAcknowledgeMode(AcknowledgeMode.AUTO);
//并发获取消息
factory.setConcurrentConsumers(10);
factory.setMaxConcurrentConsumers(15);
//按照批次消费消息
factory.setBatchSize(10);
return factory;
}
/**
* com.rabbitmq.client.Channel channel对象
* org.springframework.amqp.core.Message message对象 可以直接操作原生的AMQP消息
* org.springframework.messaging.Message to use the messaging abstraction counterpart
* @Payload 注解方法参数,改参数的值就是消息体
* @Header 注解方法参数,访问指定的消息头字段的值
* @Headers 该注解的方法参数获取该消息的消息头的所有字段,参数类型对应于map集合。
* MessageHeaders 参数类型,访问所有消息头字段
* MessageHeaderAccessor or AmqpMessageHeaderAccessor 访问所有消息头字段
*/
@RabbitListener(queues = "queue.ex")
public void whenMessageCome(Message message) throws UnsupportedEncodingException {
System.out.println(new String(message.getBody(),message.getMessageProperties().getContentEncoding()));
}
演示代码地址
消息可靠性
异常捕获机制
发送端确认机制
RabbitMQ后来引入了一种轻量量级的方式,叫发送方确认(publisher confirm)机制。生产者将信道设置成confirm(确认)模式,一旦信道进入confirm 模式,所有在该信道上⾯面发布的消息都会被指派一个唯一的ID(从1 开始),一旦消息被投递到所有匹配的队列之后(如果消息和队列是持久化的,那么确认消息会在消息持久化后发出),RabbitMQ 就会发送一个确认(Basic.Ack)给生产者(包含消息的唯一ID),这样生产者就知道消息已经正确送达了。
channel.exchangeDeclare(EX_PUBLISHER_CONFIRMS, BuiltinExchangeType.DIRECT);
channel.queueDeclare(QUEUE_PUBLISHER_CONFIRMS, false, false, false, null);
channel.queueBind(QUEUE_PUBLISHER_CONFIRMS, EX_PUBLISHER_CONFIRMS,QUEUE_PUBLISHER_CONFIRMS);
String message = "hello";
channel.basicPublish(EX_PUBLISHER_CONFIRMS, QUEUE_PUBLISHER_CONFIRMS, null,
message.getBytes());
try {
channel.waitForConfirmsOrDie(5_000);
System.out.println("消息被确认:message = " + message);
} catch (IOException e) {
e.printStackTrace();
System.err.println("消息被拒绝! message = " + message);
} catch (InterruptedException e) {
e.printStackTrace();
System.err.println("在不是Publisher Confirms的通道上使用该方法");
} catch (TimeoutException e) {
e.printStackTrace();
System.err.println("等待消息确认超时! message = " + message);
}
持久化存储机制
持久化是提高RabbitMQ可靠性的基础,否则当RabbitMQ遇到异常时(如:重启、断电、停机等)数据将会丢失。主要从以下几个方面来保障消息的持久性:
- Exchange的持久化。通过定义时设置durable 参数为ture来保证Exchange相关的元数据不不丢失。
- Queue的持久化。也是通过定义时设置durable 参数为ture来保证Queue相关的元数据不不丢失。
- 消息的持久化。通过将消息的投递模式 (BasicProperties 中的 deliveryMode 属性)设置为 2即可实现消息的持久化,保证消息自身不丢失。
RabbitMQ中的持久化消息都需要写入磁盘(当系统内存不不足时,非持久化的消息也会被刷盘处理理),这些处理理动作都是在“持久层”中完成的。持久层是一个逻辑上的概念,实际包含两个部分: - 队列索引(rabbit_queue_index),rabbit_queue_index 负责维护Queue中消息的信息,包括消息的存储位置、是否已交给消费者、是否已被消费及Ack确认等,每个Queue都有与之对应的rabbit_queue_index。
- 消息存储(rabbit_msg_store),rabbit_msg_store 以键值对的形式存储消息,它被所有队列列共享,在每个节点中有且只有一个。
Consumer ACK
前面我们讲了生产者发送确认机制和消息的持久化存储机制,然而这依然无法完全保证整个过程的可靠性,因为如果消息被消费过程中业务处理失败了但是消息却已经出列了(被标记为已消费了),我们又没有任何重试,那结果跟消息丢失没什么分别。
RabbitMQ在消费端会有Ack机制,即消费端消费消息后需要发送Ack确认报文给Broker端,告知自己是否已消费完成,否则可能会一直重发消息直到消息过期(AUTO模式)。
这也是我们之前一直在讲的“最终一致性”、“可恢复性” 的基础。
一般而言,我们有如下处理手段:
1. 采用NONE模式,消费的过程中自行捕获异常,引发异常后直接记录日志并落到异常恢复表,再通过后台定时任务扫描异常恢复表尝试做重试动作。如果业务不自行处理则有丢失数据的风险
2. 采用AUTO(自动Ack)模式,不主动捕获异常,当消费过程中出现异常时会将消息放回Queue中,然后消息会被重新分配到其他消费者节点(如果没有则还是选择当前节点)重新被消费,默认会一直重发消息并直到消费完成返回Ack或者一直到过期
3. 采用MANUAL(手动Ack)模式,消费者自行控制流程并手动调用channel相关的方法返回Ack
/**
* NONE模式,则只要收到消息后就立即确认(消息出列,标记已消费),有丢失数据的风险
* AUTO模式,看情况确认,如果此时消费者抛出异常则消息会返回到队列中
* MANUAL模式,需要显式的调用当前channel的basicAck方法
* @param channel
* @param deliveryTag
* @param message
*/
@RabbitListener(queues = "lagou.topic.queue", ackMode = "AUTO")
public void handleMessageTopic(Channel channel,@Header(AmqpHeaders.DELIVERY_TAG) long deliveryTag, @Payload byte[] message) {
System.out.println("RabbitListener消费消息,消息内容:" + new String((message)));
try {
// 手动ack,deliveryTag表示消息的唯一标志,multiple表示是否是批量确认
channel.basicAck(deliveryTag, false);
// 手动nack,告诉broker消费者处理失败,最后一个参数表示是否需要将消息重新
入列
channel.basicNack(deliveryTag, false, true);
// 手动拒绝消息。第二个参数表示是否重新入列
channel.basicReject(deliveryTag, true);
} catch (IOException e) {
e.printStackTrace();
}
}
SpringBoot项目中支持如下的一些配置:
#最大重试次数
spring.rabbitmq.listener.simple.retry.max-attempts=5
#是否开启消费者重试(为false时关闭消费者重试,意思不是“不重试”,而是一直收到消息直到jack
确认或者一直到超时)
spring.rabbitmq.listener.simple.retry.enabled=true
#重试间隔时间(单位毫秒)
spring.rabbitmq.listener.simple.retry.initial-interval=5000
# 重试超过最大次数后是否拒绝
spring.rabbitmq.listener.simple.default-requeue-rejected=false
#ack模式
spring.rabbitmq.listener.simple.acknowledge-mode=manual
消费端限流
RabbitMQ中有一种QoS保证机制,可以限制Channel上接收到的未被Ack的消息数量,如果超过这个数量限制RabbitMQ将不会再往消费端推送消息。这是一种流控手段,可以防止大量消息瞬时从Broker送达消费端造成消费端巨大压力(甚至压垮消费端)。比较值得注意的是QoS机制仅对于消费端推模式有效,对拉模式无效。而且不支持NONE Ack模式。执行channel.basicConsume 方法之前通过 channel.basicQoS 方法可以设置该数量。消息的发送是异步的,消息的确认也是异步的。在消费者消费慢的时候,可以设置Qos的prefetchCount,它表示broker在向消费者发送消息的时候,一旦发送了prefetchCount个消息而没有一个消息确认的时候,就停止发送。消费者确认一个,broker就发送一个,确认两个就发送两个。换句话说,消费者确认多少,broker就发送多少,消费者等待处理的个数永远限制在prefetchCount个。
如果对于每个消息都发送确认,增加了网络流量,此时可以批量确认消息。如果设置了multiple为true,消费者在确认的时候,比如说id是8的消息确认了,则在8之前的所有消息都确认了。
@Bean
public RabbitListenerContainerFactory rabbitListenerContainerFactory(ConnectionFactory connectionFactory) {
// SimpleRabbitListenerContainerFactory发现消息中有content_type有text
就会默认将其
// 转换为String类型的,没有content_type都按byte[]类型
SimpleRabbitListenerContainerFactory factory = new
SimpleRabbitListenerContainerFactory();
factory.setConnectionFactory(connectionFactory);
// 设置并发线程数
factory.setConcurrentConsumers(10);
// 设置最大并发线程数
factory.setMaxConcurrentConsumers(20);
return factory;
}
消息可靠性保障
消息可靠传输一般是业务系统接入消息中间件时首要考虑的问题,一般消息中间件的消息传输保障分为三个层级:
1. At most once:最多一次。消息可能会丢失,但绝不会重复传输
2. At least once:最少一次。消息绝不会丢失,但可能会重复传输
3. Exactly once:恰好一次。每条消息肯定会被传输一次且仅传输一次
可靠性分析
在使用任何消息中间件的过程中,难免会出现消息丢失等异常情况,这个时候就需要有一个良好的机制来跟踪记录消息的过程(轨迹溯源),帮助我们排查问题。
在RabbitMQ 中可以使用Firehose 功能来实现消息追踪,Firehose 可以记录每一次发送或者消费消息的记录,方便RabbitMQ 的使用者进行调试、排错等。
Firehose 的原理是将生产者投递给RabbitMQ 的消息,或者RabbitMQ 投递给消费者的消息按照指定的格式发送到默认的交换器上。这个默认的交换器的名称为 amq.rabbitmq.trace ,它是一个topic 类型的交换器。发送到这个交换器上的消息的路由键为 publish.{exchangename} 和 deliver.{queuename} 。其中 exchangename 和 queuename 为交换器和队列的名称,分别对应生产者投递到交换器的消息和消费者从队列中获取的消息。
TTL(Time To Live超时机制)
RabbitMQ 可以对消息和队列两个维度来设置TTL。
任何消息中间件的容量和堆积能力都是有限的,如果有一些消息总是不被消费掉,那么需要有一种过期的机制来做兜底。
目前有两种方法可以设置消息的TTL。
1. 通过Queue属性设置,队列中所有消息都有相同的过期时间。
2. 对消息自身进行单独设置,每条消息的TTL 可以不同
try(Connection connection = factory.newConnection();Channel channel = connection.createChannel()) {
// 创建队列(实际上使用的是AMQP default这个direct类型的交换器)
// 设置队列属性
Map<String, Object> arguments = new HashMap<>();
// 设置队列的TTL,有效期是30000
arguments.put("x-message-ttl", 30000);
// 设置队列的空闲存活时间(如该队列根本没有消费者,一直没有使用,队列可以存活多久)
arguments.put("x-expires", 10000);
channel.queueDeclare(QUEUE_NAME, false, false, false, arguments);
for (int i = 0; i < 1000000; i++) {
String message = "Hello World!" + i;
channel.basicPublish("",QUEUE_NAME,
new AMQP.BasicProperties().builder().expiration("30000").build(),message.getBytes());
System.out.println(" [X] Sent '" + message + "'");
}
} catch (TimeoutException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
死信队列
在定义业务队列时可以考虑指定一个 死信交换机,并绑定一个死信队列。当消息变成死信时,该消息就会被发送到该死信队列上,这样方便我们查看消息失败的原因。
DLX,全称为Dead-Letter-Exchange,死信交换器。消息在一个队列中变成死信(Dead Letter)之后,被重新发送到一个特殊的交换器(DLX)中,同时,绑定DLX的队列就称为“死信队列”。
以下几种情况导致消息变为死信:
1. 消息被拒绝(Basic.Reject/Basic.Nack),并且设置requeue参数为false;
2. 消息过期;
3. 队列达到最大长度。
对于RabbitMQ 来说,DLX 是一个非常有用的特性。它可以处理异常情况下,消息不能够被消费者正确消费(消费者调用了Basic.Nack 或者Basic.Reject)而被置入死信队列中的情况,后续分析程序可以通过消费这个死信队列中的内容来分析当时所遇到的异常情况,进而可以改善和优化系统
@Configuration
public class RabbitConfig {
@Bean
public Queue queue() {
Map<String, Object> props = new HashMap<>();
// 消息的生存时间 10s
props.put("x-message-ttl", 10000);
// 设置该队列所关联的死信交换器(当队列消息TTL到期后依然没有消费,则加
入死信队列)
props.put("x-dead-letter-exchange", "ex.go.dlx");
// 设置该队列所关联的死信交换器的routingKey,如果没有特殊指定,使用原
队列的routingKey
props.put("x-dead-letter-routing-key", "go.dlx");
Queue queue = new Queue("q.go", true, false, false, props);
return queue;
}
@Bean
public Queue queueDlx() {
Queue queue = new Queue("q.go.dlx", true, false, false);
return queue;
}
@Bean
public Exchange exchange() {
DirectExchange exchange = new DirectExchange("ex.go", true,
false, null);
return exchange;
}
/**
* 死信交换器
* @return
*/
@Bean
public Exchange exchangeDlx() {
DirectExchange exchange = new DirectExchange("ex.go.dlx",
true, false, null);
return exchange;
}
@Bean
public Binding binding() {
return BindingBuilder.bind(queue()).to(exchange()).with("go").noargs();
}
/**
* 死信交换器绑定死信队列
* @return
*/
@Bean
public Binding bindingDlx() {
return BindingBuilder.bind(queueDlx()).to(exchangeDlx()).with("go.dlx").noargs();
}
}
延迟队列
- TTL存放消息在死信队列(delayqueue)里
- 二基于插件存放消息在延时交换机里(x-delayed-message exchange)
@Bean
public Exchange exchange() {
Map<String, Object> arguments = new HashMap<>();
// 使用x-delayed-type指定交换器的类型
arguments.put("x-delayed-type", ExchangeTypes.DIRECT);
// 使用x-delayed-message表示使用delayed exchange插件处理消息
return new CustomExchange("ex.delayed", "x-delayed-message", true, false, arguments);
}
RabbitMQ集群和运维
1.分片可以扩容
2.主备可以高可用
HAProxy负载均衡
支持四层,七层负载均衡