文档章节

redis-cluster和Codis关于slot迁移原理对比

黑客画家
 黑客画家
发布于 01/31 22:54
字数 14052
阅读 410
收藏 13

概述

      Codis是基于proxy架构的redis集群方案,如图1所示,即客户端的请求会先发送到proxy,由proxy做sharding后转发到后端redis实例。这个sharding的规则(常称之为路由表、转发表、slot表等)保存在集中化的组件(比如zookeeper、文件系统等)上,然后由Dashboard统一配置到所有Proxy上。相比而言,redis自己的集群方案redis-cluster则是无中心化的架构,如图2所示,它没有集中化的控制组件和proxy,客户端可以向集群内的任意一台节点发送请求,然后根据节点的返回值做重定向(MOVE或ASK)操作,客户端本地也会缓存slot表,并根据每次的重定向信息来更新这个表。由于没有中心化组件存储或配置路由表,因此redis-cluster使用gossip在集群间同步路由表和集群拓补信息,在经过一段时间时候,理想情况下集群中每个节点都掌握了整个集群的路由信息。

图1 Codis架构图

图2 redis-cluster

      对nosql数据库而言,水平扩缩容(Scale in/out)是一项基本的能力。Scale in/out是指可以动态的添加或删除集群中的节点,来水平扩展或收缩集群容量和CPU算力,它和纵向扩缩容(Scale up/down)是相对的。由于nosql是没有schema的,一般都是简单的kv结构(或者是kkv结构),因此做Scale in/out还是相对而言比较容易的。因为key是按照slot为单位进行sharding的(常见公式有:crc16(key) % slot_num,如图3 ),因此只要将一个实例上的某些slots迁移到其它节点上,再把路由表(即slot和node的映射关系)更新即可。虽然Codis和redis-cluster都支持这种slot迁移的Scale in/out,但是他们的实现方式还是有诸多区别的,接下来本文会阐述它们的不同。

图3 key-slot-node映射关系

Slot迁移难点

      将一个redis上指定slot下的所有key迁移到其他redis上并不麻烦。其实只要两步,第一步先获取这个slot下所有key,然后对每个key发送迁移命令即可。由于redis本身没有slot的概念,更不维护key与slot的映射关系,因此第一步是需要改造redis引擎,使其可以维护key与slot的映射关系,这一点redis-cluster和Codis都是这么做的(比如使用一个单独的dict数组来维护这种索引关系,每个数组的下标就是slot num,每个数组元素是一个dick,里面存放的是<key、crc> pair)。第二步发送就比较简单了,redis原生支持对一些key进行迁移的命令:MIGRATE,如下:

MIGRATE host port "" dbid timeout [COPY | REPLACE | AUTH password] KEYS key1 key2 ... keyN 

      redis-cluster的确就是直接使用MIGRATE 命令进行key的迁移,但是这个命令是同步阻塞的,鉴于redis单线程的特性,当MIGRATE耗时太久(比如网络较慢、迁移bigkey)时会导致主线程无法处理用户请求,从而导致用户RT变大甚至超时。因此,直接使用MIGRATE命令虽然方便,但是有诸多限制。Codis自己修改了redis引擎,加入了slots同步迁移和异步迁移的功能(同步迁移比较简单,本文不再赘述)。

      因此,要想做到平滑的、用户基本无感的scale in/out,slot迁移需要解决以下几个难点:

  • 不能使用同步阻塞的迁移方式,否则对于bigkey或者慢网络迁移会阻塞主线程正常服务
  • 对bigkey的迁移需要特殊处理,否则在bigkey的序列化、发送、反序列化时都可能导致源redis实例和目标redis实例主线程阻塞
  • 单个key的迁移过程需要保证原子性,即要么一个key全部迁移成功,要么全部迁移失败,不存在中间状态
  • 对迁移中的slot的读写处理,即一个slot如果正处于迁移过程中,其中的key(已迁移走、迁移中或等待迁移)是否可以被正常读写

Redis-Cluster实现

图4 redis-cluster slot迁移