七、RabbitMQ与Spring实战

1.场景

用户下订单买商品,订单处理成功后,去扣减库存,在这个场景里,订单系统是生产者,库存系统是消费者。

库存是必须扣减的,在业务上来说,有库存直接扣减即可,没库存或者低于某个阈值,可以扣减成功,不过要通知其他系统(如通知采购系统尽快采购,通知用户订单系统我们会尽快调货)。

2.RPC实现

通过RPC的实现,可以看到RPC会造成耦合。一旦库存系统失败,订单系统也会跟着失败。我们希望库存系统本身的失败,不影响订单系统的继续执行,在业务流程上,进行订单系统和库存系统的解耦。

3.消息中间件的实现

对于我们消息模式的实现,为保证库存必须有扣减,我们要考虑几个问题:

  • 1、订单系统发给Mq服务器的扣减库存的消息必须要被Mq服务器接收到,意味着需要使用发送者确认。
  • 2、Mq服务器在扣减库存的消息被库存服务正确处理前必须一直保存,那么需要消息进行持久化。
  • 3、某个库存服务器出了问题,扣减库存的消息要能够被其他正常的库存服务处理,需要我们自行对消费进行确认,意味着不能使用消费者自动确认,而应该使用手动确认。

所以生产者订单系统这边需要 ,配置文件中队列和交换器进行持久化,消息发送时的持久化,发送者确认的相关配置和代码。
所以消费者库存系统这边要进行手动确认。

4.与Spring集成时的更多配置

4.1 发送者确认

在这里插入图片描述

设置发送消息时的mandatory以及接收RabbitMQ中间件的应答

在这里插入图片描述

4.2 交换器持久化配置

在这里插入图片描述

4.3 消息持久化

在这里插入图片描述

4.4 队列参数(死信交换器、持久化、队列级别消息过期、独占模式)

在这里插入图片描述

4.5 消费者手动确认消息

在这里插入图片描述

处理库存类要实现ChannelAwareMessageListener。

在这里插入图片描述

和对消息确认或拒绝

在这里插入图片描述

5.实战

5.1 订单生产者

Spring配置

<?xml version="1.0" encoding="UTF-8"?>
<!-- 查找最新的schemaLocation 访问 http://www.springframework.org/schema/ -->
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:jee="http://www.springframework.org/schema/jee"
       xmlns:p="http://www.springframework.org/schema/p"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:task="http://www.springframework.org/schema/task"
       xmlns:rabbit="http://www.springframework.org/schema/rabbit"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
    http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
    http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-4.0.xsd
    http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd
    http://www.springframework.org/schema/tx  http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
    http://www.springframework.org/schema/aop  http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
    http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-4.0.xsd
    http://www.springframework.org/schema/rabbit http://www.springframework.org/schema/rabbit/spring-rabbit-2.0.xsd">

    <!-- 配置扫描路径 -->
    <context:component-scan base-package="com.zava">
        <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
    </context:component-scan>

    <!-- rabbitMQ配置 -->
    <bean id="rabbitConnectionFactory" class="org.springframework.amqp.rabbit.connection.CachingConnectionFactory">
        <constructor-arg value="127.0.0.1"/>
        <property name="username" value="guest"/>
        <property name="password" value="guest"/>
        <property name="channelCacheSize" value="8"/>
        <property name="port" value="5672"/>

        <!-- 发布确认 -->
        <property name="publisherConfirms" value="true"/>
    </bean>

    <rabbit:admin connection-factory="rabbitConnectionFactory"/>

    <rabbit:queue name="depot_queue" durable="true"/>

    <rabbit:direct-exchange name="depot-amount-exchange" durable="true">
        <rabbit:bindings>
            <rabbit:binding queue="depot_queue" key="amount.depot"></rabbit:binding>
        </rabbit:bindings>
    </rabbit:direct-exchange>

    <rabbit:template id="rabbitTemplate" connection-factory="rabbitConnectionFactory" mandatory="true"
                     return-callback="sendReturnCallback" confirm-callback="confirmCallback">
	</rabbit:template>
</beans>

发送消息

package com.zava.service;

import com.google.gson.Gson;
import com.zava.vo.GoodTransferVo;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.core.MessageDeliveryMode;
import org.springframework.amqp.core.MessageProperties;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

/**
 */
@Service
@Qualifier("mq")
public class MqMode  implements IProDepot {

    private final static String DEPOT_RK = "amount.depot";
    private final static String DEPOT_EXCHANGE = "depot-amount-exchange";

    @Autowired
    RabbitTemplate rabbitTemplate;

    private static Gson gson = new Gson();

    public void processDepot(String goodsId, int amount) {
        GoodTransferVo goodTransferVo = new GoodTransferVo();
        goodTransferVo.setGoodsId(goodsId);
        goodTransferVo.setChangeAmount(amount);
        goodTransferVo.setInOrOut(false);
        String goods = gson.toJson(goodTransferVo);
        MessageProperties messageProperties = new MessageProperties();
        messageProperties.setDeliveryMode(MessageDeliveryMode.PERSISTENT);

        rabbitTemplate.send(DEPOT_EXCHANGE, DEPOT_RK, new Message(goods.getBytes(), messageProperties));

        rabbitTemplate.send(DEPOT_EXCHANGE, "ErrorRoute",
                new Message(goods.getBytes(), messageProperties));
    }
}

