基于社区电商场景的Redis缓存架构实战01-redis内核知识

发布于:2025-06-28 ⋅ 阅读:(20) ⋅ 点赞:(0)

001_Redis 基于文件事件的网络通信模型

002_Redis 多路复用监听与文件事件模型

redis基于文件事件的网络通信模型 file event,网络事件,抽象为文件事件,在redis server内部,网络通信各种事件,其实都是一些文件事件 Socket 概念。

redis server 单机,可以抗几千的QPS,几百上千的并发,同时有几百个redis client 对这个 redis server 发起请求,都需要去建立网络连接,同时间可能会有几百个redis client 通过 socket 和我们的redis server socket 建立网络连接 redis server 而言的话,因为可能有很多的、大量的redis client,redis server 内部为了支撑 并发访问的大量的redis client,redis server 内部就会有几百个socket网络连接同时在维护和维持着

如果所大家自己做过java socket网络编程,bio模式,nio模式,一旦要是说跟一个server 跟一个client完成了一个网络连接之后,都会多出来一个socket,socket是关键抽象和模型, 通过一个socket就可以跟对方的一个socket形成一个连接。当你手头有大量的客户端来并发访问的时候,server端里就可能会有大量的socket 可以多个线程,也可以一个线程,bio模式下,一个socket连接,server端就对应了一个线程来进行监听他的请求,java nio模式下,可以实现IO多路复用,一个线程,就可以多路复用去监听多个socket 的网络事件(请求发送过来)

socket 里可能会产生一些网络事件,accept(连接应答)、read(有数据可以读取的事件)、 write(有数据可以写出的事件)、close(连接被关闭) ,在Redis的世界里,这些网络事件都是会被抽象成为文件事件file event

003_基于队列串行化的文件事件处理机制

针对大量的socket,不太可能用每个socket 一个线程的模式来监听他的请求以及发送响应,并发的有几百个socket,上千个socket,难道你要准备几百个甚至上千个线程来处理这么多的并发请求,线程资源根本不够,对机器的cpu负载消耗会极为极大的

针对大量的socket,都是采用一个线程监听n多个socket,IO多路复用模式

read、write -> read(客户端发送请求过来了,此时就有read事件,redis要返回响应回去了,此时就有write事件)

同一时间有大量的redis client,并发的给我们发送大量的请求,在短时间里,大量的请求到达redis server,redis server 内部的大量的socket,会突然同一时间产生大量的事件,read事件,write事件。

此时有两种处理方案,方案一:我们可以搞一个queue队列,把这些有事件的socket一个一个怼入 queue 里,串行化排队,不管你同一时间有多少请求过来,要返回多少响应,我就让你们所有有事件发生的socket都去排队,串行化等着处理。方案二:我们可以把有事件发生的socket,分发给不同的线程,来进行并发的处理,开启大量的多线程,多个线程并发的去处理不同的socket里面的事件。redis选择的是方案一

004_完整的Redis Server 网络通信流程

上述的排队队列queue中,就会有普通连接socket,还有server socket,普通连接socket里面肯定就是普通的读写事件,客户端的读事件就分派给命令响应处理器进行处理,写事件就分派给命令请求处理器进行处理,队列中进行排队的server socket,肯定就是客户端有了新的连接申请请求发送了过来,需要服务端给一个连接的请求应答,就分派给连接应答处理器进行处理,只有连接应答处理器给出了应答,进行了三次握手,才算是服务端与客户端建立了一个新的连接socket,这个新socket也会被注册到IO多路复用器上去。IO多路复用器,就会去监听这个新的连接socket是否有读写事件到达,如果有,则IO多路复用器就会把这个socket放入queue中进行排队

Redis Server网络通信的大致流程:

1、初始化网络监听: Redis Server在启动时会创建一个监听套接字(listening socket),用于接收客户端的连接请求。 使用bind()函数将监听套接字绑定到指定的IP地址和端口,并使用listen()函数开始监听连接请求。

