RabbitMQ实战总结
一 RabbitMQ介绍
1. RabbitMQ概念
RabbitMQ是AMQP(高级消息队列协议)的一种实现,主要用来对消息进行发送,存储和转发。
下图是RabbitMQ的整体架构模型:

从整体架构模型理解RabbitMQ,RabbitMQ主要包括下面的组成部分:
1. 生产者Producer,用来生产消息,可以是普通文本,或是JSON字符串,或是XML消息,会作为MQ的消息来源。
2. 消费者Consumer,消息接收方,用来获取MQ中的消息(普通文本、JSON或XML格式等等),进行业务逻辑处理。
3. Exchange交换机和路由键BindingKey,用来接收生产者的消息,通过不同的组合来确定将消息发送到哪个队列。
4.Queue队列,用来存储生产的消息,并提供给消费段进行消息处理。
2. RabbitMQ特点
- 异步消息处理,Queue可以对消息进行持久化存储,当Consumer需要的时候,再拉取消息进行处理
- 流量削峰,Consumer在拉取消息消费是,可以指定每秒消费的消息条数,减缓消费端的压力。
- 系统解耦,上下游的生产端和消费端完全无关,可以用不同的语言编写,可以是不同的系统。
- 横向扩展,RabbitMQ支持横向扩展机制,大流量情况下,可以增大服务器的平均负载能力。
- 其他特点,比如RabbitMQ提供丰富的插件,并且支持集群负载均衡和高可用等等。
二 RabbitMQ Client
1. 交换机声明

public DeclareOk exchangeDeclare(String exchange, BuiltinExchangeType type, boolean durable, boolean autoDelete, boolean internal, Map<String, Object> arguments) throws IOException {...}
参数解释:
exchange,交换机的名字,自定义。
BuiltinExchangeType ,交换机的类型。
durable,是否持久化,这里设置成true。
autoDelete,是否自动删除,这里设置成false。
internal,内部使用,这里设置为false。
arguments, 交换机可能会有的其他参数,设置成NULL。
交换机的类型介绍,交换机类型决定消息的发送规则
direct(直连交换机),绑定了该交换机,并且绑定了指定路由键的队列才能接收到消息。
fanout(扇形交换机), 不需要路由键,绑定了该交换机的队列,都能接收到消息。
top(发布订阅交换机),绑定了交换机,并且绑定了指定路由键(支持正则表达式匹配)的队列能收到消息。
2. 队列声明

public com.rabbitmq.client.impl.AMQImpl.Queue.DeclareOk queueDeclare(String queue, boolean durable, boolean exclusive, boolean autoDelete, Map<String, Object> arguments) throws IOException{...}
参数解释:
queue,队列名字,自定义。
durable,队列是否持久化,建议为true(将更多的消息放在磁盘存储,然后再消费,节约内存)。
exclusive,排他队列,设置为false。
autoDelete,设置为false,队列不能自动删除,不然数据咋办?
arguments,附加的参数。
arguments附加参数具体有哪些:
- x-message-ttl 消息过期时间,队列中的消息最长存活时间。
- x-expires 队列的过期时间。
- x-max-length 队列能够保存消息的最大条数。
- x-max-length-bytes 队列中消息大小限定的最大值。
- x-dead-letter-exchange 死信队列的交换机,若队列需要绑定到私信队列,则需要设置该参数。
- x-dead-letter-routing-key 死信队列的路由键,若队列需要私信队列,则需要制定键,否则用自己的路由键进行匹配
- x-max-priority 队列支持的消息最大优先级(设置为数字), 发送消息时,单独设置消息的优先级,比该数字小
3. 绑定(队列)

public com.rabbitmq.client.impl.AMQImpl.Queue.BindOk queueBind(String queue, String exchange, String routingKey) throws IOException {...}
4. 消息发送