失败通知

package com.zava.service.callback;

import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.stereotype.Component;

/**
 */
@Component
public class SendReturnCallback implements RabbitTemplate.ReturnCallback {
    public void returnedMessage(Message message, int replyCode,
                                String replyText, String exchange,
                                String routingKey) {
        String msg = new String(message.getBody());
        System.out.println("返回的replyText :"+replyText);
        System.out.println("返回的exchange :"+exchange);
        System.out.println("返回的routingKey :"+routingKey);
        System.out.println("返回的message :"+message);

    }
}

发布者确认

package com.zava.service.callback;

import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.amqp.rabbit.support.CorrelationData;
import org.springframework.stereotype.Component;

/**
 */
@Component
public class ConfirmCallback implements RabbitTemplate.ConfirmCallback {
    public void confirm(CorrelationData correlationData,
                        boolean ack,
                        String cause) {
        if(ack){
            System.out.println("消息发送给mq成功");
        }else{
            System.out.println("消息发送给mq失败,原因:"+cause);
        }

    }
}

5.2 库存消费者

Spring配置

<?xml version="1.0" encoding="UTF-8"?>
<!-- 查找最新的schemaLocation 访问 http://www.springframework.org/schema/ -->
<beans xmlns="http://www.springframework.org/schema/beans"
	   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	   xmlns:mvc="http://www.springframework.org/schema/mvc"
	   xmlns:tx="http://www.springframework.org/schema/tx"
	   xmlns:jee="http://www.springframework.org/schema/jee"
	   xmlns:p="http://www.springframework.org/schema/p"
	   xmlns:aop="http://www.springframework.org/schema/aop"
	   xmlns:context="http://www.springframework.org/schema/context"
	   xmlns:task="http://www.springframework.org/schema/task"
	   xmlns:rabbit="http://www.springframework.org/schema/rabbit"
	   xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.0.xsd
    http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.0.xsd
    http://www.springframework.org/schema/jee http://www.springframework.org/schema/jee/spring-jee-4.0.xsd
    http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-4.0.xsd
    http://www.springframework.org/schema/tx  http://www.springframework.org/schema/tx/spring-tx-4.0.xsd
    http://www.springframework.org/schema/aop  http://www.springframework.org/schema/aop/spring-aop-4.0.xsd
    http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-4.0.xsd
    http://www.springframework.org/schema/rabbit http://www.springframework.org/schema/rabbit/spring-rabbit-2.0.xsd">

     <!-- 配置扫描路径 -->
     <context:component-scan base-package="com.zava">
     	<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
     </context:component-scan>

	<!-- rabbitMQ配置 -->
	<bean id="rabbitConnectionFactory"
		  class="org.springframework.amqp.rabbit.connection.CachingConnectionFactory">
		<constructor-arg value="127.0.0.1"/>
		<property name="username" value="guest"/>
		<property name="password" value="guest"/>
		<property name="channelCacheSize" value="8"/>
		<property name="port" value="5672"></property>
	</bean>
	<rabbit:admin connection-factory="rabbitConnectionFactory"/>

    <rabbit:queue name="depot_queue" durable="true"/>

	<rabbit:direct-exchange name="depot-amount-exchange"
          xmlns="http://www.springframework.org/schema/rabbit" durable="true">
        <rabbit:bindings>
            <rabbit:binding queue="depot_queue" key="amount.depot"></rabbit:binding>
        </rabbit:bindings>
	</rabbit:direct-exchange>

	<!-- 对消息要手动确认 -->
    <rabbit:listener-container connection-factory="rabbitConnectionFactory"
	acknowledge="manual">
        <rabbit:listener queues="depot_queue" ref="processDepot"
                         method="onMessage" />
    </rabbit:listener-container>
</beans>

消费消息:手动确认

package com.zava.mq;

import com.google.gson.Gson;
import com.rabbitmq.client.Channel;
import com.zava.service.DepotManager;
import com.zava.vo.GoodTransferVo;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.core.ChannelAwareMessageListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

/**
 */
@Service
public class ProcessDepot  implements ChannelAwareMessageListener{

    private static Logger logger = LoggerFactory.getLogger(ProcessDepot.class);

    @Autowired
    private DepotManager depotManager;

    private static Gson gson = new Gson();

    @Override
    public void onMessage(Message message, Channel channel) throws Exception {
        String msg = new String(message.getBody());
        logger.info(">>>>>>>>>>>>>>接收到消息:"+msg);
        GoodTransferVo goodTransferVo = gson.fromJson(msg,GoodTransferVo.class);
        try {
            depotManager.operDepot(goodTransferVo);
            channel.basicAck(message.getMessageProperties().getDeliveryTag(), false);
            logger.info(">>>>>>>>>>>>>>库存处理完成,应答Mq服务");
        } catch (Exception e) {
            logger.error(e.getMessage());
            channel.basicNack(message.getMessageProperties().getDeliveryTag(), false, true);
            logger.info(">>>>>>>>>>>>>>库存处理失败,拒绝消息,要求Mq重新派发");
            throw e;
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值