2、创建IO多路复用器: Redis会创建一个IO多路复用器,如select、poll或epoll,用于监听套接字上的事件。 将监听套接字添加到IO多路复用器的套接字集合中: 将监听“监听套接字”的accept事件

3、进入事件循环: Redis进入一个事件循环,不断等待和处理事件。 IO多路复用器会等待并监听套接字集合中的各个套接字,当哪个或者哪些套接字有事件发生时,将其返回给Redis Server。

4、处理事件: 当有新的客户端连接请求时,IO多路复用器会通知Redis Server。 Redis Server调用accept()函数接受客户端的连接请求,并创建一个新的套接字来处理该连接。 将新的客户端套接字(connection socket)添加到IO多路复用器的套接字集合中,监听其读事件(read事件)。

5、处理读写事件: 当有客户端发送请求数据时,IO多路复用器会通知Redis Server。 Redis Server从套接字读取请求数据,并进行相应的处理和响应。 当处理完毕后,将响应数据写入套接字,准备发送给客户端。

当客户端与 Redis Server 建立连接时,客户端和 Redis Server 之间会创建一个 TCP 连接,该连接会占用一个 socket,但是不会占用一个新的端口号。因此,无论有多少个客户端连接 Redis Server,它们都会通过相同的端口号(默认为6379)进行通信,不会占用多个端口

005_Redis 串行化单线程模型为什么能高并发?

redis并发能力很强,单机可以抗每秒几千并发是少了说了,机器配置高一些,可能可以抗几万并发。结合redis server端的网络通信模型来分析一下,这个他的高并发能力,到底是为什么

几万的高并发,就说明有大量的redis client,要在短时间内高并发的模式发起对你的单个redis server的请求,这个时候会出现哪些事情呢?第一点,必然会出现的一个事情,有大量的redis client 同时间要跟你建立网络连接。负责连接的socket,短时间内会有大量的要建立连接的请求和事件。

为什么短时间内redis server 可以跟大量的client 建立连接?第一点,连接的建立,往往来说,性能开销是可控的,短时间内完成大量的连接的建立,是没问题的,第二点,对于redis client 而言,你是短连接还是长连接,建立长连接,短连接模式(频繁的建立和断开连接, 这个不靠谱),建立的都是长连接,第一次建立连接是要花费一些时间的,后续这个连接就不用重复建立和断开,可以复用。所以,短时间之内大量的客户端来同时建立连接,这个是没有问题的

大量的客户端会基于socket在短时间之内,高并发的发送请求过来,redis server端的各个 socket 短时间内频繁的出现网络事件,为什么要全部进行队列串行化 + 单线程来处理每个请求呢?针对内存里的共享的数据结构,如果说你要是允许多个线程并发的访问共享内存数据结构, 会导致频繁的加锁和互斥。另外,多线程对cpu负载消耗是很大的,如果说cpu负载太高了以后,多线程运转的效率会急转直下的,多线程访问一块共享内存数据结构,大量的加锁和互斥,竞争,会导致性能也是不高的。

全部进行队列串行化 + 单线程来处理每个请求,避免了多线程切换对cpu负载消耗避免对内存数据结构大量的加锁和互斥,竞争

能否抗高并发的根本,是靠的是什么?靠的是每个请求执行和处理的速度和效率,假设说你一个请求, 如果要耗费10ms,才能处理完毕,此时每秒钟只能处理100个请求,如果说你一个请求只要1ms就可以处理完毕,1s内就可以处理1000个请求,如果说你一个请求基于纯内存数据结构来操作,而且避免了加锁和互斥之后,单次请求处理是远低于1ms的, 一秒钟进来了几千个请求,都积压在一个队列里,单个线程拿出一个请求就直接基于纯内存来操作,单次请求处理低于的1ms,1s 之内,单个线程就可以快速的把几千个请求基于内存数据结构全部都处理掉,从而实现高并发

