REDIS-FULL-CHECK校验工具
上一篇 / 下一篇 2019-06-18 19:33:44 / 个人分类:Redis
>redis-full-check是阿里云Redis&MongoDB团队开源的用于校验2个redis数据是否一致的工具,通常用于redis数据迁移(redis-shake)后正确性的校验。
<!--more-->
基本原理
------------
下图给出了最基本的比较逻辑。
[![](https://i.loli.net/2019/06/18/5d08615bc875215796.png)](https://i.loli.net/2019/06/18/5d08615bc875215796.png)
`redis-full-check`通过全量对比源端和目的端的`redis`中的数据的方式来进行数据校验,其比较方式通过多轮次比较:每次都会抓取源和目的端的数据进行差异化比较,记录不一致的数据进入下轮对比(记录在sqlite3 db中)。然后通过多伦比较不断收敛,减少因数据增量同步导致的源库和目的库的数据不一致。最后s`qlite`中存在的数据就是最终的差异结果。
`redis-full-check`对比的方向是单向:抓取源库A的数据,然后检测是否位于B中,反向不会检测,也就是说,它检测的是源库是否是目的库的子集。如果希望对比双向,则需要对比2次,第一次以A为源库,B为目的库,第二次以B为源库,A为目的库。
下图是基本的数据流图,`redis-full-check`内部分为多轮比较,也就是黄色框所指示的部分。每次比较,会先抓取比较的key,第一轮是从源库中进行抓取,后面轮次是从sqlite3 db中进行抓取;抓取`key`之后是分别抓取`key`对应的`field`和`value`进行对比,然后将存在差异的部分存入`sqlite3 db`中,用于下次比较。
[![](https://i.loli.net/2019/06/18/5d086173c5aee27790.png)](https://i.loli.net/2019/06/18/5d086173c5aee27790.png)
不一致类型
-------------
`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`、`se`t和`zset`,只重新比较不一致的`field`,之前已经比较且相同的`filed`不再比较。这是为了防止对于大`key`情况下,如果更新频繁,将会导致校验永远不通过的情况。
`value`不一致的`lis`t,重新比较`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`中主要参数如下:
```java
-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`:
```sql
./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信息格式如下:
```java
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.3`3个文件,
- 表`key`:保存不一致的`key`
- 表`field`:保存`hash`,`set`,`zset`,`list`不一致的`field`, `list` 存的是下标值
- 表`feild`的`key_id`字段关联表key的id字段
- 表`key_<N>`和`field_<N>``:保存第N轮比较后的结果,即中间结果。
使用举例:
```sql
$ 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
```
开源地址
------------
redis-full-check: https://github.com/aliyun/redis-full-check。
数据迁移工具redis-shake: https://github.com/aliyun/redis-shake。
如果有问题或者建议,请在github issue上进行留言,也欢迎大家一起加入开源开发。
TAG: