Redis作为一种高性能的内存数据库,普遍用于目前主流的分布式架构系统中。为了提高系统的容错率,使用多实例的Redis是不可避免的,但是同时复杂度也相比单实例高出很多

一、主从复制

在分布式系统中为了解决单点问题,通常会把数据复制多个副本部署到其他机器,满足故障恢复和负载均衡等需求。Redis也是如此,它为我们提供了复制功能,实现了相同数据的多个Redis副本

复制功能是高可用Redis的基础,哨兵机制和Redis集群都是在复制的基础上实现高可用的。在复制的概念中,数据库分为两类。一类是主数据库(master),一类是从数据库(slave)。master可以进行读写操作,当写操作发生变化时,会自动将数据同步给slave。slave一般只提供读操作,并接收主数据库同步过来的数据。一个master可以对应多个slave,一个slave只能对应一个master

引入主从复制的目的有两个:①读写分离,分担master的压力②容灾备份

Redis的复制拓扑结构可以支持单层或多层复制关系,根据拓扑复杂性可以分为以下三种:一主一从、一主多从、树状主从结构

1、一主一从

1)建立复制

建立复制的方式有以下三种:

  • 配置文件中加入slaveof {masterHost} {masterPort},随Redis启动生效
  • 在redis-server启动命令后加入--slaveof {masterHost} {masterPort}生效
  • Redis服务器启动后,直接使用命令slaveof {masterHost} {masterPort}生效

综上所述,slaveof命令在使用时,可以在运行期动态配置。当然也可以选择提前写到配置文件中

假设需要配置192.168.0.1为主节点,192.158.0.2为从节点,则在从节点执行以下命令:

slaveof 192.168.0.1 6379

slaveof都是在从节点发起。slaveof本身是异步命令,执行slaveof命令时,从节点只保存主节点信息后返回,后续复制流程在从节点内部异步执行。主从节点复制成功建立后,可以使用info replication命令查看复制相关状态

2)断开复制

slaveof命令不但可以建立复制,还可以在从节点执行以下命令来断开与主节点复制关系

slaveof no one

断开复制流程如下:

  • 断开与主节点复制关系
  • 从节点晋升为主节点

从节点断开复制后并不会抛弃原有数据,只是无法再获取主节点上的数据变化

3)切主

通过slaveof命令还可以实现切主操作,所谓切主是指把当前从节点对主节点的复制切换到另一个主节点

与新主节点建立复制关系命令如下:

slaveof {newMasterIp} {newMasterPort}

切主流程如下:

  • 断开与旧主节点复制关系
  • 删除从节点当前所有数据
  • 与新主节点建立复制关系
  • 对新主节点进行复制操作

4)只读

默认情况下,从节点使用以下命令配置为只读模式:

slave-read-only = yes

5)传输延迟

实际上,主从节点一般部署在不同机器上,复制时的网络延迟就成为需要考虑的问题,Redis为我们提供了以下参数用于控制是否关闭TCP_NODELAY,默认关闭:

repl-disable-tcp-nodelay no

当关闭时,主节点产生的命令数据无论大小都会及时地发送给从节点,这样主从之间延迟会变小,但增加了网络带宽的消耗。适用于主从之间的网络环境良好的场景,如同机架或同机房部署

当开启时,主节点会合并较小的TCP数据包从而节省带宽。默认发送时间间隔取决于Linux的内核,一般默认为40毫秒。这种配置节省了带宽但增大主从之间的延迟。适用于主从网络环境复杂或带宽紧张的场景,如跨机房部署

2、一主多从

一主多从针对读较多的场景,读由多个从节点来分担,但从节点越多,主节点同步到从节点的次数也越多,影响带宽,也影响主节点的稳定性

对于配置方式同样采取上述一主一从方式进行配置,但还存在slaveof命令之外的配置方式,在redis.conf配置文件中可通过配置replicaof的方式来配置:

replicaof <masterip> <masterport>

1)主节点配置

实际上主节点无需做过多配置,但为了安全性可以配置从节点密码

masterauth:123456

2)从节点配置

两从节点和主节点配置也类似,但从节点需要指定主节点的IP和端口

#slave1
replicaof 192.168.0.1 6379 
#slave2
replicaof 192.168.0.1 6379

启动报错:

①防火墙

#查看防火墙状态
systemctl status firewalld
#启动防火墙
systemctl start firewalld
#开放端口
firewall-cmd --add-port=6379/tcp --permanent --zone=public
#重启防火墙(修改配置后需要重启防火墙)
firewall-cmd --reload

