一、使用场景
入库单入库完成后,需要生成财务流水和推送财务凭证等操作
传统的做法:将入库数据写入数据库后,直接生成财务流水和财务凭证
这里的问题是,财务凭证可能需要推送到第三方系统,可能会出现失败的情况,而且和入库单业务产生了耦合
使用消息中间件:入库单写入数据库后,发送[入库完成]的mq消息,消费者监听该消息。这样有两个好处,1.入库单业务层不再耦合财务流水和凭证了 2.财务流水和凭证可实现异步入库和推送,提升了系统响应速度
二、消息中间件的选择
持久化消息比较:zeroMq不支持,activeMq和rabbitMq都支持。持久化消息主要是指:MQ down或者MQ所在的服务器down了,消息不会丢失的机制。
可靠性、灵活的路由、集群、事务、高可用的队列、消息排序、问题追踪、可视化管理工具、插件系统、社区—RabbitMq最好,ActiveMq次之,ZeroMq最差。
高并发:从实现语言来看,RabbitMQ最高,原因是它的实现语言是天生具备高并发高可用的erlang语言。
综上所述:RabbitMQ的性能相对来说更好更全面,是消息中间件的首选。
三、RabbitMq简介
RabbitMQ是实现了高级消息队列协议(AMQP)的开源消息代理软件(亦称面向消息的中间件)
RabbitMQ消息推送到接收的流程图:
黄色的圈圈就是我们的消息推送服务,例如用户注册时发送短信和邮件的异步消息,然后将消息推送到中间方框里面也就是 rabbitMq的服务器,然后经过服务器里面的交换机、队列等将数据处理入列后,最终右边的蓝色圈圈就是消费者监听到的消息,监听到消息后可以处理我们的业务逻辑,例如发送短信和邮件
四、RabbitMq的使用
接下来,以MagicErp为例,具体说明RabbitMq的使用
1.pom.xml文件中引入依赖
<!--rabbitmq-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
2.application.yml加入RabbitMq配置
spring:
rabbitmq:
host: rabbitmq服务的ip
port: 5672 #rabbitmq默认端口
username: guest
password: guest
virtual-host: / #默认的vhost,用户也可以自己创建
3.定义消息发送的实体类
public class MqMessage {
private String exchange;
private String routingKey;
private Object message;
public MqMessage(String exchange, String routingKey, Object message) {
this.exchange = exchange;
this.routingKey = routingKey;
this.message = message;
}
public String getExchange() {
return exchange;
}
public void setExchange(String exchange) {
this.exchange = exchange;
}
public String getRoutingKey() {
return routingKey;
}
public void setRoutingKey(String routingKey) {
this.routingKey = routingKey;
}
public Object getMessage() {
return message;
}
public void setMessage(Object message) {
this.message = message;
}
}
4.定义发送异步事件的方法。我们需要先使用Spring的ApplicationEventPublisher来发送一个异步事件,原因是我们需要在事务提交后再发送rabbitmq消息
@Service
public class MessageSenderImpl implements MessageSender {
@Autowired
private ApplicationEventPublisher publisher;
@Override
public void send(MqMessage message) {
publisher.publishEvent(message);
}
}
5.定义监听异步事件的方法。使用Spring的@TransactionalEventListener监听异步事件,然后发送rabbitmq消息
@Component
public class TransactionalMessageListener {
@Autowired
private AmqpTemplate amqpTemplate;
/**
* 默认在事务提交后执行
* @param message
*/
@TransactionalEventListener(fallbackExecution = true)
public void handleSupplierBillPush(MqMessage message){
this.amqpTemplate.convertAndSend(message.getExchange(), message.getRoutingKey(), message.getMessage());
}
}
配置好以上内容后可以使用rabbitmq发送消息了,下面以入库完成为例展示发送和接收消息的过程
- 入库单审核通过后入库完成
//会员注册业务
...
//组织数据结构发送入库完成消息
WarehouseEntryAuditPassMessage message = new WarehouseEntryAuditPassMessage();
message.setList(warehouseEntryList);
this.messageSender.send(new MqMessage(AmqpExchange.WAREHOUSE_ENTRY_AUDIT_PASS, AmqpExchange.WAREHOUSE_ENTRY_AUDIT_PASS + "_ROUTING", message));
- 消费者客户端接收消息,
@Component
public class WarehouseEntryAuditPassReceiver {
private final Logger logger = LoggerFactory.getLogger(getClass());
@Autowired(required = false)
private List<WarehouseEntryAuditPassEvent> events;
@RabbitListener(bindings = @QueueBinding(
value = @Queue(value = AmqpExchange.WAREHOUSE_ENTRY_AUDIT_PASS + "_QUEUE"),
exchange = @Exchange(value = AmqpExchange.WAREHOUSE_ENTRY_AUDIT_PASS, type = ExchangeTypes.FANOUT)
))
public void receive(WarehouseEntryAuditPassMessage message) {
if (events != null) {
for (WarehouseEntryAuditPassEvent event : events) {
try {
event.onWarehouseEntryAuditPass(message);
} catch (Exception e) {
logger.error("入库单审核通过消息出错" + event.getClass().getName(), e);
}
}
}
}
}