查看原文
其他

高并发消息队列补充篇:在所依赖存储不授信的场景下实现柔性事务降级

Coder的技术之路 Coder的技术之路 2021-05-19

消息队列补充篇,欢迎收藏,欢迎讨论

本文内容预览:

  1. 新公司做设计的些许不适应
  2. 基础服务支持不到位的坑
    2.1 项目背景和整理设计思路
    2.2 不可缺少的强依赖--存储
    2.3 存储的调研和选择
  3. 消息队列的介入
    3.1 非强一致存储为啥会丢数据
    3.2 消息队列登场
    3.3 更进一步--存储降级
  4. 总结

Part1新公司做设计的些许不适应

在大厂和小厂做研发到底有啥不一样?其他好的坏的方方面面没法细说,咱主要还得说技术方面。

本来大部分的架构体系和思维都是在大厂构建的,到了小厂之后发现,起止是不适用,简直就是不适用(夸大了,😄)。其实就本人理解主要是在底层的基础能力的支持程度上有所差异。

很多时候,在大厂待惯了的同学,或者一直在大厂待着的同学,思维惯性是不可避免的,感觉某些能力是理所应当的时候,往往实际情况是无法百分之百契合的。

所以,这种情况下,更要求我们有更强的细节把控能力,在系统设计和实施之前,要把所有涉及到的外部服务、底层支撑程度全都考虑进去,并一一应证,以做到万无一失。

本文阐述一个本人真实的采坑经历,希望帮大家加深对系统设计把控能力的重视。

Part2基础服务支持不到位的坑

2.1项目背景和整理设计思路

项目背景其实还是我们的数据一致性保证长事务引擎。

技术架构的设计思路在《分布式事务从入门到放弃(二)--详述DT引擎一致性原理及设计》一文中已有详述。这里就简单叙述下。(已经被我说了好几次,没办法,只有自己从0 到 1搭起来的,才理解的最深刻)

整体架构如下:

  • 通过状态机来组织和支持多种不同的业务流
  • 通过将外部服务节点化来抽象和规范化参与者的服务
  • 通过节点的异常捕获来感知参与方的执行结果
  • 通过实时或异步的恢复机制,实现柔性事务·

2.2不可缺少的强依赖--存储

想要业务请求保证最终一致,不可能是没有存储参与的一锤子买卖,因为还要考虑本身服务器的抖动,业务上的异步要求等等。所以,存储是一个不可缺少的依赖,如下图所示:存储服务作用在引擎的整个生命周期:

  • 在请求进入初期,进行上下文落地(因为广告流量只有一次操作,不像普通支付可以允许用户重试,广告场景下,用户的多次操作需要进行分别计费,并且不太适合利用MQ的ack机制进行重试,因为那需要等流程全部执行完,会影响消息消费速率)
  • 在节点执行结束,进行节点执行状态落地(这样,在遇到需要补偿的情况,可以避免冗余调用,防止不需要重试的系统被其他抖动的系统冲击)
  • 在异步恢复时,获取上下文和节点执行状态集合,以完成事务的最终一致性处理。

2.3存储的调研和选择

业界的选择--数据库

对于支付类业务,其实,最好的是用数据库。业务层的订单表相当于业务请求上下文,会将请求的所需信息落地(没有上下文落地其实也没关系,返回用户失败即可)。

节点执行状态数据存在和订单表同库的另一个表中,即可支持一个在一个本地事务内保证分布式事务的最终一致性逻辑。

业务特性决定了数据库不合适

由于种种原因,我们这里不太适合用数据库,一个是广告上下文太大,且绝大多时候没有用,为了少数异常存到订单表的话太浪费;二是目前采用实时库+历史库的方式进行,没有分库,如果增加了中间状态表,目前的并发量,对数据库压力会很大,但目前搞分库又不是那么必要。

所以数据库不太适合。

公司自研存储的选择和使用

正好,公司自研了一套基于rocksdb的高性能存储,在读写性能和数据结构方面基本可以代替MySQL和Redis。在公司内部被广泛使用。

两个版本,一个是paxos强一致版本,读写性能稍弱,但保证数据强一致;另一个是普通版本,读写性能更高,但不保证强一致,即有可能主从切换会丢数据。

对于带金融属性的业务来说,在理论上的读写性能满足业务要求的情况下,当然是首选强一致的版本了。特别是业务上下文,丢失的结果就是该请求的整个异常恢复流程无法被正常唤起。

然而,理想照进现实,由于该版本之前应对的业务场景较为简单,并发也没有我们这么大,一些底层调优不到位导致服务抖动频繁。

如果是在大厂,就像之前用OB,只要OB有承若数据不丢,那基本不用考虑丢失的问题。如果丢了,我想可能大概率就会把锅抛给存储团队,限时优化之类。

但,我们小厂可都是相亲相爱的一家人,怎么能干这种事。所以选了另一个版本,使用更广泛,更成熟的非强一致版本。而数据丢失的问题由业务自己来想办法。

Part3消息队列的介入

3.1非强一致存储为啥会丢数据

如上图所示,该存储架构采用的是主从模式,数据由主写入,同步到从,当主异常时,进行主从切换,恢复服务。

但是,由于不是强一致协议,写主成功即为成功,当主宕机时,虽然主从切换很快,10秒完成,但还没有来得及同步到从的那部分数据,就会因为因为主从切换而丢失。

怎么办?

3.2消息队列登场

为了应对存储不授信的情况,我们引入了消息队列来实现存储的丢失补偿。如上图所示,引入延迟消息进入处理流程。

  • 当节点执行发生异常时,发送当前业务上下文到消息队列。如果是正常执行的情况则无需发送。
  • 消息的延迟间隔,要大于主从切换的时间,并且需要小于定时任务的触发间隔。比如,主从切换需要10s,那延迟消息的延迟间隔就设置为30s , 接收消息都重新插入上下文到存储。在节点异常一分钟之后,被定时任务捞取,执行处理。

用两个时间差来覆盖掉主从切换带来的数据丢失的影响。

3.3更进一步--存储降级

那么,更极端的情况,如果整个存储服务持续不可用怎么办?

降级: 自己的命运要把握在自己手中。目的是保证绝大部分服务正常。

  • 监控系统。用于收集和汇报操作存储时的异常,并统计错误率和超时率
  • 一旦错误率和超时率达到阈值(比如持续30s,所有服务全部超时)执行关联脚本。
  • 脚本负责触发配置中心的配置切换,由正常模式切换为柔性模式。
  • 柔性模式下,所有涉及存储读写的操作将被忽略,以保障绝大部分请求可以正常执行。
  • 遇到执行异常的节点,将上下文发送至消息队列,消费时不再插入存储,而是改为直接消费。

Part4总结

本文想从实际的案例出发,给大家提供一种利用消息队列解决问题的思路。希望大家遇到其他问题的时候,可以有所借鉴。毕竟业务场景千千万,只有思路得人心。

高并发系列历史文章微信链接文档

  1. 垂直性能提升
    1.1. 架构优化:集群部署,负载均衡
    1.2. 万亿流量下负载均衡的实现
    1.3. 架构优化:消息中间件的妙用
    1.4. 存储优化:mysql的索引原理和优化
    1.5. 索引优化补充篇:explain索引优化实战
    1.6. 存储优化:详解分库分表


    您可能也对以下帖子感兴趣

    文章有问题?点此查看未经处理的缓存