006_Redis 内核级请求处理流程与原理

set k1 v1 hset,诸如此类的请求命令,对redis里的内存数据结构进行各种操作的,或者是实现一些复杂的高阶功能、事务、lua脚本

内存中的数据结构就包括字符串、hash、队列等等,这些数据结构都在内存里, set,get(查询数据) 命令,不管是get还是set,执行完以后,都会有对应的响应结果

redis服务端,会为每个socket创建一个RedisClient的内存数据结构,多个socket对应的RedisClient,是一个链表的形式串联起来的,想查找某个socket对应的RedisClient,只需要遍历这个RedisClient链表就好

整体内核级数据流转过程:

客户端通过socket1,发送一个get key1请求到达服务端,服务端的IO多路复用器从socket1中监听到有一个get请求,IO多路复用器就会把socket1丢入queue中进行排队,排队结束后socket1就会被文件事件dispatcher分发给命令请求处理器,命令请求处理器将socket1中的get请求丢入socket1在服务端对应的输入缓冲区,然后redis服务器就会从该输入缓冲区中拿到get请求,找到get请求对应的RedisCommand处理逻辑,这个RedisCommand处理逻辑就会操作内存中的数据结构,也就是读取内存中key1对应的value值value1,然后将value1写入输出缓冲区中,这时服务端的socket1又会产生一个write写事件,从输出缓冲区中,将value1写出到客户端的socket1的输入缓冲区中

007_Redis 通信协议与内核级请求数据结构

redis client和server端进行通信的协议,client 要发送什么样的数据给server端,格式去发送数据,才能让server端可以理解我们想要干什么

RedisClient.set(dfs, dsfdsf) -> 底层必须把这个命令和对应的 key value 的数据,组织成一条数据,通过网络发送过去 client 端发送的数据是这样组织的:REQUEST10568COMMANDsetKEYdfsVALUEdsfdsf,server 端需要去理解你的这条数据,他里面表达的是什么意思,如果不理解的话,server端 怎么来帮助我们进行请求的执行呢,不理解你要干什么

server 端期望的请求数据的格式可能是这样的:REQ10568CMDsetKEYdfsVALdsfdsf,他说我理解 的请求数据的格式是这样的,我也希望按照这样的格式来进行请求数据解析和理解,结果呢, 拿到的数据是这样的:REQUEST10568COMMANDsetKEYdfsVALUEdsfdsf。client 端发送的数据,到了server端手里的时候,是鸡同鸭讲,互相之间根本就不理解。

所以网络协议,协议是很关键,很重要的 client 和 server 端之间数据是按照什么样的格式来进行组织的 能不能约定好一套相同的数据组织的格式,client端按照和这个格式来组织请求数据,server 端也按照同样的格式来处理数据,按照一套协议来进行通信,如何组织数据,数据组织的格 式,就是一套网络通信协议

SET key value -> jedis.set(key, value) -> 通过协议组织成一定格式的数据 *3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$5\r\nvalue\r\n

008_Redis 内核中的请求数据结构分析

SET key value -> jedis.set(key, value) -> 通过协议组织成数据

网络通信的过程,代码里的对象和数据结构,往往需要进行按照协议进行封装和组织,按照协议进行组织之后,会得到一坨协议格式的数据:*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$5\r\nvalue\r\n

按照协议组织的数据,还必须进行序列化,请求数据从client端发送到server端来,序列化成字节数据流,byte[]字节数组,然后再去通过 socket,使用网络去传输字节数据流,以字节数据流的格式,传输到server端去

server端,通过socket 读取出来的一般是字节流,还得把字节流进行反序列化的过程,拿到协议格式的原数据:*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$5\r\nvalue\r\n , 请 求 串 , 此 时 会 被 放入 到 你的 RedisClient 的输入缓冲区里

