关于后端:揭露数据不一致的利器-实时核对系统

8次阅读

共计 6277 个字符,预计需要花费 16 分钟才能阅读完成。

本文首发于微信公众号“Shopee 技术团队”。

摘要

随着企业业务倒退,以及微服务化大趋势下单体服务的拆分,服务间的通信交互越来越多。与单体服务不同,微服务间的数据往往须要通过额定的伎俩来保障一致性,例如事务音讯、异步工作弥补等。除了从机制上最大水平保障以外,如何观测并及时发现数据不统一也十分重要。

本文介绍 Shopee Financial Products 团队设计和开发的 实时核查零碎(Real-time Checking System),它接入简略,只需依据核查需要配置对应的核查规定,实现了规定热加载,并能在不侵入业务的前提下对系统数据进行实时监测比照,及时发现数据的不统一。零碎落地至今,已在 Shopee 多个产品线推广应用,帮忙不同团队疾速发现线上数据不统一问题,为数据保驾护航。

1. 背景

1.1 零碎数据的不一致性

在日常的开发迭代中咱们能发现,零碎的数据有时并不依照咱们构想的那样进行变更。常见的场景如:用户进行了还款(Repay),零碎 A 收到了还款申请后调用零碎 B,将已解冻的账户进行冻结,但因为某些起因(如系统故障、网络分区等),冻结的申请没有到达 B,或者冻结胜利的响应没有返回给 A,此时会呈现曾经确定收款但未冻结,或未确认收款却已冻结的状况,从而引起用户投诉或资金损失。

造成这类问题的起因通常有:代码逻辑 Bug、并发场景处理不当、根底组件(网络、数据库、中间件)故障、跨零碎间不足原生的一致性保障等等。随着业务扩大,企业内的利用越来越多,且有许多 单体利用 (Monolithic Application)向 微服务(Microservices)拆分转型,分布式的场景下失落了数据库事务的反对,须要解决数据一致性的问题。

保障数据统一的计划有很多种,在单体服务且短少不同组件间(例如跨 Database、不同存储中间件)事务反对的场景下,能够应用本地事务表 + 弥补工作的组合,将主表数据与查看工作通过事务写入,再通过异步工作一直查看指标数据是否统一并进行弥补,可实现最终一致性;在跨服务场景下,Saga 模式通过可靠消息及服务提供回滚事务的能力,来实现分布式事务。

然而,对于重要的业务,不论应用何种一致性计划,提供额定的查看、核查、兜底伎俩都是必要的,由此衍生出了很多的业务核查、对账的需要。服务间通过特定伎俩保障数据一致性,并设计无侵入的旁路零碎进行数据核查和校验,是微服务架构下的典型搭配。

1.2 离线核查的缺点

常见的离线数据核查能够通过定时工作,依照肯定的筛选条件,从不同数据源中获取特定数据,再进行比拟。这种计划的伪代码如:

func Check() {// 获取上游 update_time 落在 [a, b) 的数据行
    upstreamRows := QueryUpstreamDB(a, b)

    for uniqueKey, sourceData := range upstreamRows {
        // 为每个上游数据查找对应的上游数据
        targetData := QueryDownstreamDB(uniqueKey)

        // 比照上下游数据
        Compare(sourceData, targetData)
    }
}

时效性低 是这类查表计划的通病。核查操作通常放在异步工作中定时执行,执行工夫和离数据变更工夫有肯定提早,且定时工作的查问条件也会对核查指标造成影响。当出现异常数据时,不能及时发现问题,只能期待下次定时工作执行后能力发现。

引入了 额定的扫表开销 同样是个不容忽视的问题。在数据量较大,尤其是存在大量 INSERT 操作的场景下,想要核查就须要 SELECT 出上下游的指标数据。为了在不影响失常业务的状况下及时处理完核查工作,开发者可通过将查问转移到从库,甚至引入核查工作独占的从库,但此类查表核查计划在资源应用和实现复杂度方面都不够现实。

