当前位置:首页 » 《随便一记》 » 正文

Redis基础原理_SlagSea

26 人参与  2022年04月17日 12:34  分类 : 《随便一记》  评论

点击全文阅读


公众号
笔记不易,如果喜欢点个关注吧

底层数据结构

image.png

Redis 解决哈希冲突的方式,就是链式哈希
image.png

​ 如果一次性把哈希表 1 中的数据都迁移完,会造成 Redis 线程阻塞,无法服务其他请求。此时,Redis 就无法快速访问数据了。 为了避免这个问题,Redis 采用了渐进式 rehash。 简单来说就是在第二步拷贝数据时,Redis 仍然正常处理客户端请求,每处理一个请求 时,从哈希表 1 中的第一个索引位置开始,顺带着将这个索引位置上的所有 entries 拷贝到哈希表 2 中;等处理下一个请求时,再顺带拷贝哈希表 1 中的下一个索引位置的。

image.png

​ 压缩列表实际上类似于一个数组,数组中的每一个元素都对应保存一个数据。和数组不同的是,压缩列表在表头有三个字段 zlbytes、zltail 和 zllen,分别表示列表长度、列表尾的偏移量和列表中的 entry 个数;压缩列表在表尾还有一个 zlend,表示列表结束。

image.png

​ 有序链表只能逐一查找元素,导致操作起来非常缓慢,于是就出现了跳表。具体来说,跳表在链表的基础上,增加了多级索引,通过索引位置的几个跳转,实现数据的快速定位

持久化机制

AOF

Redis 是先执行命令,把数据写入内存,然后才记录日志。AOF 里记录的是 Redis 收到的每一条命令,这些命令是以文本形式保存的。为了避免额外的检查开销,Redis 在向 AOF 里面记录日志的时候,并不会先去对这些命令进行语法检查。所以,如果先记日志再执行命令的话,日志中就有可能记录了错误的命令,Redis 在使用日志恢复数据时,就可能会出错。在命令执行后才记录日志,不会阻塞当前的写操作。

潜在的风险

  • 执行完命令,没有来得及记日志就宕机,那么这个命令和相应的数据就有丢失的风险。
  • AOF 虽然避免了对当前命令的阻塞,但可能会给下一个操作带来阻塞风险。这是因为,AOF 日志也是在主线程中执行的。

三种写回策略
image.png

解决AOF文件过大带来的性能问题

  • 文件系统对文件大小有限制,无法保存过大的文件。
  • 文件过大追加命令效率变低。
  • 发生宕机,使用AOF记录的命令去恢复,如果文件过大,恢复过程缓慢。

AOF重写机制
简单点就是在重写时,Redis 根据数据库的现状创建一个新的 AOF 文件。和 AOF 日志由主线程写回不同,重写过程是由后台线程 bgrewriteaof 来完成的,避免阻塞主线程导致数据库性能下降。
image.png

RDB

  记录的是某一时刻的数据而不是操作,在做数据恢复时,可以直接把 RDB 文件读入内存,很快完成恢复操作。Redis的数据都在内存中,为了提供所有数据的可靠性保证,执行的是**全量快照**,把内存中的所有数据都记录到磁盘中。

Redis 提供了两个命令来生成 RDB 文件

  • save:在主线程中执行,会导致阻塞。
  • bgsave:创建一个子进程,专门用于写入 RDB 文件,避免了主线程的阻塞,这是RDB 文件生成的默认配置。

image.png
如果主线程对这些数据也都是读操作(例如图中的键值对 A),那么,主线程和bgsave 子进程相互不影响。但是,如果主线程要修改一块数据(例如图中的键值对 C), 那么,这块数据就会被复制一份,生成该数据的副本。然后,bgsave 子进程会把这个副本数据写入 RDB 文件,而在这个过程中,主线程仍然可以直接修改原来的数据。

可以每秒做一次快照吗?
虽然 bgsave 执行时不阻塞主线程,但是,如果频繁地执行全量快照,也会带来两方面的开销

  • 频繁将全量数据写入磁盘,会给磁盘带来很大压力,多个快照竞争有限的磁盘带宽
  • fork 这个创建过程本身会阻塞主线程,而且主线程的内存越大,阻塞时间越长。

