Redis高可用方案

Redis高可用常见的有两种方式:

  • 主从复制(Replication-Sentinel模式)
  • Redis集群(Redis-Cluster模式)

下面将分别介绍这两种高可用方案。

搭建环境:
redis版本:redis-5.0.4
服务器环境:centos7

主从复制(Replication-Sentinel模式)

工作原理-Replication

Redis主从结构如下图所示,主节点(master)负责读写,从节点(slave)负责读。这个系统的运行依靠三个主要的机制:

  • 当一个 master 实例和一个 slave 实例连接正常时, master 会发送一连串的命令流来保持对 slave 的更新,以便于将自身数据集的改变复制给 slave ,包括客户端的写入、key 的过期或被逐出等等。
  • 当 master 和 slave 之间的连接断开之后,因为网络问题、或者是主从意识到连接超时, slave 重新连接上 master 并会尝试进行部分重同步:这意味着它会尝试只获取在断开连接期间内丢失的命令流。
  • 当无法进行部分重同步时, slave 会请求进行全量重同步。这会涉及到一个更复杂的过程,例如 master 需要创建所有数据的快照,将之发送给 slave ,之后在数据集更改时持续发送命令流到 slave 。
搭建步骤

安装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>

参考文档

http://www.redis.cn/topics/cluster-tutorial.html

Redis常见数据丢失情况分析及解决

情况分析

(1)异步复制导致的数据丢失

因为master->slave的数据同步是异步的,所以可能存在部分数据还没有同步到slave,master就宕机了,此时这部分数据就丢失了。

(2)脑裂导致的数据丢失

当master所在的机器突然脱离的正常的网络,与其他slave、sentinel失去了连接,但是master还在运行着。此时sentinel就会认为master宕机了,会开始选举把slave提升为新的master,这个时候集群中就会出现两个master,也就是所谓的脑裂。

此时虽然产生了新的master节点,但是客户端可能还没来得及切换到新的master,会继续向旧的master写入数据。

当网络恢复正常时,旧的master会变成新的master的从节点,自己的数据会清空,重新从新的master上复制数据。

解决方案

Redis提供了这两个配置用来降低数据丢失的可能性

min-slaves-to-write 1 
min-slaves-max-lag 10

上面两行配置的意思是,要求至少有1个slave,数据复制和同步的延迟不能超过10秒,如果不符合这个条件,那么master将不会接收任何请求。

(1)减少异步复制的数据丢失

有了min-slaves-max-lag这个配置,就可以确保,一旦slave复制数据和ack延时太长,就认为master宕机后损失的数据太多了,那么就拒绝写请求,这样可以把master宕机时由于部分数据未同步到slave导致的数据丢失降低到可控范围内。

(2)减少脑裂的数据丢失

如果一个master出现了脑裂,跟其他slave丢了连接,那么上面两个配置可以确保,如果不能继续给指定数量的slave发送数据,而且slave超过10秒没有给自己ack消息,那么就直接拒绝客户端的写请求

这样脑裂后的旧master就不会接受client的新数据,也就避免了数据丢失。

Redis并不能保证数据的强一致性,看官方文档的说明


参考文档:

http://www.redis.cn/topics/cluster-tutorial.html

推荐阅读更多精彩内容