likes
comments
collection
share

可能是最亲民的Redis一文解读

作者站长头像
站长
· 阅读数 68

1. 什么是Redis

Redis(Remote Dictionary Server)是一种开源的、基于内存的键值数据库,主要用于作为缓存中间件。缓存是数据存储的一种形式,最重要的数据存储系统那一定是数据库了,那么缓存中间件的价值是什么呢?我认为主要有两点:

  • 高并发场景下降低数据库的压力。这里最常见的例子就是
  • 对存储敏感性弱的数据进行生命周期的管理。

Redis是k-v类型的缓存中间件,直白的说,Redis中存储的就是一个个k-v对,我们可以像使用map一样,通过key查询到存储的value,其中的key一般都是字符串,value可以是redis中的各种数据类型。

redis的数据是存储在redis服务器的内存中的,这也是其访问速度快的原因之一。

另外,redis的核心处理线程是单线程的,这就避免了线程切换时的性能开销。

2. 数据类型

  • 字符串:最基础的数据类型,可以存储任意类型的数据,例如文本、数字、二进制数据等,与java中的string类似。
  • 哈希表:一个哈希是一个键值对集合,其中每个键值对都是一个字段与其值的映射,与java中的map类似。
  • 列表:列表是一个按照插入顺序排序的字符串元素集合。你可以将元素从左边或右边推入、弹出列表,,与java中的linkedlist类似。
  • 集合:集合是一个不包含重复元素的无序集合,与java中的set类似。
  • 有续集合:有序集合类似于集合,但每个成员都会绑定一个分数(score),并根据分数进行排序。
  • 位图:每个bit位存储一个二进制数据。
  • HyperLogLog:时间复杂度为 O(1)估算基数。
  • 地理空间索引:用于存储地理空间位置信息并进行操作。

3. 过期策略与内存淘汰机制

Redis 提供了多种机制来管理键的过期时间。通过设置键的过期时间,可以确保键在指定的时间之后会自动被删除。过期键的管理策略主要有两大类:主动删除(Active Deletion)和被动删除(Passive Deletion),这两种策略结合使用,确保过期键能够及时被删除,同时不会对数据库性能造成明显影响。

  • 定期删除:redis默认是每隔 100ms 就随机抽取一些设置了过期时间的key,检查其是否过期,如果过期就删除。注意这里是随机抽取的。为什么要随机呢?你想一想假如 redis 存了几十万个 key ,每隔100ms就遍历所有的设置过期时间的 key 的话,就会给 CPU 带来很大的负载!
  • 惰性删除:定期删除可能会导致很多过期 key 到了时间并没有被删除掉。所以就有了惰性删除。假如你的过期 key,靠定期删除没有被删除掉,还停留在内存里,除非你的系统去查一下那个 key,才会被redis给删除掉。这就是所谓的惰性删除。

如果定期删除漏掉了很多过期 key,没及时去查,走惰性删除,此时会有大量过期key堆积在内存里,导致redis内存块耗尽了。怎么解决这个问题呢? 就要靠redis 内存淘汰机制:

  • volatile-lru:从已设置过期时间的数据集(server.db[i].expires)中挑选最近最少使用的数据淘汰
  • volatile-ttl:从已设置过期时间的数据集(server.db[i].expires)中挑选将要过期的数据淘汰
  • volatile-random:从已设置过期时间的数据集(server.db[i].expires)中任意选择数据淘汰
  • volatile-lfu:从已设置过期时间的数据集(server.db[i].expires)中挑选最不经常使用的数据淘汰
  • allkeys-lru:当内存不足以容纳新写入数据时,在键空间中,移除最近最少使用的key(这个是最常用的)
  • allkeys-random:从数据集(server.db[i].dict)中任意选择数据淘汰
  • allkeys-lfu:当内存不足以容纳新写入数据时,在键空间中,移除最不经常使用的key
  • no-eviction:禁止驱逐数据,也就是说当内存不足以容纳新写入数据时,新写入操作会报错。

4. 可用性保证

4.1. 持久化机制

Redis 支持 RDB 和 AOF两种持久化机制:

RDB:RDB 是 Redis 默认的持久化方式,它会在某个时间点将内存中的数据生成快照并保存到磁盘。RDB 文件是压缩的二进制文件,适合用于备份和灾难恢复。

优点:

  • 快照备份:RDB 文件是一种紧凑的二进制格式,适合进行全量备份。
  • 性能优越:在执行 RDB 持久化时,Redis 主进程会 fork 一个子进程来创建快照,这样可以最大限度地减少主进程的阻塞时间。
  • 重启恢复快:由于 RDB 文件是紧凑的二进制格式,加载和恢复速度较快。

缺点:

  • 数据丢失:由于 RDB 是定时快照,若在两次快照之间发生故障,可能会丢失部分数据。
  • 开销高:由于 RDB 快照是通过 fork 子进程来创建,如果数据集较大,可能会占用较多的 CPU 和内存资源。

AOF:会记录每一个写操作到一个日志文件中,以便在服务器重启时可以按照操作顺序重现数据。

优点:

  • 高持久性:可以配合不同的同步策略(always、everysec、no)来保证数据持久性,通常情况下每秒同步一次(everysec)是比较常用的配置,它在性能和可靠性之间提供了较好的平衡。
  • 可修复性:AOF 文件是追加的文本格式,如果文件末尾损坏,可以尝试通过 Redis 自带的 redis-check-aof 工具来修复。