一般使用增量快照。就是指做了一次全量快照后,后续的快照只对修改的数据进行快照记录,这样可以避免每次全量快照的开销。

AOF和RDB选择问题
数据不能丢失时,内存快照和 AOF 的混合使用是一个很好的选择; 如果允许分钟级别的数据丢失,可以只使用 RDB; 如果只用 AOF,优先使用 everysec 的配置选项,因为它在可靠性和性能之间取了一个平衡。

主从同步

 主从库间数据第一次同步的三个阶段

image.png

** 通过“主 - 从 - 从”模式将主库生成 RDB 和传输 RDB 的压力, 以级联的方式分散到从库上。
image.png
一旦主从库完成了全量复制,它们之间就会一 直维护一个网络连接,主库会通过这个连接将后续陆续收到的命令操作再同步给从库,这个过程也称为
基于长连接的命令传播**,可以避免频繁建立连接的开销。

主从库间网络断了怎么办?
当主从库断连后,主库会把断连期间收到的写操作命令,写入 replication buffer,同时也
会把这些操作命令也写入 repl_backlog_buffer 这个缓冲区。 repl_backlog_buffer 是一个环形缓冲区,主库会记录自己写到的位置,从库则会记录自己已经读到的位置
image.png
因为 repl_backlog_buffer 是一个环形缓冲区,所以在缓冲区写满后,主库会继续写入,此时,就会覆盖掉之前写入的操作。如果从库的读取速度比较慢,就有可能导致从库还未读取的操作被主库新写的操作覆盖了,这会导致主从库间的数据不一致
一般而言,我们可以调整 repl_backlog_size 这个参数。这个参数和所需的缓冲空间大小有关。缓冲空间的计算公式是:缓冲空间大小 = 主库写入命令速度 * 操作大小 - 主从库间网络传输命令速度 * 操作大小。在实际应用中,考虑到可能存在一些突发的请求压力,我们通常需要把这个缓冲空间扩大一倍,即
repl_backlog_size = 缓冲空间大小 * 2,这也就是 repl_backlog_size 的最终值。

哨兵集群

场景

哨兵集群某个实例出现故障,其他哨兵完成主从切换工作(判断主库是否下线、选择新主库、通知从库以及客户端)

image.png

主观下线和客观下线

** 哨兵进程会使用 PING 命令检测它自己和主、从库的网络连接情况,用来判断实例的状态**。如果哨兵发现主库或从库对 PING 命令的响应超时,哨兵就会先把它标记为“主观下线”。如果检测的是主库,那么,哨兵还不能简单地把它标记为“主观下线”。
哨兵机制也是类似的,它通常会采用多实例组成的集群模式进行部署,这也被称为哨兵集群。引入多个哨兵实例一起来判断,就可以避免单个哨兵因为自身网络状况不好而误判主库下线的情况。同时,多个哨兵的网络同时不稳定的概率较小,由它们一起做决策,误判率也能降低。
image.png
“客观下线”的标准就是,当有 N 个哨兵实例时,最好要有 N/2 + 1 个实例判断主库为“主观下线”,才能最终判定主库为“客观下线”。

选定新主库

  先按照**一定的筛选条件**,把不符合条件的从库去掉。然后,我们再按照**一定的规则**,给剩下的从库逐个打分,将得分最高的从库选为新主库。在选主时,**除了要检查从库的当前在线状态,还要判断它之前的网络连接状态(**down-after-milliseconds**)。**

** **剩余从库打分筛选条件

  • 优先级最高的从库得分高
  • 和旧主库同步程度最接近的从库得分高
  • ID 号小的从库得分高

切片集群

场景

用Redis保存5000万键值对,每个键值对约512B(5000万 * 512B)为了能对外提供服务,采用云主机来运行Redis实例,那么该如何选择云主机的内存容量?

方案一

采用选择一台32GB(或者更高)内存的云足迹来部署Redis。同时采用RDB对数据做持久化,以确保Redis实例故障后能从RDB恢复数据。
缺点:Redis响应会有点慢。通过INFO命令查看Redis的latest_fork_usec指标(最近一次fork的耗时),结果到秒级。这个跟持久化有关系。在使用RDB进行持久化时,Redis会fork子进程来完成,fork操作的用时和Redis的数据量是正相关的,fork在执行时会阻塞主线程,数据量越大,fork操作的主线程阻塞的时间越长。就会导致Redis响应变慢

