6 Redis分布式系统
Redis分布式系统,官方称为Redis Cluster,Redis集群,其是Redis 3.0开始推出的分布式解决方案。其可以很好地解决不同Redis节点存放不同数据,并将用户请求方便地路由到不同Redis的问题。
6.1 数据分区算法
分布式数据库系统会根据不同的数据分区算法,将数据分散存储到不同的数据库服务器节点上,每个节点管理着整个数据集合中的一个子集。 常见的数据分区规则有两大类:顺序分区与哈希分区。
6.1.1 顺序分区
顺序分区规则可以将数据按照某种顺序平均分配到不同的节点。不同的顺序方式,产生了不同的分区算法。例如,轮询分区算法、时间片轮转分区算法、数据块分区算法、业务主题分区算法等。由于这些算法都比较简单,所以这里就不展开描述了。
6.1.1.1 轮询分区算法
每产生一个数据,就依次分配到不同的节点。该算法适合于数据问题不确定的场景。其分配的结果是,在数据总量非常庞大的情况下,每个节点中数据是很平均的。但生产者与数据节点间的连接要长时间保持。
6.1.1.2 时间片轮转分区算法
在某人固定长度的时间片内的数据都会分配到一个节点。时间片结束,再产生的数据就会被分配到下一个节点。这些节点会被依次轮转分配数据。该算法可能会出现节点数据不平均的情况(因为每个时间片内产生的数据量可能是不同的)。但生产者与节点间的连接只需占用当前正在使用的这个就可以,其它连接使用完毕后就立即释放。
6.1.1.3 数据块分区算法
在整体数据总量确定的情况下,根据各个节点的存储能力,可以将连接的某一整块数据分配到某一节点。
6.1.1.4 业务主题分区算法
数据可根据不同的业务主题,分配到不同的节点。
6.1.2 哈希分区
哈希分区规则是充分利用数据的哈希值来完成分配,对数据哈希值的不同使用方式产生了不同的哈希分区算法。哈希分区算法相对较复杂,这里详细介绍几种常见的哈希分区算法。
6.1.2.1 节点取模分区算法
该算法的前提是,每个节点都已分配好了一个唯一序号,对于N个节点的分布式系统,其序号范围为[0, N-1]。然后选取数据本身或可以代表数据特征的数据的一部分作为key,计算hash(key)与节点数量N的模,该计算结果即为该数据的存储节点的序号。 该算法最大的优点是简单,但其也存在较严重的不足。如果分布式系统扩容或缩容,已经存储过的数据需要根据新的节点数量N进行数据迁移,否则用户根据key是无法再找到原来的数据的。生产中扩容一般采用翻倍扩容方式,以减少扩容时数据迁移的比例。
6.1.2.2 一致性哈希分区算法
一致性hash算法通过一个叫作一致性hash环的数据结构实现。这个环的起点是0,终点是232 - 1,并且起点与终点重合。环中间的整数按逆/顺时针分布,故这个环的整数分布范围是[0, 232-1]。 上图中存在四个对象o1、o2、o3、o4,分别代表四个待分配的数据,红色方块是这四个数据的hash(o)在Hash环中的落点。同时,图上还存在三个节点m0、m1、m2,绿色圆圈是这三节点的hash(m)在Hash环中的落点。 现在要为数据分配其要存储的节点。该数据对象的hash(o) 按照逆/顺时针方向距离哪个节点的hash(m)最近,就将该数据存储在哪个节点。这样就会形成上图所示的分配结果。 该算法的最大优点是,节点的扩容与缩容,仅对按照逆/顺时针方向距离该节点最近的节点有影响,对其它节点无影响。 当节点数量较少时,非常容易形成数据倾斜问题,且节点变化影响的节点数量占比较大,即影响的数据量较大。所以,该方式不适合数据节点较少的场景。
6.1.2.3 虚拟槽分区算法
该算法首先虚拟出一个固定数量的整数集合,该集合中的每个整数称为一个slot槽。这个槽的数量一般是远远大于节点数量的。然后再将所有slot槽平均映射到各个节点之上。例如,Redis分布式系统中共虚拟了16384个slot槽,其范围为[0, 16383]。假设共有3个节点,那么slot槽与节点间的映射关系如下图所示: 而数据只与slot槽有关系,与节点没有直接关系。数据只通过其key的hash(key)映射到slot槽:slot = hash(key) % slotNums。这也是该算法的一个优点,解耦了数据与节点,客户端无需维护节点,只需维护与slot槽的关系即可。 Redis数据分区采用的就是该算法。其计算槽点的公式为:slot = CRC16(key) % 16384。CRC16()是一种带有校验功能的、具有良好分散功能的、特殊的hash算法函数。其实Redis中计算槽点的公式不是上面的那个,而是:slot = CRC16(key) &16383。 若要计算 a % b,如果b是2的整数次幂,那么 a % b = a & (b-1)。
6.2 系统搭建与运行
6.2.1 系统搭建
6.2.1.1 系统架构
下面要搭建的Redis分布式系统由6个节点构成,这6个节点的地址及角色分别如下表所示。一个master配备一个slave,不过master与slave的配对关系,在系统搭建成功后会自动分配。
序号 | 角色 | 地址 |
---|---|---|
1 | master | 127.0.0.1:6380 |
2 | master | 127.0.0.1:6381 |
3 | master | 127.0.0.1:6382 |
4 | slave | 127.0.0.1:6383 |
5 | slave | 127.0.0.1:6384 |
6 | slave | 127.0.0.1:6385 |
6.2.1.2 删除持久化文件
先将之前“Redis主从集群”中在Redis安装目录下生成的RDB持久化文件dump638*.conf与AOF持久化文件删除。因为Redis分布式系统要求创建在一个空的数据库之上。注意,AOF持久化文件全部在appendonlydir目录中。
6.2.1.3 创建目录
在Redis安装目录中mkdir一个新的目录cluster-dis,用作分布式系统的工作目录。
6.2.1.4 复制2个配置文件
将cluster目录中的redis.conf与redis6380.conf文件复制到cluster-dis目录。
6.2.1.5 修改redis.conf
对于redis.conf配置文件,主要涉及到以下三个四个属性:
6.2.1.5.1 dir
指定工作目录为前面创建的cluster-dis目录。持久化文件、节点配置文件将来都会在工作目录中自动生成。
6.2.1.5.2 cluster-enabled
该属性用于开启Redis的集群模式。
6.2.1.5.3 cluster-config-file
该属性用于指定“集群节点”的配置文件。该文件会在第一次节点启动时自动生成,其生成的路径是在dir属性指定的工作目录中。在集群节点信息发生变化后(如节点下线、故障转移等),节点会自动将集群状态信息保存到该配置文件中。 不过,该属性在这里仍保持注释状态。在后面的每个节点单独的配置文件中配置它。
6.2.1.5.4 cluster-node-timeout
用于指定“集群节点”间通信的超时时间阈值,单位毫秒。
6.2.1.6 修改redis6380.conf
仅添加一个cluster-config-file属性即可。
6.2.1.7 复制5个配置文件
使用redis6380.conf复制出5个配置文件redis6381.conf、redis6382.conf、redis6383.conf、redis6384.conf、redis6385.conf。 cluster-dis中出现了7个配置文件。
6.2.1.8 修改5个配置文件
修改5个配置文件redis6381.conf、redis6382.conf、redis6383.conf、redis6384.conf、redis6385.conf的内容,将其中所有涉及的端口号全部替换为当前文件名称中的端口号。例如,下面的是redis6381.conf的配置文件内容。
6.2.2 系统启动与关闭
6.2.2.1 启动节点
启动所有Redis节点。 此时查看cluster-dis目录,可以看到生成了6个nodes的配置文件。
6.2.2.2 创建系统
6个节点启动后,它们仍是6个独立的Redis,通过redis-cli --cluster create命令可将6个节点创建了一个分布式系统。 该命令用于将指定的6个节点连接为一个分布式系统。--cluster replicas 1指定每个master会带有一个slave作为副本。 回车后会立即看到如下日志: 输入yes后回车,系统就会将以上显示的动态配置信息真正的应用到节点上,然后就可可看到如下日志:
6.2.2.3 测试系统
通过cluster nodes命令可以查看到系统中各节点的关系及连接情况。只要能看到每个节点给出connected,就说明分布式系统已经成功搭建。不过,对于客户端连接命令redis-cli,需要注意两点:
- redis-cli带有-c参数,表示这是要连接一个“集群”,而非是一个节点。
- 端口号可以使用6个中的任意一个。
6.2.2.4 关闭系统
对于分布式系统的关闭,只需将各个节点shutdown即可。
6.3 集群操作
6.3.1 连接集群
无论要怎样操作分布式系统,都需要首先连接上。 与之前单机连接相比的唯一区别就是增加了参数-c。
6.3.2 写入数据
6.3.2.1 key单个写入
无论value类型为String还是List、Set等集合类型,只要写入时操作的是一个key,那么在分布式系统中就没有问题。例如,
6.3.2.2 key批量操作
对一次写入多个key的操作,由于多个key会计算出多个slot,多个slot可能会对应多个节点。而由于一次只能写入一个节点,所以该操作会报错。 不过,系统也提供了一种对批量key的操作方案,为这些key指定一个统一的group,让这个group作为计算slot的唯一值。
6.3.3 集群查询
6.3.3.1 查询key的slot
通过cluster keyslot可以查询指定key的slot。例如,下面是查询emp的slot。
6.3.3.2 查询slot中key的数量
通过cluster countkeysinslot命令可以查看到指定slot所包含的key的个数。
6.3.3.3 查询slot中的key
通过cluster getkeysinslot命令可以查看到指定slot所包含的key。
6.3.4 故障转移
分布式系统中的某个master如果出现宕机,那么其相应的slave就会自动晋升为master。如果原master又重新启动了,那么原master会自动变为新master的slave。
6.3.4.1 模拟故障
通过cluster nodes命令可以查看系统的整体架构及连接情况。 当然,也可以通过info replication查看当前客户端连接的节点的角色。可以看到,6381节点是master,其slave为6383节点。 为了模拟6381宕机,直接将其shutdown。 通过客户端连接上6383节点后可以查看到,其已经自动晋升为了master。 重启6381节点后查看其角色,发现其自动成为了6383节点的slave。
6.3.4.2 全覆盖需求
如果某slot范围对应节点的master与slave全部宕机,那么整个分布式系统是否还可以对外提供读服务,就取决于属性cluster-require-full-coverage的设置。 该属性有两种取值:
- yes:默认值。要求所有slot节点必须全覆盖的情况下系统才能运行。
- no:slot节点不全的情况下系统也可以提供查询服务。
6.3.5 集群扩容
下面要在正在运行的分布式系统中添加两个新的节点:端口号为6386的节点为master节点,其下会有一个端口号为6387的slave节点。
6.3.5.1 复制并修改2个配置文件
使用redis6380.conf复制出2个配置文件redis6386.conf与redis6387.conf,并修改其中的各处端口号为相应端口号,为集群扩容做前期准备。
6.3.5.2 启动系统与2个节点
由于要演示的是在分布式系统运行期间的动态扩容,所以这里先启动分布式系统。 要添加的两个节点是两个Redis,所以需要先将它们启动。只不过,在没有添加到分布式系统之前,它们两个是孤立节点,每个节点与其它任何节点都没有关系。
6.3.5.3 添加master节点
通过命令redis-cli --cluster add-node {newHost}:{newPort} {existHost}:{existPort}可以将新的节点添加到系统中。其中{newHost}:{newPort}是新添加节点的地址,{existHost}:{existPort}是原系统中的任意节点地址。 添加成功后可看到如下日志。 添加成功后,通过redis-cli -c -p 6386 cluster nodes命令可以看到其它master节点都分配有slot,只有新添加的master还没有相应的slot。当然,通过该命令也可以看到该新节点的动态ID。
6.3.5.4 分配slot
为新的master分配的slot来自于其它节点,总slot数量并不会改变。所以slot分配过程本质是一个slot的移动过程。 通过redis-cli –c --cluster reshard {existIP}:{existPort}命令可开启slot分配流程。其中地址{existIP}:{existPort}为分布式系统中的任意节点地址。 该流程中会首先查询出当前节点的slot分配情况。 然后开始Q&A交互。一共询问了四个问题,这里有三个:
- 准备移动多少slot?
- 准备由谁来接收移动的slot?
- 选择要移动slot的源节点。有两种方案。如果选择键入all,则所有已存在slot的节点都将作为slot源节点,即该方案将进行一次slot全局大分配。也可以选择其它部分节点作为slot源节点。此时将源节点的动态ID复制到这里,每个ID键入完毕后回车,然后再复制下一个slot源节点动态ID,直至最后一个键入完毕回车后再键入done。
这里键入的是all,进行全局大分配。 其首先会检测指定的slot源节点的数据,然后制定出reshard的方案。 这里会再进行一次Q&A交互,询问是否想继续处理推荐的方案。键入yes,然后开始真正的全局分配,直至完成。 此时再通过redis-cli -c -p 6386 cluster nodes命令查看节点信息,可以看到6386节点中已经分配了slot,只不过分配的slot编号并不连续。master节点新增完成。
6.3.5.5 添加slave节点
现要将6387节点添加为6386节点的slave。当然,首先要确保6387节点的Redis是启动状态。 通过redis-cli --cluster add-node {newHost}:{newPort} {existHost}:{existPort} --cluster-slave --cluster-master-id masterID命令可将新添加的节点直接添加为指定master的slave。 回车后可看到如下的日志,说明添加成功。 此时再通过redis-cli -c -p 6386 cluster nodes命令可以看到其已经添加成功,且为指定master的slave。
6.3.6 集群缩容
下面要将slave节点6387与master节点6386从分布式系统中删除。
6.3.6.1 删除slave节点
对于slave节点,可以直接通过redis-cli --cluster del-node <delHost>:<delPort> delNodeID命令删除。 此时再查看集群,发现已经没有了6387节点。
6.3.6.2 移出master的slot
在删除一个master之前,必须要保证该master上没有分配有slot。否则无法删除。所以,在删除一个master之前,需要先将其上分配的slot移出。 以上交互指定的是将6386节点中的1999个slot移动到6380节点。 注意:
- 要删除的节点所包含的slot数量在前面检测结果中都是可以看到的,例如,6386中的并不是2000个,而是1999个
- What is the receiving node ID?仅能指定一个接收节点
回车后继续。 此时再查看发现,6386节点中已经没有slot了。
6.3.6.3 删除master节点
此时就可以删除6386节点了。 此时再查看集群,发现已经没有了6386节点。
6.4 分布式系统的限制
Redis的分布式系统存在一些使用限制:
- 仅支持0号数据库
- 批量key操作支持有限
- 分区仅限于key
- 事务支持有限
- 不支持分级管理