红框中buf字节数组,存的就是协议格式数据*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$5\r\nvalue\r\n所对应的二进制字节流数据,字节数据反序列化后就是*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$5\r\nvalue\r\n,*3\r\n$3\r\nSET\r\n$3\r\nkey\r\n$5\r\nvalue\r\n按照网络协议解析后,存放在argv数组中,记录了是一个set命令

querybuf就是RedisClient所对应的socket的输入缓冲区中的一个变量

009_Redis 内核中的命令函数查找和执行

存放在argv数组中,记录了是一个set命令,然后通过命令查找表,查找每个命令对应的RedisCommand

通过命令查找表,查找到set命令对应的RedisCommand后,就会让当前RedisClient所对应的输入缓冲区中的另外一个变量cmd,让这个cmd指针变量去指向上面查找到的RedisCommand。

此时,就可以调用set命令对应的RedisCommand中的函数,执行set key value的操作。执行完后,又会将执行结果写入到当前RedisClient所对应的输出缓冲区中去

响应结果,跟请求数据是一样的,都必须按照约定好的协议,去组织你的数据。set key value的执行结果是:OK,对应的协议格式数据:+OK\r\n0

上述红框内的buf就是当前RedisClient的输出缓冲区

010_Redis Server 启动时的流程原理分析

redis核心是基于内存的nosql数据存储,分布式缓存系统仅仅是他的其中一个用处, 我更愿意把redis定位为一个基于内存的nosql数据存储,针对他的所有的一些操作、他的数据结构,都是基于内存来的,因为他是基于内存的,所以才可以用于进行缓存,主要就是基于内存来进行缓存

因为是基于内存的,必然会面临一个问题,如果redis进程重启的话,内存里的数据就全部都丢失了,所以一般来说我们也会给redis开启一个数据持久化机制

一般来说,都是用 AOF 来进行数据持久 化,用RDB可以去做一个周期性的冷备份 AOF,每次当你对内存里的数据进行更新之后,都会有一个对应的记录写入到一个内存里的缓冲结构里去,我们可以设定AOF记录刷新到磁盘的周期

比如说可以设定为,每条数据写入内存后就把他的AOF记录刷到磁盘去,确保每条数据都不会丢失,这会导致你的redis的性能退化到磁盘的数据存储的级别。也可以每秒钟,把内存里的AOF的记录数据刷到磁盘文件里去,此时如果突然把redis-server进程进行重启,此时可能会丢失最近1秒内写入到内存里的数据对应的AOF内存里的记录,但是只要他redis-server重启了之后,就会把磁盘文件里的之前的AOF记录都读取出来,还原出来内存里的数据情况

除了AOF以外,RDB则是周期性的,把内存的数据写一份快照放到磁盘文件里去,这个比较适合做数据冷备份,你可以数据备份成一个文件,放到其他服务器上去,如果这台服务器万一磁盘坏 了,导致AOF都没了,此时可以基于1个小时之前的RDB去做一个数据恢复

redis重新启动时,除了读取AOF磁盘文件以为,还会读取redis.conf配置文件等等,比如初始化一些数据,比如初始化命令查找表等数据

011_为什么我们需要Redis分布式集群模式?

上面已经讲完了redis单机版本的一些内核级的原理,redis最核心的是什么?他会基于内存对外提供操作数据结构的功能。redis提供了字符串、hash、list,各种各样的内存里的数据结构,存储数据, 基于存储好的数据做一些操作

redis 单机,毕竟他所有的数据都是要基于内存来存放的,必然有一个问题,单台机器的内存量是有限的,比如说单台机器4核8G,8核16G,16核32G,内存量大致单机超高配机器,也就是64G,128G,也就这样了

单机的内存容量是有限的,所以说的话,如果说我们要在redis里存储大量的数据,几百GB的数据,几百GB的缓存数据要放到redis里去,单机肯定是放不下的,如果硬是在的单台机器里放置超过他内存空间的数据量