方案二

切片集群也叫分片集群,就是启动多个Redis实例组成一个集群,按照一定规则,把收到的数据划分成多份,每一份用一个实例来保存。按照刚才的场景,可以如下图所示。

优点:与纵向扩展相比,横向扩展是拓展性更好的选择方案。这是因为,要想保留更多的数据,采用这种方案的话,只增加Redis实例个数就可以了,不用担心单个实例的硬件和成本限制。

这种方式切引入了新的问题。

  • 数据切片后在多个实例之间如何分布
  • 客户端怎么确定想要访问的数据在哪个实例上

Redis3.0之后使用Redis Cluster方案实现切片集群。在Redis Cluster的方案中,一个切片集群共有16384个哈希槽,这些哈希槽类似于数据分区,每个键值对都会根据它的key被映射到一个hash槽中。使用 cluster create 命令创建集群,此时,Redis会自动把这些槽平均分布在集群实例上。例如,如果集群中有 N 个实例,那么,每个实例上的槽个数为 16384/N 个。假设集群中不同 Redis 实例的内存大小配置不一,如果把哈希槽均分在各个实例上,在保存相同数量的键值对时,和内存大的实例相比,内存小的实例就会有更大的容量压力。遇到这种情况时,可以根据不同实例的资源配置情况,使用 cluster addslots命令手动分配哈希槽。如下图所示,分配哈希槽的过程如下。


通过切面集群,实现了数据到哈希槽、哈希槽再到实例的分配。但是即使实例有了哈希槽的映射信息,客户端如何知道数据在哪个实例上呢?

客户端定位数据

一般来说,客户端和集群实例建立连接后,实例就会把哈希槽的分配信息发给客户端。但是,在集群刚刚创建的时候,每个实例只知道自己被分配了哪些哈希槽,是不知道其他实

例拥有的哈希槽信息的。Redis 实例会把自己的哈希槽信息发给和它相连接的其它实例,来完成哈希槽分配信息的扩散。当实例之间相互连接后,每个实例就有所有哈希槽的映射关系了。

集群中Redis实例新增/删除,需重新分配哈希槽,为负载均衡,需要把哈希槽在所有实例上重新分布一遍。客户端无法感知变化,会通过一种重定向机制实现。


以上是MOVED命令响应流程。
在实际应用时,如果 Slot 2 中的数据比较多,就可能会出现一种情况:客户端向实例 2 发送请求,但此时,Slot 2 中的数据只有一部分迁移到了实例 3,还有部分数据没有迁移。

在这种迁移部分完成的情况下,客户端就会收到一条ASK报错信息,ASK 命令表示两层含义:第一,表明 Slot 数据还在迁移中;第二,ASK 命令把客户端所请求数据的最新实例地址返回给客户端,此时,客户端需要给实例 3 发送 ASKING 命令,然后再发送操作命令。

MOVED 命令不同,ASK 命令并不会更新客户端缓存的哈希槽分配信息

Redis性能的影响因素

单线程的阻塞

  • 客户端
    网络 IO,键值对增删改查操作,数据库操作;
  • 磁盘
    生成 RDB 快照,记录 AOF 日志,AOF 日志重写;
  • 主从节点
    主库生成、传输 RDB 文件,从库接收 RDB 文件、清空数据库、加载 RDB
  • 文件
  • 切片集群实例
    向其他实例传输哈希槽信息,数据迁移。

处理方式:非关键路径进行异步处理

