消息队列基础篇

消息队列适用场景

A、工序流程:A->B->C 

问题:下游消费速度不同,导致消费积压

方案:B和C节点配置暂存成品库

 

 

 

B、异步处理

 

   秒杀下单流程:

1、 风险控制

2、 库存锁定

3、 生成订单

4、 短信通知

5、 更新统计数据

 

实际库存锁定后,即可返回给用户;后续请求数据放入消息队列,异步消费

 

 

 

C、流量控制

   如何避免过请求压垮系统?

   方案一:使用消息队列隔离网关和后台服务,达到流量控制和保护后端服务目的

  

 

 

1、 短期大量秒杀请求达到网关时,先将消息堆积到消息队列

2、 后端服务按照自己最大处理能力,从消息队列消费请求处理

3、 超时请求直接丢弃,APP将超时无相应请求处理为秒杀失败

4、 运维人员可随时增加秒杀服务实例数进行水平扩容

 

   优点:根据下游服务能力自动调节流量,达到“削峰填谷“作用

   缺点:

1、 增加系统调用链环节,总体相应时间变长

2、 上下游系统都需要将同步改成异步消息,增加系统复杂度

 

方案二:消息队列实现令牌桶,更简单进行流量控制

  

 

 

1、 单位时间只发放固定数量令牌到令牌桶中

2、 规定服务在请求之前从令牌桶中拿到一个令牌。

如果令牌桶无令牌,则拒绝请求。

   

   D、微服务聚合根解耦

      支付系统需要发起支付流程;

风控系统需要审核订单的合法性;

客服系统需要给用户发短信告知用户;

经营分析系统需要更新统计数据;

 

订单服务订单变化时,发送消息到主题Order中,所有下游系统订阅主体Order

   E、发布/订阅系统实现微服务的观察者模式

   G、连接流计算任务和数据

   I、将消息广播给大量接收者

 

消息队列选型

360维度评估

1、 开源:可维护性

2、 流行:活跃度高,待解决issure少;与周边生态系统很好兼容

例如:Flink内置Kafka的DataSource

3、 消息可靠传递:确保不丢消息

4、 Cluster: 支持集群,确保不会某个节点宕机导致服务不可用

5、 性能:具备足够好的性能

 

开源产品

流行度

开发语言

厂商

备注

RabbitMQ 

第1梯队

Erlang

 

1、 轻量级消息队列,设计理念:消息队列是管道

2、 Exchange模块,位于Producer和Queue之间

根据配置路由将消息发送到不同队列中

 

问题

1、 消息队列堆积支持不好;大量消息积压不正常,

应尽量避免;大量积压,消息队列性能急剧下降

2、 性能最差:每s处理1W-10几W级别消息

如果对性能要求非常高,不要选择RabbitMQ

3、开发语言Erlang,维护困难

RocketMQ

第1梯队

Java

阿里巴巴

性能:每s处理几十W级别消息;比RabbitMQ高出1个数量级

低延迟:在线业务ms级响应 =》适合在线金融级别业务,稳定

缺点:国内产品,流行度不高;与周边生态系统的集成和兼容程度相对差些

Kafka

第1梯队

Scala、Java

Apache

设计目的:用于处理海量的日志,采用异步和批量处理思想

性能:同RocketMQ,极限处理能力2kw条

同步时延:较高,异步批量需先“先攒一波再一起处理“  =》 不适合在线业务场景,收集日志&前端埋点&监控信息等

Pulsar

 

 

Yahoo

采用存储和计算分离的设计, 未来黑马

 

消息队列模型

队列(Queue)、主题(Topic)、分区(Partition) =》每个消息队列含义不都一样

 

两种模型:

队列模型:纯数据结构队列 RabbitMQ

发布和订阅模型:增加主题,队列(分区),消费组概念  RocketMQ和Kafka

 

问题:1份订单数据,需支持风控、分析、支付等系统接收信息

 

 

 

1、RabbitMQ消息模型

 

 

 

Exchange:配置策略决定消息投递到具体队列

 

 

 


2、RocketMQ 消息模型
消息ACK确认机制  =确保消息可靠传递

生产者:发送给服务器,服务器将消息写入主题或队列后,发送确认给生成者;

        如果未收到确认或收到失败消息,则重发消息

消费者:收到消息并完成业务消费逻辑,也会给服务端发送消费成功消息。

        服务端收到消费确认后,才认为消息被成功消费,否则会给消费者重新发送消息,直到收到消息成功确认

 

  问题:保证消息有序性,某条消息被成功消费前,下一个消息不能被消费

  =》每个主题在任意时刻,至多只有1个消费者实例消费,没法通过水平扩展消费者的数量来提升消费端总体的消费性能。

 

  RocketMQ解决方案:

a、主题包含多个队列,通过队列实现多实例并行生产和消费;

 b、队列上保证消息有序性,主题层面无法保证消息严格有序

 

 

 

 

订阅:通过消费组(Consumer Group)实现, 每个组消费主体中一份完整消息,不同消费组之间消费进度不受影响;即一条消息支持多个消费组同时消费,彼此不干涉;

 

主题的各个队列,需要为每个消费组维护消费位置(Consumer Offset );每消费1条消息,消费位置+1;

 

3、 Kafak消息模型同RocketMQ

主题Topic中的队列,叫做分区(Partition)

 

 

 

 

 

事务消息实现分布式事务

应用场景:创建订单后,从购物车删除已下单商品

原因:订单库和用户购物车跨库;从购物车删除已下单商品,非用户下单支付流程核心步骤

 

 

 

