Redis基础原理

底层数据结构

Redis基础原理

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

Redis基础原理

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

Redis基础原理

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

Redis基础原理

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

持久化机制

AOF

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

潜在的风险

三种写回策略

Redis基础原理

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

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

Redis基础原理

RDB

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

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

Redis基础原理

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

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

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

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

主从同步

 主从库间数据第一次同步的三个阶段
Redis基础原理

** 通过“主 - 从 - 从”模式将主库生成 RDB 和传输 RDB 的压力, 以级联的方式分散到从库上。

Redis基础原理


一旦主从库完成了全量复制,它们之间就会一 直维护一个网络连接,主库会通过这个连接将后续陆续收到的命令操作再同步给从库,这个过程也称为基于长连接的命令传播**,可以避免频繁建立连接的开销。

主从库间网络断了怎么办?当主从库断连后,主库会把断连期间收到的写操作命令,写入 replication buffer,同时也会把这些操作命令也写入 repl_backlog_buffer 这个缓冲区。 repl_backlog_buffer 是一个环形缓冲区,主库会记录自己写到的位置,从库则会记录自己已经读到的位置

Redis基础原理

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

哨兵集群

场景

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

Redis基础原理

主观下线和客观下线

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

Redis基础原理

“客观下线”的标准就是,当有 N 个哨兵实例时,最好要有 N/2 + 1 个实例判断主库为“主观下线”,才能最终判定主库为“客观下线”。

选定新主库

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

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

切片集群

场景

用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基础原理

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

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

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

Redis基础原理

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

客户端定位数据

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

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

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

Redis基础原理

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

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

Redis基础原理

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

Redis性能的影响因素

单线程的阻塞

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

波动的响应延迟

CPU

多CPU架构

Redis基础原理

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

CPU多核

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

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

内存碎片

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

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

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

缓冲区

Redis基础原理

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

输出缓冲区溢出的情况有

缓存淘汰机制

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

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

Redis基础原理

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

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

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

Redis基础原理

如何解决缓存不一致?

Redis基础原理

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

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

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

缓存雪崩

  缓存雪崩是指大量的应用请求无法在 Redis 缓存中进行处理,紧接着,应用将大量请求发送到数据库层,导致数据库层的压力激增。产生的原因以及处理方式:

缓存击穿

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

缓存穿透

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

处理措施

布隆过滤器原理

Redis基础原理

只要有一个为 0,那么,X 就肯定不在数据库中,都为1有可能在数据库中。

缓存污染

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

处理方式

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

并发访问

分布式锁实现

单Redis节点实现

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

多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 属性是我们使用事务进行正确操作的基本要求。
Redis基础原理

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

主从不一致

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

处理方式

读取过期数据

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

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

数据倾斜

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

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

Redis基础原理

实例间使用 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 线程的协作分成四个阶段。

Redis基础原理

展开阅读全文

页面更新:2024-02-27

标签:主从   哨兵   集群   主线   缓存   客户端   实例   命令   原理   操作   基础   数据

1 2 3 4 5

上滑加载更多 ↓
推荐阅读:
友情链接:
更多:

本站资料均由网友自行发布提供,仅用于学习交流。如有版权问题,请与我联系,QQ:4156828  

© CopyRight 2008-2024 All Rights Reserved. Powered By bs178.com 闽ICP备11008920号-3
闽公网安备35020302034844号

Top