###其他命令
#关闭防火墙
systemctl stop firewalld
#开机禁用
systemctl disable firewalld
#开机启用
systemctl enable firewalld

②redis.conf配置文件

bind 127.0.0.1改为bind 0.0.0.0,或者直接注释掉bind字段

如果Redis主节点服务器绑定了127.0.0.1,那么跨服务器IP的访问就会失败,因为从节点用服务器IP和端口访问主节点服务器的时候,主节点服务器发现本机6379端口绑在了127.0.0.1上,也就是只能本机才能访问,外部请求都会被过滤(这是Linux的网络安全策略管理的)。如果bind的IP地址是192.168.0.1,那么本机通过localhost和127.0.0.1、或者直接输入命令redis-cli登录本机Redis也就会失败了,只能加上本机IP才能访问到。所以,在开发、测试环境可以考虑bind 0.0.0.0,线上生产环境建议绑定IP地址

③主节点定义了密码,从节点在连接时没有指定主节点的密码

④主节点设置成了slave模式,登录客户端,用slaveof no one命令改回来

3、树状主从

树状主从结构,它可以使从节点不仅可以复制主节点数据,同时可以作为其他从节点的主节点继续向下层复制。通过引入复制中间层,可以有效降低主节点负载和需要传送给从节点的数据量,它解决了一主多从的缺点(主节点推送次数多压力大)

对于其搭建这里不做演示,因为一般用得少

二、哨兵机制

名词解释:

名词

说明

主节点(master)

Redis主服务,一个独立的Redis进程

从节点(slave)

Redis从服务,一个独立的Redis进程

Redis数据节点

主节点和从节点的统称

Sentinel节点

监控Redis数据节点,一个独立的Sentinel进程

Sentinel节点集合

若干Sentinel节点的组合

Redis Sentinel

Redis高可用实现方案,Sentinel节点集合和Redis数据节点集合

应用方

泛指一个或多个客户端进程或者线程

作为主节点的一个备份,一旦主节点出现故障不可达的情况,从节点可以作为后备顶上来,并且保证数据尽量不丢失(主从复制是最终一致性)Redis的主从复制模式可以将主节点的数据变化同步给从节点,这样从节点就可以起到两个作用:

  • 作为主节点的一个备份,一旦主节点出现故障不可达的情况,从节点可以作为后备顶上来,并且保证数据尽量不丢失(主从复制是最终一致性)
  • 从节点可以扩展主节点的读能力,一旦主节点不能支撑住大并发量的读操作,从节点可以在一定程度上帮助主节点分担读压力

但是主从复制也带来了以下问题:

  • 一旦主节点出现故障,需要手动将一个从节点晋升为主节点,同时需要修改应用方的主节点地址,还需要命令其他从节点去复制新的主节点,整个过程都需要人工干预
  • 主节点的写能力受到单机的限制
  • 主节点的存储能力受到单机的限制

其中第一个问题就是Redis的高可用问题,可以通过Redis Sentinel实现高可用。第二、三个问题属于Redis的分布式问题,需要使用Redis集群,这里先说Redis Sentinel

1、可用性

Redis主从复制模式下,一旦主节点出现故障不可达,则需要人工干预进行故障转移,无论对于 Redis的应用方还是运维方都带来了很大的不便:

  • 对于应用方:无法及时感知到主节点的变化,必然会造成一定的写数据丢失和读数据错误,甚至可能造成应用方服务不可用
  • 对于运维方:整个故障转移的过程是需要人工来介入的,故障转移实时性和准确性上都无法得到保障

Redis Sentinel是一个分布式架构,其中包含若干个Sentinel节点和Redis数据节点,每个Sentinel节点会对数据节点和其余Sentinel节点进行监控,当它发现节点不可达时,会对节点做下线标识。如果被标识的是主节点,它还会和其他Sentinel节点进行协商,当大多数Sentinel节点都认为主节点不可达时,它们会选举出一个Sentinel节点来完成自动故障转移的工作,同时会将这个变化实时通知给Redis应用方。整个过程完全是自动的,不需要人工来介入,所以这套方案很有效地解决了 Redis的高可用问题

注:这里的分布式是指Redis数据节点、Sentinel节点集合、客户端分布在多个物理节点的架构

Redis Sentinel与Redis主从复制模式相比只是多了若干Sentinel节点,Redis Sentinel并没有针对Redis节点做特殊处理。从逻辑架构上看,Sentinel节点集合会定期对所有节点进行监控,特别是对主节点的故障实现自动转移

