Redis集群主要有两种模式

  • Redis Sentinel:着眼于高可用,在master宕机时会自动将slave提升为master,继续提供服务。

  • Redis Cluster:着眼于扩展性,在单个redis内存不足时,使用Cluster进行分片存储。

主备复制

在复制的概念中,数据库分为两类,一类是主数据库(master),另一类是从数据库(slave)。主数据库可以进行读写操作,当写操作导致数据变化时会自动将数据同步给从数据库。而从数据库一般是只读的,并接受主数据库同步过来的数据。一个主数据库可以拥有多个从数据库,而一个从数据库只能拥有一个主数据库。

在 Redis 中使用复制功能非常容易,只需要在从数据库的配置文件中加入“slaveof 主数据库地址 主数据库端口”即可,主数据库无需进行任何配置。

例如下面的一个例子,主数据库为 192.168.3.204,端口7000,从数据库为 192.168.3.205,端口7001,7002,7003

  • 主数据库配置
1
2
3
4
5
port 7000
daemonize yes
bind 192.168.3.204
appendonly yes
logfile ./7000/log.txt
  • 从数据库配置,以7001为例,其他两个除了端口不一样,其余均一样
1
2
3
4
5
6
port 7001
daemonize yes
bind 192.168.3.205
slaveof 192.168.3.204 7000
appendonly yes
logfile ./7001/log.txt

先启动主数据库,其中PATHTO为配置文件所在的路径

1
redis-server PATHTO/redis.conf

再以此启动3个从服务器,启动后连接到服务器上,使用 info 命令查看服务器信息。

image-20210904173629200

默认情况下,从服务器是只读的,如果在从服务器上进行set操作,会报如下的错误。

image-20210904173842205

可以通过设置从数据库的配置文件中的 slave-read-only 为 no 以使从数据库可写,但是因为对从数据库的任何更改都不会同步给任何其

他数据库,并且一旦主数据库中更新了对应的数据就会覆盖从数据库中的改动,所以通常的场景下不应该设置从数据库可写,以免导致易

被忽略的潜在应用逻辑错误。

在主服务器上进行的对数据库的操作,会同步到从服务器上,例如在主服务器上进行

1
set test test1

在从服务器上使用 get test 操作,就能够读取到test的值。

Redis Sentinel

Sentinel(哨岗、哨兵)是Redis的高可用性(high availability)解决方案:由一个或多个Sentinel实例(instance)组成的Sentinel系统(system)可以监视任意多个主服务器,以及这些主服务器属下的所有从服务器,并在被监视的主服务器进入下线状态时,自动将下线主服务器属下的某个从服务器升级为新的主服务器,然后由新的主服务器代替已下线的主服务器继续处理命令请求。

image-20210904162747141

上图展示了一个Sentinel系统监视服务器的例子,其中:

  • 用双环图案表示的是当前的主服务器server1。

  • 用单环图案表示的是主服务器的三个从服务器server2、server3以及server4。

  • server2、server3、server4三个从服务器正在复制主服务器server1,而Sentinel系统则在监视所有四个服务器。

假设这时,主服务器server1进入下线状态,那么从服务器server2、server3、server4 对主服务器的复制操作将被中止,并且Sentinel系统会察觉到server1已下线,如下图所示(下线的服务器用虚线表示)。

image-20210904163014913

当server1的下线时长超过用户设定的下线时长上限时,Sentinel系统就会对server1执行故障转移操作:

  • 首先,Sentinel系统会挑选server1属下的其中一个从服务器,并将这个被选中的从服务器升级为新的主服务器。

  • 之后,Sentinel系统会向server1属下的所有从服务器发送新的复制指令,让它们成为新的主服务器的从服务器,当所有从服务器都开始复制新的主服务器时,故障转移操作执行完毕。

  • 另外,Sentinel还会继续监视已下线的server1,并在它重新上线时,将它设置为新的主服务器的从服务器。

