Redis主从复制原理和高可用方案
Redis的几种常见高可用使用方式包括:
- Redis多副本(主从);
- Redis Sentinel(主从 + 哨兵);
- Redis Cluster;
主从同步基础概念
互联网系统一般是以主从架构为基础的,所谓主从架构设计的思路大概是:
- 在多台数据服务器中,只有一台主服务器,而主服务器只负责写入数据,不负责让外部程序读取数据。
- 存在多台从服务器,从服务器不写入数据,只负责同步主服务器的数据,并让外部程序读取数据。
- 主服务器在写入数据后,即刻将写入数据的命令发送给从服务器,从而使得主从数据同步。
- 应用程序可以随机读取某一台从服务器的数据,这样就分摊了读数据的压力。
- 当从服务器不能工作的时候,整个系统将不受影响;当主服务器不能工作的时候,可以方便地从从服务器中选举一台来当主服务器。
请注意上面的思路,笔者用了“大概”这两个字,因为这只是一种大概的思路,每一种数据存储的软件都会根据其自身的特点对上面的这几点思路加以改造,但是万变不离其宗,只要理解了这几点就很好理解 Redis 的复制机制了。主从同步机制如图 1 所示。
图 1 主从同步机制
这个时候读数据就可以随机从服务器上读取,当从服务器是多台的时候,那么单台服务器的压力就大大降低了,这十分有利于系统性能的提高,当主服务器出现不能工作的情况时,也可以切换为其中的一台从服务器继续让系统稳定运行,所以也有利于系统运行的安全性。当然由于 Redis 自身具备的特点,所以其也有实现主从同步的特殊方式。
Redis 主从同步配置
对 Redis 进行主从同步的配置分为主机与从机,主机是一台,而从机可以是多台。
首先,明确主机。当你能确定哪台机子是主机的时候,关键的两个配置是 dir 和 dbfilename 选项,当然必须保证这两个文件是可写的。对于 Redis 的默认配置而言,dir 的默认值为“./”,而对于 dbfilename 的默认值为“dump.rbd”。换句话说,默认采用 Redis 当前目录的 dump.rbd 文件进行同步。对于主机而言,只要了解这多信息,很简单。
其次,在明确了从机之后,进行进一步配置所要关注的只有 slaveof 这个配置选项,它的配置格式是:
slaveof server port
其中,server 代表主机,port 代表端口。当从机 Redis 服务重启时,就会同步对应主机的数据了。当不想让从机继续复制主机的数据时,可以在从机的 Redis 命令客户端发送 slaveof no one 命令,这样从机就不会再接收主服务器的数据更新了。又或者原来主服务器已经无法工作了,而你可能需要去复制新的主机,这个时候执行 slaveof sever port 就能让从机复制另外一台主机的数据了。
在实际的 Linux 环境中,配置文件 redis.conf 中还有一个 bind 的配置,默认为 127.0.0.1,也就是只允许本机访问,把它修改为 bind 0.0.0.0,其他的服务器就能够访问了。
上面的文字描述了如何配置,但是有时候需要进一步了解 Redis 主从复制的过程,这些内容对于复制而言是很有必要的,同时也是很有趣的。
Redis 主从同步的过程原理
Redis 主从同步的过程如图 2 所示。
图 2 Redis 主从同步
图 2 中左边的流程是主服务器,而右边的流程为从服务器,这里有必要进行更深层次的描述:
1)无论如何要先保证主服务器的开启,开启主服务器后,从服务器通过命令或者重启配置项可以同步到主服务器。
2)当从服务器启动时,读取同步的配置,根据配置决定是否使用当前数据响应客户端,然后发送 SYNC 命令。
当主服务器接收到同步命令的时候,就会执行 bgsave 命令备份数据,但是主服务器并不会拒绝客户端的读/写,而是将来自客户端的写命令写入缓冲区。从服务器未收到主服务器备份的快照文件的时候,会根据其配置决定使用现有数据响应客户端或者拒绝。
3)当 bgsave 命令被主服务器执行完后,开始向从服务器发送备份文件,这个时候从服务器就会丢弃所有现有的数据,开始载入发送的快照文件。
4)当主服务器发送完备份文件后,从服务器就会执行这些写入命令。此时就会把 bgsave 执行之后的缓存区内的写命令也发送给从服务器,从服务完成备份文件解析,就开始像往常一样,接收命令,等待命令写入。
5)缓冲区的命令发送完成后,当主服务器执行一条写命令后,就同时往从服务器发送同步写入命令,从服务器就和主服务器保持一致了。而此时当从服务器完成主服务器发送的缓冲区命令后,就开始等待主服务器的命令了。
以上 5 步就是 Redis 主从同步的过程。
只是在主服务器同步到从服务器的过程中,需要备份文件,所以在配置的时候一般需要预留一些内存空间给主服务器,用以腾出空间执行备份命令。一般来说主服务器使用 50%~65% 的内存空间,以为主从复制留下可用的内存空间。
多从机同步机制,如图 3 所示。
图 3 多从机同步机制
如果出现多台同步,可能会出现频繁等待和频繁操作 bgsave 命令的情况,导致主机在较长时间里性能不佳,这个时候我们会考虑主从链进行同步的机制,以减少这种可能。
主从复制(Replication-Sentinel模式)
搭建
安装Redis
$ yum -y install gcc $ yum -y install gcc-c++
$ wget http://download.redis.io/releases/redis-5.0.4.tar.gz
$ tar -zvxf redis-5.0.4.tar.gz
$ cd redis-5.0.4
$ make
这里我们将redis.conf文件复制两份slave.conf和slave2.conf并修改配置
# 服务器端口号,主从分别修改为7001 7002 7003
port 7001
# 使得Redis可以跨网络访问
bind 0.0.0.0
# 配置reids的密码
requirepass "111111"
# 下面两个配置只需要配置从节点(slave)
# 配置主服务器地址、端口号
replicaof 127.0.0.1 7001
# 主服务器密码
masterauth "111111"
分别启动这三个Redis服务
$ ./src/redis-server redis.conf
$ ./src/redis-server slave.conf
$ ./src/redis-server slave2.conf
使用redis-cli工具连接redis服务查看主从节点是否搭建成功
$ ./src/redis-cli -h <主机名> -p <端口号>
$ auth <password>
$ info replication
看到类似如下所示信息则主从搭建成功
############主节点(master)信息#############
# Replication
role:master
connected_slaves:2
slave0:ip=192.168.1.164,port=7002,state=online,offset=1015511,lag=0
slave1:ip=192.168.1.164,port=7003,state=online,offset=1015511,lag=0
master_replid:ffff866d17e11dcc9a9fd7bf3a487ad9e499fca9
master_replid2:1c8a6f05891dc72bbe4fefd9a54ff65eb46ce35d
master_repl_offset:1015511
second_repl_offset:424773
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:99239
repl_backlog_histlen:916273
############从节点(slave)信息#############
# Replication
role:slave
master_host:192.168.1.164
master_port:7001
master_link_status:up
master_last_io_seconds_ago:0
master_sync_in_progress:0
slave_repl_offset:560709
slave_priority:100
slave_read_only:1
connected_slaves:0
master_replid:ffff866d17e11dcc9a9fd7bf3a487ad9e499fca9
master_replid2:0000000000000000000000000000000000000000
master_repl_offset:560709
second_repl_offset:-1
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:549628
repl_backlog_histlen:11082
工作原理-Sentinel
简单的主从集群有个问题,就是主节点挂了之后,无法从新选举新的节点作为主节点进行写操作,导致服务不可用。所以接下来介绍Sentinel(哨兵)功能的使用。哨兵是一个独立的进程,哨兵会实时监控master节点的状态,当master不可用时会从slave节点中选出一个作为新的master,并修改其他节点的配置指向到新的master。
该系统执行以下三个任务:
- 监控(Monitoring):Sentinel 会不断地检查你的主服务器和从服务器是否运作正常。
- 提醒(Notification):当被监控的某个 Redis 服务器出现问题时, Sentinel 可以通过 API 向管理员或者其他应用程序发送通知。
- 自动故障迁移(Automatic failover): 当一个主服务器不能正常工作时, Sentinel 会开始一次自动故障迁移操作, 它会将失效主服务器的其中一个从服务器升级为新的主服务器, 并让失效主服务器的其他从服务器改为复制新的主服务器; 当客户端试图连接失效的主服务器时, 集群也会向客户端返回新主服务器的地址, 使得集群可以使用新主服务器代替失效服务器。
搭建步骤
新开一台服务器,并按上面的步骤下载安装Redis。
将sentinel.conf文件复制两份为sentinel2.conf、sentinel3.conf,并分别修改配置
# 三个配置文件分别配置不同的端口号
port <端口号>
# 设置为后台启动
daemonize yes
# 主节点的名称(可以自定义,与后面的配置保持一致即可)
# 主机地址
# 端口号
# 数量(2代表只有两个或两个以上的哨兵认为主服务器不可用的时候,才会进行failover操作)
sentinel monitor mymaster 127.0.0.1 6379 2
# 多长时间没有响应认为主观下线(SDOWN)
sentinel down-after-milliseconds mymaster 60000
# 表示如果15秒后,mysater仍没活过来,则启动failover,从剩下从节点序曲新的主节点
sentinel failover-timeout mymaster 15000
# 指定了在执行故障转移时, 最多可以有多少个从服务器同时对新的主服务器进行同步, 这个数字越小, 完成故障转移所需的时间就越长
sentinel parallel-syncs mymaster 1
启动三个sentinel
$ ./src/server-sentinel sentinel.conf
$ ./src/server-sentinel sentinel2.conf
$ ./src/server-sentinel sentinel3.conf
然后手动关闭主节点的redis服务,并查看两个slave信息是否有一个变成了master。
程序中使用
SpringBoot连接Redis主从集群配置
spring:
redis:
sentinel:
master: mymaster
nodes: 192.168.1.167:26379,192.168.1.167:26380,192.168.1.167:26381
host: 192.168.1.164
port: 7003
database: 0
password: <password>
参考文档
http://www.redis.cn/topics/replication.html
http://www.redis.cn/topics/sentinel.html
Redis集群(Redis-Cluster)
工作原理
Redis 集群是一个提供在多个Redis节点间共享数据的程序集。下图以三个master节点和三个slave节点作为示例。Redis 集群有16384个哈希槽,每个key通过CRC16校验后对16384取模来决定放置哪个槽。集群的每个节点负责一部分hash槽,如图中slots所示。
为了使在部分节点失败或者大部分节点无法通信的情况下集群仍然可用,所以集群使用了主从复制模型,每个节点都会有1-n个从节点。例如master-A节点不可用了,集群便会选举slave-A节点作为新的主节点继续服务。
搭建步骤
Redis5.0之后的版本放弃了 Ruby 的集群方式,改为使用 C 语言编写的redis-cli的方式,使集群的构建方式复杂度大大降低。
下载安装Redis(见主从复制模式的搭建步骤)。
创建6个Redis的配置文件,如下所示:
/usr/local/redis-5.0.4/redis-cluster-conf/7001/redis.conf
/usr/local/redis-5.0.4/redis-cluster-conf/7002/redis.conf
/usr/local/redis-5.0.4/redis-cluster-conf/7003/redis.conf
/usr/local/redis-5.0.4/redis-cluster-conf/7004/redis.conf
/usr/local/redis-5.0.4/redis-cluster-conf/7005/redis.conf
/usr/local/redis-5.0.4/redis-cluster-conf/7006/redis.conf
配置文件内容:
port 7001 # 端口,每个配置文件不同7001-7006
cluster-enabled yes # 启用集群模式
cluster-config-file nodes.conf #节点配置文件
cluster-node-timeout 5000 # 超时时间
appendonly yes # 打开aof持久化
daemonize yes # 后台运行
protected-mode no # 非保护模式
pidfile /var/run/redis_7001.pid # 根据端口修改
启动6个Redis节点。
./src/redis-server redis-cluster-conf/7001/redis.conf
./src/redis-server redis-cluster-conf/7002/redis.conf
./src/redis-server redis-cluster-conf/7003/redis.conf
./src/redis-server redis-cluster-conf/7004/redis.conf
./src/redis-server redis-cluster-conf/7005/redis.conf
./src/redis-server redis-cluster-conf/7006/redis.conf
此时启动的6个Redis服务是相互独立运行的,通过以下命令配置集群。
./src/redis-cli --cluster create --cluster-replicas 1 127.0.0.1:7001 127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005 127.0.0.1:7006
配置完看到如下图所示信息表示集群搭建成功:
从图中可以看到启动了3个master节点,3个slave节点,16384个槽点平均分配到了3个master节点上。图中很长的一串字母数字的组合(07000b3a90......)为节点的ID。后面对节点的操作中会用到。
集群重新分片
如果对默认的平均分配不满意,我们可以对集群进行重新分片。执行如下命令,只需要指定集群中的其中一个节点地址即可,它会自动找到集群中的其他节点。(如果设置了密码则需要加上 -a <password>,没有密码则不需要,后面的命令我会省略这个,设置了密码的自己加上就好)。
./src/redis-cli -a <password> --cluster reshard 127.0.0.1:7001
输入你想重新分配的哈希槽数量
How many slots do you want to move (from 1 to 16384)?
输入你想接收这些哈希槽的节点ID
What is the receiving node ID?
输入想从哪个节点移动槽点,选择all表示所有其他节点,也可以依次输入节点ID,以done结束。
Please enter all the source node IDs.
Type 'all' to use all the nodes as source nodes for the hash slots.
Type 'done' once you entered all the source nodes IDs.
Source node #1:
输入yes执行重新分片
Do you want to proceed with the proposed reshard plan (yes/no)?
自动故障转移
当运行中的master节点挂掉了,集群会在该master节点的slave节点中选出一个作为新的master节点。这里不做演示。
手动故障转移
有的时候在主节点没有任何问题的情况下强制手动故障转移也是很有必要的,比如想要升级主节点的Redis进程,我们可以通过故障转移将其转为slave再进行升级操作来避免对集群的可用性造成很大的影响。
Redis集群使用 cluster failover 命令来进行故障转移,不过要在被转移的主节点的从节点上执行该命令(使用redis-cli连接slave节点并执行 cluster failover命令进行转移)。
添加一个主节点
按之前的方式再复制一份配置文件,并修改配置
/usr/local/redis-5.0.4/redis-cluster-conf/7007/redis.conf
然后启动该Redis服务,执行以下命令将该节点添加到集群中去
./src/redis-cli --cluster add-node 127.0.0.1:7007 127.0.0.1:7001
第一个参数为新增加的节点的IP和端口,第二个参数为任意一个已经存在的节点的IP和端口。
此时该新节点已经成为集群的一份子
127.0.0.1:7007> cluster nodes
90c78386c39c8258435b5b61f49a623b358ec8a6 127.0.0.1:7007@17007 myself,master - 0 1553239975877 10 connected
c4180e02149e2d853a80683433773ef4bceffc78 127.0.0.1:7001@17001 master - 0 1553240017595 11 connected 0-5544 10923-11004
b6584514edbf57331a65f367304f33ad1bd0903e 192.168.1.164:7005@17005 slave 3bdbc4ac0d5902dcf8a5ebbfb88db8fad224c066 0 1553240016992 2 connected
07000b3a905df0ab0c86361adcb2774a487ce650 192.168.1.164:7004@17004 slave c4180e02149e2d853a80683433773ef4bceffc78 0 1553240016000 11 connected
28fa7bbf6b2a46991c7a5fe8eec53db1a5f1e9f6 192.168.1.164:7003@17003 master - 0 1553240017595 3 connected 11005-16383
3bdbc4ac0d5902dcf8a5ebbfb88db8fad224c066 192.168.1.164:7002@17002 master - 0 1553240017000 2 connected 5545-10922
fd9cba359d94ba6c9beecc91fbd491f9cf7a39ca 192.168.1.164:7006@17006 slave 28fa7bbf6b2a46991c7a5fe8eec53db1a5f1e9f6 0 1553240016000 3 connected
但是该节点没有包含任何的哈希槽,所以没有数据会存到该主节点。我们可以通过上面的集群重新分片给该节点分配哈希槽,那么该节点就成为了一个真正的主节点了。
添加一个从节点
跟添加主节点一样添加一个节点7008,然后连接上该节点并执行如下命令
cluster replicate <nodeId>
这样就可以指定该节点成为哪个节点的从节点。
节点的移除
可以使用如下命令来移除节点
./src/redis-cli --cluster del-node 127.0.0.1:7001 <nodeId>
第一个参数是任意一个节点的地址,第二个参数是你想要移除的节点ID。如果是移除主节点,需要确保这个节点是空的,如果不是空的则需要将这个节点上的数据重新分配到其他节点上。
程序中使用
SpringBoot中连接Redis集群配置
spring:
redis:
cluster:
nodes: 192.168.1.164:7001,192.168.1.164:7002,192.168.1.164:7003,192.168.1.164:7004,192.168.1.164:7005,192.168.1.164:7006
database: 0
password: <password>
参考文档
目前维护的开源产品:https://gitee.com/475660