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發送消息就行了。