举个例子,下图展示了Sentinel系统将server2升级为新的主服务器,并让服务器server3和server4成为server2的从服务器的过程。

image-20210904163442847

之后,如果server1重新上线的话,它将被Sentinel系统降级为server2的从服务器,如下图所示。

image-20210904163529376

  • sentinel.conf
1
2
3
port 27000
dir /tmp
sentinel monitor mymaster 192.168.3.204 7000 1

启动哨兵:

1
2
3
redis-sentinel /path/to/sentinel.conf
或者
redis-server /path/to/sentinel.conf --sentinel

image-20210904180551035

其中+slave 表示新发现了从数据库,可见哨兵成功地发现了三个从数据库。现在哨兵已经在监控这3个Redis实例了,这时我们将主数据库(192.168.3.204上端口7000的Redis实例)关闭(杀死进程或使用SHUTDOWN 命令)。等待指定时间后(可以配置,默认为 30 秒),哨兵会输出如下内容:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
5930:X 04 Sep 10:10:17.020 # +try-failover master mymaster 192.168.3.204 7000
5930:X 04 Sep 10:10:17.022 # +vote-for-leader abf06ff707a3d791e40e45d5a2a1349dc2a7acaf 1
5930:X 04 Sep 10:10:17.023 # +elected-leader master mymaster 192.168.3.204 7000
5930:X 04 Sep 10:10:17.023 # +failover-state-select-slave master mymaster 192.168.3.204 7000
5930:X 04 Sep 10:10:17.119 # +selected-slave slave 192.168.3.205:7003 192.168.3.205 7003 @ mymaster 192.168.3.204 7000
5930:X 04 Sep 10:10:17.119 * +failover-state-send-slaveof-noone slave 192.168.3.205:7003 192.168.3.205 7003 @ mymaster 192.168.3.204 7000
5930:X 04 Sep 10:10:17.189 * +failover-state-wait-promotion slave 192.168.3.205:7003 192.168.3.205 7003 @ mymaster 192.168.3.204 7000
5930:X 04 Sep 10:10:17.734 # +promoted-slave slave 192.168.3.205:7003 192.168.3.205 7003 @ mymaster 192.168.3.204 7000
5930:X 04 Sep 10:10:17.734 # +failover-state-reconf-slaves master mymaster 192.168.3.204 7000
5930:X 04 Sep 10:10:17.789 * +slave-reconf-sent slave 192.168.3.205:7001 192.168.3.205 7001 @ mymaster 192.168.3.204 7000
5930:X 04 Sep 10:10:18.822 * +slave-reconf-inprog slave 192.168.3.205:7001 192.168.3.205 7001 @ mymaster 192.168.3.204 7000
5930:X 04 Sep 10:13:17.765 # +failover-end-for-timeout master mymaster 192.168.3.204 7000
5930:X 04 Sep 10:13:17.765 # +failover-end master mymaster 192.168.3.204 7000
5930:X 04 Sep 10:13:17.765 * +slave-reconf-sent-be slave 192.168.3.205:7003 192.168.3.205 7003 @ mymaster 192.168.3.204 7000
5930:X 04 Sep 10:13:17.766 * +slave-reconf-sent-be slave 192.168.3.205:7001 192.168.3.205 7001 @ mymaster 192.168.3.204 7000
5930:X 04 Sep 10:13:17.766 * +slave-reconf-sent-be slave 192.168.3.205:7002 192.168.3.205 7002 @ mymaster 192.168.3.204 7000
5930:X 04 Sep 10:13:17.766 # +switch-master mymaster 192.168.3.204 7000 192.168.3.205 7003
5930:X 04 Sep 10:13:17.767 * +slave slave 192.168.3.205:7001 192.168.3.205 7001 @ mymaster 192.168.3.205 7003
5930:X 04 Sep 10:13:17.768 * +slave slave 192.168.3.205:7002 192.168.3.205 7002 @ mymaster 192.168.3.205 7003