同时,因为查表失去的后果只是以后的数据版本,在两次查看之间,数据可能产生了屡次变更,定时工作无奈感知和观测到每个状态变更,在数据被频繁 UPDATE 的场景下也存在肯定的核查和检测难度。

因而,要实现更好的数据核查,咱们须要思考以下几点指标:

  • 实现秒级核查
  • 尽量减少数据库查问
  • 核查数据变更,而非核查数据快照
  • 简略灵便的接入形式

2. 实时数据核查

为了更好地发现数据不统一的状况,Shopee Financial Products 团队在 2021 年中设计并实现了 Real-time Checking System(实时核查零碎,RCS)。RCS 具备以下外围劣势:

  • 秒级数据核查。
  • 对业务逻辑无侵入。
  • 可配置化接入。

从上线至今,RCS 帮忙团队及时检测到了屡次数据问题,能够将起因演绎为以下几个方面:

  • 代码逻辑 Bug:包含幂等解决、并发问题、业务逻辑谬误等。
  • 零碎运行环境:DB 异样、网络抖动、MQ 异样等。

本节次要介绍 RCS 的实现,包含零碎架构和核查流程、核查性能优化、音讯告诉机制等。

2.1 零碎架构与核查流程

在零碎设计上,咱们将 RCS 分为了三层:

  • 变更数据获取(Data Fetching Layer)
  • 数据核查(Data Checking Layer)
  • 核查后果解决(Result Handling Layer)

2.1.1 变更数据获取

实时核查,顾名思义须要着重关注“实时”和“核查”两个要点。Data Fetching Layer 负责达成实时的指标,通过对不同 CDC(Change Data Capture,变更数据抓取)计划的调研,咱们应用了 Log-Based 的计划来提供时效性保障。

扩大浏览

CDC 模式用于感知数据变更,次要能够分为以下 4 类:

  • Timestamps,基于 update_time 或相似字段进行查问来获取变更数据。
  • Table Differencing,获取残缺数据快照进行比对。
  • Triggers,为 DDL、DML 设置 Trigger,将变更内容用额定的操作记录至数据库。
  • Log-Based,典型例子为利用 MySQL binlog 和 MongoDB oplog。

其中,Timestamps 计划和 Table Differencing 均由定时工作驱动,时效性较弱。Timestamps 计划无奈感知被删除的数据,应用时须要由软删除代替;Table Differencing 计划补救了这个毛病,然而屡次获取残缺数据会让整套计划显得十分轻便。

Triggers 计划和 Log-Based 计划获取到的均为数据变更而非数据快照,但 Triggers 感知后以特定的语句将其记录下来,实质上是一次写操作,仍给数据库带来了额定的累赘。

当 MySQL 产生数据变更时,高可用的 binlog 同步组件会获取到对应 binlog,并将其投递至 Kafka 中,以此获取变更数据的数据值用于核查。

在理论应用中,须要核查的数据可能并非都存在于 MySQL 中,例如咱们也须要核查 MySQL 与 MongoDB 的数据、MySQL 与 Redis 的数据。为此,业务零碎也能够通过自行投递特定格局的 Kafka 音讯来接入,从而保障接入的灵活性。

2.1.2 数据核查

Data Checking Layer 负责解决接管到的数据流,包含获取特定的核查规定,接管到数据时进行暂存或比对。RCS 对 binlog 数据进行形象,提炼了一套通用的可配置化的核查规定。用户只须要填写对应的规定,即可实现自助接入。规定定义示例如下:

不难想象,不同零碎间数据的变更是有先后的,且变更的音讯被 RCS 接管到也会有先后顺序。因而,先到达的数据须要被存储下来作为后续比对的指标,后到达的数据则依照规定与已有数据进行比对。

