RabbitMQ作为消息队列,可以采用异步的方式将消息放入到消息队列中等待处理。减轻了服务器的压力,并在一定程度上保证了服务的稳定性,健壮性。
对于RabbitMQ的介绍,可以看一下我之前的整理文章,例如:https://mp.csdn.net/postedit/80221725
在优惠券项目中,是采用spring对项目进行管理的。我将以操作日志的写入做例子,分享一下消息队列是如何实现的。优惠券项目是博主在git上趴下来的项目,本博客的分析具体见:https://mp.csdn.net/postedit/80191083
1,定义消息队列和它的routingKey rabbitmq-server.properties
#操作日志相关属性 rabbit.actionLog.queue=spring.actionLog.queue rabbit.actionLog.routingKey=spring.actionLog.queueKey
2,在配置文件中定义通过spring 注入的方式 将属性配置引入,消息队列定义,exchange定义。绑定定义。spring-rabbitmq-share.xml
<context:property-placeholder location="/rabbitmq-server.properties" ignore-unresolvable="true"/> <rabbit:connection-factory id="connectionFactory" host="${rabbit.hosts}" port="${rabbit.port}" username="${rabbit.username}" password="${rabbit.password}" virtual-host="${rabbit.virtualHost}" channel-cache-size="50"/> <rabbit:admin connection-factory="connectionFactory"/>
<!--操作日志--> <rabbit:queue id="spring.actionLog.queue" durable="true" auto-delete="true" exclusive="false" name="${rabbit.actionLog.queue}"/> <rabbit:direct-exchange name="${rabbit.exchange.direct}" durable="true" auto-delete="false" id="${rabbit.exchange.direct}"> <rabbit:bindings> <rabbit:binding queue="spring.actionLog.queue" key="${rabbit.actionLog.routingKey}"></rabbit:binding> </rabbit:bindings> </rabbit:direct-exchange>
3,消息生产者定义 引入spring-rabbitmq-producer.xml
<import resource="spring-rabbitmq-share.xml"/> <context:component-scan base-package="com.peiyu.mem.rabbitmq.produces"/> <context:property-placeholder location="/rabbitmq-server.properties" ignore-unresolvable="true"/> <!--定义rabbitmq模板类--> <rabbit:template exchange="spring.exchange.direct" id="rabbitTemplate" connection-factory="connectionFactory" message-converter="jsonMessageConverter"/>
4,消息生产者的java代码:
package com.peiyu.mem.rabbitmq.produces; import org.apache.log4j.Logger; import org.springframework.amqp.rabbit.core.RabbitTemplate; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; /** * Created by Administrator on 2016/12/8. */ @Component public class MqSenderHandler { @Autowired private RabbitTemplate rabbitTemplate; private Logger log = Logger.getLogger(MqSenderHandler.class); /** * 发送信息 * * @param messageInfo */ public void sendMessage(String routingKey,Object messageInfo) { try { //这块就是什么消息发送到什么队列中。通过绑定实现 rabbitTemplate.convertAndSend(routingKey,messageInfo); } catch (Exception e) { log.error("发送消息失败"+e); } } }
5,消息消费者配置 spring-rabbitmq-consumer.xml
<!--操作日志--> <rabbit:listener-container connection-factory="connectionFactory"> <rabbit:listener ref="actionLogHandler1" method="onMessage" queues="spring.actionLog.queue"/> </rabbit:listener-container> <rabbit:listener-container connection-factory="connectionFactory"> <rabbit:listener ref="actionLogHandler2" method="onMessage" queues="spring.actionLog.queue"/> </rabbit:listener-container>
6,消息消费者的java代码实现
服务一
package com.peiyu.mem.rabbitmq.consumers; import com.migr.common.util.JsonUtil; import com.migr.common.util.StringUtils; import com.peiyu.mem.dao.ActionLogDao; import com.peiyu.mem.domian.entity.ActionLog; import com.peiyu.mem.rabbitmq.Gson2JsonMessageConverter; import com.rabbitmq.client.Channel; import org.apache.log4j.Logger; import org.springframework.amqp.core.Message; import org.springframework.amqp.rabbit.core.ChannelAwareMessageListener; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; /** * Created by Administrator on 2016/12/19. */ @Component public class ActionLogHandler1 implements ChannelAwareMessageListener { private Logger log = Logger.getLogger(ActionLogHandler1.class); @Autowired private ActionLogDao actionLogDao; @Autowired private Gson2JsonMessageConverter jsonMessageConverter; @Override public void onMessage(Message message, Channel channel) throws Exception { try { channel.basicQos(1); if (message == null || message.getBody() == null) { return; } String data = jsonMessageConverter.fromMessage(message).toString(); if (StringUtils.isNotBlank(data)) { ActionLog actionLog = JsonUtil.g.fromJson(data, ActionLog.class); actionLogDao.insert(actionLog); } } catch (Exception e) { log.error("操作日志异常" + e); } } }
服务二
package com.peiyu.mem.rabbitmq.consumers; import com.migr.common.util.JsonUtil; import com.migr.common.util.StringUtils; import com.peiyu.mem.dao.ActionLogDao; import com.peiyu.mem.domian.entity.ActionLog; import com.peiyu.mem.rabbitmq.Gson2JsonMessageConverter; import com.rabbitmq.client.Channel; import org.apache.log4j.Logger; import org.springframework.amqp.core.Message; import org.springframework.amqp.rabbit.core.ChannelAwareMessageListener; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Component; /** * Created by Administrator on 2016/12/19. */ @Component public class ActionLogHandler2 implements ChannelAwareMessageListener { private Logger log = Logger.getLogger(ActionLogHandler1.class); @Autowired private ActionLogDao actionLogDao; @Autowired private Gson2JsonMessageConverter jsonMessageConverter; @Override public void onMessage(Message message, Channel channel) throws Exception { try { channel.basicQos(1); if (message == null || message.getBody() == null) { return; } String data = jsonMessageConverter.fromMessage(message).toString(); if (StringUtils.isNotBlank(data)) { ActionLog actionLog = JsonUtil.g.fromJson(data, ActionLog.class); actionLogDao.insert(actionLog); } } catch (Exception e) { log.error("操作日志异常" + e); } } }
加入消息队列(采用rabbitMQ)对于某一批次添加失败,把失败的放入对列中,通过队列进行补救,已到达高可用。避免大批量优惠券来回重新导入 消息队列对于异常信息拒绝解决并重返消息队列中,配置2个消费者以避免其中一个服务异常,消息处理出现死循环。个人觉得这块是误区。其实一个消费者就可以。有待进一步研讨,有想法的朋友可以留言。
RabbitMQ的消息收发流程图为:
1. 生产者产生一条消息,通过channel发向RabbitMQ,RabbitMQ收到消息后,根据消息指定的exchange(交换机) 来查找binding(绑定) 然后根据规则分发到不同的Queue
2.Queue将消息转发到具体的消费者。
3.a.消费者收到消息后,会根据自己对消息的处理对RabbitMQ进行返回,如果返回Ack,就表示已经确认这条消息,RabbitMQ会对这条消息进行处理(一般是删除)
3.b.如果消费者收到消息后处理不了,或者崩溃了,就可能不能对RabbitMQ做出返回,或者拒绝对消息处理,返回reject,RabbitMQ在一定时间没收到返回或者收到reject后,会重新派遣消息。
整个消息传递模型中,生产者只需要指定规则,然后发出消息就行,不需要知道最后是哪个消费者收到了消息,也不需要操心消费者是否收到消息,只需要往RabbitMQ发送消息就行了。