+try-failover表示哨兵开始进行故障恢复,+failover-end表示哨兵完成故障恢复,期间涉及的内容比较复杂,包括领头哨兵的选举、备选从数据库的选择等。这里输出的

1
5930:X 04 Sep 10:13:17.766 # +switch-master mymaster 192.168.3.204 7000 192.168.3.205 7003

表示master已经从 192.168.3.204:7000 切换到了 192.168.3.205:7003。

Sentinel系统选举领头Sentinel的方法是对Raft算法的领头选举方法的实现,关于这一方法的详细信息可以观看Raft算法的作者录制的“Raft教程”视频:http://v.youku.com/v_show/id_XNjQxOTk5MTk2.html,或者Raft算法的论文。

Sentinel.conf 配置示例[5]:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# Example sentinel.conf  
  
# 哨兵sentinel实例运行的端口 默认26379  
port 26379  
  
# 哨兵sentinel的工作目录  
dir /tmp  
  
# 哨兵sentinel监控的redis主节点的 ip port   
# master-name  可以自己命名的主节点名字 只能由字母A-z、数字0-9 、这三个字符".-_"组成。  
# quorum 当这些quorum个数sentinel哨兵认为master主节点失联 那么这时 客观上认为主节点失联了  
# sentinel monitor <master-name> <ip> <redis-port> <quorum>  
sentinel monitor mymaster 127.0.0.1 6379 2  
  
# 当在Redis实例中开启了requirepass foobared 授权密码 这样所有连接Redis实例的客户端都要提供密码  
# 设置哨兵sentinel 连接主从的密码 注意必须为主从设置一样的验证密码  
# sentinel auth-pass <master-name> <password>  
sentinel auth-pass mymaster MySUPER--secret-0123passw0rd  
  
  
# 指定多少毫秒之后 主节点没有应答哨兵sentinel 此时 哨兵主观上认为主节点下线 默认30秒  
# sentinel down-after-milliseconds <master-name> <milliseconds>  
sentinel down-after-milliseconds mymaster 30000  
  
# 这个配置项指定了在发生failover主备切换时最多可以有多少个slave同时对新的master进行 同步,这个数字越小,完成failover所需的时间就越长,但是如果这个数字越大,就意味着越 多的slave因为replication而不可用。  
# 可以通过将这个值设为 1 来保证每次只有一个slave 处于不能处理命令请求的状态。  
# sentinel parallel-syncs <master-name> <numslaves>  
sentinel parallel-syncs mymaster 1  
  
# 故障转移的超时时间 failover-timeout 可以用在以下这些方面:   
#1. 同一个sentinel对同一个master两次failover之间的间隔时间。  
#2. 当一个slave从一个错误的master那里同步数据开始计算时间。直到slave被纠正为向正确的master那里同步数据时。  
#3.当想要取消一个正在进行的failover所需要的时间。    
#4.当进行failover时,配置所有slaves指向新的master所需的最大时间。不过,即使过了这个超时,slaves依然会被正确配置为指向master,但是就不按parallel-syncs所配置的规则来了  
# 默认三分钟  
# sentinel failover-timeout <master-name> <milliseconds>  
sentinel failover-timeout mymaster 180000  
  
# SCRIPTS EXECUTION  
  
#配置当某一事件发生时所需要执行的脚本,可以通过脚本来通知管理员,例如当系统运行不正常时发邮件通知相关人员。  
#对于脚本的运行结果有以下规则:  
#若脚本执行后返回1,那么该脚本稍后将会被再次执行,重复次数目前默认为10  
#若脚本执行后返回2,或者比2更高的一个返回值,脚本将不会重复执行。  
#如果脚本在执行过程中由于收到系统中断信号被终止了,则同返回值为1时的行为相同。  
#一个脚本的最大执行时间为60s,如果超过这个时间,脚本将会被一个SIGKILL信号终止,之后重新执行。  
  
