博客已经搬家https://tianmingxing.com
复制的问题
由于复制中每个数据库都是拥有完整的数据,因此复制的总数据存储量,受限于内存最小的数据库节点,如果数据量过大复制就无能为力了。
分片
分片(Partitioning)就是将你的数据拆分到多个Redis实例的过程,这样每个Redis实例只包含完整数据的一部分。
- 常见的分片方式
- 按照范围分片
- 哈希分片,比如一致性哈希
常见的分片实现
- 在客户端进行分片
- 通过代理来进行分片,比如:Twemproxy
- 查询路由
- 发送查询到一个随机实例,这个实例会保证转发你的查询到正确的节点。
- Redis集群在客户端的帮助下,实现了查询路由的一种混合形式,请求不是直接从Redis实例转发到另一个,而是客户端收到重定向到正确的节点。
- 在服务器端进行分片
- Redis采用哈希槽(hash slot)的方式在服务器端进行分片
- Redis集群有16384个哈希槽,使用键的CRC16编码对16384取模来计算一个键所属的哈希槽。
分片的问题
- 不支持涉及多键的操作,如mget。如果所操作的键都在同一个节点就正常执行,否则会提示错误。
- 分片的粒度是键,因此每个键对应的值不要太大。
- 数据备份会比较麻烦,备份数据时你需要聚合多个实例和主机的持久化文件
- 扩容的处理比较麻烦。
- 故障恢复的处理会比较麻烦,可能需要重新梳理Master和Slave的关系,并调整每个复制集里面的数据。
集群
由于数据量过大单个复制集难以承担,因此需要对多个复制集进行集群,形成水平扩展,每个复制集只负责存储整个数据集的一部分,这就是Redis的集群。
- 在以前版本中Redis的集群是依靠客户端分片来完成,但是这会有很多缺点,比如维护成本高,需要客户端编码解决;增加、移出节点都比较繁琐等。
- Redis3.0新增的一大特性就是支持集群,在不降低性能的情况下还提供了网络分区后的可访问性和支持对主数据库故障的恢复。
- 使用集群后只能使用默认的0号数据库。
- 每个Redis集群节点需要两个TCP连接打开,正常的TCP端口用来服务客户端,例如6379,加10000的端口用作数据端口,必须保证防火墙打开这两个端口。
- Redis集群不保证强一致性,这意味着在特定的条件下Redis集群可能会丢掉一些被系统收到的写入请求命令。
集群架构
- 所有的Redis节点彼此互联,内部使用二进制协议优化传输速度和带宽。
- 节点的fail是通过集群中超过半数的节点检测失效时才生效。
- 客户端与Redis节点直连,不需要中间proxy层。客户端不需要连接集群所有节点,只要连接集群中任何一个可用节点即可。
- 集群把所有的物理节点映射到[0-16383]插槽上,集群负责维护
节点-插槽-值
的关系。
相关操作命令
CLUSTER INFO
获取集群的信息CLUSTER NODES
获取集群当前已知的所有节点,以及这些节点的相关信息CLUSTER MEET <ip> <port>
将ip和port所指定的节点添加到集群当中CLUSTER FORGET <node_id>
从集群中移除 node_id 指定的节点CLUSTER REPLICATE <node_id>
将当前节点设置为 node_id 指定的节点的从节点CLUSTER SAVECONFIG
将节点的配置文件保存到硬盘里面CLUSTER ADDSLOTS <slot> [slot ...]
将一个或多个槽分配给当前节点CLUSTER DELSLOTS <slot> [slot ...]
从当前节点移除一个或多个槽CLUSTER FLUSHSLOTS
移除分配给当前节点的所有槽CLUSTER SETSLOT <slot> NODE <node_id>
将槽分配给 node_id 指定的节点,如果槽已经分配给另一个节点,那么先让另一个节点删除该槽>,然后再进行分配CLUSTER SETSLOT <slot> MIGRATING <node_id>
将本节点的槽迁移到指定的节点中CLUSTER SETSLOT <slot> IMPORTING <node_id>
从指定节点导入槽到本节点CLUSTER SETSLOT <slot> STABLE
取消对槽的导入(import)或迁移(migrate)CLUSTER KEYSLOT <key>
计算键 key 应该被放置在哪个槽CLUSTER COUNTKEYSINSLOT <slot>
返回槽目前包含的键值对数量CLUSTER GETKEYSINSLOT <slot> <count>
返回 count 个槽中的键MIGRATE 目的节点ip 目的节点port 键名 数据库号码 超时时间 [copy] [replace]
迁移某个键值对
手动创建插槽
- 准备6个实例来组成一个集群,你可以在同一台虚拟机上复制6份配置文件
redis.conf
,通过里面的端口来进行区分,除端口外还有一些其它的配置也要改,具体可参考之前的文章。 - 假设你准备了6个redis实例,端口范围从
6374
至6379
。 - 保证没有之前文章中讲述的复制集的内容,如果有请恢复到原始状态。
[redis@bogon redis-3.2.9]$ ll redis*
-rw-rw-r--. 1 redis redis 46716 7月 4 15:38 redis6374.conf
-rw-rw-r--. 1 redis redis 46716 7月 4 15:39 redis6375.conf
-rw-rw-r--. 1 redis redis 46716 7月 4 15:41 redis6376.conf
-rw-rw-r--. 1 redis redis 46716 7月 4 15:41 redis6377.conf
-rw-rw-r--. 1 redis redis 46750 7月 4 15:41 redis6378.conf
-rw-rw-r--. 1 redis redis 46740 7月 4 15:42 redis6379.conf
- 修改这6个配置文件的内容
cluster-enabled yes
cluster-config-file nodes-6374.conf
-
请再一下检查这些值是否做了区分
pidfile
port
logfile
dbfilename
unixsocket
(每份配置文件都不能完全相同,建议用端口来作区分) -
分别启动这些redis数据库
redis-server redis6374.conf
,使用redis-cli -p 6374
登录对应的实例并使用info cluster
查看信息。 -
使用
cluster meet
连接各个节点,这样可以把所有的数据库都放到一个集群中。
[redis@bogon redis-3.2.9]$ redis-cli -p 6374
127.0.0.1:6374> cluster nodes
052100ccbf0d912fc3f9ecbac63bfa7ff5256d96 127.0.0.1:6375 master - 0 1499156448710 1 connected
b537b00eed7a2271f52c4b9de3dce0a0b4b3c798 127.0.0.1:6379 slave 5ad7c308c8e9cb41f5c6d8f46b0f7a283a353826 0 1499156450747 4 connected
5ad7c308c8e9cb41f5c6d8f46b0f7a283a353826 127.0.0.1:6378 master - 0 1499156444637 4 connected 12706
5f5238a05fce1f2f75a9f487813c4b883d1c03ac 127.0.0.1:6376 master - 0 1499156451772 2 connected
af95d2c2bb83bb7c5a923655c472d9b3956a51b7 127.0.0.1:6377 slave 5ad7c308c8e9cb41f5c6d8f46b0f7a283a353826 0 1499156449726 4 connected
88d4c64307564d3d2d515db929e42e70647909d0 127.0.0.1:6374 myself,master - 0 0 3 connected
- 使用
cluster replicate (节点编号)
设置部分数据库为slave,这个命令要在你计划它为从的实例上执行。我演示的是3主3从(6374->6375,6376->6377,6378->6379)
127.0.0.1:6375> cluster replicate 88d4c64307564d3d2d515db929e42e70647909d0
OK
至此手动创建集群已经结束。
什么是插槽
插槽是Redis对Key进行分片的单元。在Redis的集群实现中内置了数据自动分片机制,集群内部会将所有的key映射到
16384
个插槽中,集群中的每个数据库实例负责其中部分的插槽的读写。
键与插槽的关系
- Redis会将key的有效部分使用
CRC16
算法计算出散列值,然后对16384
取余数,从而把key分配到插槽中。 - 键名的有效部分规则
- 如果键名包含
{}
那么有效部分就是{}
中的值 - 否则就是取整个键名
移动已分配的插槽
- 假设要迁移
10023
号插槽从实例A
到实例B
- 在B上执行
cluster setslot 10023 importing A
- 在A上执行
cluster setslot 10023 migrating B
- 在A上执行
cluster getkeysinslot 10023
要返回的数量 - 对上一步获取的每个键执行migrate命令将其从A迁移到B
- 在集群中每个服务器上执行
cluster setslot 10023 node B
防止在移动已分配插槽过程中键的临时丢失
上面迁移方案中的前两步就是用来避免在移动已分配插槽过程中,键的临
时丢失问题的,大致思路如下:
- 当前两步执行完成后,如果客户端向A请求插槽10023中的键时,如果键还未被转移,A将继续处理请求。
- 如果键已经转移则返回新的地址给客户端,由客户端发起新的请求以获取数据。
获取插槽对应的节点
- 当客户端向某个数据库发起请求时,如果键不在这个数据库里面,将会返回一个move重定向的请求,里面包含新的地址,客户端收到这个信息后,需要重新发起请求到新的地址去获取数据。
- 大部分的Redis客户端都会自动去重定向,也就是这个过程对开发人员是透明的。
- redis-cli也支持自动重定向,只需要在启动时加入 -c 的参数。
故障判定
- 集群中每个节点都会定期向其他节点发出ping命令,如果没有收到回复,就认为该节点为疑似下线,然后在集群中传播该信息。
- 当集群中的某个节点,收到半数以上认为某节点已下线的信息,就会真的标记该节点为已下线,并在集群中传播该信息。
- 如果已下线的节点是master节点,那就意味着一部分插槽无法写入了。
- 如果集群任意master挂掉,且当前master没有slave,集群进入fail状态。
- 如果集群超过半数以上master挂掉,无论是否有slave,集群进入fail状态。
- 当集群不可用时,所有对集群的操作做都不可用,收到
CLUSTERDOWN The cluster is down
错误信息。
故障恢复
发现某个master下线后,集群会进行故障恢复操作,来将一个slave变成master(基于Raft算法),大致步骤如下:
- 某个slave向集群中每个节点发送请求,要求选举自己为master。
- 如果收到请求的节点没有选举过其他slave会同意
- 当集群中有超过节点数一半的节点同意该slave的请求,则该Slave选举成功。
- 如果有多个slave同时参选,可能会出现没有任何slave当选的情况,将会等待一个随机时间,再次发出选举请求。
- 选举成功后,slave会通过
slaveof no one
命令把自己变成master。如果故障后还想集群继续工作,可设置cluster-require-full-coverage no
。
对于集群故障恢复的说明
- master挂掉了重启还可以加入集群;但挂掉的slave重启,如果对应的master变化了是不能加入集群的,除非修改它们的配置文件将其master指向新master。
- 只要主从关系建立就会触发主和该从采用save方式持久化数据,不论你是否禁止save。
- 在集群中如果默认主从关系的主挂了并立即重启,如果主没有做持久化,数据会完全丢失,从而从的数据也被清空。
使用redis-trib.rb来操作集群
- redis-trib.rb是Redis源码中提供的一个辅助工具,可以非常方便的来操作集群,它是用ruby写的,因此需要在服务器上安装相应环境。
- 下面两种方式选择其中一种即可
使用yum安装
yum install ruby -y
源码安装
- 安装Ruby
- 下载安装包,地址https://www.ruby-lang.org/en/downloads/
- 然后分别configure、make、make install
- 还需要安装rubygems
- 下载安装包,地址https://rubygems.org/pages/download
- 解压后进入解压文件夹运行
ruby setup.rb
。
安装redis的ruby library
- 由于连接国外源不太稳定,建议安装国内源
gem sources --add https://gems.ruby-china.org/ --remove https://rubygems.org/
。 - 可以通过
gem sources -l
查看源并确保只有gems.ruby-china.org。 - 运行
gem install redis
使用redis-trib.rb来初始化集群
ruby redis-trib.rb create --replicas 1 127.0.0.1:6374
127.0.0.1:6375 127.0.0.1:6376 127.0.0.1:6377 127.0.0.1:6378
127.0.0.1:6379
create
表示要初始化集群,--replicas 1
表示每个驻数据库拥有的从数据
库为1个。
使用redis-trib.rb来迁移插槽
- 执行
ruby redis-trib.rb reshard ip:port
,这就告诉Redis要重新分片,
ip:port
可以是集群中任何一个节点。 - 然后按照提示去做
- 这种方式不能指定要迁移的插槽号
预分区
为了实现在线动态扩容和数据分区,Redis的作者提出了预分区的方案,实
际就是在同一台机器上部署多个Redis实例,当容量不够时将多个实例拆分到不同的机器上,这样就达到了扩容的效果。
拆分过程
- 在新机器上启动好对应端口的Redis实例
- 配置新端口为待迁移端口的从库
- 待复制完成并与主库完成同步后,切换所有客户端配置到新的从库的端口
- 配置从库为新的主库
- 移除老的端口实例
- 重复上述过程把要迁移的数据库转移到指定服务器上
以上拆分流程是Redis作者提出的一个平滑迁移的过程,不过该拆分方法还
是很依赖Redis本身的复制功能的,如果主库快照数据文件过大,这个复制的过程也会很久,同时会给主库带来压力。