一、先搞懂核心问题:为啥Cloud Spanner灾备要验证数据一致性?
做云数据库的灾备,说白了就是怕主库挂了、或者主库所在的区域出问题,得把业务切到备用的库上继续跑。但切过去之后最要命的是什么?不是业务能不能跑,是跑起来的数据对不对——比如用户刚付的100块钱,切完之后变成90块,或者订单状态乱了,那比业务挂了还麻烦。
Cloud Spanner是谷歌的分布式关系型数据库,它的灾备逻辑跟普通数据库不太一样,自带跨区域的复制能力,还有快照备份的功能。但就算自带这些,也不能保证切换之后数据就一定没问题,比如复制过程中可能丢了没同步完的小批量数据,或者备份恢复的时候出了错,所以必须有一套能反复用的方法,来验证切换后的数据对不对,还要明确哪些情况是我们不用管的,避免做无用功。
二、先铺垫两个基础能力:Cloud Spanner的备份与恢复
要做数据一致性验证,得先搞懂Cloud Spanner自己的两个核心能力,不然没法设计验证流程。
2.1 快照备份:啥时候拍的,就能还原到啥状态
Cloud Spanner的快照备份,是在某个时间点(比如2024-05-20 14:30:00)给整个数据库拍个“照片”,这个照片里的所有数据,跟拍的时候完全一致,哪怕之后主库的数据变了,备份里的也不会变。
举个例子,你在14:30拍了备份,然后14:31有人改了某条订单的状态,那你把备份还原出来的库,那条订单的状态还是14:30的样子。
2.2 复制灾备:实时同步的“热”备用
Cloud Spanner的复制灾备,是主库跟备用库之间实时同步数据,只要主库的数据改了,备用库很快(通常几秒钟)就会同步过去。但这种同步不是“绝对实时”的,会有一点点延迟,比如主库14:30:00改的数据,备用库可能14:30:02才收到。
切换的时候,我们一般会等复制的延迟降到0(或者一个可以接受的很小值)再切,切完之后备用库就变成新的主库了。
三、核心:可复用的对比验证流程
我们要做的,就是不管什么时候做灾备演练,都能用同一套流程来验证切换后的数据对不对,不用每次都重新想怎么验证。这套流程的核心是“用备份当基准,跟切换后的库比”,因为备份是某个时间点的“标准照”,不会变。
3.1 提前准备:演练前拍备份(基准库)
在做灾备演练的前1分钟,给主库拍一个快照备份,然后把这个备份还原成一个单独的“基准库”——这个基准库就是我们的“标准答案”,后面所有的对比都以它为准。
这里要注意,拍备份的时间必须是“演练开始前的最后一刻”,因为备份拍的是当时主库的状态,我们要让这个状态跟演练前主库的状态完全一致。
3.2 做灾备演练:切到备用库
按公司的灾备流程,把业务从主库切到备用库。切完之后,备用库就变成了新的主库,我们叫它“切换后库”。
3.3 核心对比:基准库VS切换后库
这一步是最关键的,我们要对比基准库和切换后库的所有数据是不是一致。但对比不能瞎比,得按规则来,不然比不完。
3.3.1 先对比“静态数据”:不会变的数据
静态数据就是演练前不会变的数据,比如商品的基本信息(商品ID、名称、价格)、用户的基础信息(用户ID、注册时间)。这些数据在演练过程中不会被修改,所以基准库和切换后库的这些数据应该完全一样。
对比的时候,我们可以写一个脚本,把两个库的同一张表的所有数据查出来,然后对比每一条的每一个字段。这里给个具体的例子,用Python写,因为Python比较好懂,而且有专门的Cloud Spanner客户端。
首先明确技术栈:Python 3.9 + Cloud Spanner Client Library(google-cloud-spanner)
然后是脚本,脚本的逻辑是:先连基准库,再连切换后库,然后选一张静态表(比如商品表),把两个库的表数据都查出来,按主键排序,然后逐条对比。
# 导入Cloud Spanner的客户端库
from google.cloud import spanner
# ------------------- 配置部分 -------------------
# 基准库的配置:项目ID、实例ID、数据库ID
BASE_INSTANCE_ID = "spanner-instance-base"
BASE_DATABASE_ID = "spanner-database-base"
# 切换后库的配置:项目ID、实例ID、数据库ID(演练后切换到的库)
TARGET_INSTANCE_ID = "spanner-instance-target"
TARGET_DATABASE_ID = "spanner-database-target"
# 要对比的静态表名
TABLE_NAME = "products"
# 静态表的主键字段(用来排序,保证两个库的查询顺序一致)
PRIMARY_KEY = "product_id"
# ------------------- 配置结束 -------------------
# 初始化两个库的客户端
base_client = spanner.Client()
base_instance = base_client.instance(BASE_INSTANCE_ID)
base_database = base_instance.database(BASE_DATABASE_ID)
target_client = spanner.Client()
target_instance = target_client.instance(TARGET_INSTANCE_ID)
target_database = target_instance.database(TARGET_DATABASE_ID)
# 定义一个函数,查询指定库的指定表的所有数据,按主键排序
def get_table_data(database, table_name, primary_key):
# 执行SQL查询:选所有字段,按主键排序
sql = f"SELECT * FROM {table_name} ORDER BY {primary_key} ASC"
# 执行查询,返回结果
with database.snapshot() as snapshot:
results = snapshot.execute_sql(sql)
# 把结果转成列表,方便对比
return list(results)
# 获取两个库的商品表数据
base_data = get_table_data(base_database, TABLE_NAME, PRIMARY_KEY)
target_data = get_table_data(target_database, TABLE_NAME, PRIMARY_KEY)
# 对比两个数据列表
if base_data == target_data:
print(f"静态表 {TABLE_NAME} 数据一致!")
else:
# 如果不一致,找出不一样的地方
print(f"静态表 {TABLE_NAME} 数据不一致!")
# 遍历每一条数据,找差异
for i, (base_row, target_row) in enumerate(zip(base_data, target_data)):
if base_row != target_row:
print(f"第{i+1}条数据不一致:")
print(f"基准库数据:{base_row}")
print(f"切换后库数据:{target_row}")
break
# 如果两个列表长度不一样,说明有数据丢了或者多了
if len(base_data) != len(target_data):
print(f"两个库的表行数不一样!基准库行数:{len(base_data)},切换后库行数:{len(target_data)}")
这个脚本很简单,核心就是把两个库的同一张表的数据按同样的顺序查出来,然后直接对比列表是否相等。如果相等,说明静态数据没问题;如果不相等,就会打印出具体的差异在哪里。
3.3.2 再对比“动态数据”:演练前修改过的数据
动态数据就是演练前可能被修改的数据,比如订单的状态、用户的余额。这些数据的对比要注意,不能直接跟基准库比,因为基准库是演练前拍的,而演练前可能有业务在改数据,这些数据在主库和备用库的同步是有延迟的。
比如演练前10秒,用户改了订单状态,主库改了,但备用库还没同步完,这时候拍的基准库是主库的状态,而切换后的库(原来的备用库)还没同步到这个修改,所以直接比的话就会有差异,这是正常的,不是问题。
那动态数据怎么比?我们要加一个“时间窗口”的限制:只对比演练前10分钟(或者你能接受的同步延迟时间)之前修改过的动态数据。因为10分钟之前修改的数据,肯定已经同步到备用库了,不会有延迟。
具体怎么实现?比如订单表有个“update_time”字段,记录这条数据最后修改的时间。我们在对比的时候,只对比update_time在“演练开始时间 - 10分钟”之前的订单数据。
修改上面的脚本,对比动态表(比如orders表)的时候,SQL语句改成:
SELECT * FROM orders WHERE update_time < '2024-05-20 14:20:00' ORDER BY order_id ASC
这里的'2024-05-20 14:20:00'就是演练开始时间(比如14:30:00)减去10分钟的时间。这样对比出来的差异才是真的问题,不是因为同步延迟导致的正常差异。
3.4 明确验证边界:哪些不用验证?
很多人做验证的时候,会把所有数据都比一遍,浪费很多时间,其实有些情况是不用验证的,这就是验证边界:
- 演练前10分钟之内修改的动态数据:因为同步延迟,这些数据可能没同步到备用库,切完之后会同步,所以不用比。
- 演练过程中产生的新数据:比如切完之后业务又产生了新的订单,这些数据本来就是切换后库的新数据,不用跟基准库比。
- 日志类数据:比如操作日志,这些数据的修改时间如果是在演练前10分钟之内,也不用比,因为日志本来就是动态的,延迟同步很正常。
明确了这些边界,我们就不用做无用功,只需要对比应该对比的部分。
四、应用场景、优缺点、注意事项
4.1 应用场景
这套方法适合所有用Cloud Spanner做跨区域灾备的公司,尤其是对数据一致性要求高的行业,比如金融(支付、银行)、电商(订单、库存)、医疗(患者数据)。不管是每月一次的常规灾备演练,还是紧急情况下的真实灾备切换,都能用这套方法来验证数据。
4.2 技术优缺点
优点:
- 可复用:不管什么时候做演练,流程都是一样的,不用每次重新设计。
- 准确:用备份当基准,避免了用“当前主库”当基准的误差(因为当前主库可能还在改数据)。
- 高效:明确了验证边界,不用对比所有数据,节省时间。
缺点:
- 依赖Cloud Spanner的备份能力:如果备份失败,就没有基准库,验证就做不了。
- 对比大表的时候会慢:如果表有几百万条数据,查询和对比的时间会比较长,需要优化(比如按主键分批次对比)。
- 动态数据的时间窗口需要提前定义:如果时间窗口设得太小,可能会把正常的延迟当成问题;设得太大,可能会对比到不该对比的数据。
4.3 注意事项
- 拍备份的时间必须准确:一定要在演练开始前的最后一刻拍,不然基准库的状态跟演练前主库的状态不一致,对比就没有意义。
- 备份要能正常还原:拍了备份之后,最好先试还原一次,确保备份是好的,不然演练的时候才发现备份坏了,就麻烦了。
- 同步延迟的时间要提前测:比如你平时测的Cloud Spanner的跨区域同步延迟最大是5分钟,那你的时间窗口就设成10分钟,留够余量。
- 脚本要提前测试:对比脚本要在非演练环境先测几次,确保能正常运行,不然演练的时候脚本出问题,验证就做不了。
五、总结
Cloud Spanner的灾备验证,核心就是“用备份当基准,对比应该对比的数据”。我们先拍一个基准备份,然后切到备用库,再对比静态数据和时间窗口内的动态数据,同时明确哪些不用对比,这样就能快速、准确地验证切换后的数据一致性。
这套方法的关键是“可复用”和“明确边界”,不用每次都重新想怎么验证,节省时间,也能避免漏测或者误测。只要按这套流程做,就能保证灾备切换后的业务数据是对的,不会出大问题。
评论
围绕“Cloud Spanner灾备演练中验证切换后数据一致性,结合备份与恢复机制构建可复用对比流程并明确验证边界的完整方法”参与讨论