波动的响应延迟

  • 自身的操作
    • 通过Redis日志或者latency monitor 工具查看慢查询命令 ,使用高效命令替代,
      客户端去做数据的集合操作。
    • 过期Key操作,使用随机数避免大量的key同时过期。
  • 文件系统

    fsync 的执行时间很长,如果是在 Redis 主线程中执行 fsync,就容易阻塞主线程。
    AOF 重写会对磁盘进行大量 IO 操作,压力比较大时会导致 fsync 被阻塞。虽然 fsync 是由后台子线程负责执行的,但主线程会监控 fsync 的执行进度。
  • 操作系统
    • 内存swap
      是操作系统里将内存数据在内存和磁盘间来回换入和换出的机制,涉及到磁盘的读写。
      一旦触发 swap,无论是被换入数据的进程,还是被换出数据的进程,其性能都会受到慢速磁盘读写的影响。
      触发swap 主要原因是物理机器内存不足
      解决思路:增加机器的内存或者使用 Redis 集群
    • 内存大页
      Linux 内核从 2.6.38 开始支持内存大页机制,该机制支持 2MB 大小的内存页分配,而常规的内存页分配是按 4KB 的粒度来执行的。
      一般不建议使用内存大页机制。

CPU

多CPU架构

在多CPU架构上,应用程序可以在多处理器上运行,在多Socket切换运行进行内存访问会增加应用程序延迟。

CPU多核

不同核之间切换运行时信息

解决的方式:应用程序绑核

内存碎片

  • 内存分配器的分配策略
    操作系统一般无法做到按需分配,一般是按照固定大小分配
  • 键值对大小不一样和删改操作
    不同的业务数据存储在Redis会带来大小不一的键值对

使用Redis自身的Info命令查看 mem_fragmentation_ratio,该指标表示当前的内存碎片率,是used_memory_rss 和 used_memory 相除的结果。

used_memory_rss 是操作系统实际分配给 Redis 的物理内存空间,里面就包含了碎片;而 used_memory 是 Redis 为了保存数据实际申请使用的空间。

一般来说1< mem_fragmentation_ratio <1.5
解决方法:

  • 重启
    如果 Redis 中的数据没有持久化数据会丢失;
    即使 Redis 数据持久化了,还需要通过 AOF 或 RDB 进行恢复,恢复时长取决于AOF 或 RDB 的大小,如果只有一个 Redis 实例,恢复阶段无法提供服务。
  • 开启自动清理
    开启自动清理配置 config set activedefrag yes
    active-defrag-ignore-bytes 100mb:表示内存碎片的字节数达到 100MB 时,开始清理;
    active-defrag-threshold-lower 10:表示内存碎片空间占操作系统分配给 Redis 的总空间比例达到 10% 时,开始清理。
    active-defrag-cycle-min 25: 表示自动清理过程所用 CPU 时间的比例不低于25%,保证清理能正常开展;
    active-defrag-cycle-max 75:表示自动清理过程所用 CPU 时间的比例不高于75%,一旦超过,就停止清理,从而避免在清理时,大量的内存拷贝阻塞 Redis,导致响应延迟升高。
    碎片清理是有代价的,操作系统需要把多份数据拷贝到新位置,把原有空间释放出来,这会带来时间开销。
    因为 Redis 是单线程,在数据拷贝时,Redis 只能等着,这就导致 Redis 无法及时处理请求,性能就会降低。

缓冲区


输入缓冲区就是用来暂存客户端发送的请求命令的,导致溢出的情况主要是下面两种:

  • 写入了 bigkey,比如一下子写入了多个百万级别的集合类型数据;
  • 服务器端处理请求的速度过慢,例如,Redis 主线程出现了间歇性阻塞,无法及时处理正常发送的请求,导致客户端发送的请求在缓冲区越积越多。

输出缓冲区溢出的情况有

  • 服务器端返回 bigkey 的大量结果;
  • 执行了 MONITOR 命令,是用来监测 Redis 执行的,该命令不能用在线上生产环境
  • 缓冲区大小设置得不合理。

缓存淘汰机制

缓存满了如何处理?缓存淘汰机制

  • noeviction
    Redis 在使用的内存空间超过 maxmemory 值时,并不会淘汰数据。
  • volatile-random
    在筛选时,会针对设置了过期时间的键值对,根据过期时间的先后进行删除,越早过期的越先被删除。
  • volatile-random
    在设置了过期时间的键值对中,进行随机删除。
  • volatile-lru
    会使用 LRU 算法筛选设置了过期时间的键值对volatile-lru。
  • volatile-lfu
    会使用 LFU 算法选择设置了过期时间的键值对。
  • allkeys-random
    从所有键值对中随机选择并删除数据
  • allkeys-lru
    使用 LRU(按照最近最少使用)算法在所有数据中进行筛选。
  • allkeys-lfu
    使用 LFU 算法在所有数据中进行筛选。

