使用场景
物联网数据写库服务,调用工单服务生成报警工单,调用短信服务发报警短信,调用mobPush服务发app通知,这些过程的解耦可以使用RabbitMQ。
出库单出库服务,调用短信服务通知平台库管和机构管理员出库成功
为什么引入消息中间件
解耦 减少耦合
异步 节省时间
削峰 应对高并发
消息中间件落地
//安装部署RabbitMQ
//引入到项目中
//订单服务中通知仓储服务调度发货
//声明queue的name
String QUEUE_NAME = "warehouse_schedule_delivery";
//定义queue
channel.queueDeclare(QUEUE_NAME,true,false,false,null);
//推送message到queue
String message = "订单消息";
channel.basicPublish("",QUEUE_NAME,null,message.getBytes("UTF-8"));
//仓储服务消费消息
//声明queue的name
String QUEUE_NAME = "warehouse_schedule_delivery";
//声明queue的name
channel.queueDeclare(QUEUE_NAME,true,false,false,null);
//消息监听,把queue的消息交给deliverCallback回调接口处理
channel.basicConsume(QUEUE_NAME,true,deliverCallback,consumerTag -> {});
//回调接口,在回调里获取订单消息并处理
DeliverCallback deliverCallback = (consumerTag,deliver) -> {
//获取订单消息
String message = new String(deliver.getBody(),"UTF-8");
//根据获取到的订单消息执行调度发货的业务逻辑
...
}
//总结
//本质上就是上游发消息到指定的queue,下游从queue中获取消息,调用回调接口处理
消息的持久化
//queue持久化
channel.queueDeclare("warehouse_schedule_delivery",true,false,false,null);
//message持久化
channel.basicPublish("","warehouse_schedule_delivery",MessageProperties.PERSISTENT_TEXT_PLAIN,message.getBytes());
可靠消息最终一致性
上游
业务操作和MQ放在本地事务里
业务操作失败不执行消息投递
消息投递失败回滚业务操作
下游
消费完消息更新消息表状态为已完成
保证上游服务对消息100%可靠投递
开发一个后台定时运行的线程,不停的检查消息表状态,若一直是待确认状态,就去查一下对应的业务操作执行成功没,若成功就投递消息并更新消息表状态为已发送。若失败就删掉消息表中消息。
保证下游服务对消息的100%可靠接收
开发一个后台定时运行的线程,不停的检查消息表状态,若一直是已发送状态,那么就说明下游服务始终没有处理成功,此时可靠消息服务就可以再次尝试重新投递消息到MQ,让下游服务来再次处理。只要下游服务的接口逻辑实现幂等性,保证多次处理一个消息,不会插入重复数据即可。
总结:
上游
业务操作+消息表在同一本地事务
后台定时轮询处理一直是待确认状态的消息,查询业务操作并更新消息状态
下游
消费完消息更新消息表状态为已完成
下游服务接口要实现幂等性
后台定时轮询处理一直是已发送状态的消息,再次尝试重新投递让下游服务来处理
消息投递的高可用
感知到MQ挂了(比如连续10次重试尝试投递消息到MQ都发现异常报错,网络无法联通)
自动触发降级,按照hash分配到几个redis队列里处理