消息队列基础篇
消息队列适用场景
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的消息严格有序