Redis-full-check 是阿里云 Redis&MongoDB 团队开源的用于校验 2 个 Redis 数据是否一致的工具, 通常用于 Redis 数据迁移后正确性的校验.
基本原理
Redis-full-check 通过全量对比源端和目的端的 Redis 中的数据的方式来进行数据校验, 其比较方式通过多轮次比较: 每次都会抓取源和目的端的数据进行差异化比较, 记录不一致的数据进入下轮对比(记录在 sqlite3 db 中). 然后通过多伦比较不断收敛, 减少因数据增量同步导致的源库和目的库的数据不一致. 最后 SQLite 中存在的数据就是最终的差异结果.
Redis-full-check 对比的方向是单向: 抓取源库 A 的数据, 然后检测是否位于 B 中, 反向不会检测, 也就是说, 它检测的是目的库是否是源库的一个子集. 如果希望对比双向, 则需要对比 2 次, 第一次以 A 为源库, B 为目的库, 第二次以 B 为源库, A 为目的库.
下图是基本的数据流图, Redis-full-check 内部分为多轮比较, 也就是黄色框所指示的部分. 每次比较, 会先抓取比较的 key, 第一轮是从源库中进行抓取, 后面轮次是从 sqlite3 db 中进行抓取; 抓取 key 之后是分别抓取 key 对应的 field 和 value 进行对比, 然后将存在差异的部分存入 sqlite3 db 中, 用于下次比较.
不一致类型
Redis-full-check 判断不一致的方式主要分为 2 类: key 不一致和 value 不一致.
key 不一致
key 不一致主要分为以下几种情况:
lack_target: key 存在于源库, 但不存在于目的库.
type: key 存在于源库和目的库, 但是类型不一致.
value: key 存在于源库和目的库, 且类型一致, 但是 value 不一致.
value 不一致
不同数据类型有不同的对比标准:
string: value 不同.
hash: 存在 field, 满足下面 3 个条件之一:
field 存在于源端, 但不存在与目的端.
field 存在于目的端, 但不存在与源端.
field 同时存在于源和目的端, 但是 value 不同.
set/zset: 与 hash 类似.
list: 与 hash 类似.
field 冲突类型有以下几种情况(只存在于 hash,set,zset,list 类型 key 中):
lack_source: field 存在于源端 key,field 不存在与目的端 key.
lack_target: field 不存在与源端 key,field 存在于目的端 key.
value: field 存在于源端 key 和目的端 key, 但是 field 对应的 value 不同.
比较原理
对比模式 (comparemode) 有三种可选:
KeyOutline: 只对比 key 值是否相等.
ValueOutline: 只对比 value 值的长度是否相等.
FullValue: 对比 key 值, value 长度, value 值是否相等.
对比会进行 comparetimes 轮 (默认 comparetimes=3) 比较:
第一轮, 首先找出在源库上所有的 key, 然后分别从源库和目的库抓取进行比较.
第二轮开始迭代比较, 只比较上一轮结束后仍然不一致的 key 和 field.
对于 key 不一致的情况, 包括 lack_source,lack_target 和 type, 从源库和目的库重新取 key,value 进行比较.
value 不一致的 string, 重新比较 key: 从源和目的取 key,value 比较.
value 不一致的 hash,set 和 zset, 只重新比较不一致的 field, 之前已经比较且相同的 filed 不再比较. 这是为了防止对于大 key 情况下, 如果更新频繁, 将会导致校验永远不通过的情况.
value 不一致的 list, 重新比较 key: 从源和目的取 key,value 比较.
每轮之间会停止一定的时间(Interval).
对于 hash,set,zset,list 大 key 处理采用以下方式:
len <= 5192, 直接取全量 field,value 进行比较, 使用如下命令: hgetall,smembers,
- zrange 0 -1 withscores
- ,lrange 0 -1.
len> 5192, 使用 hscan,sscan,zscan,lrange 分批取 field 和 value.
参数说明
Redis-full-check 中主要参数如下:
-s, --source=SOURCE 源 Redis 库地址(ip:port)
-p, --sourcepassword=Password 源 Redis 库密码
--sourceauthtype=AUTH-TYPE 源库管理权限, 开源 reids 下此参数无用.
-t, --target=TARGET 目的 Redis 库地址(ip:port)
-a, --targetpassword=Password 目的 Redis 库密码
--targetauthtype=AUTH-TYPE 目的库管理权限, 开源 reids 下此参数无用.
-d, --db=Sqlite3-DB-FILE 对于差异的 key 存储的 sqlite3 db 的位置, 默认 result.db
--comparetimes=COUNT 比较轮数
-m, --comparemode= 比较模式
--id= 用于打 metric
--jobid= 用于打 metric
--taskid= 用于打 metric
-q, --qps= qps 限速阈值
--interval=Second 每轮之间的时间间隔
--batchcount=COUNT 批量聚合的数量
--parallel=COUNT 比较的并发协程数, 默认 5
--log=FILE log 文件
--result=FILE 不一致结果记录到 result 文件中, 格式:'db diff-type key field'
--metric=FILE metric 文件
-v, --version
例如: 源 Redis 库是 10.1.1.1:1234, 目的库是 10.2.2.2:5678:
./Redis-full-check -s 10.1.1.1:1234 -t 10.2.2.2:5678 -p mock_source_password -a mock_target_password --metric metric --log log --result result
metric 信息格式如下:
- type Metric struct {
- DateTime string `json:"datetime"` // 时间 格式: 2018-01-09T15:30:03Z
- Timestamp int64 `json:"timestamp"` // unix 秒级时间戳
- Id string `json:"id"` // run id
- CompareTimes int `json:"comparetimes"` // 对比轮次
- Db int32 `json:"db"` // db id
- DbKeys int64 `json:"dbkeys"` // db 里的总 key 数
- Process int64 `json:"process"` // 进度, 百分比
- OneCompareFinished bool `json:"has_finished"` // 本次 compare 是否完成
- AllFinished bool `json:"all_finished"` // 全部 compare 是否完成
- KeyScan *CounterStat `json:"key_scan"` // scan key 的数量
- TotalConflict int64 `json:"total_conflict"` // conflict 的总数, 包含 key + field
- TotalKeyConflict int64 `json:"total_key_conflict"` // key conflict 总数
- TotalFieldConflict int64 `json:"total_field_conflict"` // field conflict 总数
- // 以下两个 map 第一层 key 是字段类型, 有 string, hash, list, set, zset, 第二层 key 是冲突类型, 有有 type(类型冲突) / value(值冲突) / lack source(源端缺失) / lack target(目标端缺失) / equal(无冲突)
- KeyMetric map[string]map[string]*CounterStat `json:"key_stat"` // key metric
- FieldMetric map[string]map[string]*CounterStat `json:"field_stat"` // field metric
- }
- type CounterStat struct {
- Total int64 `json:"total"` // 总量
- Speed int64 `json:"speed"` // 速度
- }
SQLite 3 db 文件
结果会保存在 sqlite3 db file 中, 不指定的话, 就是当前目录的 result.db 文件: 比如有 3 轮比较, 那么会有 result.db.1,result.db.2,result.db.33 个文件,
表 key: 保存不一致的 key
表 field: 保存 hash,set,zset,list 不一致的 field, list 存的是下标值
表 feild 的 key_id 字段关联表 key 的 id 字段
表 key_<N > 和 field_<N>: 保存第 N 轮比较后的结果, 即中间结果.
使用举例:
- $ sqlite3 result.db
- SQLite> select * from key;
- id key type conflict_type db source_len target_len
- ---------- --------------- ---------- ------------- ---------- ---------- ----------
- 1 keydiff1_string string value 1 6 6
- 2 keydiff_hash hash value 0 2 1
- 3 keydiff_string string value 0 6 6
- 4 key_string_diff string value 0 6 6
- 5 keylack_string string lack_target 0 6 0
- SQLite>
- SQLite> select * from field;
- id field conflict_type key_id
- ---------- ---------- ------------- ----------
- 1 k1 lack_source 2
- 2 k2 value 2
- 3 k3 lack_target 2
开源地址
GitHub: coming soon.
如果有问题或者建议, 请在 GitHub issue 上进行留言, 也欢迎大家一起加入开源开发.
来源: https://yq.aliyun.com/articles/690463