redis学习篇主从&哨兵&集群架构详解
Posted Java学习者柯十一
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了redis学习篇主从&哨兵&集群架构详解相关的知识,希望对你有一定的参考价值。
一、Redis主从架构
1.1 redis主从架构搭建
1、复制一份redis.conf文件
2、将相关配置修改为如下值:
port 6380
pidfile /var/run/redis_6380.pid # 把pid进程号写入pidfile配置的文件
logfile "6380.log"
dir /usr/local/redis-5.0.3/data/6380 # 指定数据存放目录
# 需要注释掉bind
# bind 127.0.0.1(bind绑定的是自己机器网卡的ip,如果有多块网卡可以配多个ip,代表允许客户端通过机器的哪些网卡ip去访问,内网一般可以不配置bind,注释掉即可)
3、配置主从复制
replicaof 192.168.0.60 6379 # 从本机6379的redis实例复制数据,Redis 5.0之前使用slaveof
replica-read-only yes # 配置从节点只读
4、启动从节点
redis-server redis.conf
5、连接从节点
redis-cli -p 6380
6、测试在6379实例上写数据,6380实例是否能及时同步新修改数据
7、可以自己再配置一个6381的从节点
1.2 主从同步数据原理
主从库模式一旦采用了读写分离,所有数据的写操作只会在主库上进行,不用协调三个实例。
主库有了最新的数据后,会同步给从库,这样,主从库的数据就是一致的。
那么主从库同步是如何完成的呢?主库数据是一次性传给从库,还是分批同步?正常运行中又怎么同步呢?要是主从库间的网络断连了,重新连接后数据还能保持一致吗?
同步分为三种情况:
- 第一次主从库全量复制
- 主从正常运行期间的同步
- 主从库间网络断开重连同步
在 redis2.8
版本之前的同步流程
-
如果你为master配置了一个slave,不管这个slave是否是第一次连接上Master,它都会发送一个
PSYNC
命令给master请求复制数据。 -
master收到PSYNC命令后,会在后台进行数据持久化通过
bgsave
生成最新的rdb快照文件 -
持久化期间,master会继续接收客户端的请求,它会把这些可能修改数据集的请求
缓存在内存
中 -
当持久化进行完毕以后,master会把这份rdb文件数据集发送给slave,slave会把接收到的数据进行持久化生成rdb,然后再加载到内存中。
-
然后,master再将之前缓存在内存中的命令通过二进制的方式发送给slave,slave会重新执行一遍。
-
当master与slave之间的连接由于某些原因而断开时,slave能够自动重连Master,如果master收到了多个slave并发连接请求,它只会进行
一次持久化
,而不是一个连接一次,然后再把这一份持久化的数据发送给多个并发连接的slave。
流程图
在断线后重复制的情况下,在 2.8 版本之前,会再次执行同步(sync 命令)和命令传播。
如果说,在断线期间,主服务器(已有上万键值对)只执行了几个写命令,为了让从服务器弥补这几个命令,却要重新执行 sync 来生成新的 rdb 文件,这也是非常低效的。
为了解决这个问题,2.8 开始就使用 psync 命令来代替 sync 命令去执行同步操作,从此就有了全量同步和增量同步两种方式来保障主从节点的数据一致性。
全量同步
先从主从库间第一次同步说起吧。
主从库第一次复制过程大体可以分为 3 个阶段:连接建立阶段(即准备阶段)、主库同步数据到从库阶段、发送同步期间新写命令到从库阶段;
直接上图,从整体上有一个全局观的感知,后面具体介绍。
第一阶段 (建立连接)
该阶段的主要作用是在主从节点之间建立连接,为数据全量同步做好准备。从库会和主库建立连接,从库执行 replicaof 并发送 psync 命令并告诉主库即将进行同步,主库确认回复后,主从库间就开始同步了。
从库怎么知道主库信息并建立连接的呢?
- 在从节点的配置文件中的 replicaof 配置项中配置了主节点的 IP 和 port 后,从节点就知道自己要和那个主节点进行连接了。
- 从节点内部维护了两个字段,masterhost 和 masterport,用于存储主节点的 IP 和 port 信息。
从库执行 replicaof 并发送 psync 命令,表示要执行数据同步,主库收到命令后根据参数启动复制。命令包含了主库的 runID 和 复制进度 offset 两个参数。
每个 Redis 实例启动都会自动生成一个 唯一标识 ID,第一次主从复制的时,从库不知道主库 runID是多少,所以第一次复制设置为 -1,表示第一次复制,记录复制进度偏移量。
主库收到 psync 命令后,会用 FULLRESYNC 响应命令带上两个参数:主库 runID 和主库目前的复制进度 offset,返回给从库。从库收到响应后,会记录下这两个参数。
FULLRESYNC 响应表示第一次复制采用的全量复制,也就是说,主库会把当前所有的数据都复制给从库。
第二阶段 (主库同步数据给从库)
master 执行 bgsave命令生成 RDB 文件,并将文件发送给从库,同时主库为每一个 slave 开辟一块 replication buffer 缓冲区记录从生成 RDB 文件开始收到的所有写命令。
从库收到 RDB 文件后保存到磁盘,并清空当前数据库的数据,再加载 RDB 文件数据到内存中。
第三阶段 (发送新写命令到从库)
从节点加载 RDB 完成后,主节点将 replication buffer 缓冲区的数据发送到从节点(二进制执行命令),Slave 接收并执行,从节点同步至主节点相同的状态。
主库将数据同步到从库过程中,可以正常接受请求么?
主库不会被阻塞,在生成 RDB 文件之后的写操作并没有记录到刚刚的 RDB 文件中,为了保证主从库数据的一致性,所以主库会在内存中使用一个叫 replication buffer 记录 RDB 文件生成后的所有写操作。
replication buffer 到底是什么玩意?
一个在 master 端上创建的缓冲区,存放的数据是下面三个时间内所有的 master 数据写操作。
1)master 执行 bgsave 产生 RDB 的期间的写操作;
2)master 发送 rdb 到 slave 网络传输期间的写操作;
3)slave load rdb 文件把数据恢复到内存的期间的写操作。
Redis 和客户端通信也好,和从库通信也好,Redis 都分配一个内存 buffer 进行数据交互,客户端就是一个 client,从库也是一个 client,我们每个 client 连上 Redis 后,Redis 都会分配一个专有 client buffer,所有数据交互都是通过这个 buffer 进行的。
Master 先把数据写到这个 buffer 中,然后再通过网络发送出去,这样就完成了数据交互。
不管是主从在增量同步还是全量同步时,master 会为其分配一个 buffer ,只不过这个 buffer 专门用来传播写命令到从库,保证主从数据一致,我们通常把它叫做 replication buffer。
replication buffer 太小会引发的问题
replication buffer 由 client-output-buffer-limit slave 设置,当这个值太小会导致主从复制连接断开。
1)当 master-slave 复制连接断开,master 会释放连接相关的数据。replication buffer 中的数据也就丢失了,此时主从之间重新开始复制过程。
2)还有个更严重的问题,主从复制连接断开,导致主从上出现重新执行 bgsave 和 rdb 重传操作无限循环。
当主节点数据量较大,或者主从节点之间网络延迟较大时,可能导致该缓冲区的大小超过了限制,此时主节点会断开与从节点之间的连接;
这种情况可能(具体看offset的位置在缓冲区是否被覆盖)
引起全量复制 -> replication buffer 溢出导致连接中断 -> 重连 -> 全量复制 -> replication buffer 缓冲区溢出导致连接中断……的循环。
具体详情:[top redis headaches for devops – replication buffer] 因而推荐把 replication buffer 的 hard/soft limit 设置成 512M。
config set client-output-buffer-limit “slave 536870912 536870912 0”
主从库复制为何不使用 AOF 呢?相比 RDB 来说,丢失的数据更少。
- RDB 文件是二进制文件,网络传输 RDB 和写入磁盘的 IO 效率都要比 AOF 高。
- 从库进行数据恢复的时候,RDB 的恢复效率也要高于 AOF。
主从库间的网络断了咋办?断开后要重新全量复制么?
在 Redis 2.8 之前,如果主从库在命令传播时出现了网络闪断,那么,从库就会和主库重新进行一次全量复制,开销非常大。
从 Redis 2.8 开始,网络断了之后,主从库会采用增量复制的方式继续同步。
增量同步
用于网络中断等情况后的复制,只将中断期间主节点执行的写命令发送给从节点,与全量复制相比更加高效。
-
断开重连增量复制的实现奥秘就是
repl_backlog_buffer
缓冲区,不管在什么时候 master 都会将写指令操作记录在 repl_backlog_buffer 中 -
因为内存有限, repl_backlog_buffer 是一个定长的环形数组,如果数组内容满了,就会从头开始覆盖前面的内容。
-
master 使用
master_repl_offset
记录自己写到的位置偏移量,slave 则使用slave_repl_offset
记录已经读取到的偏移量。 -
master 收到写操作,偏移量则会增加。从库持续执行同步的写指令后,在 repl_backlog_buffer 的已复制的偏移量 slave_repl_offset 也在不断增加。
-
正常情况下,这两个偏移量基本相等。在网络断连阶段,主库可能会收到新的写操作命令,所以 master_repl_offset会大于 slave_repl_offset。
当主从断开重连后,slave 会先发送 psync
命令给 master,同时将自己的 runID
和 slave_repl_offset
发送给 master。
master 只需要把 master_repl_offset与 slave_repl_offset之间的命令同步给从库即可。
增量复制执行流程如下图:
repl_backlog_buffer 太小的话从库还没读取到就被 Master 的新写操作覆盖了咋办?
我们要想办法避免这个情况,一旦被覆盖就会执行全量复制。我们可以调整 repl_backlog_size 这个参数用于控制缓冲区大小。计算公式:
repl_backlog_buffer = second * write_size_per_second
second:从服务器断开重连主服务器所需的平均时间;
write_size_per_second:master 平均每秒产生的命令数据量大小(写命令和数据大小总和);
例如,如果主服务器平均每秒产生 1 MB 的写数据,而从服务器断线之后平均要 5 秒才能重新连接上主服务器,那么复制积压缓冲区的大小就不能低于 5 MB。
为了安全起见,可以将复制积压缓冲区的大小设为2 * second * write_size_per_second,这样可以保证绝大部分断线情况都能用部分重同步来处理。
基于长连接的命令传播
完成全量同步后,正常运行过程如何同步呢?
当主从库完成了全量复制,它们之间就会一直维护一个网络连接,主库会通过这个连接将后续陆续收到的命令操作再同步给从库,这个过程也称为基于长连接的命令传播,使用长连接的目的就是避免频繁建立连接导致的开销。
在命令传播阶段,除了发送写命令,主从节点还维持着心跳机制:PING 和 REPLCONF ACK。
主->从:PING
每隔指定的时间,主节点会向从节点发送 PING 命令,这个 PING 命令的作用,主要是为了让从节点进行超时判断。
从->主:REPLCONF ACK
在命令传播阶段,从服务器默认会以每秒一次的频率,向主服务器发送命令:
REPLCONF ACK <replication_offset>
其中 replication_offset 是从服务器当前的复制偏移量。发送 REPLCONF ACK 命令对于主从服务器d 的作用:
- 检测主从服务器的网络连接状态。
- 检测命令丢失, 从节点发送了自身的 slave_replication_offset,主节点会用自己的 master_replication_offset 对比,如果从节点数据缺失,主节点会从 repl_backlog_buffer 缓冲区中找到并推送缺失的数据。
注意:offset 和 repl_backlog_buffer 缓冲区,不仅可以用于部分复制,也可以用于处理命令丢失等情形;区别在于前者是在断线重连后进行的,而后者是在主从节点没有断线的情况下进行的。
如何确定执行全量同步还是部分同步?
在 Redis 2.8 及以后,从节点可以发送 psync 命令请求同步数据,此时根据主从节点当前状态的不同,同步方式可能是全量复制或部分复制。本文以 Redis 2.8 及之后的版本为例。
关键就是 psync的执行
-
从节点根据当前状态,发送 psync命令给 master:
- 如果从节点从未执行过 replicaof ,则从节点发送
psync ? -1
,向主节点发送全量复制请求; - 如果从节点之前执行过 replicaof 则发送
psync <runID> <offset>
, runID 是上次复制保存的主节点 runID,offset 是上次复制截至时从节点保存的复制偏移量。
- 如果从节点从未执行过 replicaof ,则从节点发送
-
主节点根据接受到的psync命令和当前服务器状态,决定执行全量复制还是部分复制:
- runID 与从节点发送的 runID 相同,且从节点发送的 slave_repl_offset之后的数据在 repl_backlog_buffer缓冲区中都存在,则回复
CONTINUE
,表示将进行部分复制,从节点等待主节点发送其缺少的数据即可; - runID 与从节点发送的 runID 不同,或者从节点发送的 slave_repl_offset 之后的数据已不在主节点的 repl_backlog_buffer缓冲区中 (在队列中被挤出了),则回复从节点
FULLRESYNC <runid> <offset>
表示要进行全量复制 - 其中 runID 表示主节点当前的 runID,offset 表示主节点当前的 offset,从节点保存这两个值,以备使用。
- runID 与从节点发送的 runID 相同,且从节点发送的 slave_repl_offset之后的数据在 repl_backlog_buffer缓冲区中都存在,则回复
-
一个从库如果和主库断连时间过长,造成它在主库 repl_backlog_buffer 的 slave_repl_offset 位置上的数据已经被覆盖掉了,此时从库和主库间将进行全量复制。
总结
每个从库会记录自己的 slave_repl_offset,每个从库的复制进度也不一定相同。
在和主库重连进行恢复时,从库会通过 psync 命令把自己记录的 slave_repl_offset发给主库,主库会根据从库各自的复制进度,来决定这个从库可以进行增量复制,还是全量复制。
replication buffer 和 repl_backlog
- replication buffer 对应于每个 slave,通过 config set client-output-buffer-limit slave设置。
- repl_backlog_buffer是一个环形缓冲区,整个 master 进程中只会存在一个,所有的 slave 公用。
总的来说,replication buffer 是主从库在进行全量复制时,主库上用于和从库连接的客户端的 buffer,而 repl_backlog_buffer 是为了支持从库增量复制,主库上用于持续保存写操作的一块专用 buffer。
- repl_backlog_buffer是一块专用 buffer,在 Redis 服务器启动后,开始一直接收写操作命令,这是所有从库共享的。主库和从库会各自记录自己的复制进度,所以,不同的从库在进行恢复时,会把自己的复制进度(slave_repl_offset)发给主库,主库就可以和它独立同步。
如图所示:
主从复制的场景下,从节点会删除过期数据么?
为了主从节点的数据一致性,从节点不会主动删除数据。我们知道 Redis 有两种删除策略:
- 惰性删除:当客户端查询对应的数据时,Redis 判断该数据是否过期,过期则删除。
- 定期删除:Redis 通过定时任务删除过期数据。
那客户端通过从节点读取数据会不会读取到过期数据?
Redis 3.2 开始,通过从节点读取数据时,先判断数据是否已过期。如果过期则不返回客户端,并且删除数据。
总结
- 主从复制的作用:AOF 和 RDB 二进制文件保证了宕机快速恢复数据,尽可能的防止丢失数据。但是宕机后依然无法提供服务,所以便演化出主从架构、读写分离。
- 主从复制虽然解决或缓解了数据冗余、故障恢复、读负载均衡等问题,但其缺陷仍很明显:故障恢复无法自动化;写操作无法负载均衡;存储能力受到单机的限制;这些问题的解决,需要哨兵和集群的帮助
注意:如果有很多从节点,为了缓解主从复制风暴(多个从节点同时复制主节点导致主节点 压力过大
),可以做如下架构,让部分从节点与从节点(与主节点同步)同步数据
管道(Pipeline)
-
客户端可以一次性发送多个请求而不用等待服务器的响应,待所有命令都发送完后再一次性读取服务的响应,这样可以极大的降低多条命令执行的网络传输开销,管道执行多条命令的网络开销实际上只相当于一次命令执行的网络开销。
-
需要注意到是用pipeline方式打包命令发送,redis必须在处理完所有命令前先缓存起所有命令的处理结果。
-
打包的命令越多,缓存消耗内存也越多。所以并不是打包的命令越多越好。
-
pipeline中发送的每个command都会被server立即执行,如果执行失败,将会在此后的响应中得到信息;
-
也就是pipeline并不是表达
“所有command都一起成功”
的语义,管道中前面命令失败,后面命令不会有影响,继续执行。
Pipeline pl = jedis.pipelined();
for (int i = 0; i < 10; i++)
pl.incr("pipelineKey");
pl.set("zhuge" + i, "zhuge");
//模拟管道报错
// pl.setbit("zhuge", -1, true);
List<Object> results = pl.syncAndReturnAll();
System.out.println(results);
二、Redis哨兵高可用架构
-
sentinel哨兵是特殊的redis服务,不提供读写服务,主要用来监控redis实例节点。
-
sentinel实时监视主从集群,能实时知道哪个节点是主节点,哪些是从节点,哨兵架构下client端
第一次
会访问sentinel,sentinel会将master信息推送给客户端,后续就直接访问redis的主节点,不会每次都通过sentinel代理访问redis的主节点 -
当redis的主节点挂了,sentinel会在从节点中选取一个主节点 ,并且将新的redis主节点推送给client端(这里面redis的client端一般都实现了订阅功能,订阅sentinel发布的节点变动消息)
2.1 哨兵leader选举流程
-
当一个master服务器被某sentinel视为下线状态后,该sentinel会与其他sentinel协商选出sentinel的leader进行故障转移工作。
-
每个发现master服务器进入下线的sentinel都可以要求其他sentinel选自己为sentinel的leader,选举是先到先得。同时每个sentinel每次选举都会自增配置纪元(选举周期),每个纪元中只会选择一个sentinel的leader。
-
如果所有超过一半的sentinel选举某sentinel作为leader。之后该sentinel进行故障转移操作,从存活的slave中选举出新的master,这个选举过程跟集群的master选举很类似。
-
哨兵集群只有一个哨兵节点,redis的主从也能正常运行以及选举master,如果master挂了,那唯一的那个哨兵节点就是哨兵leader了,可以正常选举新master。
-
不过为了高可用一般都推荐至少部署三个哨兵节点。为什么推荐奇数个哨兵节点原理跟集群奇数个master节点类似。
2.2 redis哨兵架构搭建步骤
1、复制一份sentinel.conf文件
cp sentinel.conf sentinel-26379.conf
2、将相关配置修改为如下值:
port 26379
daemonize yes
pidfile "/var/run/redis-sentinel-26379.pid"
logfile "26379.log"
dir "/usr/local/redis-5.0.3/data"
# sentinel monitor <master-redis-name> <master-redis-ip> <master-redis-port> <quorum>
# quorum是一个数字,指明当有多少个sentinel认为一个master失效时(值一般为:sentinel总数/2 + 1),master才算真正失效
sentinel monitor mymaster 192.168.0.60 6379 2 # mymaster这个名字随便取,客户端访问时会用到
3、启动sentinel哨兵实例
src/redis-sentinel sentinel-26379.conf
4、查看sentinel的info信息
src/redis-cli -p 26379
127.0.0.1:26379>info
可以看到Sentinel的info里已经识别出了redis的主从
5、可以自己再配置两个sentinel,端口26380和26381,注意上述配置文件里的对应数字都要修改
6、sentinel集群都启动完毕后,会将哨兵集群的元数据信息写入所有sentinel的配置文件里去(追加在文件的最下面),我们查看下如下配置文件sentinel-26379.conf,如下所示:
sentinel known-replica mymaster 192.168.0.60 6380 #代表redis主节点的从节点信息
sentinel known-replica mymaster 192.168.0.60 6381 #代表redis主节点的从节点信息
sentinel known-sentinel mymaster 192.168.0.60 26380 52d0a5d70c1f90475b4fc03b6ce7c3c56935760f #代表感知到的其它哨兵节点
sentinel known-sentinel mymaster 192.168.0.60 26381 e9f530d3882f8043f76ebb8e1686438ba8bd5ca6 #代表感知到的其它哨兵节点
7、当redis主节点如果挂了,哨兵集群会重新选举出新的redis主节点,同时会修改所有sentinel节点配置文件的集群元数据信息,比如6379的redis如果挂了,假设选举出的新主节点是6380,则sentinel文件里的集群元数据信息会变成如下所示:
sentinel known-replica mymaster 192.168.0.60 6379 #代表主节点的从节点信息
sentinel known-replica mymaster 192.168.0.60 6381 #代表主节点的从节点信息
sentinel known-sentinel mymaster 192.168.0.60 26380 52d0a5d70c1f90475b4fc03b6ce7c3c56935760f #代表感知到的其它哨兵节点
sentinel known-sentinel mymaster 192.168.0.60 26381 e9f530d3882f8043f76ebb8e1686438ba8bd5ca6 #代表感知到的其它哨兵节点
8、同时还会修改sentinel文件里之前配置的mymaster对应的6379端口,改为6380
sentinel monitor mymaster 192.168.0.60 6380 2
9、当6379的redis实例再次启动时,哨兵集群根据集群元数据信息就可以将6379端口的redis节点作为从节点加入集群
2.3 整合spring boot 测试
哨兵的Spring Boot整合Redis连接代码见示例项目:redis-sentinel-cluster
- 引入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-pool2</artifactId>
</dependency>
- springboot项目核心配置
server:
port: 8080
spring:
redis:
database: 0
timeout: 3000
sentinel: #哨兵模式
master: mymaster #主服务器所在集群名称
nodes: 192.168.0.60:26379,192.168.0.60:26380,192.168.0.60:26381
lettuce:
pool:
max-idle: 50
min-idle: 10
max-active: 100
max-wait: 1000
- 测试代码
@RestController
public class IndexController
private static final Logger logger = LoggerFactory.getLogger(IndexController.class);
@Autowired
private StringRedisTemplate stringRedisTemplate;
/**
* 测试节点挂了哨兵重新选举新的master节点,客户端是否能动态感知到
* 新的master选举出来后,哨兵会把消息发布出去,客户端实际上是实现了一个消息监听机制,
* 当哨兵把新master的消息发布出去,客户端会立马感知到新master的信息,从而动态切换访问的masterip
*
* @throws InterruptedException
*/
@RequestMapping("/test_sentinel")
public void testSentinel() throws InterruptedException
int i = 1;
while (true)
try
stringRedisTemplate.opsForValue().set("zhuge"+i, i+"");
System.out.println("设置key:"+ "zhuge" + i);
i++;
Thread.sleep(1000);
catch (Exception e)
logger.error("错误:", e);
三、Redis高可用集群架构
3.1 Redis集群原理分析
-
Redis Cluster 将所有数据划分为
16384 个 slots(槽位)
,每个节点负责其中一部分槽位。槽位的信息存储于每个节点中。 -
当 Redis Cluster 的客户端来连接集群时,它也会得到一份集群的槽位配置信息并将其缓存在客户端本地
-
这样当客户端要查找某个 key 时,会对 key 值使用 crc16 算法进行hash 得到一个整数值,然后用这个整数值对 16384 进行取模来得到具体槽位可以直接定位到目标节点。
-
同时因为槽位的信息可能会存在客户端与服务器不一致的情况,还需要纠正机制来实现槽位信息的校验调整。
3.2 槽位定位算法
Cluster 默认会对 key 值使用 crc16 算法进行 hash 得到一个整数值,然后用这个整数值对 16384 进行取模来得到具体槽位。
HASH_SLOT = CRC16(key) mod 16384
3.3 跳转重定位
-
当客户端向一个错误的节点发出了指令,该节点会发现指令的 key 所在的槽位并不归自己管理,这时它会向客户端发送一个特殊的跳转指令携带目标操作的节点地址,告诉客户端去连这个节点去获取数据。
-
客户端收到指令后除了跳转到正确的节点上去操作,还会同步更新纠正本地的槽位映射表缓存,后续所有 key 将使用新的槽位映射表。
3.4 Redis集群节点间的通信机制
redis cluster节点间采取gossip协议进行通信
集中式
-
优点在于元数据的更新和读取,时效性非常好,一旦元数据出现变更立即就会更新到集中式的存储中,其他节点读取的时候立即就可以立即感知到;
-
不足在于所有的元数据的更新压力全部集中在一个地方,可能导致元数据的存储压力。 可以借助zookeeper集中式存储元数据。
gossip
gossip协议包含多种消息,包括ping,pong,meet,fail等等。
meet
:某个节点发送meet给新加入的节点,让新节点加入集群中,然后新节点就会开始与其他节点进行通信;
ping
:每个节点都会频繁给其他节点发送ping,其中包含自己的状态还有自己维护的集群元数据,互相通过ping交换元数据(类似自己感知到的集群节点增加和移除,hash slot信息等); 同时其他节点接收到ping消息之后返回pong消息
pong
: 对ping和meet消息的返回,包含自己的状态和其他信息,也可以用于信息广播和更新;
fail
: 某个节点判断另一个节点fail之后,就发送fail给其他节点,通知其他节点,指定的节点宕机了。
-
gossip协议的优点在于元数据的更新比较分散,不是集中在一个地方,更新请求会陆陆续续,打到所有节点上去更新,有一定的延时,降低了压力;
-
缺点在于元数据更新有延时可能导致集群的一些操作会有一些滞后。
gossip通信的10000端口
-
每个节点都有一个专门用于节点间gossip通信的端口,就是自己提供服务的端口号+10000,比如7001,那么用于节点间通信的就是17001端口。
-
每个节点
每隔一段时间
都会往另外几个节点发送ping消息,同时其他节点接收到ping消息之后返回pong消息。
3.5 网络抖动
-
真实世界的机房网络往往并不是风平浪静的,它们经常会发生各种各样的小问题。比如网络抖动就是非常常见的一种现象,突然之间部分连接变得不可访问,然后很快又恢复正常。
-
为解决这种问题,Redis Cluster 提供了一种选项
cluster-node-timeout
,表示当某个节点持续 timeout 的时间失联时,才可以认定该节点出现故障,需要进行主从切换。 -
如果没有这个选项或者配置的时间太短的话,稍微有点网络抖动就会导致主从频繁切换 (数据的重新复制)。
3.6 Redis集群选举原理分析
当slave发现自己的master变为FAIL状态时,便尝试进行Failover,以期成为新的master。由于挂掉的master可能会有多个slave,从而存在多个slave竞争成为master节点的过程 其过程如下:
-
slave发现自己的master变为FAIL,会发送fail给其他节点,通知其他节点master宕机了
-
将自己记录的集群currentEpoch加1,并广播
FAILOVER_AUTH_REQUEST
信息 -
其他节点收到该信息,只有master响应,判断请求者的合法性,并发送
FAILOVER_AUTH_ACK
,对每一个epoch(一轮选举周期)只会对先来的请求发送一次ack -
尝试failover的slave收集master返回的FAILOVER_AUTH_ACK
-
slave收到超过集群主节点的
半数
的ack后变成新Master(这里解释了集群为什么至少需要三个主节点,如果只有两个,当其中一个挂了,只剩一个主节点是不能选举成功的) -
然后选举成master的slave会广播Pong消息通知其他集群节点。
注意
:如果当前这轮的选举周期结束时所有的slave的收到的ack次数相同,则接着currentEpoch加1,再走一次选举
为了规避反复的重新选举的情况,从节点并不是在主节点一进入 FAIL 状态就马上尝试发起选举,而是有一定 延迟
,一定的延迟确保我们等待FAIL状态在集群中传播,slave如果立即尝试选举,其它masters或许尚未意识到FAIL状态,可能会拒绝投票
延迟计算公式
DELAY = 500ms + random(0 ~ 500ms) + SLAVE_RANK * 1000ms
SLAVE_RANK表示此slave已经从master复制数据的总量的rank。Rank越小代表已复制的数据越新。这种方式下,持有最新数据的slave将会首先发起选举(理论上)。
3.7 集群脑裂数据丢失问题
-
redis集群没有过半机制会有脑裂问题,网络分区导致脑裂后多个主节点对外提供写服务,一旦网络分区恢复,会将其中一个主节点变为从节点,这时会有大量数据丢失。
-
规避方法可以在redis配置里加上参数,写数据成功最少同步的slave数量这个数量可以模仿大于半数机制配置,比如集群总共三个节点可以配置1,加上leader就是2,超过了半数(这种方法不可能百分百避免数据丢失,参考集群leader选举机制):
min-replicas-to-write 1 //写数据成功最少同步的slave数量,这个数量可以模仿大于半数机制配置,比如集群总共三个节点可以配置1,加上leader就是2,超过了半数
注意: 这个配置在一致性得到了提升,但是一定程度上会影响集群的可用性,比如slave要是少于1个,这个集群就算leader正常也不能提供服务了,需要具体场景权衡选择。
3.8 集群是否完整才能对外提供服务
当redis.conf的配置cluster-require-full-coverage为no时,表示当负责一个插槽的主库下线且没有相应的从库进行故障恢复时,整个集群仍然可用,如果为yes则集群不可用。
3.9 Redis集群为什么至少需要三个master节点,并且推荐节点数为奇数?
-
因为新master的选举需要大于半数的集群master节点同意才能选举成功,如果只有两个master节点,当其中一个挂了,是达不到选举新master的条件的。
-
奇数个master节点可以在满足选举该条件的基础上节省一个节点,比如三个master节点和四个master节点的集群相比,大家如果都挂了一个master节点都能选举新master节点,如果都挂了两个master节点都没法选举新master节点了,所以奇数的master节点更多的是从节省机器资源角度出发说的。
3.10 Redis集群对批量操作命令的支持
对于类似mset,mget这样的多个key的原生批量操作命令,redis集群只支持所有key落在同一slot的情况,如果有多个key一定要用mset命令在redis集群上操作,则可以在key的前面加上XX,这样参数数据分片hash计算的只会是大括号里的值,这样能确保不同的key能落到同一slot里去,示例如下:
mset user1:1:name zhuge user1:1:age 18
假设name和age计算的hash slot值不一样,但是这条命令在集群下执行,redis只会用大括号里的 user1 做hash slot计算,所以算出来的slot值肯定相同,最后都能落在同一slot。
Redis分片主从哨兵集群,原理详解,集群的配置安装,8大数据类型,springboot整合使用
文章目录
Redis介绍
Redis 是一个开源(BSD许可)的,内存中的数据结构存储系统,它可以用作数据库、缓存和消息中间件。 它支持多种类型的数据结构,如 字符串(strings), 散列(hashes), 列表(lists), 集合(sets), 有序集合(sorted sets) 与范围查询, bitmaps, hyperloglogs 和 地理空间(geospatial) 索引半径查询。 Redis 内置了 复制(replication),LUA脚本(Lua scripting), LRU驱动事件(LRU eviction),事务(transactions) 和不同级别的 磁盘持久化(persistence), 并通过 Redis哨兵(Sentinel)和自动 分区(Cluster)提供高可用性(high availability)。
Redis分片
多台服务器对数据存储,如果其中一台宏机,则整个Redis分片将不能正常的使用.。实现了内存扩容,没有高可用的效果。不推荐使用。
Redis主从
主从模式主机可读可写,从机只读,主机宏机将导致只能读。可以配置1主-2从-3从,第3级的从可以承担RDB持久化压力。不推荐使用。
Redis哨兵
解决主机宏机带来的问题,哨兵用来判断主从宏机状况,主机宏机可以选举出新的主机,让从库与主库同步,通知客户端与主库连接。实现了高可用效果, 没有实现内存数据的扩容。这种方案需要配置哨兵集群,不推荐使用。
执行流程:
1.当哨兵启动时,会动态的监控主机,之后利用PING-PONG 心跳检测机制,检查主机是否正常.
2.哨兵链接主机之后,获取相关的主从的服务信息. 方便以后选举.
3.当哨兵发现主机宕机,之后采用随机算法 选择新的主机. 之后其他节点当新主机的从.
Redis集群
redis集群是最佳使用方案。内存扩容,节点实现高可用。可配置多主多从,实现去中心化。
Redis持久化策略
RDB
RDB模式介绍:
1.RDB模式是Redis默认持久化机制。
2.RDB模式记录的是内存数据的快照,持久化效率更高.。(只会保留当前最新数据)
3.RDB模式是定期持久化. 也可以手动操作 save(同步) 用户操作可能阻塞 /bgsave 后端运行(异步)
4.由于是定期备份,所以可能导致数据丢失。
RDB配置选项解释:
save 900 1 ->900秒 1次更新,则持久化一次
save 300 10 ->300秒 10次更新 则持久化一次
save 60 10000 ->60秒 10000更新 则持久化一次
save 1 1 效率极低. 不要这样配置 压测之后进行适当的调整.
AOF
AOF模式介绍:
1.默认条件下AOF模式处于关闭状态,如果需要开启,则手动配置
2.AOF模式记录用户的操作的过程,可以实现实时的持久化操作, 持久化文件相对较大维护不易.
3.如果同时开启了 RDB与AOF模式,则默认以AOF模式为准.
4.AOF模式是一种异步的操作,不会影响程序的正常使用
5.可以通过修改AOF存储的执行过程的命令达到修改数据的目的
AOF模式持久化策略:
appendfsync always 用户执行一步操作,则持久化一次
appendfsync everysec 每秒持久化一次 性能略低于RDB模式
appendfsync no 不主动持久化
持久化方案选择
1.如果允许少量的数据丢失 首选RDB模式,速度快。
2.如果不允许数据丢失, 首选AOF模式。
3.工作中一般如何选择 主机 选用RDB模式 从机 选用AOF模式。
Redis内存策略
LRU算法
LRU是Least Recently Used的缩写,即最近最少使用,是一种常用的页面置换算法,选择最近最久未使用的页面予以淘汰。该算法赋予每个页面一个访问字段,用来记录一个页面自上次被访问以来所经历的时间 t,当须淘汰一个页面时,选择现有页面中其 t 值最大的,即最近最少使用的页面予以淘汰。
维度: 时间T
LFU算法
LFU(least frequently used (LFU) page-replacement algorithm)。即最不经常使用页置换算法,要求在页置换时置换引用计数最小的页,因为经常使用的页应该有一个较大的引用次数。但是有些页在开始时使用次数很多,但以后就不再使用,这类页将会长时间留在内存中,因此可以将引用计数寄存器定时右移一位,形成指数衰减的平均使用次数。
维度: 使用次数
Random算法
随机算法
TTL算法
根据剩余的存活时间,排序,根据时间少的删除数据.
修改内存优化策略
volatile-lru 在设定超时时间的数据中采用LRU算法
allkeys-lru 在所有数据中采用LRU算法
volatile-lfu 在设定了超时时间的数据采用LFU机制进行计算
allkeys-lfu 在所有数据中采用LFU算法
volatile-random 设定超时时间的随机
allkeys-random 所有数据的随机算法
volatile-ttl 在设定了超时时间的数据中,采用TTL算法
noeviction 默认规则 如果内存满了,则不做任何操作 直接报错返回.
Redis集群一致性hash数据挂载&特性
原理:
1.节点计算nodecode=hash(ip:port)
2.计算数据的位置keycode=hash(key)
3.数据挂载图解
插入数据顺时针挂载到node2上。如果没有虚拟节点,这样存在节点分布不均匀可能导致挂载的数据集中到小部分的节点主机上。
平衡性:
通过虚拟节点实现数据的平衡,挂载虚拟节点使得hash结果均匀分布
单调性:
单调性是指在新增或者删减节点时,不影响系统正常运行
分散性:
分散性是指数据应该分散地存放在分布式集群中的各个节点(节点自己可以有备份),不必每个节点都存储所有的数据
Redis集群常见知识点
1.Redis集群中有16384个槽位,最多能配置16384台主机,hash(key1)%16384。
2.redis-cli -p 命令操作,从机不能写,而且数据存储严格按照分区算法完成。
3.集群的状态都已经写入nodes.conf文件中.所以集群重启之后集群恢复。
4.Redis集群崩溃的条件是主机缺失集群崩溃。(其他主机保证它最少有一台从机前提下,其他主机可以把自己多余的从机给缺少从机的主机)
如果有1主1从共3组组成了redis集群. 问题: redis节点至少宕机几台.集群崩溃?? 2台
如果有1主2从共3组组成了redis集群. 问题: redis节点至少宕机几台.集群崩溃?? 5台
缓存穿透&缓存击穿&缓存雪崩
缓存穿透:
在高并发环境下,用户长时间访问数据库中不存在的数据,称之为缓存穿透。
解决方案:
1.IP限流 单位时间内设定IP的请求的次数。
2.布隆过滤器。(需要了解自行搜索)
缓存击穿:
由于某个热点数据在缓存中失效.导致大量的用户直接访问数据库.导致数据库宕机。
缓存雪崩:
由于大量的数据在缓存中失效.导致用户访问缓存的命中率低.直接导致用户访问数据库。
Redis 8大数据类型&命令操作
String(字符串)
List(列表)
Set (集合)
Hash(哈希)
zset (有序集合)
geospatial(地理位置)
hyperloglog
bitmap(位图)
https://blog.csdn.net/UnicornRe/article/details/117572541
Redis单台安装步骤
1.上传安装包到linux目录/home/app/里
2.解压安装包
tar -xvf redis-5.0.4.tar.gz
3.为方便使用把解压后的redis-5.0.4改名为redis
mv redis-5.0.4 redis
4.安装redis,跳入到redis根目录中顺序执行
(1):make
(2):make install
5.修改redis配置文件
vim redis.conf
进入编辑页面后修改
(1)将IP绑定注释
(2)关闭保护模式
(3)开启后台启动
6.启动关闭命令
(1)启动命令 redis-server redis.conf
(2)查看进程 ps -ef |grep redis
(3)进入客户端 redis-cli -p 6379
(4)关闭命令 redis-cli -p 6379 shutdown
Springboot整合Redis单台
1.maven依赖
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
</dependency>
2.在工程目录resource目录下创建redis.properties文件
文件内容
redis.host=192.168.126.129
redis.port=6379
3.编写配置类
@Configuration
@PropertySource("classpath:/reids.properties")
public class RedisConfigure {
@Value("${redis.host}")
private String host;
@Value("${redis.port}")
private int port;
@Bean
public Jedis jedis(){
return new Jedis(host,port);
}
}
常用方法示例
//如果数据存在 不做任何操作,如果数据不存在则赋值
@Test
public void test0(){
//也可以注入jedis
//Jedis jedis = new Jedis("192.168.126.129", 6379);
//jedis.flushAll();
//jedis.set("redis", "AAA");
jedis.setnx("redis","BBB");
System.out.println(jedis.get("redis"));
}
Redis分片伪集群安装步骤
安装三台分片
1.同单台开头类似
2.在redis目录下创建文件夹shards
mkdir shards
cp redis.conf shards/6379.conf
cp redis.conf shards/6380.conf
cp redis.conf shards/6381.conf
分别vim 6380.conf,6381.conf
分别修改对应端口号6380和6381
启动多台redis,进入shards目录下
redis-server 6379.conf
redis-server 6380.conf
redis-server 6381.conf
查看进程
ps -ef |grep redis
Springboot整合Redis分片
在工程目录resource目录下创建redis.properties文件
文件内容
redis.nodes=192.168.126.129:6379,192.168.126.129:6380,192.168.126.129:6381
编写配置类
@Configuration
@PropertySource("classpath:/reids.properties")
public class RedisConfigure {
@Value("${redis.nodes}")
private String nodes;//指定分片节点 node,node,node
@Bean
public ShardedJedis shardedJedis(){
List<JedisShardInfo> shardInfos=new ArrayList<>();
String[] nodeArray=nodes.split(",");
for (String node:nodeArray){
String[] s=node.split(":");//host:port
JedisShardInfo jedisShardInfo=new JedisShardInfo(s[0],Integer.parseInt(s[1]));
shardInfos.add(jedisShardInfo);
}
return new ShardedJedis(shardInfos);
}
}
Redis主从->哨兵安装步骤
前提:要保证集群数据一致,如果有dump.rdb,appendonly.aof文件需要先删除,通常这些文件会在要启动的配置文件xxx.conf同目录下
先将分片实现主从挂载
1.同单台开头类似
2.在redis目录下创建sentinel文件夹
mkdir sentinel
cp redis.conf sentinel/6379.conf
cp redis.conf sentinel/6380.conf
cp redis.conf sentinel/6381.conf
启动多台redis,进入sentinel目录下
redis-server 6379.conf
redis-server 6380.conf
redis-server 6381.conf
3.实现主从挂载
主机: 6379
从机: 6380/6381
主从挂载步骤
(1)进入6380客户端 redis-cli -p 6380
(2)执行slaveof 192.168.126.131 6379 (语法:slaveof ip port)
(3)查看主从关系 info replication (role:slave自己为奴隶从)
(4)把6381也挂到6379的从,同理步骤执行即可
(5)进入6379客户端 redis-cli -p 6379 查看主从关系info replication
主从总体和分片比较相似,只是多了主从挂载
接下来安装哨兵,哨兵用来管理主机选举新主机
1.复制文件,在redis目录下
cp sentinel.conf sentinel
2.修改哨兵配置文件
进入sentinel目录
vim sentinel.conf
关闭保护模式
开启后台启动
监控主机(主机ip 主机端口 哨兵选举票数)
设定哨兵选举的时间
3.启动哨兵
redis-sentinel sentinel.conf
查看
现在可以关闭redis主机6379(强杀kill -9 39829),一段时间(上面配置了30s)后哨兵会选举新的从机当主机。
Springboot整合Redis哨兵
在工程目录resource目录下创建redis.properties文件
文件内容(地址端口皆为哨兵而不是主机)
redis.sentinel=192.168.126.129:26379
配置类
@Configuration
@PropertySource("classpath:/reids.properties")
public class RedisConfigure {
@Value("${redis.sentinel}")
private String sentinel;
@Bean
public JedisSentinelPool jedisSentinelPool(){
//1.设定连接池大小
JedisPoolConfig poolConfig = new JedisPoolConfig();
poolConfig.setMinIdle(5); //设定最小的空闲数量
poolConfig.setMaxIdle(10); //设定最大空闲数量
poolConfig.setMaxTotal(100); //最大链接数
//2.链接哨兵的集合,上面只安装了单台哨兵
Set<String> sentinels = new HashSet<>();
sentinels.add(sentinel);
return new JedisSentinelPool("mymaster",sentinels,poolConfig);
}
}
//使用
Jedis jedis = jedisSentinelPool.getResource();
Redis伪集群安装步骤
搭建三主三从
1.与单台安装类似
2.在redis目录下创建文件夹cluster
mkdir cluster
3.在cluster文件夹中分别创建7000-7005文件夹
mkdir 7000 7001 7002 7003 7004 7005
4.复制配置文件
将redis根目录中的redis.conf文件复制到cluster/7000/ 并以原名保存
cp redis.conf cluster/7000/
先修改7000目录下的配置文件
注释本地绑定IP地址(69行)
关闭保护模式(88行)
开启后台启动 (136行)
修改端口号(92行)
修改pid文件(存储进程号)(158行)
修改持久化文件路径(rdb,aof存储文件都在这个目录)(263行)
设定内存优化策略,其他类型选择上述文章有解释(597行)
关闭AOF模式(699行)
开启集群配置(838行)
开启集群配置文件(846行)
修改集群超时时间(852行)
将7000文件夹下的redis.conf文件分别复制到7001-7005中
批量修改,把复制过来的配置文件的7000端口改成自己响应的端口(92行,158行,263行)
5.在cluster目录下创建启动脚本
vim start.sh
#!/bin/sh
redis-server 7000/redis.conf &
redis-server 7001/redis.conf &
redis-server 7002/redis.conf &
redis-server 7003/redis.conf &
redis-server 7004/redis.conf &
redis-server 7005/redis.conf &
同理也可以创建关闭脚本(自行操作)
在cluster目录下运行脚本
sh start.sh
6.创建redis集群
#5.0版本执行 使用C语言内部管理集群,执行命令
redis-cli --cluster create --cluster-replicas 1 192.168.126.131:7000 192.168.126.131:7001 192.168.126.131:7002 192.168.126.131:7003 192.168.126.131:7004 192.168.126.131:7005
从机只能读不可写,key的hashcode%16384顺时针顺序和虚拟节点推算得到特定的主机,这个特定主机才能写这个key,比如set ok haha里的key是ok,计算出在7000主机,这个命令只能在7000主机写,其他主机写会报错
Springboot整合Redis集群
在工程目录resource目录下创建redis.properties文件
文件内容(地址端口皆为哨兵而不是主机)
(以下ip未与上述配置一致,一切按照实际情况配置)
redis.nodes=192.168.126.129:7000,192.168.126.129:7001,192.168.126.129:7002,192.168.126.129:7003,192.168.126.129:7004,192.168.126.129:7005
配置类
@Configuration
@PropertySource("classpath:/reids.properties")
public class RedisConfigure {
@Value("${redis.nodes}")
private String nodes;
@Bean
public JedisCluster jedisCluster(){
Set<HostAndPort> set=new HashSet<>();
String [] nodeArray=nodes.split(",");
for (String node:nodeArray){
String host=node.split(":")[0];
int port=Integer.parseInt(node.split(":")[1]);
set.add(new HostAndPort(host,port ));
}
JedisCluster jedisCluster=new JedisCluster(set);
return jedisCluster;
}
}
Redis集群生产环境搭建,动态增删
以上是关于redis学习篇主从&哨兵&集群架构详解的主要内容,如果未能解决你的问题,请参考以下文章
RedisSpringBoot集群搭建redis主从复制&哨兵模式
Redis分片主从哨兵集群,原理详解,集群的配置安装,8大数据类型,springboot整合使用