为了便于形容,这里先定义几个名称:

  • 数据上游:先达到 RCS 的数据为上游。
  • 数据上游:后达到 RCS 的数据为上游。
  • 核查项:某个数据核查需要,包含上游数据和上游数据。例如:System A 与 System B 核查用户资金状态的需要。

以上面这一次核查为例,它须要判断数据是否在 10 秒达成统一,整体的核查流程能够简要形容为:

  • (图 8)核查项的上游数据达到,暂存 Redis 和提早队列。
  • (图 8)RCS 期待核查项的上游数据:

    • 比对数据达到,进行核查,并删除 Redis key;
    • 比对数据未达到,判断提早队列中的数据。
  • (图 9)提早队列达到工夫后,再次查问在 Redis 中是否有对应数据:

    • 存在,则超过核查工夫阈值,发送异样告警,删除 Redis key;
    • 不存在,则已核查。

2.1.3 音讯告诉机制

RCS 的指标是及时发现数据不统一的问题,因而,在 Result Handling Layer 中接入了 Shopee 企业 IM(SeaTalk)的机器人进行告警。将来告警接口也会进行凋谢,便于扩大和让其它音讯利用进行接入。

咱们设计了四种音讯告诉机制:

  • Mismatch Notice
  • Aggregated Notice
  • Recovery Notice
  • Statistical Notice

Mismatch Notice 应答个别场景下的核查失败,及时告诉到对应的业务负责人,便于疾速定位问题起因并修复数据。但当大量数据呈现不统一时,Aggregated Notice 会取而代之,将告警进行聚合发送,防止影响到值班人员的失常浏览。

RCS 也会将核查失败的数据长久化,因此具备复原感知的能力。当异样数据恢复时,Recovery Notice 会发送音讯告知使用者何种不统一曾经复原,距离了多少工夫。

最初,Statistical Notice 会向使用者报告惯例的统计数据,包含 DB 主从提早、当日核查成功率等。

2.2 核查性能演进

零碎上线至今,接入或自行部署应用 RCS 的团队越来越多,对应的业务场景也各不相同,晚期的核查规定难以满足不同团队的核查需要。在 2021 年末,Shopee Financial Products 研发团队又对 Data Checking Layer 进行了一系列的扩大,目标是缩小保护老本,以较为通用的形式反对不同团队的应用。

2.2.1 等值 / 映射核查

在最早上线的版本中,RCS 零碎蕴含了等值和状态映射核查的性能,是针对组内理论面临的场景设计的,满足日常的应用需要。

核查零碎次要解决的是上下游零碎之间金额数值、状态的变动,通常咱们能获取到的 binlog 外围字段示例和核查逻辑如下:

假如先接管到 System A 的 binlog 音讯,暂存 Redis,规定工夫内也接管到了 System B 的 binlog 音讯:

  • 依据 System B 这条 binlog 的特色,发现配置有两条核查规定:

    • loan_amount 为 200,须要找到一条对应的 System A 的 binlog,且 order_amount 需与之匹配;
    • loan_status 为 4,须要找到一条对应的 System A 的 binlog,且 order_status 需为 2。

对于不同零碎间产生的单条记录变更的核查,等值和映射查看能笼罩到大部分场景。然而因为这两种核查的逻辑都是固定下来的,所以业务方如果有不同的核查须要,则须要新的代码逻辑实现。为此,研发团队思考 将核查逻辑交给应用方来形容,因此催生出了表达式核查的性能。

2.2.2 表达式核查

如果咱们思考以下的 binlog 示例,不同零碎间的数据模型设计并不统一,字段非一一对应。

System A 记录了 订单的金额为 100,而 System B 记录了订单的 已领取金额为 30,借贷金额为 70,须要核查的是 System A order_amount 是否等于 System B paid_amount + loan_amount,原有的设计无奈反对。

为此,咱们引入了表达式求值的计划,当 binlog 到达时,应用方通过一个返回值为布尔类型的表达式来形容本人的核查逻辑,如:

  • 判断 2.2.2 中求和场景:a.order_amount == b.paid_amount + b.loan_amount
  • 兼容判断 2.2.1 中场景:

    • a.order_amount == b.loan_amount
    • a.order_status == 2 && b.loan_status == 4

在表达式核查计划下,两个零碎间的简直所有的单条数据核查场景都能进行笼罩,且这种计划的益处在于研发团队不必再费心理提供新的计算、映射、与或非逻辑实现的反对,大大减少了保护老本。

2.2.3 动静配置数据核查

在电商和金融的场景中,存在一些动态数据,例如费率、流动优惠折扣等,会随着业务和经营打算产生实时变动。这类数据通常存储在配置表中,因而通过简略的表达式无奈进行定义,而不同业务零碎中的配置表结构设计也不一样,很难在核查零碎代码中进行申明。

为了满足这种场景,RCS 引入了对业务零碎 SQL 查问的反对,当获取到新的 binlog 时,查看这条 binlog 满足的核查规定,应用方在核查规定中会配置须要执行的 SQL 语句,以及分库分表规定,由核查零碎执行并失去比对的内容,再进行表达式核查:

  • binlog 中获取到以后订单的费率 order_rate 为 0.5。
  • 依据配置信息执行 SELECT 语句查问实时的费率 rate
  • 执行表达式核查 a.order_rate == rate

除此之外,RCS 也能反对 JSON 串核查,譬如 System A 须要核查 order_rate,然而存储 order_rate 信息是一个 JSON 串,rate_info = {"decimal_base":"10000", "order_rate":"0.5"}。能够在 RCS 的核查规定中,自定义 JSON 解析表达式,提取实在须要核查的字段。

3. 性能体现

RCS 零碎的性能次要取决于 Data Fetching Layer 和 Data Checking Layer。

Data Fetching Layer 的性能代表实时获取变更数据的能力,受 binlog 解析(CPU 密集型工作)及 Kafka 的音讯长久化(I/O 密集型工作)影响。业务团队可依据须要抉择对应的硬件搭建 CDC 模块,以咱们应用场景为例,每秒可投递的音讯数量超过 20K

Data Checking Layer 则负责进行数据核查,为了测试 RCS 的性能极限,Data Fetching 采纳 Kafka 发送源数据,核查零碎采纳单机部署。测试结果表明,RCS 每秒可实现核查 10K+ 次,具体数据如下:

Component Machine
Kafka 3 * 48 Core 128 GB
Redis 3 * 48 Core 128 GB
Real-time Checking System 1 * 48 Core 128 GB

<br/>

Number of check entry TPS CPU Cost
1 entry 14.3K 454%
2 entries 12.0K 687%
3 entries 10.4K 913%

从压测后果剖析,RCS 的性能瓶颈次要取决于 Redis 集群的性能,单次核查耗时约为 0.5ms。
当然,RCS 反对集群部署,做为 Kafka 的消费者,能够利用 Kafka consumer group 的 Rebanancing 机制,从而实现动静扩 / 缩容的机制。

4. 总结

Shopee Financial Products 团队在 2021 年落地的 RCS 目前在多个产品线推广和应用,次要解决传统 T+1 式离线数据核查提早高、业务耦合严密,且随新业务上线还带来额定的开发累赘的问题。

RCS 通过灵便的核查规定配置化、表达式场景笼罩以及 Log-Based 的 CDC 计划,提供近实时的数据核查解决方案,最大水平地升高数据不统一导致的资金、信息安全等危险。咱们也欢送不同的用户和团队接入或部署应用,在后续的更新迭代中,RCS 会进一步晋升核查的性能,以撑持业务量增长带来的核查需要。

本文作者

Yizhong、Songtao,后端研发工程师。来自 Shopee Financial Products 团队。

Jiekun,后端研发工程师,热衷于分布式系统 & Kubernetes。来自 Shopee Off-Platform Ads 团队。

正文完
 0