缺点:

  • 文件较大:AOF 文件会不断增长,可能会比 RDB 文件大很多,且需要定期重写以避免文件过大。
  • 恢复速度慢:由于需要重放所有的写操作,相比 RDB 的全量恢复,恢复时间可能较长。

4.2. 哨兵模式

Sentinel(哨兵)是Redis的高可用性解决方案,由一个或多个Sentinel实例组成的Sentinel系统可以监视任意多个master以及属下的所有slave。Sentinel在被监视的master下线后,自动将其属下的某个slave升级为新的master,然后由新的master继续处理命令请求。具体流程如下:

  • 每个发现master进入客观下线的Sentinel都会要求其他Sentinel将自己设为局部领头Sentinel。
  • 当一个Sentinel向另一个Sentinel发送SENTINEL is-master-down-by-addr,且命令中的runid参数是自己的运行ID,这表明源Sentinel要求目标Sentinel将他设置为局部领头。
  • Sentinel设置局部领头的规则是先到先得。
  • 如果某个Sentinel被半数以上的Sentinel设置为局部领头,那么这个Sentinel就成为领头Sentinel。
  • 如果给定时限内,没有产生领头Sentinel,那么各个Sentinel过段时间再次选举,直到选出领头为止。

领头Sentinel会对已下线的master执行故障转移,包括以下三个步骤:

  • 从已下线master属下的所有slave选出一个新的master。
  • 让已下线master属下的所有slave改为新复制新的master。
  • 让已下线master成为新master的slave,重新上线后就是新slave。

新master的挑选规则:

  • 在线(必须)
  • 五秒内回复过领头Sentinel的INFO命令(必须)
  • salve的自身的优先级最高选择
  • 如果优先级相同,按复制偏移量最大选择
  • 如果偏移量一致,按照run id最小选择

5. Redis集群

5.1. Redis分槽

Redis集群通过分片的方式保存数据库中的键值对:集群中的整个数据库被分为16384个槽(slot),数据库中的每个键都属于其中的一个,集群中的每个节点可以处理0个或最多16384个槽。

当数据库中的16384个槽都有节点在处理时,集群处于上线状态(ok),如果任何一个槽都没有得到处理,就处于下线状态(fail)。

CLUSTER MEET只是将节点连接起来,集群仍处于下线状态,通过向节点发送CLUSTER ADDSLOTS,可以为一个或多个槽指派(assign)给节点负责。

可能是最亲民的Redis一文解读

对于 Redis 而言,不管是扩容还是缩容的节点迁移过程都大同小异:

  • 通知目标节点target发送准备导入槽slot数据。
  • 通知源节点source准备迁出槽数据。
  • 源节点source获取count个数据槽slot的键。
  • 源节点进行指定key迁移。
  • 重复上面两步,直到槽slot下所有的键值数据迁移到目标节点target。
  • 通知集群,槽slot已经分配给目标节点target。

5.2. 一致性哈希算法

Redis 分槽机制就是实现一致性哈希的方式之一,而一致性哈希说白了,就是复杂均衡算法的一种。

传统的哈希负载均衡算法如下所示:

可能是最亲民的Redis一文解读

直接对于请求的key值进行哈希取余,来决定其需要分配到哪台服务器上。

但是,如果采取传统的哈希取余,当我们需要进行扩缩容时,就会面临对所有的请求的哈希迁移问题,开销是很大的,因此,就引出了我们的一致性哈希。

一致哈希算法也用了取模运算,但与哈希算法不同的是,哈希算法是对节点的数量进行取模运算,而一致哈希算法是对 2^32 进行取模运算,是一个固定的值

我们可以把一致哈希算法是对 2^32 进行取模运算的结果值组织成一个圆环,就像钟表一样,钟表的圆可以理解成由 60 个点组成的圆,而此处我们把这个圆想象成由 2^32 个点组成的圆,这个圆环被称为哈希环,如下图:

可能是最亲民的Redis一文解读

当需要对指定 key 的值进行读写的时候,要通过下面 2 步进行寻址:

  • 首先,对 key 进行哈希计算,确定此 key 在环上的位置;
  • 然后,从这个位置沿着顺时针方向走,遇到的第一节点就是存储 key 的节点。

假设节点数量从 3 增加到了 4,新的节点 D 经过哈希计算后映射到了下图中的位置:

可能是最亲民的Redis一文解读

在一致哈希算法中,如果增加或者移除一个节点,仅影响该节点在哈希环上顺时针相邻的后继节点,其它数据也不会受到影响

一致性哈希算法虽然减少了数据迁移量,但是存在节点分布不均匀的哈希偏移问题(比如说上图节点B分配的key远少于节点C分配的key)。

为了解决这个问题,就引入了虚拟节点的概念,具体做法是,不再将真实节点映射到哈希环上,而是将虚拟节点映射到哈希环上,并将虚拟节点映射到实际节点

可能是最亲民的Redis一文解读

虚拟节点除了会提高节点的均衡度,还会提高系统的稳定性。当节点变化时,会有不同的节点共同分担系统的变化,因此稳定性更高

6. 常见问题

缓存雪崩:大量的key在同一时间过期,导致大量请求走到了数据库->【解决】选择合适的过期机制。

缓存穿透:大量请求打到了不存在缓存的key->【解决】使用布隆过滤器过滤无效请求。

缓存击穿:单个高频的key过期,导致大量请求走到了数据库->【解决】热门key不过期。

转载自:https://juejin.cn/post/7380163683010379839
评论
请登录