一、pom.xml依赖
<!-- rabbitMQ -->
<dependency>
<groupId>com.rabbitmq</groupId>
<artifactId>amqp-client</artifactId>
<version>3.5.1</version>
</dependency>
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit</artifactId>
<version>1.4.5.RELEASE</version>
</dependency>
二、生产者
1、spring-rabbitmq.xml配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:rabbit="http://www.springframework.org/schema/rabbit"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd
http://www.springframework.org/schema/rabbit http://www.springframework.org/schema/rabbit/spring-rabbit-1.0.xsd"
default-lazy-init="true">
<description>rabbitmq 连接服务配置</description>
<!-- 加载配置属性文件 -->
<context:property-placeholder ignore-unresolvable="true" location="classpath:weijinrong.properties" />
<!-- 消息对象json转换类 -->
<bean id="jsonMessageConverter" class="org.springframework.amqp.support.converter.Jackson2JsonMessageConverter" />
<!-- 连接配置 -->
<rabbit:connection-factory id="connectionFactory" host="${mq.host}" username="${mq.username}" password="${mq.password}" port="${mq.port}"/>
<rabbit:admin connection-factory="connectionFactory"/>
<!-- spring template声明 -->
<rabbit:template id="amqpTemplate" exchange="test-mq-exchange" connection-factory="connectionFactory" message-converter="jsonMessageConverter" />
<!-- 消息队列Queue
durable:是否持久化
exclusive: 仅创建者可以使用的私有队列,断开后自动删除
auto_delete: 当所有消费客户端连接断开后,是否自动删除队列 -->
<rabbit:queue id="test_queue_id" name="test_queue_name" durable="true" auto-delete="false" exclusive="false" />
<!-- 声明一个Exchange
rabbit:direct-exchange:定义exchange模式为direct,意思就是消息与一个特定的路由键完全匹配,才会转发。
rabbit:binding:设置消息queue匹配的key -->
<rabbit:direct-exchange name="test-mq-exchange" durable="true" auto-delete="false" id="test-mq-exchange">
<rabbit:bindings>
<rabbit:binding queue="test_queue_name" key="test_queue_routing_key"/>
</rabbit:bindings>
</rabbit:direct-exchange>
</beans>
2、测试类
/**
* rabbitMQ测试类
*/
import java.util.HashMap;
import java.util.Map;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
@RunWith(value = SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {"classpath:/spring-context-rabbitMQ.xml"})
public class TestQueue {
@Autowired
private AmqpTemplate amqpTemplate;
private final String queue_key = "test_queue_routing_key";
@Test
public void send(){
// Map<String,Object> msg = new HashMap<String,Object>();
// msg.put("data","hello,rabbmitmq!");
amqpTemplate.convertAndSend(queue_key, "123");
}
}
三、消费者
spring-rabbitmq.xml配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:rabbit="http://www.springframework.org/schema/rabbit"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.2.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.2.xsd
http://www.springframework.org/schema/rabbit http://www.springframework.org/schema/rabbit/spring-rabbit-1.0.xsd"
default-lazy-init="true">
<description>rabbitmq 连接服务配置</description>
<!-- 加载配置属性文件 -->
<context:property-placeholder ignore-unresolvable="true" location="classpath:weijinrong.properties" />
<!-- 消息对象json转换类 -->
<bean id="jsonMessageConverter" class="org.springframework.amqp.support.converter.Jackson2JsonMessageConverter" />
<!-- 连接配置 -->
<rabbit:connection-factory id="connectionFactory" host="${mq.host}" username="${mq.username}" password="${mq.password}" port="${mq.port}"/>
<rabbit:admin connection-factory="connectionFactory"/>
<!-- spring template声明 -->
<rabbit:template id="amqpTemplate" exchange="test-mq-exchange" connection-factory="connectionFactory" message-converter="jsonMessageConverter" />
<!-- 消息队列Queue
durable:是否持久化
exclusive: 仅创建者可以使用的私有队列,断开后自动删除
auto_delete: 当所有消费客户端连接断开后,是否自动删除队列 -->
<rabbit:queue id="test_queue_id" name="test_queue_name" durable="true" auto-delete="false" exclusive="false" />
<!-- 声明一个Exchange
rabbit:direct-exchange:定义exchange模式为direct,意思就是消息与一个特定的路由键完全匹配,才会转发。
rabbit:binding:设置消息queue匹配的key -->
<rabbit:direct-exchange name="test-mq-exchange" durable="true" auto-delete="false" id="test-mq-exchange">
<rabbit:bindings>
<rabbit:binding queue="test_queue_name" key="test_queue_routing_key"/>
</rabbit:bindings>
</rabbit:direct-exchange>
<!-- 监听器 -->
<bean id="queueListenter" class="com.autoserve.weijinrong.rytc.QueueListenter"/>
<!-- 配置监听queue -->
<rabbit:listener-container connection-factory="connectionFactory" acknowledge="auto">
<rabbit:listener queues="test_queue_name" ref="queueListenter"/>
</rabbit:listener-container>
</beans>
监听器实体类
/**
* rabbitMQ监听器
* @author lzp
*
*/
public class QueueListenter implements MessageListener{
@Override
public void onMessage(Message message) {
try{
String str = new String(message.getBody(), "UTF-8");
System.out.print("=====获取消息"+str);
}catch(Exception e){
e.printStackTrace();
}
}
}
三、如何保证消息不被重复消费?(如何保证消息消费时的幂等性)
其实还是得结合业务来思考,几个思路:
- 比如你拿个数据要写库,你先根据主键查一下,如果这数据都有了,你就别插入了,update一下好吧
2.比如你是写redis,那没问题了,反正每次都是set,天然幂等性。
3.比如你不是上面两个场景,那做的稍微复杂一点,你需要让生产者发送每条数据的时候,里面加一个全局唯一的id,类似订单id之类的东西,然后你这里消费到了之后,先根据这个id去比如redis里查一下,之前消费过吗?如果没有消费过,你就处理,然后这个id写redis。如果消费过了,那你就别处理了,保证别重复处理相同的消息即可。
四、如何保证消息不丢失
如果说你这个是用 MQ 来传递非常核心的消息,比如说计费、扣费的一些消息,那必须确保这个 MQ 传递过程中绝对不会把计费消息给弄丢。消息丢失怎么办?如何保证消息的可靠性传输?或者说,如何处理消息丢失的问题?
4.1 生产者弄丢了数据
生产者将数据发送到 RabbitMQ 的时候,可能数据就在半路给搞丢了,因为网络问题啥的,都有可能。
RabbitMQ提供事务机制和confirm模式来确保生产者不丢消息。
RabbitMQ 事务机制(同步)就是说,发送消息前开启事务,发送过程中如果出现什么异常,事务就会回滚,如果发送成功则提交事务,它的缺点是吞吐量会下降,因为是同步的,提交一个事务之后会阻塞在哪儿,太耗性能。
事务的实现主要是对信道(Channel)的设置,主要的方法有三个:
channel.txSelect()声明启动事务模式;
channel.txComment()提交事务;
channel.txRollback()回滚事务;
用confirm模式的居多。在生产者那里设置开启confirm模式之后,每次写的消息都会分配一个唯一的id,然后如果写入了RabbitMQ中,RabbitMQ会告诉你这个消息是否接受成功,如果接收失败,你可以重试。confirm机制是异步的,你发送消息之后就可以发送下一个消息,然后RabbitMQ接收之后会异步回调你的一个接口通知你这个消息接收到了。
Confirm的三种实现方式:
方式一:channel.waitForConfirms()普通发送方确认模式;
看代码可以知道,我们只需要在推送消息之前,channel.confirmSelect()声明开启发送方确认模式,再使用channel.waitForConfirms()等待消息被服务器确认即可。
方式二:channel.waitForConfirmsOrDie()批量确认模式;
以上代码可以看出来channel.waitForConfirmsOrDie(),使用同步方式等所有的消息发送之后才会执行后面代码,只要有一个消息未被确认就会抛出IOException异常。
方式三:channel.addConfirmListener()异步监听发送方确认模式;
代码是异步执行的,消息确认有可能是批量确认的,是否批量确认在于返回的multiple的参数,此参数为bool值,如果true表示批量执行了deliveryTag这个值以前的所有消息,如果为false的话表示单条确认。
4.2 消息队列弄丢数据
这个一般是开启 RabbitMQ 的持久化,写入消息之后会自动持久化到磁盘,哪怕RabbitMQ 挂了,恢复之后会自动读取之前存储的数据,一般数据不会丢。如果RabbitMQ 还没持久化就挂了,这个可以配合confirm模式,你可以在消息持久化后发送给生产者一个Ack确认信号,告诉生产者已经持久成功,如果生产者没有收到确认信号,生产者会自动重发。
如何进行持久化?
1.创建队列时,将持久化标识durable设置为true。
2. 第二个是发送消息的时候将消息的 deliveryMode设置为 2
这个就是将消息设置为持久化的。
必须要同时设置这两个持久化才行,RabbitMQ 哪怕是挂了,再次重启,也会从磁盘上重启恢复 queue,恢复这个 queue 里的数据。
4.3 消费者丢数据
启用手动确认模式可以解决这个问题