下面以一个主节点、两个从节点、三个Sentinel节点(官方文档中建议为3个)组成的Redis Sentinel为例子进行说明。整个故障转移的处理逻辑有下面四个步骤:

  • 主节点出现故障,此时两个从节点与主节点失去连接,主从复制失败
  • 每个Sentinel节点通过定期监控发现主节点出现了故障
  • 多个Sentinel节点对主节点的故障达成一致,选举出其中一个节点(假如为Sentinel-3)作为领导者负责故障转移
  • Sentinel领导者节点执行了自动化故障转移,包括通知客户端,重新选择主节点,建立新的复制关系等等

2、主要功能

通过上面介绍的Redis Sentinel逻辑架构以及故障转移的处理,可以看出Redis Sentinel具有以下几个功能:

  • 监控:Sentinel节点会定期检测Redis数据节点、其余Sentinel节点是否可达
  • 通知:Sentinel节点会将故障转移的结果通知给应用方
  • 主节点故障转移:实现从节点晋升为主节点并维护后续正确的主从关系
  • 配置提供者:在Redis Sentinel结构中,客户端在初始化的时候连接的是Sentinel节点集合,从中获取主节点信息

同时看到,Redis Sentinel包含了若干个Sentinel节点,这样做也带来了两个好处:

  • 对于节点的故障判断是由多个Sentinel节点共同完成,这样可以有效地防止误判
  • Sentinel节点集合是由若干个Sentinel节点组成的,这样即使个别Sentinel节点不可用,整个 Sentinel节点集合依然是健壮的

但是Sentinel节点本身就是独立的Redis节点,只不过它们有一些特殊,它们不存储数据,只支持部分命令

3、部署

下面以三个Redis节点(一主二从)+三个Sentinel节点为例作部署,即一主两从三哨兵

1)部署Redis节点

①主节点配置

#允许所有IP连接,上面说过,生产环境为具体的IP
bind 0.0.0.0
protected-mode yes
#端口号
port 6379
#后台运行,可自行设置
daemonize yes
#指定slave只读
replica-read-only yes
#指定登录密码,为方便测试不设置
#requirepass "123456"
#指定master节点登录密码,为方便测试不设置
#masterauth "123456"

②从节点配置

基本配置和主节点相同,bind地址各自对应各自的

bind 0.0.0.0
#指定master的ip,端口信息
replicaof 192.168.0.1 6379

③启动

先启动主节点,再启动两个从节点

2)部署Sentinel节点

三个Sentinel节点的部署方法和配置是完全一致的,在Redis源码包下存在sentinel.conf文件,Sentinel节点的默认端口是 26379

①修改配置文件

#端口默认为26379
port 26379
#关闭保护模式,可以外部访问,允许远程连接
protected-mode no
#设置为后台启动
daemonize yes
#指定主节点IP地址和端口,并且指定当有两台哨兵认为主节点挂了,则对主机进行容灾切换
sentinel monitor mymaster 192.168.0.1 6379 2
#当在Redis节点中开启了requirepass,这里就需要提供密码,这里暂未设置
#sentinel auth-pass mymaster 123456
#设定5秒内没有响应,说明服务器挂了,需要将配置放在sentinel monitor master 192.168.0.1 6379 2下面
sentinel down-after-milliseconds mymaster 5000
#设定18秒内master没有活起来,就重新选举主节点,默认3分钟
sentinel failover-timeout mymaster 180000
#主备切换时,最多有多少个slave同时对新的master进行同步,这里设置为默认的1
#表示如果master重新选出来后,其它slave节点能同时并行从新master同步缓存的台数有多少个,显然该值越大,所有slave节点完成同步切换的整体速度越快,但如果此时正好有人在访问这些slave,可能造成读取失败,影响面会更广。最保底的设置为1,指同一时间,只能有一台干这件事,这样其它slave还能继续服务,但是所有slave全部完成缓存更新同步的进程将变慢
snetinel parallel-syncs mymaster 1

②启动

Sentinel节点的启动方法有两种,两种方法本质上是—样的:

  • 使用redis-sentinel命令
./redis-sentinel ../sentinel.conf
  • redis-server命令加--sentinel参数
./redis-server ../sentinel.conf --sentinel

至此Redis Sentinel已经搭建起来了,整体上还是比较容易的,但是需要注意的是Redis Sentinel中的数据节点和普通的Redis数据节点在配置上没有任何区别,只不过是添加了一些Sentinel节点对它们进行监控

3)部署建议

①Sentinel节点不应该部署在一台物理机器上

这里特意强调物理机是因为一台物理机做成了若干虚拟机或者现今比较流行的容器,它们虽然有不同的IP地址,但实际上它们都是同一台物理机,同一台物理机意味着如果这台机器有什么硬件故障,所有的虚拟机都会受到影响,为了实现Sentinel节点集合真正的高可用,请勿将Sentinel节点部署在同一台物理机器上

