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;
}
}
}