LRU 会把所有的数据组织成一个链表,链表的头和尾分别表示MRU 端和 LRU 端,分别代表最近最常使用的数据和最近最不常用的数据。LRU 算法在实际实现时,需要用链表管理所有的缓存数据,这会带来额外的空间开销
在 Redis 中,LRU 算法被做了简化,以减轻数据淘汰对缓存性能的影响。具体来说,Redis 默认会记录每个数据的最近一次访问的时间戳(由键值对数据结构 RedisObject 中的 lru 字段记录)。然后,Redis 在决定淘汰的数据时,第一次会随机选出 N 个数据,把它们作为一个候选集合。接下来,Redis 会比较这 N 个数据的 lru 字段,把 lru 字段值最小的数据从缓存中淘汰出去。
image.png

Redis 提供了一个配置参数 maxmemory-samples,这个参数就是 Redis 选出的数据个数N。例如,我们执行如下命令,可以让 Redis 选出 100 个数据作为候选数据集:
CONFIG SET maxmemory-samples 100

缓存容量设置多大?需结合应用数据实际访问特征成本开销来综合考虑的。
一般来说,建议把缓存容量设置为总数据量的 15% 到 30%,兼顾访问性能和内存空间开销。

如何处理被淘汰的数据?** **
一般来说,一旦被淘汰的数据选定后,如果这个数据是干净数据,那么我们就直接删除。如果这个数据是脏数据,我们需要把它写回数据库。
image.png

如何解决缓存不一致?

image.png
使用重试机制,可以把要删除的缓存值或者是要更新的数据库值暂存到消息队列中(例如Kafka消息队列)。并发大的情况下,也可能会存在读到不一致的数据。

  • 先删除缓存再更新数据库
    image.png

处理方式:在线程 A 更新完数据库值以后,我们可以让它先 sleep 一小段时间,再进行一次缓存删除操作。
(延迟双删策略)

  • 先更新数据库值,再删除缓存值
    image.png
    这种方案对业务影响比较小。

如何解决缓存雪崩、击穿、穿透?

缓存雪崩

  缓存雪崩是指大量的应用请求无法在 Redis 缓存中进行处理,紧接着,应用将大量请求发送到数据库层,导致数据库层的压力激增。产生的原因以及处理方式:
  • 缓存中有大量数据同时过期,导致大量请求无法得到处理
    • 给缓存的key过期四件增加随机数。
    • 服务降级直接返回预定义信息、空值、错误信息或者允许部分请求直接走数据库。
  • 缓存实例故障宕机
    • 在业务系统中实现服务熔断或请求限流机制。
    • 事前预发,通过主从节点的方式构建 Redis 缓存高可靠集群。

缓存击穿

  针对某个访问非常频繁的热点数据的请求,无法在缓存中进行处理,导致访问该数据的大量请求都发送到了后端数据库,使数据库压力激增。处理方式:特别频繁的热点数据不设置过期时间。

缓存穿透

查询的数据既不在 Redis 缓存中,也不在数据库中,导致请求在访问缓存时,发生缓存缺失,再去访问数据库时,发现数据库中也没有要访问的数据。
产生的原因

  • 业务层误操作,缓存中的数据和数据库中的数据被误删除。
  • 恶意攻击:专门访问数据库中没有的数据。

处理措施

  • 缓存空值或缺省值。
  • 使用布隆过滤器快速判断数据是否存在,避免从数据库中查询数据是否存在,减轻数据库压力。
  • 前端进行请求合法性检测,过滤无效请求。

布隆过滤器原理
image.png
只要有一个为 0,那么,X 就肯定不在数据库中,都为1有可能在数据库中。

缓存污染

  在一些场景下,有些数据被访问的次数非常少,甚至只会被访问一次。当这些数据服务完访问请求后,如果还继续留存在缓存中的话,就只会白白占用缓存空间。这种情况,就是缓存污染。

处理方式

  • LRU策略