如果单机是32GB,如果硬要放入64GB的数据,32GB的单机中的数据就会被淘汰掉很多,当你写入数据量超过32GB了以后, 此时就会把老的一些数据从内存里淘汰掉

所有缓存数据,分布式的存储在redis的多个节点里,就是一个集群的模式来运作的

012_Redis 内核中的集群模式数据结构分析

在redis 集群模式下,每个节点在内存中,都会给其他的节点创建对应的内存中的数据结构, 来保存对其他节点的信息

013_Redis 节点之间的三次握手原理分析

当我们准备了一批服务器,打算在上面每台服务器启动一个redis 节点,多个服务器上的redis节点 互相通信,组成一个redis集群,各个节点之间,会需要去进行一个他自己的meet协议之下的三次握手

我们可以通过一些命令、配置文件也好,一些 redis 基础实操,就不再我们课程范围内了, 因为都比较简单,redis集群搭建和部署,一系列的命令下来, 把一个redis集群搭建和部署、启动好,这个都很简单

通过一系列的命令,可以让多台redis机器组成一个集群:首先会以一台redis1节点作为基础,告诉他有另外一个redis2节点,那么redis1要做的第一步,就是给要连接的redis2节点,在内存里创建一个ClusterNode数据结构,第二步就是发送一个meet消息,redis2也会在内存里为redis1也创建一个clusterNode数据结构

                              

当前的三次握手协议,是redis自己协议,跟TCP网络连接的三次握手还不是一个概念,TCP三次握手是用来建立基础的底层网络连接,redis这个是他的应用层面的三次握手,是用来作为集群里的各个节点进行连接

启动和部署一个集群,实施一些命令,肯定会在一个node上去执行的,对于你的一个node通过你的命令一般来说都可以感知到其他的node

redis中有一个Gossip协议,流言协议,就是说把一个信息像散播流言一下,散播给集群里他能感知到的其他所有的节点,这个类似于流言传播, 刚刚建立连接的node信息,传播给其他的节点就可以了。redis1和redis2,redis1和redis3都三次握手后,redis2和redis3不用进行三次握手,而是通过redis1的流言传播就可以完成信息的相互保存

总结Redis的集群建立 = 三次握手+流言传播

014_基于slots槽位机制的数据分片原理分析

redis 多个节点组成一个集群了,我肯定要把数据分布式的放在各个redis节点里,业务系统,此时他需要往redis集群里,不断的写入数据,大致可能要写入100GB的数据,此时100GB的数据肯定要分布在多个redis节点里。假设一共5个节点,每个节点存储20GB的数据在他的内存里分布式数据存储,我把100GB的数据分布式的存储在各个节点里了

此时出现一个问题:当我写入一条数据的时候,这条数据到底是写入哪个节点里?首要决定的就是,我的数据是写入哪个数据分片,data sharding,其二是解决数据在redis各个节点之间的“迁移 /rebalance ”的过程

数据节点的扩容、缩容,redis集群刚开始是5个节点,比如加到6个节点或者8个节点,往集群里加 入1个、3个节点,刚刚加入集群的节点是空的,没有数据的,这是不合理的。就需要进行rebalance, 把已有的几个节点上的数据,迁移到新的空节点上去 让已有的节点上的数据变少,让新的空节点上的数据变多,实现这样的效果

缩容,本来是5个节点,公司里要节约成本,缩容为3个节点,减少的2个节点的数据迁移到剩余的3个节点上去,数据迁移,如何进行数据迁移

解决上面数据迁移的问题,就需要抽象出数据分片的概念,各个节点里有一个数据分片的概念了,每个节点包含n个数据分片,在写入数据的时候,可以通过一定的路由算法,把每条数据写入到一个节点的1个数据分片里去。路由算法可以是随机分配、哈希分配、轮询分配等等

