4. 集群
1. 从结构上,单个Redis服务器会发生单点故障,同时一台服务器需要承受所有的请求负载。这就需要为数据生成多个副本并且分配在不同的服务器上。
2. 从容量上,单个Redis服务器的内存非常容易成为存储瓶颈,所以需要进行数据分片。
三种方式:复制(replication)、哨兵(sentinel)、集群(cluster)
4.1 复制:主从复制,读写分离
用于解决单点故障:如果出现服务器硬盘故障等问题,就会导致数据丢失,将数据库复制多个副本以部署在不同的服务器上,实现当一台数据库中的数据更新后,自动将新的数据同步到其他数据库上。
适合读多写少的场景,当单个主数据库不能够满足需求时,就需要使用集群功能。
当一台数据库中的数据更新后,自动将更新的数据同步到其他数据库上。
主数据库:读写操作,当写操作导致数据变化时会自动将数据同步给从数据库
从数据库:一般只读,并接受主数据库同步过来的数据
一个主数据库可以拥有多个从数据库,而一个从数据库只能拥有一个主数据库,如图。
4.1.1 实现的三种方式
1. 在配置文件中加入slaveof {masterHost} {masterPort}
2. 启动Redis服务器的时候加参数 redis-server –slaveof {masterHost} {masterPort}
3. 直接在客户端执行slaveof {masterHost} {masterPort}
如:设置6380端口是6379的从服务器
默认情况下,从数据库是只读的,直接修改会出现以下错误
对于从数据库来说,还可以使用SLAVEOF NO ONE命令来使当前数据库停止接受其他数据库的同步并转换成为主数据库。
4.1.2 原理
分为复制初始化阶段和复制同步阶段。
当一个从数据库启动后,会向主数据库发送SYNC命令。同时主数据库接收到SYNC命令后会开始在后台保存快照(即RDB持久化的过程),并将保存快照期间接收到的命令缓存起来当快照完成后Redis会将快照文件和所有缓存的命令发送给从数据库。从数据库收到后,会载入快照文件并执行收到的缓存的命令。以上过程称为复制初始化。复制初始化结束后,主数据库每当收到写命令时就会将命令同步给从数据库,从而保证主从数据库数据一致。
当主从数据库之间的连接断开重连后,Redis 2.6以及之前的版本会重新进行复制初始化(即主数据库重新保存快照并传送给从数据库),即使从数据库可以仅有几条命令没有收到,主数据库也必须要将数据库里的所有数据重新传送给从数据库。这使得主从数据库断线重连后的数据恢复过程效率很低下,在网络环境不好的时候这一问题尤其明显。Redis 2.8版的一个重要改进就是断线重连能够支持有条件的增量数据传输,当从数据库重新连接上主数据库后,主数据库只需要将断线期间执行的命令传送给从数据库,从而大大提高Redis复制的实用性。
使用telnet模拟原理的实现:使用telnet工具伪装成一个从数据库来与主数据库通信
telnet IP 端口
查看是否连接上:ping,连接成功返回pong
sync 就可以进行同步了
如下,set k1 v1同步给本地,原理就是TCP协议通信,AOF文件中也是以这种形式存储的
断开telnet连接:quit
Redis采用了乐观复制(optimistic replication)的复制策略,容忍在一定时间内主从数据库的内容是不同的,但是两者的数据会最终同步。
使用异步将命令同步到从数据库,如果传送给从数据库之前,连接断开,此时二者之间的数据就会是不一致的。从这个角度看,主数据库无法得知某个命令最终同步给了多少个从数据库,不过Redis提供了两个配置选项来限制只有当数据至少同步给指定数量的从数据库时,主数据库才是可写的。
min-slaves-to-write 3:只有当三个或者三个以上的从数据库连接到数据库时,主数据库才是可写的,否则会返回错误。
min-slaves-max-lag 10:允许从数据库最长失去连接时间,如果从数据库最后与主数据库联系的时间小于这个值,则认为从数据库还在保持与主数据库的连接。
4.1.3 图结构
从数据库不仅可以接收主数据库的同步数据,自己也可以同时作为数据库存在,形成类似图的结构。
数据库A的数据会同步到B和C中,而B中的数据会同步到D和E中。向B中写入数据不会同步到A或C中,只会同步到D和E中。
4.1.4 从数据库持久化
为提升性能,在从数据库开启持久化,主数据库禁用持久化。从数据库崩溃重启后,主数据库会自动将数据同步过来,无需担心数据丢失。主数据库崩溃就稍显复杂了,要保证主数据库崩溃后不能马上重启,不然从数据库主动同步主数据库数据,从数据库的数据也会不存在了。需严格按照以下两步进行。
1. 在从数据库中使用SLAVEOF NO ONE命令将从数据库提升成主数据库继续服务。
2. 启动之前崩溃的主数据库,然后使用SLAVEOF命令将其设置成新数据库的从数据库,即可将数据同步回来。
存在的问题:手动维护从数据库或主数据库的重启以及数据恢复都相对麻烦
4.1.5 无硬盘复制
复制的工作原理是基于RDB方式的持久化实现的,即主数据库端在后台保存RDB快照,从数据库则接受并载入快照文件。进行复制初始化时,从数据库会将收到的内容写入到硬盘的临时文件上,当写入完成后从数据库会用该临时文件替换RDB快照文件,那么每次和从数据库同步时,Redis都会执行一次快照,并同时对硬盘进行读写,导致性能降低。
2.8.18版本以后,引入无硬盘复制选项,在与从数据库进行复制初始化时,直接将快照通过网络发送给从数据库,避免了硬盘的性能瓶颈。
开启:设置repl-diskless-sync yes
4.1.6 增量复制
主从断线重连情况下的增量复制 – 大多数情况下,对开发者透明。
原理:从数据库会存储主数据库的运行ID;在复制同步阶段,主数据库将每一个命令传递给从数据库时,都会同时把该命令存放到一个积压队列中,并记录下当前积压队列中存放的命令的偏移范围;同时,从数据库接受到主数据库传来的命令时,会记录下该命令的偏移量。
repl-backlog-size 1mb:积压队列大小,默认为1mb
repl-backlog-ttl 3360:当所有从数据库与主数据库断开连接之后,经过多长时间可以释放积压队列的内容空间,默认1小时。
4.2 哨兵
解决复制方式存在的问题,用来实现自动化的系统监控和故障恢复功能。
什么是哨兵?
哨兵的作用就是监控Redis系统的运行状态,他的功能包括以下两个:
1. 监控主数据库和从数据库是否正常运行
2. 主数据库出现故障时自动将从数据库转化为主数据库
也可以使用多个哨兵进行监控任务以保证系统足够稳健,此时不仅哨兵会同时监控主数据库和从数据库,哨兵之间也会互相监控。
4.2.1 马上上手
1. 配置好一主二从,查看是否配置成功:info replication
2. 配置哨兵,建立一个配置文件,如sentinel.conf,内容为:
<master-name> 要监控的主数据库的名字,可以自己定义一个
<quorum> 最低通过票数
3. 启动sentinel进程,并将上述配置文件的路径传递给哨兵
redis-sentinel /etc/myRedis/conf/sentinel.conf
需要注意的是,配置哨兵监控一个系统时,只需要配置其监控主数据库即可,哨兵会自动发现所有复制该主数据库的从数据库。
+slave 表示新发现了从数据库
+sdown 表示哨兵主观认为该主数据库停止服务了
+odown 表示哨兵客观认为主数据停止服务了
+try-failover 表示哨兵开始进行故障恢复
+failover-end 表示哨兵完成故障恢复
4.2.2 原理
和主数据库的连接建立完成后,哨兵会定时执行下面3个操作。
1. 每10秒哨兵会向主数据库和从数据库发送INFO命令,从而实现新节点的自动发现。
2. 每2秒哨兵会向主数据库和从数据库的_sentinel_:hello频道发送自己的信息。
3. 每1秒哨兵会向主数据库、从数据库和其他哨兵节点发送PING命令。
选举领头哨兵的过程使用了Raft算法,具体过程如下。
1. 发现主数据库客观下线的哨兵节点(下面称作A)向每个哨兵节点发送命令,要求对方选自己成为领头哨兵。
2. 如果目标哨兵节点没有选过其他人,则会同意A设置成领头哨兵。
3. 如果A发现有超过半数且超过quorum参数值的哨兵节点同意自己成为领头哨兵,则A成功成为领头哨兵。
4. 当有多个哨兵节点同时参选领头哨兵,则会出现没有任何节点当选的可能。此时每个参选节点将等待一个随机时间重新发起参选请求,进行下一轮选举,直到选举成功。
提示
使用哨兵可能会出现闪断的情况,主数据库崩溃后,在网络不好的情况下,1~2秒才能通过哨兵节点从从数据库中选出主数据库,而在这个时间内,可能对丢失很多数据。
4.3 集群
解决木桶效应 – 水平扩展。
即使使用哨兵,此时Redis集群的每个数据库依然存有集群中的所有数据,从而导致集群的总数量存储受限于可用存储内存最小的数据库节点,形成木桶效应。
集群的特点在于拥有和单机实例同样的性能,同时在网络分区后能够提供一定的可访问性以及对主数据库故障恢复的支持。
集群只能使用默认的0号数据库,如果执行select切换数据库则会提示错误。
4.3.1 配置集群
使用集群,只需要每个数据库节点的cluster-enabled配置选项打开即可。每个集群中至少需要3个主数据库才能正常运行。
每个节点对应的文件必须不同,否则会造成启动失败,所以启动节点时要注意最后为每个节点使用不同的工作目录,或者通过cluster-config-file选项修改持久化文件的名称。
使用info命令来判断集群是否正常启用了:info cluster,如果cluster_enabled:1表示集群正常启用了。现在每个节点都是相互独立的,要将他们加入到同一个集群还需要几个步骤。
1. Redis源代码中提供了一个辅助工具redis.trib.rb可以非常方便地完成这一任务。因为redis.trib是用Ruby语言编写的,所以运行前需要在服务器安装Ruby程序,执行gem install redis
2. 使用redis-trib.rb来初始化集群,只需要执行:
create参数表示要初始化集群,--replicas 1 表示每个主数据库拥有的从数据库个数为1 ,所以整个集群共有3主3从,前面三个是主数据库,后面三个是从数据库。
4.3.2 配置案例
说明 2018年10月 Redis 发布了稳定版本的 5.0 版本,推出了各种新特性,其中一点是放弃 Ruby的集群方式,改为 使用 C语言编写的 redis-cli的方式,使集群的构建方式复杂度大大降低。
以下使用redis-cli的方式来搭建集群,参考地址:https://my.oschina.net/ruoli/blog/2252393
1. 创建目录
新建目录:/root/software/redis
2. 下载源码并解压编译
wget http://download.redis.io/releases/redis-5.0.0.tar.gz
tar xzf redis-5.0.0.tar.gz
cd redis-5.0.0
make
3. 创建6个Redis配置文件
6个配置文件不能在同一个目录,此处我们定义如下:
/root/software/redis/redis-cluster-conf/7001/redis.conf
/root/software/redis/redis-cluster-conf/7002/redis.conf
/root/software/redis/redis-cluster-conf/7003/redis.conf
/root/software/redis/redis-cluster-conf/7004/redis.conf
/root/software/redis/redis-cluster-conf/7005/redis.conf
/root/software/redis/redis-cluster-conf/7006/redis.conf
配置文件的内容为:
port 7001 #端口
cluster-enabled yes #启用集群模式
cluster-config-file nodes-7001.conf
cluster-node-timeout 5000 #超时时间
appendonly yes
daemonize yes #后台运行
protected-mode no #非保护模式
pidfile /var/run/redis_7001.pid
其中 port 和 pidfile 需要随着 文件夹的不同调增
4. 启动节点
/root/software/redis/redis-5.0.0/src/redis-server /root/software/redis/redis-cluster-conf/7001/redis.conf
/root/software/redis/redis-5.0.0/src/redis-server /root/software/redis/redis-cluster-conf/7002/redis.conf
/root/software/redis/redis-5.0.0/src/redis-server /root/software/redis/redis-cluster-conf/7003/redis.conf
/root/software/redis/redis-5.0.0/src/redis-server /root/software/redis/redis-cluster-conf/7004/redis.conf
/root/software/redis/redis-5.0.0/src/redis-server /root/software/redis/redis-cluster-conf/7005/redis.conf
/root/software/redis/redis-5.0.0/src/redis-server /root/software/redis/redis-cluster-conf/7006/redis.conf
5. 启动集群
/root/software/redis/redis-5.0.0/src/redis-cli --cluster create 192.168.2.40:7001 192.168.2.40:7002 192.168.2.40:7003 192.168.2.40:7004 192.168.2.40:7005 192.168.2.40:7006 --cluster-replicas 1
启动后,可看到成功信息,如下:
>>> Performing Cluster Check (using node 192.168.2.40:7001)
M: 191c645200a8b4d267f71e3354c8248dbb533dde 192.168.2.40:7001
slots:[0-5460] (5461 slots) master
1 additional replica(s)
M: 684f6aa0fbccda295ce6818a8c01ee7255a7b002 192.168.2.40:7003
slots:[10923-16383] (5461 slots) master
1 additional replica(s)
S: 9fdc1e375436767ab815cbddd3df674f3bc2ca99 192.168.2.40:7005
slots: (0 slots) slave
replicates 191c645200a8b4d267f71e3354c8248dbb533dde
S: e7742888ed85b37cff4a98e861e99bb16e8bae2c 192.168.2.40:7006
slots: (0 slots) slave
replicates 400a08d4e5a534c1b609988105d3e045395fbd12
M: 400a08d4e5a534c1b609988105d3e045395fbd12 192.168.2.40:7002
slots:[5461-10922] (5462 slots) master
1 additional replica(s)
S: f2701549ae98315b432d73b49d139ee77d5685b4 192.168.2.40:7004
slots: (0 slots) slave
replicates 684f6aa0fbccda295ce6818a8c01ee7255a7b002
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.
4.3.3 插槽
为主数据库分配插槽,其实就是分配哪些键归哪些节点负责,即:只有主数据库才有插槽。
4.3.4 故障恢复
在一个集群中,每个节点都会定期向其他节点发送ping命令,并通过有没有收到回复来判断目标节点是否已经下线。具体来说,集群中的每个节点每隔1秒钟就会随机选择5个节点,然后选择其中最久没有响应的节点发送ping命令。
在集群中,当一个主数据库下线时,就会出现一部分插槽无法写入的问题。这时如果该主数据库拥有至少一个从数据库,集群就进行故障恢复来将其中的一个从数据库变成主数据库来保证集群的完整。选择哪个从数据库来作为主数据库的过程与在哨兵中选择领头哨兵的过程一样,都是基于Raft算法。
如果一个至少负责一个插槽的主数据库下线且没有相应的从数据库可以进行故障恢复,则整个集群默认会进入下线状态无法继续工作。如果想在这种情况下使集群仍能正常工作,可以修改配置cluster-require-full-coverage为no (默认为yes);
cluster-tequire-full-coverage no