#通知型脚本:当sentinel有任何警告级别的事件发生时(比如说redis实例的主观失效和客观失效等等),将会去调用这个脚本,这时这个脚本应该通过邮件,SMS等方式去通知系统管理员关于系统不正常运行的信息。调用该脚本时,将传给脚本两个参数, 
# 一个是事件的类型,  
# 一个是事件的描述。  
# 如果sentinel.conf配置文件中配置了这个脚本路径,那么必须保证这个脚本存在于这个路径,并且是可执行的,否则sentinel无法正常启动成功。  
#通知脚本  
# sentinel notification-script <master-name> <script-path>  
sentinel notification-script mymaster /var/redis/notify.sh  
  
# 客户端重新配置主节点参数脚本  
# 当一个master由于failover而发生改变时,这个脚本将会被调用,通知相关的客户端关于master地址已经发生改变的信息。  
# 以下参数将会在调用脚本时传给脚本:  
# <master-name> <role> <state> <from-ip> <from-port> <to-ip> <to-port>  
# 目前<state>总是“failover”,  
# <role>是“leader”或者“observer”中的一个。   
# 参数 from-ip, from-port, to-ip, to-port是用来和旧的master和新的master(即旧的slave)通信的  
# 这个脚本应该是通用的,能被多次调用,不是针对性的。  
# sentinel client-reconfig-script <master-name> <script-path>  
sentinel client-reconfig-script mymaster /var/redis/reconfig.sh

原文:https://blog.csdn.net/u012441222/article/details/80751390

Redis Cluster

即使使用哨兵,此时的 Redis 集群的每个数据库依然存有集群中的所有数据,从而导致集群的总数据存储量受限于可用存储内存最小的数据库节点,形成木桶效应。由于Redis中的所有数据都是基于内存存储,这一问题就尤为突出了,尤其是当使用 Redis 做持久化存储服务使用时。

Redis集群是Redis提供的分布式数据库方案,集群通过分片(sharding)来进行数据共享,并提供复制和故障转移功能。

在Redis cluster架构下,每个 Redis 要放开两个端口号,比如一个是 6379,另外一个就是 加1w 的端口号,比如 16379。16379 端口号是用来进行节点间通信的,也就是 cluster bus 的东西,cluster bus 的通信,用来进行故障检测、配置更新、故障转移授权。cluster bus 用了另外一种二进制的协议, gossip 协议,用于节点间进行高效的数据交换,占用更少的网络带宽和处理时间。

集群元数据的维护有两种方式:集中式Gossip协议。Redis cluster节点间采用gossip协议进行通信。

  • 集中式: 是将集群元数据(节点信息、故障等等)几种存储在某个节点上。集中式元数据集中存储的一个典型代表,就是大数据领域的storm。它是分布式的大数据实时计算引擎,是集中式的元数据存储的结构,底层基于zookeeper(分布式协调的中间件)对所有元数据进行存储维护。

img

  • **gossip协议:**Redis 维护集群元数据采用另一个方式, gossip协议,所有节点都持有一份元数据,不同的节点如果出现了元数据的变更,就不断将元数据发送给其它的节点,让其它节点也进行元数据的变更。

img

集中式的好处在于,元数据的读取和更新,时效性非常好,一旦元数据出现了变更,就立即更新到集中式的存储中,其它节点读取的时候就可以感知到;不好在于,所有的元数据的更新压力全部集中在一个地方,可能会导致元数据的存储有压力。

gossip 好处在于,元数据的更新比较分散,不是集中在一个地方,更新请求会陆陆续续打到所有节点上去更新,降低了压力;不好在于,元数据的更新有延时,可能导致集群中的一些操作会有一些滞后。

创建集群

在redis5.0之前,需要使用 redis-trib.rb 脚本创建集群,这是一个ruby的脚本,需要先安装ruby环境。

1
/path/to/redis-trib.rb create 192.168.3.204:7000 192.168.3.204:7001 192.168.3.204:7002