扩容加机器,通过一定的算法计算出来,已有的每个节点要迁移哪些数据分片给我们的新节点就可以了,把指定的数据分片去做一个迁移就可以了。缩容也就是把减少的机器上的数据分片,迁移给我们的剩余的机器就可以了。我们就是以每个数据分片为一个单位,来进行各个机器之前来回的数据迁移

数据分片 -> slots,槽位的概念,redis集群所有的数据分片是固定的数量的,无论集群大小如何,就是固定的16384个slots槽位,我们就需要给集群的各个节点分配他们负责的slots槽位,每个节点会负责一部分的slots槽位,每个slot槽位就是一个数据分片

015_Redis 集群 slots 分配与内核数据结构

slots 概念,为什么要 slots,必须在分布式集群架构里,要设计出来一个数据分片的概念, 就可以把很多很多的数据分片分散在各个节点里,数据分片,slots如何来分配给各个节点, 分配给各个节点之后,在内存里的数据结构,是什么样子的

如何通过redis的cluster,slots指令给各个节点分配槽位信息,各个redis版本的命令还不太一样,底层的原理是差不多的,可以通过一些命令给节点分配槽位范围

比如,指定给node01分配的槽位范围是1…5000,一共给他5000个槽位,node02分配的槽位范围是5001..10000,分配这些槽位

一个节点分配好了槽位之后,会把自己负责的槽位信息同步给其他的所有节点

016_基于slots槽位的命令执行流程分析

多个节点一起来组成一个集群,各个节点互相之间进行三次握手 + 基于gossip流言协议去扩散连接到的节点给其他人,就可以让一 群节点互相之间建立连接就可以了。

让一个节点知道所有其他的节点,一个节点主动去连接,其他的节点之间靠的都是gossip协议流言扩散才知道。这套设计简洁明了。

组成集群之后,内存里的数据结构是如何来构建的 slots 分配,基于命令来分配各个节点的slots,会每个节点去内存里记录clusterNode有哪些slots,slots表(每个节点都有自己的slots表,记录每个节点都分配到了哪些slots槽位)指向了各个clusterNode,而且每个人会把自己的slots同步给其他的节点, 实现集群里的槽位分配

redis集群架构,设计的很好的,有一个很关键的概念,就是去中心化的概念,各个redis master, 他都是属于对等的,大家干的活儿,做的事情,都是一样的,避免了说可能还要去选举一个 controller、leader来管控整个集群,去中心化

key-value 一定是属于某个slot,slot一定是属于一个节点,按说应该是找到那个节点,去执行这个命令,客户端随意找一个节点,发送命令过去就可以了,那个节点内部会计算一下这个key是属于哪个slot,有一个CRC 算法(key)-> 值 -> 16384做一个运算,得到的是一个0到16384范围内的一个数字,代表了这个key对应的真正的slot槽位

客户端随意找一个节点,发送命令过去就可以了,这个节点通过自己的槽位集合,发现当前key就属于自己的槽位集合中的某个槽位,则直接进行命令的执行

如果发现当前key不属于自己的槽位集合中的任何一个槽位,那么redis server就会给客户端返回MOVED指令和slot和真正的目标节点地址,方便客户端进行重定向写入

017_基于跳跃表的slots和key关联关系

在集群模式下,所有的数据都是被划分到16384个数据槽位里去的,16384个数据槽位就等于16384个数据分片,每个槽位里都有一部分的数据,客户端发送命令的时候,确实在server端也会去计算key所属的槽位,可以把数据放在槽位里,数据就属于这个槽位数据分片的一份子了

我们在一个节点里执行了一个命令之后,kv操作,操作了一条数据,每个kv是一条数据应该是跟一个slot 绑定关联在一起的,每条数据都是属于一个slot的,一个slot就是一个数据分片了

每次操作完kv数据之后,对于这个key,他会通过专门的skipList跳跃表数据结构,跟我们的slot做一个关联,跳跃表如果大家要是不理解的话,建议自己先去看一些数据结构的资料,也没那么难,你可以去理解一下跳跃表数据结构