** **只看数据的访问时间,使用 LRU 策略在处理扫描式单次查询操作时,无法解决缓存污染。

  • LFU策略

    在 LRU 策略基础上,为每个数据增加了一个计数器,来统计这个数据的访问次数。当使用 LFU 策略筛选淘汰数据时,首先会根据数据的访问次数进行筛选,把访问次数最低的数据淘汰出缓存。如果两个数据的访问次数相同,LFU 策略再比较这两个数据 的访问时效性,把距离上一次访问时间更久的数据淘汰出缓存。
    

并发访问

  • 加锁​

    加锁保证了互斥性,但是加锁也会导致系统并发性能降低。
    
  • 原子操作

    • 把多个操作在 Redis 中实现成一个操作,也就是单命令操作。
    • 把多个操作写到一个 Lua 脚本中,以原子性方式执行单个 Lua 脚本。

分布式锁实现

单Redis节点实现

//加锁, unique_value 作为客户端唯一标识,同时设置超时时间以免客户端发生异常而无法锁
SET lock_key unique_value NX PX 10000
//释放锁 比较unique_value是否相等,避免误释放
if redis.call(“get”,KEYS[1]) == ARGV[1] then
return redis.call(“del”,KEYS[1])
else
return 0
end

多Redis节点的实现

  Redlock 算法的基本思路,是让客户端和多个独立的 Redis 实例依次请求加锁,如果客户端能够和半数以上的实例成功地完成加锁操作,则认为客户端成功获得分布式锁,否则加锁失败。即使有单个 Redis 实例发生故障,因为锁变量在其它实例上也有保存,客户端仍然可以正常地进行锁操作,锁变量并不会丢失。

加锁的主要流程如下:

  1. 客户端获取当前时间
  2. **客户端按顺序依次向 N 个 Redis 实例执行加锁操作
    **加锁操作和在单实例上执行的加锁操作一样。为了保证当某个Redis实例故障了,Redlock 算法可以继续运行,需要给加锁操作设置一个超时时间。
  3. 客户端一旦完成了和所有 Redis 实例的加锁操作,客户端就要计算整个加锁过程的总耗时。
  4. 判断加锁是否成功
    • 客户端从超过半数(大于等于 N/2+1)的 Redis 实例上成功获取到了锁。
    • 客户端获取锁的总耗时没有超过锁的有效时间。**

在Redlock算法中,释放锁的操作和在单实例上释放锁的操作一样,只要执行释放锁的Lua脚本就可以了。

事务机制

  Redis 通过 MULTI、EXEC、DISCARD 和 WATCH 四个命令来支持事务机制。事务的 ACID 属性是我们使用事务进行正确操作的基本要求。

image.png

主从同步与故障切换可能会遇到的问题

主从不一致

  客户端从从库中读取到的值和主库中的最新值并不一致。出现的原因主要是主从库间的命令是异步进行的。

处理方式

  • 在硬件环境配置方面尽量保证主从库间的网络连接状况良好。

  • 开发一个外部程序来监控主从库间的复制进度。

    如果某个从库的进度差值大于预设的阈值,让客户端不读取该从库数据,减少读到不一致数据的情况。
    

读取过期数据

  由 Redis 的过期数据删除策略引起的。  Redis 同时使用了两种策略来删除过期的数据,分别是惰性删除策略和定期删除策略。在 3.2 版本后,Redis 做了改进,如果读取的数据已经过期了,从库虽然不会删除,但是会返回空值,这就避免 了客户端读到过期数据。在应用主从集群时尽量使用Redis 3.2及以上版本。

不合理配置项导致的服务挂掉

  • protected-mode

    这个配置项的作用是限定哨兵实例能否被其他服务器访问。当这个配置项设置为 yes 时, 
    

    哨兵实例只能在部署的服务器本地进行访问。

  • cluster-node-timeout

    这个配置项设置了 Redis Cluster 中实例响应心跳消息的超时时间。如果执行主从切换的实例超过半数,而主从切换时间又过长的话,就可能有半数以上的实例心跳超时,从而可能导致整个集群挂掉。建议将 cluster-node-timeout 调大些(例如 10 到 20 秒)。