在Redis Cluster 在5.0之后取消了ruby脚本 redis-trib.rb的支持(手动命令行添加集群的方式不变),集合到redis-cli里,避免了再安装ruby的相关环境。直接使用redis-clit的参数–cluster 来取代。

1
redis-cli --cluster create 192.168.3.204:7000 192.168.3.204:7001 192.168.3.204:7002

可以在创建时指定–replicas参数,表示每个master有几个复制,如果有N个复制,则最少需要3*(N+1)个节点,例如没有复制,最少需要3个节点,1个复制,最少需要6个节点。

1
redis-cli --cluster create 192.168.3.204:7000 192.168.3.204:7001 192.168.3.204:7002 192.168.3.204:7003 192.168.3.204:7004 192.168.3.204:7005 --cluster-replicas 1

命令执行后,会输出类似这样的内容:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
[root@centos7 src]# ./redis-cli --cluster create 192.168.3.204:7000 192.168.3.204:7001 192.168.3.204:7002
>>> Performing hash slots allocation on 3 nodes...
Master[0] -> Slots 0 - 5460
Master[1] -> Slots 5461 - 10922
Master[2] -> Slots 10923 - 16383
M: e0b1b0bd22381b1e72cc185df7802862a13da096 192.168.3.204:7000
   slots:[0-5460] (5461 slots) master
M: 919ebf29a0c8e105091415977c47d3a6d28f34e2 192.168.3.204:7001
   slots:[5461-10922] (5462 slots) master
M: 8db1dbea8826fa7256491ae527038bf9cdf01082 192.168.3.204:7002
   slots:[10923-16383] (5461 slots) master
Can I set the above configuration? (type 'yes' to accept): yes
>>> Nodes configuration updated
>>> Assign a different config epoch to each node
>>> Sending CLUSTER MEET messages to join the cluster
Waiting for the cluster to join
..
>>> Performing Cluster Check (using node 192.168.3.204:7000)
M: e0b1b0bd22381b1e72cc185df7802862a13da096 192.168.3.204:7000
   slots:[0-5460] (5461 slots) master
M: 8db1dbea8826fa7256491ae527038bf9cdf01082 192.168.3.204:7002
   slots:[10923-16383] (5461 slots) master
M: 919ebf29a0c8e105091415977c47d3a6d28f34e2 192.168.3.204:7001
   slots:[5461-10922] (5462 slots) master
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.

这表示集群已经创建完成了。分配完成后,会为每个主数据库分配插槽,分配插槽的过程其实就是分配哪些键归哪些节点负责。使用 Redis 命令行客户端连接任

意一个节点执行 CLUSTER NODES 命令可以获得集群中的所有节点信息。

1
2
3
4
5
D:\DevTools\redis>redis-cli -h 192.168.3.204 -p 7001
192.168.3.204:7001> cluster nodes
919ebf29a0c8e105091415977c47d3a6d28f34e2 192.168.3.204:7001@17001 myself,master - 0 1630754335000 2 connected 5461-10922
e0b1b0bd22381b1e72cc185df7802862a13da096 192.168.3.204:7000@17000 master - 0 1630754338776 1 connected 0-5460
8db1dbea8826fa7256491ae527038bf9cdf01082 192.168.3.204:7002@17002 master - 0 1630754338000 3 connected 10923-16383

节点增加

  • 添加主节点
1
redis-cli --cluster add-node 192.168.3.204:7003 192.168.3.204:7000 

说明:为一个指定集群添加节点,需要先连到该集群的任意一个节点IP(192.168.3.204:7000),再把新节点加入。该2个参数的顺序有要求:新加入的节点放前

  • 添加从节点
1
redis-cli --cluster add-node 192.168.3.204:7003 192.168.3.204:7000 --cluster-slave --cluster-master-id 919ebf29a0c8e105091415977c47d3a6d28f34e2

说明:把7003节点加入到7000节点的集群中,并且当做node_id为 919ebf29a0c8e105091415977c47d3a6d28f34e2 的从节点。如果不指定 –cluster-master-id 会随机分配到任意一个主节点。

