優惠券項目六---RabbitMQ的實現

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


發佈了49 篇原創文章 · 獲贊 16 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章