public void basicPublish(String exchange, String routingKey, boolean mandatory, boolean immediate, BasicProperties props, byte[] body) throws IOException {...}
exchange, 要发送的交换机名字
routingKey,指定路由键
mandatory, 发送消息应答机制,true表示MQ服务器会对消息返回是否收到的信息,false表示不做任何回应
immediate,设置成false
BasicProperties, 单条消息的其他属性。
byte[], 消息体,可以使JSON或者XML或者String
BasicProperties,消息其他属性介绍(主要设置两个参数):
deliveryMode 设置成2 表示,消息可以持久化存储,设置成1 重启RabbitMQ,消息将丢失
contentType,设置消息体的格式,比如JSON格式为"applicaion/json"
messageId, 消息唯一标识符

5. 生产者消息确认机制
在消息未发送到交换机上的时候,可以在发送之前注入ReturnListener,异步回调进入该接口内
channel.addReturnListener((replyCode, replyText, exchange, routingKey, properties, body) -> {});
在消息发送到交换机时,可以设置信道Channel为confirm模式,MQ收到消息会异步回调该接口
channel.confirmSelect(); channel.addConfirmListener(new ConfirmListener() { @Override public void handleAck(long seg, boolean ack) throws IOException {} @Override public void handleNack(long seg, boolean ack) throws IOException {} });
6. 消费端处理
消费端消息限流,高并发情况下,消费端可以设置每秒消费消息的条数。

public void basicQos(int prefetchSize, int prefetchCount, boolean global) throws IOException { this.exnWrappingRpc(new Qos(prefetchSize, prefetchCount, global)); }
basicRecover:是路由不成功的消息可以使用recovery重新发送到队列中。
basicReject:是接收端告诉服务器这个消息我拒绝接收,不处理,可以设置是否放回到队列中还是丢掉,而且只能一次拒绝一个消息,官网中有明确说明不能批量拒绝消息,为解决批量拒绝消息才有了basicNack。
basicNack:可以一次拒绝N条消息,客户端可以设置basicNack方法的multiple参数为true,服务器会拒绝指定了delivery_tag的所有未确认的消息(tag是一个64位的long值,最大值是9223372036854775807)。