节点删除

1
redis-cli --cluster del-node 192.168.3.204:7002 8db1dbea8826fa7256491ae527038bf9cdf01082

说明:指定IP、端口和node_id 来删除一个节点,从节点可以直接删除,有slot分配的主节点不能直接删除。

注意:当被删除掉的节点重新起来之后不能自动加入集群,但其和主的复制还是正常的,也可以通过该节点看到集群信息(通过其他正常节点已经看不到该被del-node节点的信息)。如果想要再次加入集群,则需要先在该节点执行cluster reset,再用add-node进行添加,进行增量同步复制。

分布式寻址算法

hash 算法(大量缓存重建)

来了一个 key,首先计算hash值,然后对节点数取模。然后打在不同的master节点上。一旦某一个master节点宕机,所有请求过来,都会基于最新的剩余master节点数去取模,尝试去取数据。这会导致大部分的请求过来,全部无法拿到有效的缓存,导致大量的流量涌入数据库。

img

一致性hash算法(自动缓存迁移)+ 虚拟节点(自动负载均衡)

一致性hash算法将整个hash值空间组织成一个虚拟的圆环,整个空间按顺时针方向组织,下一步将各个master节点(使用服务器的 ip 或主机名)进行hash。这样就能确定每个节点在其哈希环上的位置。

来了一个key,首先计算hash值,并确定此数据在环上的位置,从此位置沿环顺时针“行走”,遇到的第一个master节点就是key所在位置。

在一致性哈希算法中,如果一个节点挂了,受影响的数据仅仅是此节点到环空间前一个节点(沿着逆时针方向行走遇到的第一个节点)之间的数据,其它不受影响。增加一个节点也同理。

然而,一致性哈希算法在节点太少时,容易因为节点分布不均匀而造成缓存热点的问题。为了解决这种热点问题,一致性hash算法引入了虚拟节点机制,即对每一个节点计算多个hash,每个计算结果位置都放置一个虚拟节点。这样就实现了数据的均匀分布,负载均衡。

img

Redis cluster 的hash slot 算法

Redis cluster有固定的 16384 个hash slot,对每个key计算 CRC16 值,然后对 16384 取模,可以获取key对应的hash slot。

Redis cluster 中每个 master 都会持有部分slot,比如有 3 个master,那么可能每个 master持有5000多个hash slot。hash slot让node的增加和移除很简单,增加一个 master,就将其他master的hash slot移动部分过去,减少一个master,就将它的hash slot移动到其他master上去。移动hash slot的成本是非常低的。客户端的api,可以对指定的数据,让他们走同一个hash slot,通过hash tag来实现。

任何一台机器宕机,另外两个节点,不影响的。因为key找的是hash slot,不是机器。

img

生产环境部署示例

Redis cluster,10台机器,5台机器部署了redis主实例,另外5台机器部署了redis的从实例,每个主实例挂了一个从实例,5个节点对外提供读写服务,每个节点的读写高峰QPS可能可以达到每秒5万,5台机器最多是25万读写请求/s。

1. 机器是什么配置?

32G内存+ 8核CPU + 1T磁盘,但是分配给redis进程的是10 内存,一般线上生产环境,redis的内存尽量不要超过10g,超过10g可能会有问题。

5台机器对外提供读写,一共有50g内存。

因为每个主实例都挂了一个从实例,所以是高可用的,任何一个主实例宕机,都会自动故障迁移,redis从实例会自动变成主实例继续提供读写服务。

2. 你往内存里写的是什么数据?每条数据的大小是多少?

商品数据,每条数据是10kb。100条数据是1mb,10万条数据是1g。常驻内存的是200 万条商品数据,占用内存是20g,仅仅不到总内存的50%。目前高峰期每秒就是3500左右的请求量。

其实大型的公司,会有基础架构的team负责缓存集群的运维。