可能是最亲民的Redis一文解读
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 而言,不管是扩容还是缩容的节点迁移过程都大同小异:
- 通知目标节点target发送准备导入槽slot数据。
- 通知源节点source准备迁出槽数据。
- 源节点source获取count个数据槽slot的键。
- 源节点进行指定key迁移。
- 重复上面两步,直到槽slot下所有的键值数据迁移到目标节点target。
- 通知集群,槽slot已经分配给目标节点target。
5.2. 一致性哈希算法
Redis 分槽机制就是实现一致性哈希的方式之一,而一致性哈希说白了,就是复杂均衡算法的一种。
传统的哈希负载均衡算法如下所示:
直接对于请求的key值进行哈希取余,来决定其需要分配到哪台服务器上。
但是,如果采取传统的哈希取余,当我们需要进行扩缩容时,就会面临对所有的请求的哈希迁移问题,开销是很大的,因此,就引出了我们的一致性哈希。
一致哈希算法也用了取模运算,但与哈希算法不同的是,哈希算法是对节点的数量进行取模运算,而一致哈希算法是对 2^32 进行取模运算,是一个固定的值。
我们可以把一致哈希算法是对 2^32 进行取模运算的结果值组织成一个圆环,就像钟表一样,钟表的圆可以理解成由 60 个点组成的圆,而此处我们把这个圆想象成由 2^32 个点组成的圆,这个圆环被称为哈希环,如下图:
当需要对指定 key 的值进行读写的时候,要通过下面 2 步进行寻址:
- 首先,对 key 进行哈希计算,确定此 key 在环上的位置;
- 然后,从这个位置沿着顺时针方向走,遇到的第一节点就是存储 key 的节点。
假设节点数量从 3 增加到了 4,新的节点 D 经过哈希计算后映射到了下图中的位置:
在一致哈希算法中,如果增加或者移除一个节点,仅影响该节点在哈希环上顺时针相邻的后继节点,其它数据也不会受到影响。
一致性哈希算法虽然减少了数据迁移量,但是存在节点分布不均匀的哈希偏移问题(比如说上图节点B分配的key远少于节点C分配的key)。
为了解决这个问题,就引入了虚拟节点的概念,具体做法是,不再将真实节点映射到哈希环上,而是将虚拟节点映射到哈希环上,并将虚拟节点映射到实际节点。
虚拟节点除了会提高节点的均衡度,还会提高系统的稳定性。当节点变化时,会有不同的节点共同分担系统的变化,因此稳定性更高。
6. 常见问题
缓存雪崩:大量的key在同一时间过期,导致大量请求走到了数据库->【解决】选择合适的过期机制。
缓存穿透:大量请求打到了不存在缓存的key->【解决】使用布隆过滤器过滤无效请求。
缓存击穿:单个高频的key过期,导致大量请求走到了数据库->【解决】热门key不过期。
转载自:https://juejin.cn/post/7380163683010379839