如何通过这个跳跃表,查找每个slot下有哪些key?

018_集群扩容时的slots转移过程与ASK分析

集群扩容时的slots是如何进行转移,这个过程中可能出现的一个ASK问题 我们可以在集群里加入一个新的节点,可以通过命令,指定把某个节点上一部分的slots转 移到新的节点去,可以做这样的一个指令和操作 clusterState 里的两个数据结构,是类似的

当正在发生数据迁移时,如果有数据写入到redis01中的3845槽位,但是发现redis01的3845槽位正在被迁移到redis04上去,那么redis01的slots槽位集合的索引3845对应的索引值就会被从1置为0,且redis01会给客户端返回一个ACK,加3845的槽位号+重定向的redis04的服务器地址

Redis集群模式完整图示:

019_Redis 主从挂载后的内核数据结构分析

redis 内核原理课程主要是分3块去讲:单机、集群、主从

我们可以通过一定的命令,指定说有一台机器是你的 slave,具体命令,不同的版本命令是 不一样的,我们还是关注他的本质和内核,原理

clusterState,是每个redis节点内存内部都有一个重要的数据结构

020_Redis SYNC 主从复制原理以及缺陷

redis主从复制原理,演进过程,redis 2.x以前的老版本里,是采用的SYNC复制模式,这个模式有很多的缺陷。这个老版本的原理大家也是需要知道的,你就不理解redis主从复制是如何一步一步演进过来的。老版本的模式重要的机制就是BGSAVE, background save,后台保存

第一步,master会通过bgsave命令,生成命令执行时刻往前的所有内存缓存数据的数据快照RDB并写入磁盘。第二步,master执行bgsave命令以后

slave崩溃了,重启,slave 断线重启,会导致每次slave 重启,都需要去发送SYNC命令给 master,让master按照之前的步骤去把数据同步重新做一遍,每次salve重启都要执行sync, 这里开销最大的点在于bgsave操作,是一个极为重量级,耗时的操作

把redis master内存里的大量数据(几个GB,多则几十个GB),生成RDB并写入磁盘,这会执行大量的磁盘IO,这是相当耗时的,把这么大的一个文件(几个GB~几十个GB),传输给salve,也非常耗费网络资源,网络带宽可能都会被打满。slave收到了master发过来的RDB之后,几乎会阻塞掉自己对外部的服务和操作,专门把RDB通过大量的网络磁盘IO,加载到内存里来

这就是老版本的SYNC模式,执行slave->master 主从同步,缺陷就在于这里

021_PSYNC 的偏移量和复制积压缓冲区分析

redis 老版本,主从复制,就支持一个SYNC机制,每次断线重连,都要bgsave一遍,rdb 来传快照是非常不好的,在很多情况下,从服务器他可能仅仅是做一些运维工作,重启,断线重连的时间间隔,其实不长,没有必要每次都传输rdb快照

redis新版本,就玩儿了一个PSYNC,主要是针对断线重连后的主从复制做了一些优化,如果说你断开 的时间间隔还比较短的话,那其实就还好,就不需要传输rdb,bgsave,其实只要想办法把你断开这段时间里,做出一些命令变更传输给你,你把这些命令去重新执行一遍就完事了

如果说你要是断开的时间太长了,几个小时、几天,在这段时间里做出的数据变更太多了, 此时就没办法了,还是得走一个老路子,bgsave,生成rdb传输,再把剩余的命令传输过去,做一个同步

偏移量,复制积压缓冲区:如果说导致偏移量差距的这些命令,全都在复制积压缓冲区里,此时就可以把这些命令直接传输给从节点,让从节点把这些命令执行一遍,就可以让数据完成同步。如果说从节点落后的太多太多了,导致重启的时候,主从之间的数据偏移量差距太大太大了, 这些偏移量对应的命令在复制积压缓冲区里找不到了,此时就只能做一个全量的同步。也就是rdb快照传输过去,实现主从数据同步

