一、maven增加依赖包
<dependency>
<groupId>org.springframework.amqp</groupId>
<artifactId>spring-rabbit</artifactId>
<version>1.6.1.RELEASE</version>
</dependency>
二、rabbitmq配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:rabbit="http://www.springframework.org/schema/rabbit"
xsi:schemaLocation="
http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/rabbit
http://www.springframework.org/schema/rabbit/spring-rabbit-1.0.xsd"
default-lazy-init="false"
>
<!-- 连接rabbitmq -->
<rabbit:connection-factory id="connectionFactory" username="${rabbitmq.username}" password="${rabbitmq.password}"
host="${rabbitmq.url}" port="${rabbitmq.port}" />
<rabbit:admin connection-factory="connectionFactory" id="amqpAdmin" />
<!--在queue以及exchange中, 有一个重要的属性durable, 默认为true, 可以防止宕机后数据丢失。-->
<!-- queue 队列声明 -->
说明:
durable:是否持久化
exclusive: 仅创建者可以使用的私有队列,断开后自动删除
auto_delete: 当所有消费客户端连接断开后,是否自动删除队列
<!-- queue 队列声明 -->
<rabbit:queue id="realTime_data_queue" durable="true"
auto-delete="false" exclusive="false" name="realTime_data_queue">
<!-- 设置私信队列的过期时间 -->
<rabbit:queue-arguments>
<entry key="x-message-ttl">
<value type="java.lang.Long">86400000</value>
</entry>
</rabbit:queue-arguments>
</rabbit:queue>
<!-- exchange queue binging key 绑定 -->
<!--标准的AMQP Exchange有4种: Direct, Topic, Headers, Fanout, 根据实际需要选择。--><!--Direct: 如果消息的routing key与bingding的routing key直接匹配的话, 消息将会路由到该队列上。--><!--Topic: 如果消息的routing key与bingding的routing key符合通配符匹配的话, 消息将会路由到该队列上。--><!--Headers: 如果消息参数表中的头信息和值都与binding参数表中相匹配, 消息将会路由到该队列上。--><!--Fanout: 不管消息的routing key和参数表的头信息/值是什么, 消息将会路由到该队列上。-->
<rabbit:direct-exchange name="mq-exchange"
durable="true" auto-delete="false" id="mq-exchange">
<rabbit:bindings>
<rabbit:binding queue="realTime_data_queue" key="realTime_data_queue" />
</rabbit:bindings>
</rabbit:direct-exchange>
<!-- spring template声明 -->
<rabbit:template exchange="mq-exchange" id="amqpTemplate"
connection-factory="connectionFactory" message-converter="jackson2JsonMessageConverter"/>
<bean id="messageConsumer" class="com.xinyue.ConsumerService"/>
<!-- queue litener 观察 监听模式 当有消息到达时会通知监听在对应的队列上的监听对象 taskExecutor这个需要自己实现一个连接池 按照官方说法 除非特别大的数据量 一般不需要连接池 -->
<!--//prefetch="1" 这样RabbitMQ就会使得每个Consumer在同一个时间点最多处理一个Message。换句话说,在接收到该Consumer的ack前,他它不会将新的Message分发给它。 -->
<!--在listener-container中, 有acknowledge属性, 默认为auto, 即消费者成功处理消息后必须有个应答, 如果消费者程序发生异常或者宕机, 消息会被重新放回队列-->
<rabbit:listener-container connection-factory="connectionFactory" acknowledge="auto" concurrency="20" prefetch="1">
<rabbit:listener queues="realTime_data_queue" ref="messageConsumer"/>
</rabbit:listener-container>
<bean id="jackson2JsonMessageConverter" class="org.springframework.amqp.support.converter.Jackson2JsonMessageConverter"/>
</beans>
三、消费者代码
@Component
public class MessageConsumerService extends MessageListenerAdapter {
public MQNote handleMessage(MQNote mqNote) {
System.out.println(mqNote.getNoteContent());
return mqNote;
}
}
四、生成者代码
@Service
public class MessageProductorService extends SimpleMessageConverter {
@Autowired
private AmqpTemplate amqpTemplate;
public void pushToMessageQueue(String routingKey, Object message) {
Object o = amqpTemplate.convertSendAndReceive(routingKey, message);
System.out.println("收到的返回信息 " + ((MQNote)message).getNoteContent());
}
}
消息监听器MessageListener 原文地址:http://blog.youkuaiyun.com/zhaoxd_1/article/details/51837045
在Spring整合JMS的应用中我们在定义消息监听器的时候一共可以定义三种类型的消息监听器,分别是MessageListener、SessionAwareMessageListener和MessageListenerAdapter
1.3.1 MessageListener
MessageListener是最原始的消息监听器,它是JMS规范中定义的一个接口。其中定义了一个用于处理接收到的消息的onMessage方法,该方法只接收一个Message参数。
缺点:由于onMessage方法是void方法,所以当同步请求需要有返回值的情况时,这种消息监听器不适用
1.3.2 SessionAwareMessageListener
SessionAwareMessageListener是Spring为我们提供的,它不是标准的JMS MessageListener。它可以有返回值,但是是通过参数session进行,
onMessage 也是void,但有两个参数,一个是表示当前接收到的消息Message,另一个就是可以用来发送消息的Session对象
1.3.3 MessageListenerAdapter (推荐都使用这个监听器)
MessageListenerAdapter类实现了MessageListener接口和SessionAwareMessageListener接口,它的主要作用是将接收到的消息进行类型转换,然后通过反射的形式把它交给一个普通的Java类进行处理。
MessageListenerAdapter会把接收到的消息做如下转换:
TextMessage转换为String对象;
BytesMessage转换为byte数组;
MapMessage转换为Map对象;
ObjectMessage转换为对应的Serializable对象。
Spring会默认调用目标处理器的handleMessage方法
ps:问题汇总记录
一、mq运行一段时间内异常关闭
日志没有明细信息,查看控制台发现消息队列上存在N多信息,而消费者状态还会处于空闲,怀疑是消息队列信息堆亚过多导致mq崩溃。
解决:将mq的分发机制改为公平分发,预取总数量设置为1,即每次只消费一条信息
这样RabbitMQ就会使得每个Consumer在同一个时间点最多处理一个Message。换句话说,在接收到该Consumer的ack前,他它不会将新的Message分发给它。
结果:发现堆积的消息数量消费明显加快。
二、rabbitmq 因ack机制使得消费者如果出现异常没有正确返回确认,那么mq上的队列信息是一直堆积。
解决方式(ps:ack其实是异步确认,所以到压力过大时出现connection closed错误,仍会有丢数据的情况)
1、配置重试机制过滤器
2、设置消费重试机制模板,重试一段次数后,将该消息暂时扔到死信队列中,然后过段时间再讲消息返回到正常队列进行延迟消费的效果
<bean id="retryInterceptor"
class="org.springframework.amqp.rabbit.config.StatelessRetryOperationsInterceptorFactoryBean">
<!-- 事务回收器 -->
<property name="messageRecoverer" ref="messageRecoverer" />
<property name="retryOperations" ref="retryTemplate" />
</bean>
<!-- 拒绝请求消息,并回复该请求者的请求被服务端拒绝-->
<bean id="messageRecoverer" class="com.test.retry.RejectAndRplyToRequeueRecoverer">
<property name="replyToTemplate" ref="amqpTemplate"/>
</bean>
<bean id="retryTemplate" class="org.springframework.retry.support.RetryTemplate">
<!-- 重试的间隔 -->
<property name="backOffPolicy">
<bean class="org.springframework.retry.backoff.ExponentialBackOffPolicy">
<property name="initialInterval" value="1000" />
<property name="maxInterval" value="10000" />
</bean>
</property>
<!-- 重试的最大次数 -->
<property name="retryPolicy">
<bean class="org.springframework.retry.policy.SimpleRetryPolicy">
<property name="maxAttempts" value="1" />
</bean>
</property>
</bean>
三、rabbitmq 事务控制
对于生产者控制事务
是利用springframework.transaction的事务控制,在beforeCommit时先去往中间件发送信息,如果报错则抛出异常springframework.transaction.suppot.TranscationSynchronizationAdaper 抽象类实现,没有异常的时候再将数据插入到数据库中。
对于消费者是否消费问题,
是将mq做持久化信息,且消费者aop 增强失败重试机制,使用springFramework的retryTemole模板,并实现mq的messageRevoverer接口,消息接受重试一定次数后,将不能消费的信息扔到死信队列里,死信队列设置好dlx路由,延迟一定时间后再扔到业务quque中,相当于延迟处理信息。
四、consumer启动时报告异常
com.rabbitmq.client.ShutdownSignalException: channel error; protocol method: #method<channel.close>(reply-code=406, reply-text=PRECONDITION_FAILED - inequivalent arg ‘x-dead-letter-exchange’ for queue ‘queue_emailsend_result’ in vhost ‘host_test’: received the value ‘dlx’ of type ‘longstr’ but current is none, class-id=50, method-id=10)
原因:queue已经存在,但是启动 consumer 时试图设定一个 x-dead-letter-exchange 参数,这和服务器上的定义不一样,server 不允许所以报错。如果删除 queue 重新 declare 则不会有问题。或者通过 policy 来设置这个参数也可以不用删除队列。