2.1.1 RabbitMq简要使用
1.消息队列概述
分布式系统中用于解耦组件间通信的中间件技术。它通过异步处理的方式,使得生产者可以不受消费者的影响发送消息,而消费者则可以根据自己的节奏来处理这些消息。下面是RabbitMq得到内存结构图:
消息在消息队列中的数据通信流程:
- 消息队列的结构分为三个部分,分别是生产者(Producer)、服务器(Server)、消费者(Consumer)。其中生产者和消费者通过Channel与服务器建立连接进行数据通信。
- 服务器由交换机(Exchange)和队列(Queue)组成,生产者通过Channel与服务器建立连接,向服务器中的交换机发送消息,服务器中的交换机接收生产者发送的消息,并根据生产者消息中的路由健将消息路由到指定的队列中。
- 消费者通过Channel与服务器建立连接,根据队列名称找到服务器中队列再消费消息。
2. 消息队列优点和问题
消息队列在应用中使用较多,
1.优点:
- 异步:允许生产者继续处理下一个任务,而无需等待消费者的响应,从而提高了系统的响应速度(接收请求到响应请求所需的时间,单个请求好坏)和吞吐量(单位时间内处理的请求或任务数量,RPS,TPS,整体系统性能)。
- 服务解耦:生产者和消费者不需要直接通信,这使得系统更加灵活,易于扩展和维护。
- 流量削峰:在高负载期间,消息队列可以作为缓冲区存储消息,直到消费者能够处理它们,有助于平滑流量高峰。
- 可靠性保证:消息至少被消费一次或者恰好被消费一次,从而确保数据的一致性和完整性。
2.问题定义以及发生原因:
- 消息重复消费:
- 定义:消息队列中的某一条消息被消费者多次消费;消费者在处理消息后未能正确确认消息已处理。
- 原因:网络故障(网络不稳定,消息确认可能丢失);消费者崩溃(消费者处理消息过程中崩溃,未能确认消息被处理);幂等性缺失(消费者处理消息的逻辑不具备幂等性(即多次执行同一操作的效果等同于执行一次))。
- 数据一致性:
- 定义:消息队列中消息的处理结果与预期状态保持一致。
- 原因:消息重复消费;事务处理不当(处理消息的同时更新数据库或其他外部系统时,如果没有正确使用事务,可能会导致数据不一致);错误处理不当(消息处理过程中出现错误而没有妥善处理,可能会导致数据不一致)。
- 消息丢失:
- 定义:消息队列中的消息未能被正确处理或未能被消费者接收。
- 原因:消息队列故障;消费者故障(消费者在处理消息前崩溃,未能正确接收消息);配置不当(消息过期时间设置得太短,可能导致消息在被消费者接收之前就过期)。
- 消息顺序:
- 定义:消息队列中的消息按照一定的顺序被生产和消费。
- 原因:并发消费(多个消费者并发处理消息可能导致消息顺序被打);分区消息(在分布式消息队列中,消息可能被分布在不同的分区中,导致顺序无法保证)。
- 消息堆积:
- 定义:消息队列中积压了大量的未处理消息。
- 原因:消费者处理能力不足(消费者处理消息的速度慢于消息产生的速度);突发流量(在高峰时段,消息产生量剧增,超过了消费者的处理能力);系统资源限制(如 CPU、内存等资源不足,影响消息处理速度)。
3.解决方案:
- 消息重复消费:
- 幂等性设计:确保消费者处理消息的逻辑具备幂等性,即无论消息被消费多少次,结果都是一致的。
- 消息确认机制:使用消息队列提供的消息确认机制,确保消息被正确处理后再确认。
- 消息去重:消费者端实现消息去重逻辑,例如使用消息 ID 或消息内容的哈希值作为标识来避免重复处理。
- 数据一致性:
- 事务消息:使用消息队列提供的事务消息功能,确保消息的发送和业务操作作为一个完整的事务。
- 幂等性设计:确保消息处理逻辑具备幂等性。
- 错误处理机制:实现错误处理逻辑(代码发生异常时,如何处理,即在try{}catch(){}中定义异常后代码如何处理),确保消息处理过程中发生的错误能够被妥善处理。
- 消息丢失:
- 持久化:确保消息队列将消息持久化到磁盘,防止服务重启时消息丢失。
- 死信队列:使用死信队列来捕获无法处理的消息,确保消息不会丢失。
- 消息确认:确保消费者在消息处理完成后正确确认消息。
- 消息过期:合理设置消息的过期时间,避免过早过期。
- 消息顺序:
- 有序消息队列:使用支持有序消息处理的消息队列。
- 单分区消费:确保消息在一个分区中被生产和消费,避免跨分区导致的顺序问题。
- 消息排序逻辑:在消费者端实现消息排序逻辑,确保消息按顺序处理。
- 消息堆积:
- 水平扩展:增加消费者实例的数量,提高消息处理能力。
- 消息优先级:有限处理重要消息。
- 消息分组:将消息分为不同的组,由不同的消费者组处理,减轻单一消费者组的压力。
3.RabbitMq中某些问题的解决方案示例
模仿一个简易的商品下单,向库存发去扣除库存,以及安排物流接件的场景,来实现RabbitMq解决上述部门问题(消息重复,消息丢失,消息顺序,数据一致性,消息堆积)
3.1 订单生产者
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
/** * 订单生产者:分别发送订单消息到库存和物流 */ @Slf4j @Service public class OrderProducer { @Autowired private RabbitMqConfig rabbitMqConfig; public void sendMsg(){ try { Channel channel = rabbitMqConfig.getChannel(); channel.exchangeDeclare(RabbitMqConfig.ORDER_EXCHANGE_NAME, BuiltinExchangeType.DIRECT); //1.通过雪花算法保证订单id唯一,解决部门消息重复消费问题 //TODO 暂时先写一个简单消息 String orderId = "ORDER12345"; String inventoryMsg = "{\"orderId\": \"" + orderId + "\", \"action\": \"reduce_stock\"}"; String shippingMsg = "{\"orderId\": \"" + orderId + "\", \"action\": \"ship_order\"}"; //2.消息发送确认机制,解决部分消息重复消费以及消息丢失问题 //发送消息给库存 channel.basicPublish(RabbitMqConfig.ORDER_EXCHANGE_NAME, RabbitMqConfig.ROUTING_KEY_INVENTORY_NAME, null, inventoryMsg.getBytes()); //确认消息发送库存 channel.waitForConfirms(); //发送消息给物流 channel.basicPublish(RabbitMqConfig.ORDER_EXCHANGE_NAME, RabbitMqConfig.ROUTING_KEY_SHIPPING_NAME, null, shippingMsg.getBytes()); //确认消息发送库存 channel.waitForConfirms(); }catch (IOException e){ log.error("消息队列发送消息IO异常", e.getMessage()); } catch (InterruptedException e) { e.printStackTrace(); log.error("消息队列发送消息中断异常", e.getMessage()); } } }
3.2 库存消费者
3.3 物流消费者