②部署至少三个且奇数个的Sentinel节点

三个以上是通过增加Sentinel节点的个数提高对于故障判定的准确性,因为领导者选举需要至少一半加一个节点,奇数个节点可以在满足该条件的基础上节省一个节点。这是因为:

  • 在节点数量是奇数个的情况下, 集群总能对外提供服务(即使损失了一部分节点)
  • 如果节点数量是偶数个,会存在集群不能用的可能性(脑裂成两个均等的子集群的时候)
  • 假如集群1,有三个节点,3/2=1.5,即集群想要正常对外提供服务(即Leader选举成功),至少需要两个节点是正常的。换句话说,三个节点的集群,允许有一个节点宕机
  • 假如集群2,有四个节点,4/2=2,即想要正常对外提供服务(即Leader选举成功),至少需要三个节点是正常的。换句话说,四个节点的集群,也允许有一个节点宕机

那么问题就来了, 集群1与集群2都有允许一个节点宕机的容错能力,但是集群2比集群1多了一个节点。在相同容错能力的情况下,本着节约资源的原则,集群的节点数维持奇数个更好一些

③只有一套Sentinel,还是每个主节点配置一套Sentinel

Sentinel节点集合可以只监控一个主节点,也可以监控多个主节点。 那么在实际生产环境中更偏向于哪一种部署方式,下面分别分析两种方案的优缺点:

  • 一套Sentinel:很明显这种方案在一定程度上降低了维护成本,因为只需要维护固定个数的Sentinel节点,集中对多个Redis数据节点进行管理就可以了。但是这同时也是它的缺点,如果这套Sentinel节点集合出现异常,可能会对多个Redis数据节点造成影响。还有如果监控的Redis数据节点较多,会造成Sentinel节点产生过多的网络连接,也会有一定的影响
  • 多套Sentinel:显然这种方案的优点和缺点和上面是相反的,每个Redis主节点都有自己的Sentinel节点集合,会造成资源浪费。但是优点也很明显,每套Redis Sentinel都是彼此隔离的

那么如何选择?

如果Sentinel节点集合监控的是同一个业务的多个主节点集合,那么使用方案一,否则一般建议采用方案二

三、Redis集群

哨兵机制解决了Redis高可用的问题,但是由于一主多从每个节点都存储着全部数据,随着业务不断扩大,数据量会超过节点容量,即使Redis可以配置清理策略,但也有极限,于是需要搭建Redis集群,将数据分别存储到不同的Redis上,并且可以支持横向扩展

Redis Cluster(集群)是 Redis 的分布式解决方案(Redis官方推荐),在 3.0 版本正式推出,有效地解决了Redis分布式方面的需求。当遇到单机内存、并发、流量等瓶颈时,可以采用Redis集群架构方案达到负载均衡的目的

既然它是分布式存储,也就是说每台Redis节点上存储不同的数据。把整个数据按分区规则映射到多个节点,即把数据划分到多个节点上,每个节点负责整体数据的一个子集

注意:Redis集群主要采用的是哈希槽(hash slot)的方式对数据进行分区

1、搭建集群

搭建集群有以下三种方式:

  • 依照Redis协议手工搭建,使用cluster meet、cluster addslots、cluster replicate命令
  • 5.0之前使用由Ruby语言编写的redis-trib.rb,在使用前需要安装Ruby语言环境
  • 5.0及其之后Redis摒弃了redis-trib.rb,将搭建集群的功能合并到了redis-cli

因此这里直接采用第三种方式搭建,而集群中至少应该有奇数个节点,所以至少有三个节点,官方推荐三主三从的配置方式,所以按照官方推荐搭建一个三主三从的集群

1)修改所有节点的redis.conf配置文件(找到以下配置进行修改)

bind 0.0.0.0
daemonize yes
logfile "/usr/local/redis/log/6379.log"
#是否启动集群模式
cluster-enabled yes
#指定集群节点的配置文件(打开注释即可),这个文件不需要手工编辑,它由Redis节点创建和更新,每个Redis集群节点都需要不同的集群配置文件,确保在同一系统中运行的实例没有重叠集群配置文件名
cluster-config-file nodes-6379.conf
#指定集群节点超时时间,超时则认为master宕机,随后主备切换
cluster-node-timeout 15000
#指定redis集群持久化方式(默认rdb,建议使用aof方式,此处是否修改不影响集群搭建)
appendonly yes

2)启用所有节点的配置文件

