目录
分为三部分,每一部分都有分割线,慢慢看
从公司里面的wiki里面直接粘过来的,为了懒省事,一些图片就省略掉了,但是不影响观看。
摘要:
Redis-Migrate-Tool(都简称RMT),是唯品会开源的redis数据迁移工具,主要用于异构redis集群间的数据在线迁移,即数据迁移过程中源集群仍可以正常接受业务读写请求,无业务中断服务时间。
RMT特性:
1、异构集群相互之间的迁移
支持下面几种异构集群之间的数据迁移和同构集群扩容/缩容。
1)单个redis到twemproxy/cluster迁移;(sentinel集群是将集群中的一个个主从拿出来用这种模式迁移)
2)twemproxy到twemproxy/cluster迁移;(未实验)
3)cluster到twmeproxy/cluster迁移。(只实验cluster-cluster)
2、在线迁移
RMT启动后模拟成redis slave,请求master的全量数据和增量数据。RMT收到数据之后解析成redis协议格式的oplog(写操作),然后发送给目标集群。有两种请求方式,source_safe: true,对于同一ip上的redis,逐个的请求全量数据(RDB);source_safe: false,并行请求同步全量数据。source_safe: false时,需要注意多个源redis所在的同一主机是否有足够的内存 和 RDB 并发落盘时的 IOPS 性能。
3、从AOF 或 RDB恢复数据
如果在线集群数据全部丢失,不要方,RMT可以帮你从备份的 AOF 和 RDB 文件恢复到目标集群。
4、数据过滤
可以过滤算法上不属于源集群的脏数据,比如有人绕过twemproxy,非正常方式直接向后端redis写入数据。还可以在配置文件[common]中使用filter参数,过滤掉不需要的数据。
命令 | 属性 | 说明 |
geohash | ['readonly'] | 不需要,不会发给从。不支持解析 |
lastsave | ['random', 'fast'] | 不需要,不会发给从。不支持解析 |
scan | ['readonly', 'random'] | 不需要,不会发给从。不支持解析 |
monitor | ['admin', 'noscript'] | 不需要,不会发给从。不支持解析 |
pubsub | ['pubsub', 'random', 'loading', 'stale'] | 不需要,不会发给从。不支持解析 |
role | ['noscript', 'loading', 'stale'] | 不需要,不会发给从。不支持解析 |
wait | ['noscript'] | 不需要,不会发给从。不支持解析 |
georadiusbymember | ['write'] | 支持解析,但由于跨实例,无法支持数据转发 |
object | ['readonly'] | 不需要,不会发给从。不支持解析 |
pfselftest | ['admin'] | 不需要,不会发给从。不支持解析 |
debug | ['admin', 'noscript'] | 不需要,不会发给从。不支持解析 |
multi | ['noscript', 'fast'] | 支持解析,但由于事务无法跨实例,该命令不会发给目标集群 |
save | ['admin', 'noscript'] | 不需要,不会发给从。不支持解析 |
slaveof | ['admin', 'noscript', 'stale'] | 不需要,不会发给从。不支持解析 |
rename | ['write'] | 支持解析,但由于跨实例,无法支持数据转发 |
bgsave | ['admin'] | 不需要,不会发给从。不支持解析 |
discard | ['noscript', 'fast'] | 不需要,不会发给从。不支持解析 |
pfdebug | ['write'] | 已支持。 |
sync | ['readonly', 'admin', 'noscript'] | 不需要,不会发给从。不支持解析 |
rpoplpush | ['write', 'denyoom', 'noscript'] | 支持解析,但由于跨实例,无法支持数据转发 |
brpoplpush | ['write', 'denyoom', 'noscript'] | 支持解析,但由于跨实例,无法支持数据转发 |
zrevrangebylex | ['readonly'] | 不需要,不会发给从。不支持解析 |
unwatch | ['noscript', 'fast'] | 不需要,不会发给从。不支持解析 |
flushall | ['write'] | 支持解析,但由于跨实例,无法支持数据转发 |
renamenx | ['write', 'fast'] | 支持解析,但由于跨实例,无法支持数据转发 |
bgrewriteaof | ['admin'] | 不需要,不会发给从。不支持解析 |
subscribe | ['pubsub', 'noscript', 'loading', 'stale'] | 不需要,不会发给从。不支持解析 |
slowlog | ['admin'] | 不需要,不会发给从。不支持解析 |
psync | ['readonly', 'admin', 'noscript'] | 不需要,不会发给从。不支持解析 |
time | ['random', 'fast'] | 不需要,不会发给从。不支持解析 |
move | ['write', 'fast'] | 支持解析,但由于跨db,无法支持数据转发 |
watch | ['noscript', 'fast'] | 不需要,不会发给从。不支持解析 |
bitop | ['write', 'denyoom'] | 支持解析,但由于跨实例,无法支持数据转发 |
readonly | ['fast'] | 不需要,不会发给从。不支持解析 |
dbsize | ['readonly', 'fast'] | 不需要,不会发给从。不支持解析 |
touch | ['readonly', 'fast'] | 不需要,不会发给从。不支持解析 |
geoadd | ['write', 'denyoom'] | 已支持。 |
exec | ['noscript', 'skip_monitor'] | 支持解析,但由于事务无法跨实例,该命令不会发给目标集群 |
readwrite | ['fast'] | 不需要,不会发给从。不支持解析 |
randomkey | ['readonly', 'random'] | 不需要,不会发给从。不支持解析 |
geodist | ['readonly'] | 不需要,不会发给从。不支持解析 |
bitpos | ['readonly'] | 不需要,不会发给从。不支持解析 |
psubscribe | ['pubsub', 'noscript', 'loading', 'stale'] | 不需要,不会发给从。不支持解析 |
config | ['admin', 'loading', 'stale'] | 不需要,不会发给从。不支持解析 |
keys | ['readonly', 'sort_for_script'] | 不需要,不会发给从。不支持解析 |
blpop | ['write', 'noscript'] | 不需要,给从的话会转换成lpop。不支持解析 |
asking | ['fast'] | 不需要,不会发给从。不支持解析 |
client | ['admin', 'noscript'] | 不需要,不会发给从。不支持解析 |
echo | ['fast'] | 不需要,不会发给从。不支持解析 |
cluster | ['admin'] | 不需要,不会发给从。不支持解析 |
georadius | ['write'] | 支持解析,但由于跨实例,无法支持数据转发 |
flushdb | ['write'] | 支持解析,但由于跨实例,无法支持数据转发 |
restore-asking | ['write', 'denyoom', 'asking'] | 已支持,但如果源集群中的节点间进行migrate命令时,目标集群会出现‘BUSYKEY Target key name already exists’错误。 |
msetnx | ['write', 'denyoom'] | 已支持。 |
bitfield | ['write', 'denyoom'] | 已支持。 |
unsubscribe | ['pubsub', 'noscript', 'loading', 'stale'] | 不需要,不会发给从。不支持解析 |
geopos | ['readonly'] | 不需要,不会发给从。不支持解析 |
script | ['noscript'] | 支持解析,但由于可能跨实例,无法支持数据转发 |
publish | ['pubsub', 'loading', 'stale', 'fast'] | 已支持解析,但不会发给目标集群,因为不会影响key。 |
replconf | ['admin', 'noscript', 'loading', 'stale'] | 不需要,不会发给从。不支持解析 |
hstrlen | ['readonly', 'fast'] | 不需要,不会发给从。不支持解析 |
substr | ['readonly'] | 不需要,不会发给从。不支持解析 |
brpop | ['write', 'noscript'] | 不需要,给从的话会转换成rpop。不支持解析 |
migrate | ['write', 'movablekeys'] | 不需要,给从的话会转换成del。不支持解析 |
latency | ['admin', 'noscript', 'loading', 'stale'] | 不需要,不会发给从。不支持解析 |
punsubscribe | ['pubsub', 'noscript', 'loading', 'stale'] | 不需要,不会发给从。不支持解析 |
zrem | ['write', 'fast'] | 已支持。 |
smembers | ['readonly', 'sort_for_script'] | 已支持。 |
pfmerge | ['write', 'denyoom'] | 支持解析,但由于跨实例,无法支持数据转发 |
zrank | ['readonly', 'fast'] | 已支持。 |
expire | ['write', 'fast'] | 已支持。 |
ping | ['stale', 'fast'] | 已支持。 |
zremrangebylex | ['write'] | 已支持。 |
hincrby | ['write', 'denyoom', 'fast'] | 已支持。 |
srandmember | ['readonly', 'random'] | 已支持。 |
zremrangebyrank | ['write'] | 已支持。 |
decr | ['write', 'denyoom', 'fast'] | 已支持。 |
pfadd | ['write', 'denyoom', 'fast'] | 已支持。 |
spop | ['write', 'random', 'fast'] | 已支持。 |
smove | ['write', 'fast'] | 已支持。 |
llen | ['readonly', 'fast'] | 已支持。 |
getset | ['write', 'denyoom'] | 已支持。 |
sdiff | ['readonly', 'sort_for_script'] | 已支持。 |
hscan | ['readonly', 'random'] | 已支持。 |
auth | ['noscript', 'loading', 'stale', 'fast'] | 已支持。 |
decrby | ['write', 'denyoom', 'fast'] | 已支持。 |
sunion | ['readonly', 'sort_for_script'] | 已支持。 |
pexpire | ['write', 'fast'] | 已支持。 |
hvals | ['readonly', 'sort_for_script'] | 已支持。 |
zscan | ['readonly', 'random'] | 已支持。 |
get | ['readonly', 'fast'] | 已支持。 |
exists | ['readonly', 'fast'] | 已支持。 |
lindex | ['readonly'] | 已支持。 |
restore | ['write', 'denyoom'] | 已支持。 |
sort | ['write', 'denyoom', 'movablekeys'] | 已支持。 |
setex | ['write', 'denyoom'] | 已支持。 |
incr | ['write', 'denyoom', 'fast'] | 已支持。 |
set | ['write', 'denyoom'] | 已支持。 |
mget | ['readonly'] | 已支持。 |
scard | ['readonly', 'fast'] | 已支持。 |
zscore | ['readonly', 'fast'] | 已支持。 |
srem | ['write', 'fast'] | 已支持。 |
setrange | ['write', 'denyoom'] | 已支持。 |
mset | ['write', 'denyoom'] | 已支持。 |
getbit | ['readonly', 'fast'] | 已支持。 |
zrange | ['readonly'] | 已支持。 |
hget | ['readonly', 'fast'] | 已支持。 |
hexists | ['readonly', 'fast'] | 已支持。 |
ltrim | ['write'] | 已支持。 |
rpushx | ['write', 'denyoom', 'fast'] | 已支持。 |
zrevrangebyscore | ['readonly'] | 已支持。 |
zlexcount | ['readonly', 'fast'] | 已支持。 |
zunionstore | ['write', 'denyoom', 'movablekeys'] | 已支持。 |
setnx | ['write', 'denyoom', 'fast'] | 已支持。 |
hset | ['write', 'denyoom', 'fast'] | 已支持。 |
ttl | ['readonly', 'fast'] | 已支持。 |
hmget | ['readonly'] | 已支持。 |
del | ['write'] | 已支持。 |
dump | ['readonly'] | 已支持。 |
psetex | ['write', 'denyoom'] | 已支持。 |
lpop | ['write', 'fast'] | 已支持。 |
incrbyfloat | ['write', 'denyoom', 'fast'] | 已支持。 |
zrangebyscore | ['readonly'] | 已支持。 |
pfcount | ['readonly'] | 已支持。 |
command | ['loading', 'stale'] | 已支持。 |
hmset | ['write', 'denyoom'] | 已支持。 |
zadd | ['write', 'denyoom', 'fast'] | 已支持。 |
eval | ['noscript', 'movablekeys'] | 支持解析,但由于跨实例,无法支持数据转发 |
zinterstore | ['write', 'denyoom', 'movablekeys'] | 已支持。 |
persist | ['write', 'fast'] | 已支持。 |
lrange | ['readonly'] | 已支持。 |
sdiffstore | ['write', 'denyoom'] | 已支持。 |
hsetnx | ['write', 'denyoom', 'fast'] | 已支持。 |
hdel | ['write', 'fast'] | 已支持。 |
hgetall | ['readonly'] | 已支持。 |
zincrby | ['write', 'denyoom', 'fast'] | 已支持。 |
lpushx | ['write', 'denyoom', 'fast'] | 已支持。 |
pttl | ['readonly', 'fast'] | 已支持。 |
hincrbyfloat | ['write', 'denyoom', 'fast'] | 已支持。 |
hlen | ['readonly', 'fast'] | 已支持。 |
sismember | ['readonly', 'fast'] | 已支持。 |
sunionstore | ['write', 'denyoom'] | 已支持。 |
zrangebylex | ['readonly'] | 已支持。 |
info | ['loading', 'stale'] | 已支持。 |
lrem | ['write'] | 已支持。 |
sinter | ['readonly', 'sort_for_script'] | 已支持。 |
sscan | ['readonly', 'random'] | 已支持。 |
strlen | ['readonly', 'fast'] | 已支持。 |
shutdown | ['admin', 'loading', 'stale'] | 已支持。 |
rpop | ['write', 'fast'] | 已支持。 |
sinterstore | ['write', 'denyoom'] | 已支持。 |
expireat | ['write', 'fast'] | 已支持。 |
hkeys | ['readonly', 'sort_for_script'] | 已支持。 |
evalsha | ['noscript', 'movablekeys'] | 支持解析,但由于跨实例,无法支持数据转发 |
getrange | ['readonly'] | 已支持。 |
zcard | ['readonly', 'fast'] | 已支持。 |
sadd | ['write', 'denyoom', 'fast'] | 已支持。 |
select | ['loading', 'fast'] | 已支持。 |
linsert | ['write', 'denyoom'] | 已支持。 |
zremrangebyscore | ['write'] | 已支持。 |
type | ['readonly', 'fast'] | 已支持。 |
zcount | ['readonly', 'fast'] | 已支持。 |
incrby | ['write', 'denyoom', 'fast'] | 已支持。 |
bitcount | ['readonly'] | 已支持。 |
setbit | ['write', 'denyoom'] | 已支持。 |
lpush | ['write', 'denyoom', 'fast'] | 已支持。 |
rpoplpush | ['write', 'denyoom'] | 已支持。 |
rpush | ['write', 'denyoom', 'fast'] | 已支持。 |
pexpireat | ['write', 'fast'] | 已支持。 |
zrevrank | ['readonly', 'fast'] | 已支持。 |
zrevrange | ['readonly'] | 已支持。 |
append | ['write', 'denyoom'] | 已支持。 |
lset | ['write', 'denyoom'] | 已支持。 |
建议:
若对其十分的感兴趣,也推荐阅读
RMT,包括安装、配置、监控还有业务在线切换方式。(本次模拟实验的机器是在SEP环境下的远程跳板机上,地址:10.10.200.30,主要针对)
以下使用部分
1、RMT安装
github开源地址:https://github.com/JokerQueue/redis-migrate-tool
安装:
|
生成的执行文件在src目录下,使用"-h"选项查看使用方法。
|
2、RMT配置文件
RMT的配置主要由三部分组成,数据源[source],目标集群[target],通用配置部分[common]。
RMT对源集群的类型并不敏感,对每个redis节点伪装成一个slave。因而 sentinel和cluster集群,可以使用 single 和 cluster 类型。
建议配置成对应的 single 或者 cluster类型。
有两方面作用:
1)RMT在数据迁移时,可以根据 sentinel(hash出来的single节点)或者cluster(slots)数据分布规则,过滤掉原本不属于该节点的数据;
2)当源集群类型是cluster时,只用配置一个节点,简化配置。
目标集群,因为不同数据分布规则需要执行不同的写入逻辑,因而配置非常严谨。
在迁移之前可以使用一下命令做测试,默认值是1000:
src/redis-migrate-tool -c rmt.conf -o log -C "redis_testinsert 30000"
2.1、redis到cluster
[source]和[taget] 由集群类型type和redis节点server组成。
[source] type可以支持single/twemproxy/cluster/aof file/rdb file 五种类型。
[target]中的type支持single/twemproxy/cluster/rdb file 四种类型。
|
在[common]中,一些参数主要作用如下,其他参数参考GitHub(https://github.com/JokerQueue/redis-migrate-tool)
|
2.2、cluster集群到cluster集群迁移
|
[source] 和 [target]只需配置集群的一个节点。当然[source]部分cluster也可以配置成如下形式。
|
2.3、cluster到twemproxy的迁移
twemproxy 到 cluster的迁移,作为练习题,你们自己测试吧
2.4、从AOF 和 RDB中恢复数据
aof
|
rdb
|
线上测试,将6个2.7GB的RDB文件做迁移恢复,rmt工具会同时并发执行,120s完成6个rdb文件16.2GB 40063171个key的迁移。
aof和rdb无法做rmt的redis_check校验
3、RMT迁移
使用下面的命令就可以开始迁移数据
src/redis-migrate-tool -c rmt.conf -o log -d
使用tail log查看日志,根据报错内容调整
|
4、观察迁移状态
使用redis-cli可以连接 rmt.conf 中配置的端口,执行info命令,就可以观察迁移的状态。
total_msgs_outqueue可以判断是否有oplog在队列中等待处理,如果total_msgs_outqueue>0,请继续等待。
|
5 RMT检验源集群和目标集群的差异
在业务切换到目标服务器之前,可使用 RMT 抽样检查数据的一致性,默认抽样1000个key。
src/redis-migrate-tool -c rmt.conf -o log -c redis_check 1000
6 如何完成业务切换?
如果是异构集群的迁移,更改redis驱动/客户端和修改代码,重新发布是必然的事情。如果是同构集群,做配置发布就可以。如果没有做重启发布,等待源集群没有连接之后,可以关闭RMT进程(kill)。或者重启应用,强制断开长连接。
注意事项:
1、重要的事说三遍,RMT迁移数据到twemproxy,需要保持rmt.conf 中 [target] hash、distribution、servers 三个参数和目标集群的twemproxy配置严格一致。
2、迁移中和业务切换之前,请反复观察“-o rmt.log”日志信息,确认是否有异常。
3、业务切换之前,请充分检查,特别是数据的一致性。
4、RMT 建议部署在单独空闲机器上,同目的集群在同一个网段(跨机房迁移数据,可以提高迁移速度)。千万不要部署在源集群所在的机器,防止资源不足,比如内存,带宽,IOPS。
5、注意RDB传输是否超时;
6、redis client buf中的slave项,设置足够大的buffer size和超时时间。
以下讲解部分
源码使用C写的,着实让我头大。。。
很难看,函数和指针的引用让我无从下手debug
但是最近看到了migrate的这个redis原生功能,就扩展着看它的实现redis migrate官方
info command response instruction:
Server:
- version: The redis-migrate-tool version number.
- os: The os uname.
- multiplexing_api: Multiplexing API.
- gcc_version: Gcc version.
- process_id: The process id of the redis-migrate-tool.
- tcp_port: The tcp port redis-migrate-tool listening.
- uptime_in_seconds: Seconds the redis-migrate-tool running.
- uptime_in_days: Days the redis-migrate-tool running.
- config_file: The config file name for the redis-migrate-tool.
Clients:
- connected_clients: The count of clients that connected at present.
- max_clients_limit: The max number of clients that allows to accept at the same time.
- total_connections_received: The total count of connections that received so far.
Group:
- source_nodes_count: The nodes count of source redis group.
- target_nodes_count: The nodes count of target redis group.
Stats:
- all_rdb_received: Whether all the rdb of the nodes in source group received.
- all_rdb_parsed: Whether all the rdb of the nodes in source group parsed finished.
- all_aof_loaded: Whether all the aof file of the nodes in source group loaded finished.
- rdb_received_count: The received rdb count for the nodes in source group.
- rdb_parsed_count: The parsed finished rdb count for the nodes in source group.
- aof_loaded_count: The loaded finished aof file count for the nodes in source group.
- total_msgs_recv: The total count of messages that had received from the source group.
- total_msgs_sent: The total count of messages that had sent to the target group and received response from target group.
- total_net_input_bytes: The total count of input bytes that had received from the source group.
- total_net_output_bytes: The total count of output bytes that had sent to the target group.
- total_net_input_bytes_human: Same as the total_net_input_bytes, but convert into human readable.
- total_net_output_bytes_human: Same as the total_net_output_bytes, but convert into human readable.
- total_mbufs_inqueue: Cached commands data(not include rdb data) by mbufs input from source group.
- total_msgs_outqueue: Msgs will be sent to target group and msgs had been sent to target but waiting for the response.
redis_check:
1.迁移的数据是不变更的?
答:迁移之后的数据check是根据conf配置文件中指定的那两个Redis的key和value 的比较,只要这个过程中target的数据不要乱动就没事
2.迁移的数据有过期时间
答:rmt工具只要不关闭,一次的量要不大,速度同步的够快不会出现check不一致
3.迁移的数据在source集群有写操作
答:rmt工具只要不关闭,一次的量要不大,速度同步的够快不会出现check不一致
4.迁移的数据比target集群中的数据少
答:check比对使用的是conf配置文件中的source和target数据源,比对的是交集部分,
如果交集=source,不会出现check不一致
如果交集<source,出现check不一致
5.迁移的数据有重复的key
答:迁移的过程是根据rmt工具的执行先后顺序有关,
如果两个source的key和value完全一致,两个check都显示一致
如果两个source的key和value不一致,后rmt迁移的显示一致,先执行的会出现不一致
migrate过程可以参考
redis-migration:独创的redis在线数据迁移工具
大致如下内容:
技术难点解析数据文件:
包括AOF和RDB,相对而言解析AOF文件会简单些,它是文本格式的,按照redis协议纯文本处理即可;
而RDB文件是二进制格式的,自己重新实现没这个必要,因为redis已经有解析RDB的接口,但源码是和redis本身是耦合在一起的,
比如对各种共享对象、全局变量、数据结构dict/sds等的依赖,所以最后实现上变成了redis-benchmark.c和redis.c的结合体;
处理redis协议:
解析来自数据源的redis数据,读取落地的RDB和AOF文件数据组装成redis协议数据。
虽然客户端使用的还是hiredis库,但是请求和应答报文,都不能使用库提供的接口来组装和解析,需要重新实现,这一块工作量比较大。
RDB和AOF的请求报文组装以及各自应答消息的解析与校验,其中RDB数据是二进制的,所以需要逐字段进行组装,hiredis库没有提供这样的接口,而且假设提供了也需要评估起性能;
同时RDB数据里会设置key的有效时间,一条RDB数据可能需要组装成两条redis指令;
两种数据都解析出类型后,用来精确判断应答消息的正确与否;
设计高效迁移:
RDB数据有个特点,它保存的是每个key的快照,无时序要求,所以可以考虑并发发送的方式,提高迁移速度;
而AOF数据,有时序要求,在目的地进行重放加载,不能并发,否则会乱序,出现数据错误,只能一个客户端发送,这时采用的是pipeline(批量)的方式;
方便调试定位:
迁移工具和数据源、数据目的地的交互都是在线TCP流,而且都是瞬间完成的,对于中间的错误和异常,比较难以捕捉,现在的做法是在数据流入和流出的地方统一加了十六进制的报文日志;
功能特点轻量级:
仅增加了1个redis-migration.c文件,同时在Makefile文件中增加编译redis-migration二进制程序的2行指令;
单线程,异步消息驱动模型,轻量化,工具编译出来约4M大小;
高性能:
前面有人可能会好奇,单线程程序怎么实现多客户端并发?
是这样的,因为一个客户端的请求是串行的,存在RTT这样一个时间窗口,那么在这个时间窗口里并发多个客户端就可以避免系统等待,极大提高性能;
另外,AOF迁移时候使用了pipeline特性,批量发送,减少RTT来加速迁移;
低成本:
迁移过程中的数据都做了落地处理,工具本身没有对数据进行加载,内存开销就很小,这一点非常重要!
易操作:
启动后,观察迁移进度日志即可;