数据一致性1 方案
数据复制
我们把数据复制,定义的更加宽泛一些。
假设从一个可信数据源a,经过一系列计算(func)后,生成目标数据b。这个过程就算复制。
- a,可信源数据 一般有两类源数据:1. 用户输入数据,2. 用户操作变更。
- b,转换后目标数据
- func,a到b的转换过程
可信数据: 指的是可以在业务上确信是正确的,没有争议的。对业务系统来说,通常只有用户自己输入的数据才能是无可争议的,能被当做可信数据。程序自动生成的数据通常都有出错的风险。
这个复制过程几乎涵盖了业务中的任意转换过程,举例:
- 常规事务内,数据a转换为b。
- 常规DB不能满足复杂查询,需要把业务数据复制到另一个nosql存储中。
- 为了做数据联合查询,聚合多个表到一个大表中。
- 为了统计分析做的某种程度的周期性数据聚合。
只要满足一个逻辑上有某种一致性的数据被放在了两个地方,就会引发可能的一致性问题。比如常见的两种情况:
-
数据a转换为b
无论是否在事务内,都可能会出现这个转换过程错了,或者转换对了但是b没有写入成功。
-
数据从a库原样复制到b库
因为数据出现了两份,所以这个过程是有可能不一致的。
可能遇到的问题
一般情况下,我们可能会遇到下面几种错误场景:
- 数据没有同步
- 数据同步了但是做错了
- 目标数据同步多了(算是上一种的特例)
一致性方案
基本方案
为了达成最终一致性,业务系统必须满足一些基本条件:
-
a的持久化
- a是用户输入数据:必须持久化。
- a是用户操作变更
- b的准确性很重要,则变更日志必须持久化,且要跟用户操作结果保持事务性。
-
b的准确性不重要,允许偶尔丢失,可以选择宽松的方式。
- 若用消息订阅机制生成b,则a可以不持久化。
- 若用access日志去生成b,前提是access日志是持久化的(但不保证数据完整)。
在这种情况下,b的生成如果是累积结果,要注意这个累积不因单个消息的丢失造成永远不准确,需要找一个合适的可信点去隔离前面的错误。
-
b的生成必须可重入
在b出错后,允许用被覆盖的方式重复生成。
一致性工具
在保障最终一致性的过程中,有下列常见的工具。
-
数据库事务
在单个数据库实例上用事务方式保障基本的一致性。
-
消息队列
在跨模块情况下,若选择了消息队列,需要注意下面一些问题。
-
消息乱序
业务系统需要设计序列号,确保业务系统自己可以识别正确的顺序,乱序消息到达后,可以有两种选择。
-
直接丢弃
这种情况,业务有可能丢失部分数据,要等待后续的校验脚本去做补偿。
-
拒绝消费,进入死信队列。
如果消息队列支持死信,可以跳过这个消息继续消费下一个,等待一定周期后再重新消费一次。目前不推荐这种方式。
-
-
消息重放
业务系统需要设计防重入机制,满足幂等性。
-
丢失消息
等待后面校对脚本完成补偿操作。
-
队列短暂不可用
可能资源紧张,需要运维操作。
这种情况,业务需要判断自己能忍耐的延迟时长,提前做好预案。目前我们的常规预案就是脚本补偿。
-
消息堆积
消费侧可能消费过慢,需要去解决消费侧的故障。业务在上线前要做好性能测试,上线后做好性能监控。
-
-
校对与补偿脚本
在a->b的转换过程中,若逻辑错误导致大量数据错误。
- 手工逐个修复无法满足时效性要求,临时编写刷数据脚本也来不及。
- 无法及时发现错误,等发现时往往已造成重大损失。
所以,对一些关键业务数据,需要提早准备几个脚本,应对可能的快速数据修复需求。
-
增量校对脚本
这个脚本持续例行校对a到b数据生成的正确性,发现不一致后快速报警并做标记,后面的修复脚本会自动去修复。
-
错误修复脚本
根据前面校对脚本的校对结果,快速修复不一致的数据。对这个脚本的要求。
- 能快速重复批量执行func的功能,再次生成并覆盖b。
- 速度要能在可接受的时间范围内。
-
全量校对脚本
对一些依靠监控a的修改时间来做数据复制的业务来说,a的某些逻辑错误可能导致修改时间没有变更,进一步导致复制没有进行,为了防范一些意外的复制丢失,用一个全量对比脚本,从a的创建时间维度去对比两侧数据。
注意:两侧对比,目标侧有可能会多出来数据,不止是会少。目标侧多出来的数据理论上需要删除。
这个脚本的功能仅仅是为了发现意外错误,发出报警提示人工修复bug。
-
例行运行
为了达成最快的错误修复体验,可以配置为分钟级的自动运行和修复。
-
业务监控
对于前面的每个一致性工具,都需要及时的去监控业务状态,及时发现错误并修复。
逻辑错误
理论上,程序在任何位置,都可能产生逻辑错误,脚本也同样不能例外,那么脚本的意义在哪。
首先看,理论上应对错误的方式大致有两种。
-
增强程序质量,减少错误率。
如果一个软件大部分情况下都不正确,那么任何后续的工作都无意义。
-
快速发现并快速修复错误。
假设软件大部分情况下是正确的,那么少数情况下的错误,如果修复时间够快,就能最大化的降低损失,甚至在用户尚未感知到错误的情况下已修复了,这种方式在实际的业务操作中是完全可接受的。
脚本要完成的工作,即是修复速度够不够快的问题。因为脚本可以批量的重放业务逻辑,理论上重跑一遍的时间就会很快。即使是脚本逻辑错了,修改后再跑一遍也会很快。
但是,不可能为所有的场景都提前准备脚本,所以针对一个场景,需要根据实际情况判断,是做不做脚本,或是做哪些脚本。
- 业务数据出错的严重程度。
- 数据出错后,客户能接受多久的修复时间。
如果出错后果严重,且修复时间要求非常苛刻的,则需要完善的脚本,且配置为自动化运行。其他情况可以根据实际情况在“增量校对、错误修复、全量校对、例行运行”几个脚本工具之间组合式选择。