数据倾斜

  • 数据量倾斜

    在某些情况下,实例上的数据分布不均衡,某个实例上的数据特别多
    
    • bigkey

    • Slot 分配不均衡

    • Hash Tag

       是指加在键值对 key 中的一对花括号{}。这对括号会把 key 的一部分括起来,客户端在计算 key 的  
       CRC16 值时,只对 Hash Tag 花括号中的 key 内容进行计算。如果没用 Hash Tag 的话,客户端计算整
       个 key 的 CRC16 的值。
      
  • 数据访问倾斜

    每个集群实例上的数据量相差不大,但是某个实例上的数据是热点数据,被访问得非常频繁。
    

    通常来说,热点数据以服务读操作为主,在这种情况下,我们可以采用热点数据多副本的方法来应 对。如果数据是有读有写的话,就不适合采用多副本方法了,因为要保证多副本间的数据一致性,会
    带来额外的开销。
    image.png

通信开销:限制Redis Cluster规模的关键因素

** 实例间的通信开销会随着实例规模增加而增大**,在集群超过一定规模时(比如 800 节点),集群吞吐量反而会下降。所以,集群的实际规模会受到限制。
为了让集群中的每个实例都知道其它所有实例的状态信息,实例之间会按照一定的规则进
行通信。这个规则就是 Gossip 协议。
Gossip协议工作原理

  • 每个实例之间会按照一定的频率,从集群中随机挑选一些实例,把 PING 消息发送给挑选出来的实例,用来检测这些实例是否在线,并交换彼此的状态信息。PING 消息中封装了发送消息的实例自身的状态信息、部分其它实例的状态信息,以及 Slot 映射表
  • 一个实例在接收到 PING 消息后,会给发送 PING 消息的实例,发送一个 PONG 消息。PONG 消息包含的内容和 PING 消息一样。

image.png
实例间使用 Gossip协议进行通信时,通信开销受到通信消息大小通信频率这两方面的影响,消息越大、频率越高,相应的通信开销也就越大。

如何降低实例间的通信开销?

  配置项 cluster-node-timeout 定义了集群实例被判断为故障的心跳超时时间,默认是 15秒。如果 cluster-node-timeout 值比较小,那么,在大规模集群中,就会比较频繁地出现 PONG 消息接收超时的情况,从而导致实例每秒要执行 10 次“给 PONG 消息超时的实例发送 PING 消息”这个操作。 所以,为了避免过多的心跳消息挤占集群带宽,可以调大 cluster-node-timeout值,比如说调大到 20 秒或 25 秒。这样一来, PONG 消息接收超时的情况就会有所缓解,单实例也不用频繁地每秒执行 10 次心跳发送操作了。 当然也不要把 cluster-node-timeout 调得太大,否则,如果实例真的发生了故障就需要等待 cluster-node-timeout 时长后,才能检测出这个故障,这又会导致实际的故障恢复时间被延长,会影响到集群服务的正常使用。

Redis 6.0 特性

Redis 的多 IO 线程只是用来处理网络请求的,对于读写命令,Redis 仍然使用单线程来处理。
主线程和多 IO 线程的协作分成四个阶段。

  • 服务端和客户端建立 Socket 连接,并分配处理线程

    主线程负责接收建立连接请求。当有客户端请求和实例建立 Socket 连接时,主线程会创建和客户端的连
    接,并把 Socket 放入全局等待队列中。紧接着,主线程通过轮询方法把 Socket 连接分配给 IO 线程。
    
  • IO 线程读取并解析请求

    主线程一旦把 Socket 分配给 IO 线程,就会进入阻塞状态,等待 IO 线程完成客户端请求读取和解析。因
    为有多个 IO 线程在并行处理,所以,这个过程很快就可以完成。

  • 主线程执行请求操作

    主线程还是会以单线程的方式执行这些命令操作

  • IO 线程回写 Socket 和主线程清空全局队列

image.png


点击全文阅读


本文链接:http://zhangshiyu.com/post/38250.html

数据  实例  缓存  
<< 上一篇 下一篇 >>

  • 评论(0)
  • 赞助本站

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

关于我们 | 我要投稿 | 免责申明

Copyright © 2020-2022 ZhangShiYu.com Rights Reserved.豫ICP备2022013469号-1