022_Redis 定时 PING 与疑似下线分析

故障探测

每个redis节点,slave 节点也是一样,每个节点都会定时的发送ping消息给其他节点,集群里,是没有controller/leader的概念,大家 都是对等的,中心化的一个总控节点,都是通过集群里各个节点互相之间进行探测和通信实 现集群的功能

每个人尝试去探测其他节点是否还存活,定时发送ping,如果说人家是存活的,人家会返回pong, ping-pong,ping-pong,探测的消息来回。两个slave节点,发送出去给master的ping消息,并没有在指定的时间范围内收到pong,此时,每个slave节点,都会把这个master标记为pfail,也就是标记为疑似下线

023_Redis 内核故障报告数据结构与下线标记

各个节点之间会把疑似下线的情报,发送给其他的节点,去跟其他节点进行信息交换

每个节点在汇总针对某个节点的下线报告的时候,都会去判断一下,如果说超过了集群里一半的节点,都认为某个节点下线了,mater+2个slave,一共是3个节点,3/2+1 = 2,只要有2个节点,包括你自己,认为他下线了,此时就可以标记为正式下线。进行报告汇总的节点B,在将某个节点A标记为fail后,必须要把正式标记为fail的节点A的状态,同步给其他的节点,比如是节点C。

对于任何一个节点,无论是master,还是slave,关于他的故障探测,都是这套机制:每个节点都定时ping其他节点 + 一段时间没收到pong + 标记pfail + pfail交换给其他所有节点 + 汇总fail report + 过半节点数量都认为pfail + 标记fail以及同步给其他所有节点,这 个时候下线的节点正式死亡

024_Redis 主节点选举算法以及故障转移

故障转移

对于这个master节点,如果说他挂掉的话,他的slave节点肯定会感知到他的fail下线的状态,一旦说slave节点感知到master节点故障下线了,slave节点会尝试开始进行master选举

每个slave节点,都会发送投票请求给其他的一些slave,请求slave给自己投一票,这个时候其他slave如果说要是还没有给任何人投过票,此时就可以给这个slave投出一票,看你的各个salve谁的投票请求先过来

收票的时候,如果说发现自己得到了n节点/2+1大多数人的投票,此时就可以把自己选举为一个新的master。然后他就会去通知所有的节点,之前下线的master负责的槽位slots都归自己管了,所有人都会更新自己内存里的数据结构,之前的其他的salve,此时就会开始从新的master那里去同步数据 。如果一轮投票里没人收到n/2+1大多数人的投票,此时开启下一轮投票就可以了。

redis最核心的一些内核原理,都已经理解了,整体性、深入的理解。如果针对redis内核需要进行深一步的研究和学习,redis 的各种内存数据结构可以研究一 下,AOF和RDB持久化机制可以研究,pub/sub、事务、lua脚本、慢查询、监控、LRU、expire, redis提供的方方面面的机制都可以研究他的内核的原理

在Redis Cluster中,每个节点都可以是主节点或从节点。为了实现高可用性,建议将每个主节点至少配置两个从节点,这样即使一个主节点故障了,还有其他的从节点可以选举出新的主节点。 在Redis Cluster中,当主节点失效时,集群会进行自动故障转移,选举一个新的主节点来代替原来的主节点。在进行故障转移时,需要满足“节点数/2+1”个节点投票,才能选举出新的主节点。如果主节点只配置了一个从节点,那么它只能得到自己的一票,无法满足“节点数/2+1”的要求,从而无法完成故障转移。因此,至少需要两个从节点来保证故障转移的可靠性。 值得注意的是,Redis Cluster的高可用性还涉及到其他方面的配置和设计,例如节点的数量、节点的布局、数据复制策略等等。要确保Redis Cluster的高可用性,需要进行全面的规划和配置。


网站公告

今日签到

点亮在社区的每一天
去签到