./redis-server ../redis.conf

3)创建集群

通过redis-cli的方式搭建集群也有两种:

①创建集群主从节点

在任意节点执行:

#创建集群,主节点和从节点比例为1,主从的对应关系会自动分配
./redis-cli --cluster create 192.168.0.1:6379 192.168.0.2:6379 192.168.0.3:6379 192.168.0.4:6379 192.168.0.5:6379 192.168.0.6:6379 --cluster-replicas 1

说明:--cluster-replicas 1,1表示每个主节点需要1个从节点

通过该方式创建的带有从节点的机器不能自己手动指定主节点,所以如果需要指定的话,需要自己手动指定,先创建好主节点后,再添加从节点

②创建集群并指定主从节点

  • 创建集群主节点

在任意主节点执行:

./redis-cli --cluster create 192.168.0.1:6379 192.168.0.3:6379 192.168.0.5:6379

注意:记录下每个M后如a7e948208badf171d19dbfe2d444ea7295bdbf60的字符串,在添加从节点时需要用到;如果服务器存在着防火墙,那么在进行安全设置的时候,除了Redis服务器本身的端口,比如6379要加入允许列表之外,Redis 服务在集群中还有一个叫集群总线端口,其端口为客户端连接端口加上10000,即6379+10000=16379,开放每个集群节点的客户端端口和集群总线端口才能成功创建集群

  • 添加集群从节点
./redis-cli --cluster add-node 192.168.0.2:6379 192.168.0.1:6379 --cluster-slave --cluster-master-id a7e948208badf171d19dbfe2d444ea7295bdbf60

其他从节点按照上述命令依次添加即可

说明:上述命令把192.168.0.2:6379节点加入到192.168.0.1:6379节点的集群中,并且当作node_id为a7e948208badf171d19dbfe2d444ea7295bdbf60的从节点。如果不指定--cluster-master-id会随机分配到任意一个主节点

4)集群管理(更多命令自行百度了解)

①集群状态检查

./redis-cli --cluster check 192.168.0.1:6379 --cluster-search-multiple-owners

②查看集群信息

./redis-cli --cluster info 192.168.0.1:6379

2、Redis集群的投票机制和节点分配

1)投票机制

Redis主节点之间通过ping-pong机制判断各主节点是否挂掉。如果有一半以上的主节点去ping一个主节点的时候没有得到回应,集群就认为这个主节点挂掉了,然后去连接它的从节点。如果某个主节点和所有从节点全部挂掉,集群就进入fail状态。还有就是如果有一半以上的主节点挂掉,那么集群同样进入fail状态

确认当前主节点挂掉的条件:投票过程由集群中所有主节点参与,如果半数以上主节点与当前主节点通信超时(cluster-node-timeout),则认为当前主节点挂掉

整个集群挂掉的条件:如果集群任意主节点挂掉,且当前主节点没有从节点,此时集群进入fail状态,也可以理解成集群的slot映射[0-16383]不完整时进入fail状态。如果集群超过半数以上的主节点挂掉,无论这些主节点是否有从节点,集群进入fail状态

2)节点分配

假设当前三个主节点分别是:A、B、C三个节点,它们可以是一台机器上的三个端口,也可以是三台不同的服务器。那么,采用哈希槽(hash slot)的方式来分配16384个slot(位置)的话,它们三个节点分别承担的slot区间是:

节点A覆盖0-5460

节点B覆盖5461-10922

节点C覆盖10923-16383

存储、获取数据

如果需要存入一个值,按照Redis集群哈希槽的算法: CRC16('key')%16384 = 6782。 那么就会把这个key的存储分配到节点B上。同样,当连接(A、B、C)任何一个节点想获取'key'这个key时,也会进行这样的运算,然后内部跳转到节点B上获取数据

新增、删除节点

如果需要新增一个节点D,Redis集群的做法是从各个节点的前面各拿取一部分slot到节点D上,大致如下:

节点A覆盖1365-5460

节点B覆盖6827-10922

节点C覆盖12288-16383

节点D覆盖0-1364、5461-6826、10923-12287

同样删除一个节点也是类似,移动完成后就可以删除这个节点了

四、总结

对于高可用:通过Redis主从架构+哨兵机制可以实现高可用。一主多从,任何一个实例宕机,可以进行主备切换。一般来说,对于很多项目来说已经足够,单主用来写入数据,单机几万QPS,多从用来查询数据,多个从实例可以提供每秒10w的QPS

对于高并发:通过Redis 集群可以实现高并发。多主多从,使用Redis集群之后,可以提供每秒几十万的读写并发