优惠券项目六---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发送消息就行了。


猜你喜欢

转载自blog.csdn.net/wenjieyatou/article/details/80249978