三 springboot整合RabbitMQ
1. 引入依赖文件
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
2. yml配置文件配置
server: port: 8081 spring: rabbitmq: host: 192.168.0.21 port: 5672 addresses: 192.168.0.21:5672 username: pengjunjie password: pengjunjie virtual-host: /pengjunjie cache: connection: size: 1 channel: size: 30 publisher-confirms: true publisher-returns: true
3. RabbitConfig.java
生产者配置,ConnectionFactory配置。
- 生产者mandatory设置成true,增加消息confirm机制
- 添加ReturnCallback和ConfirmCallback,进行消息发送异步确认
- 有必要的情况下,可以对发送的消息进行缓存,收到确认之后清除消息。
/** * @author pengjunjie */ public class QueueConstant { /** 订单队列 */ public static final String ORDER_EXCHANGE = "order_exchange"; public static final String ORDER_BIND_KEY = "order_bind_key"; public static final String ORDER_QUEUE = "order"; /** 死信队列 */ public static final String DEAD_EXCHANGE = "dead_exchange"; public static final String DEAD_BIND_KEY = "dead_bind_key"; public static final String DEAD_QUEUE = "dead";
import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.amqp.core.Message; import org.springframework.amqp.rabbit.connection.CachingConnectionFactory; import org.springframework.amqp.rabbit.connection.ConnectionFactory; import org.springframework.amqp.rabbit.connection.CorrelationData; import org.springframework.amqp.rabbit.connection.RabbitConnectionFactoryBean; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter; import org.springframework.boot.autoconfigure.amqp.RabbitProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; /** * @author pengjunjie * @date 2019.2.27 */ @Configuration public class RabbitConfig { private static final Logger logger = LoggerFactory.getLogger(RabbitConfig.class); /** ConnectionFactory配置 */ @Bean public ConnectionFactory connectionFactory(RabbitProperties properties) throws Exception { RabbitConnectionFactoryBean factoryBean = new RabbitConnectionFactoryBean(); factoryBean.setHost(properties.getHost()); factoryBean.setVirtualHost(properties.getVirtualHost()); factoryBean.setPort(properties.getPort()); factoryBean.setUsername(properties.getUsername()); factoryBean.setPassword(properties.getPassword()); factoryBean.afterPropertiesSet(); CachingConnectionFactory cachingConnectionFactory = new CachingConnectionFactory(factoryBean.getObject()); if (properties.getAddresses() != null) { cachingConnectionFactory.setAddresses(properties.getAddresses()); } if (properties.isPublisherConfirms()) { cachingConnectionFactory.setPublisherConfirms(true); } if (properties.isPublisherReturns()) { cachingConnectionFactory.setPublisherReturns(true); } RabbitProperties.Cache cache = properties.getCache(); if (cache.getChannel().getSize() != null) { cachingConnectionFactory.setChannelCacheSize(cache.getChannel().getSize()); } if (cache.getConnection().getSize() != null) { cachingConnectionFactory.setConnectionCacheSize(cache.getConnection().getSize()); } return cachingConnectionFactory; } /** RabbitTemplate生产者配置 */ @Bean public RabbitTemplate rabbitTemplate(ConnectionFactory connectionFactory) { RabbitTemplate rabbitTemplate = new RabbitTemplate(connectionFactory); Jackson2JsonMessageConverter jackson2JsonMessageConverter = new Jackson2JsonMessageConverter(); rabbitTemplate.setMandatory(true); rabbitTemplate.setEncoding("UTF-8"); rabbitTemplate.setMessageConverter(jackson2JsonMessageConverter); /** return message */ rabbitTemplate.setReturnCallback((Message message, int replyCode, String replyText, String exchange, String routingKey) -> { /** Redis缓存处理 */ logger.error("===> 消息未发送到交换机 message={}, replyCode={}, replyText={}, " + "exchange={}, routingKey={}", message, replyCode, replyText, exchange, routingKey); }); /** confirm listener */ rabbitTemplate.setConfirmCallback((CorrelationData correlationData, boolean ack, String cause) -> { /** Redis缓存处理 */ if (ack) { logger.info("===> 消息发送成功 id={}, message={}", correlationData.getId(), correlationData.getReturnedMessage()); } else { logger.error("===> 消息发送失败 id={}, message={}, cause={}", correlationData.getId(), correlationData.getReturnedMessage(), cause); } }); return rabbitTemplate; } }
4. 发送消息
封装自己的RabbitMessage.java
/** * @author pengjunjie */ public class RabbitMessage { private String routeKey; private String bindKey; private Object source; public String getRouteKey() { return routeKey; } public void setRouteKey(String routeKey) { this.routeKey = routeKey; } public String getBindKey() { return bindKey; } public void setBindKey(String bindKey) { this.bindKey = bindKey; } public Object getSource() { return source; } public void setSource(Object source) { this.source = source; } @Override public String toString() { return "RabbitMessage{" + "routeKey='" + routeKey + '\'' + ", bindKey='" + bindKey + '\'' + ", source=" + source + '}'; } }
消息发送器RabbitSender.java
import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.amqp.core.Message; import org.springframework.amqp.core.MessageDeliveryMode; import org.springframework.amqp.core.MessageProperties; import org.springframework.amqp.rabbit.connection.CorrelationData; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; import java.util.UUID; /** * @author pengjunjie */ @Component public class RabbitSender { private static final Logger logger = LoggerFactory.getLogger(RabbitSender.class); @Autowired private RabbitTemplate rabbitTemplate; public String send(RabbitMessage rabbitMessage) { String msgId = UUID.randomUUID().toString(); MessageProperties properties = new MessageProperties(); properties.setContentType("application/json"); properties.setDeliveryMode(MessageDeliveryMode.PERSISTENT); properties.setMessageId(msgId); properties.setContentEncoding("utf-8"); CorrelationData data = new CorrelationData(); data.setId(msgId); ObjectMapper objectMapper = new ObjectMapper(); byte[] bytes = new byte[0]; try { bytes = objectMapper.writeValueAsBytes(rabbitMessage.getSource()); } catch (JsonProcessingException e) { logger.error("===> 消息发送失败 {}", e.toString()); } Message message = new Message(bytes, properties); rabbitTemplate.convertAndSend(rabbitMessage.getRouteKey(), rabbitMessage.getBindKey(), message, msg -> msg, data); logger.info("====> 消息发送成功 {}", message); return msgId; } }
5. 创建死信队列和订单队列
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.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import java.util.HashMap; import java.util.Map; /** * @author pengjunjie */ @Configuration public class QueueConfig { @Bean public Queue order() { Map<String, Object> args = new HashMap<>(2); args.put("x-dead-letter-exchange", QueueConstant.DEAD_EXCHANGE); args.put("x-dead-letter-routing-key", QueueConstant.DEAD_BIND_KEY); Queue queue = new Queue(QueueConstant.ORDER_QUEUE, true, false, false, args); return queue; } @Bean public DirectExchange orderExchange() { DirectExchange directExchange = new DirectExchange(QueueConstant.ORDER_EXCHANGE, true, false); return directExchange; } @Bean public Binding orderBinding() { return BindingBuilder.bind(order()).to(orderExchange()).with(QueueConstant.ORDER_BIND_KEY); } @Bean public Queue dead() { Queue queue = new Queue(QueueConstant.DEAD_QUEUE, true, false, false); return queue; } @Bean public DirectExchange deadExchange() { DirectExchange directExchange = new DirectExchange(QueueConstant.DEAD_EXCHANGE, true, false); return directExchange; } @Bean public Binding deadBinding() { return BindingBuilder.bind(dead()).to(deadExchange()).with(QueueConstant.DEAD_BIND_KEY); } }
6. 消费端消费消息
import org.springframework.amqp.core.AcknowledgeMode; import org.springframework.amqp.rabbit.annotation.RabbitHandler; import org.springframework.amqp.rabbit.annotation.RabbitListener; import org.springframework.amqp.rabbit.connection.ConnectionFactory; import org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer; import org.springframework.amqp.rabbit.listener.api.ChannelAwareMessageListener; import org.springframework.amqp.support.converter.Jackson2JsonMessageConverter; import org.springframework.context.annotation.Bean; import org.springframework.stereotype.Component; /** * @author pengjunjie */ @Component @RabbitListener(queues = {QueueConstant.ORDER_QUEUE}) public class OrderConsumer1 { @RabbitHandler public void process(Object msg) { System.out.println(msg); } } /** 第二种消费方式 */ @Component class OrderConsumer2 { @Bean public SimpleMessageListenerContainer orderListener(ConnectionFactory connectionFactory) { SimpleMessageListenerContainer listenerContainer = new SimpleMessageListenerContainer(connectionFactory); /** 设置消费的队列 */ listenerContainer.addQueueNames(QueueConstant.ORDER_QUEUE); /** 设置同时几个消费者线程进行消费 */ listenerContainer.setConcurrentConsumers(3); /** 最多多少个线程进行消费 */ listenerContainer.setMaxConcurrentConsumers(10); /** 手动应答模式 */ listenerContainer.setAcknowledgeMode(AcknowledgeMode.MANUAL); /** 每次拉取100条消息 */ listenerContainer.setPrefetchCount(100); listenerContainer.setMessageConverter(new Jackson2JsonMessageConverter()); listenerContainer.setMessageListener((ChannelAwareMessageListener) (message, channel) -> { /** 消息的业务逻辑在这里处理,手动应答机制 */ System.out.println(message); channel.basicAck(message.getMessageProperties().getDeliveryTag(), false); }); return listenerContainer; } }

浙公网安备 33010602011771号