核心步骤:

1、 订单库插入一条订单

2、 发消息给消息队列,消息内容为刚创建的订单

 

问题:

1、 订单创建成功,没有清理购物车  =》发消息失败

消息消费失败:重试,发送消息确认即可

2、订单没创建成功,购物车却清理掉了 =》创建订单失败

 

关键点:

创建订单和消费消息两个步骤,保证同时成功/失败,不允许1个成功/1个失败情况

 

Kafka和RocketMQ都提供事务功能

 

 

 

1、 半消息:包含完整消息内容;事务提交前,消费者不可见

2、 提交或回滚:根据本地事务执行结果决定

 

问题:第4步提交事务消息失败如何处理?

Kafak方案:

直接抛出异常,用户自行处理 =》 反复重试,直到成功;回滚之前创建的订单

 

 

RocketMQ方案:

 

 

 

优点:

MQServer未收到MQServer发送方提交/回滚消息确认时,定期反查本地事务对应的状态;再根据本地事务的结果,决定提交/回滚事务消息;

 

1、 业务方提供反查本地事务状态接口,告知RocketMQ本地事务成功/失败

例如:判断消息中的订单ID,判断本地订单库是否存在即可

2、 容灾:即使MQ发送方宕机,定期利用反查本地事务接口,依旧保证事务完整性

具体实现:扫描半消息的订单ID, 利用本地事务反查接口,决定是否提交

缺点:耦合度太高,需要知道业务逻辑

 

1. 首先通过producer.sendMessageInTransaction()方法发送一个半消息给MQ.

2. 此时会在TransactionListener中的executeLocalTransaction()方法阻塞,然后在这个方法里面进行订单创建并提交本地事务,如果commit成功,则返回COMMIT状态,否则是ROLLBACK状态,如果正常返回COMMIT或者ROLLBACK的话,不会存在第3步的反查情况。

3. 如果上面的本地事务提交成功以后,此节点突然断电,那么checkLocalTransaction()反查方法就会在某个时候被MQ调用,此方法会根据消息中的订单号去数据库确认订单是否存在,存在就返回COMMIT状态,否则是ROLLBACK状态。

4. 购物车在另外一个项目中,反正只要收到MQ的消息就将本次订单的商品从购物车中删除即可

 

确保消息不丢失

1、 消息有序性编号:当前消息的序号=上一条消息的序号+1

a、 Kafka和RocketMQ消息有序性

不保证Topic严格有序,只保证分区消息的有序性; 发送消息必须指定对应分区

b、 Producer是多实例,为每个Producer附加上Producer标识
例如: P1_M1,P1_M2, P2_M1,P2_M2 标识来源方+消息编号

c、  Consumer实例和分区数量一致,方便在Consumer检测消息序号的连续性

 

2、 确保消息可靠传递

 

 

 

生产阶段:消息从Producer创建,经过网络传输给Broker

存储阶段:消息在Broker存储,如果是集群,消息会在这个阶段复制到其他副本上

消费阶段:Consumer从Broker拉取消息,经网络发送到Consumer上

 

A、 生产阶段:正确处理返回值或捕获异常


同步发送

 

 

 

异步发送

 

 

 

B、 存储阶段:

 

单个节点的Broker

配置Broker参数将消息写入磁盘再给Producer返回确认消息

RocketMQ配置flushDiskType:SYNC_FLUSH 同步刷盘

多节点集群
至少消息发送2个以上节点,再给客户端回复确认响应

C、 消费阶段:
从Broker拉取消息后,执行用户的消费逻辑;成功后,再给Broker发送消费确认响应

 

 

 

 

 

重复消息处理

MQTT协议

At most once:至多一次

At least once:至少一次, 默认

Exactly once:恰好一次

 

幂等处理: f(f(x))=f(x)

At least once + 幂等消费 = Exactly once

 

方法

1、 数据库唯一约束:流水记录

2、 版本号技术: ABA

 

消息积压处理

消息队列:收发性能 几十w笔/s

正常业务逻辑:每秒处理几百到几千笔

 

 

原则:保证消费端消费性能>生产者发送性能

优化策略:

a、 优化消费业务逻辑性能

b、 水平扩容,增加消费端的并发数  =》 扩容Consumer实例时,同步扩容Topic的分区数量,确保Consumer实例数=分区数量

 

Consumer实例数>分区数量数,没意义;原则单个分区上实际上只能支持单线程消费

  

突发消息积压情况如何处理?

本质原因:发送变快,消费变慢  =》 监控定位

消费变慢:增加消费端实例数,应对大促或抢购场景  

无服务器资源扩容:系统降级,关闭不重要业务,减少发送方数据量

其他问题:代码bug异常,定位日志异常;打印堆栈信息,检查是否死锁等

 

消息队列并行消费和严格有序

 

 

 

消费组记载每个Broker的队列的具体消费位置

 

1、 单个队列如何支持并行消费?

JMQ方案,假设消息编号为:5,6,7,8,9,10

正常消费:5,6,7被三个消费者同时消费,都返回成功;则消费位置更新到8

异常消费:6,7返回成功,5不响应;复制5到特殊重试队列,依然消费位置更新到8

 

2、 如何保证消息的严格有序?

全局严格有序:消息丢列配置1,生产者和消费者数量为1

局部有序:根据账号ID作为Key,一致性Hash算出队列编号,相同Key的消息严格有序

 

posted @ 2022-05-18 17:41  mick0802  阅读(165)  评论(0编辑  收藏  举报