关于 Redis 的这些知识 你知道哪些
1、Redis 高可用的实现
实现 Redis 的高可用,主要有 哨兵 和 集群 两种方式。
1.1、哨兵
Redis Sentinel(哨兵)是一个 分布式架构,它包含 若干 哨兵节点 和 数据节点,每个哨兵节点 会对 数据节点 和 其余的哨兵节点 进行监控,当发现节点不可达(节点发生宕机)时,会对节点做 下线标识。
如果 被标识的 是 主节点,它就会与 其它的哨兵节点 进行协商,当 多数哨兵节点 都认为 主节点 不可达时,它们便会选举出一个 哨兵节点 来完成 自动故障转移 的工作,同时还会将这个变化实时地通知给 应用方(整个过程是自动完成,不需要人工介入)。
注:一组哨兵 可以监控 一个(或多个)主节点。
补充:哨兵节点的特征
- 哨兵节点 会 定期 监控数据节点、其它哨兵节点 是否可达;
- 哨兵节点 会 将 故障转移的结果 通知给应用方;
- 哨兵节点 可以 将 从节点 晋升为 主节点,并维护后续正确的主从关系;
- 哨兵模式下,客户端 连接的是 哨兵节点集合,并从中获取主节点信息;
- 节点的故障判断 是由 多个哨兵节点 共同完成的,可以有效防止误判;
- 哨兵节点集合 是由 多个哨兵节点 组成,即使 个别哨兵节点 不可用,整个集合依然可以正常工作;
- 哨兵节点 是 独立的、特殊的 Redis 节点,它们 不存储数据,只支持部分命令。
1.2、集群
Redis 集群 采用 虚拟槽分区 来实现 数据分片,它把 所有的键 根据 哈希函数映射 到 0 - 16383 整数槽内(计算公式为 slot = CRC16(key)&16383
),每一个节点 负责维护一部分 槽和槽所映射的键值数据。
虚拟槽分区的特点:
- 解耦 数据 和 节点 之间的关系,简化了 节点 扩容 和 收缩 的难度;
- 节点自身 维护 槽的映射关系,不需要 客户端(或代理服务)维护 曹分区的元数据;
- 支持 节点、槽、键 之间的 映射查询,用于 数据路由、在线伸缩 等场景。
2、Redis 的主从同步
主从同步,是指将一台 Redis 服务器的数据,复制到其它的 Redis 服务器上,前者称为主节点(Master),后者称为从节点(Slave)。数据的复制是单向的,只能由主节点复制到从节点(主节点以写为主、从节点以读为主)。
注:默认情况下,每台 Redis 服务器 都是主节点,一个主节点可以有 0 个或者多个从节点,但每个从节点只能有一个主节点。
Redis 使用 psync 命令 完成主从同步,同步过程分为 全量复制 和 部分复制,全量复制 一般用于 初次复制的场景,部分复制 则用于 处理因网络中断 等原因 造成数据丢失的场景。
psync 命令 所需要的 参数:
- 复制偏移量
- 主节点处理写命令后,会把命令长度做累加记录;从节点在接收到写命令后,也会做累加记录;
- 从节点会每秒上报一次自身的复制偏移量给主节点,而主节点则会保存从节点的复制偏移量。
- 积压缓存区
- 保存在主节点上的一个固定长度的队列,默认大小为 1M,当主节点有连接的从节点时被创建出来;
- 主节点在处理命令时,不但会把命令发送给从节点,还会写入积压缓冲区;
- 缓存区是先进先出的队列,可以保存最近已复制的数据,用于部分复制和命令丢失的数据补救。
- 主节点运行 ID
- 每个 Redis 节点启动后,都会动态分配一个 40 位的十六进制字符串作为运行 ID;
- 如果使用 IP 和 端口 的方式标识主节点,那么主节点重启变更了数据集(RDB/AOP),从节点再基于复制偏移量复制数据将是不安全的,因此当主节点的运行 ID 变化后,从节点将做全量复制。
3、Redis 的淘汰策略
当写入数据将导致超出 maxmemory 限制时,Redis 会采用 maxmemory-policy 所指定的策略进行数据淘汰。
8 种淘汰策略:
策略 | 说明 |
---|---|
noeviction | 直接返回错误 |
volatile-ttl | 从设置了过期时间的键中,选择过期时间最小的键,进行淘汰 |
volatile-random | 从设置了过期时间的键中,随机选择键,进行淘汰 |
volatile-lru | 从设置了过期时间的键中,使用 LRU 算法选择键,进行淘汰 |
volatile-lfu | 从设置了过期时间的键中,使用 LFU 算法选择键,进行淘汰 |
allkeys-random | 从所有的键中,随机选择键,进行淘汰 |
allkeys-lru | 从所有的键中,使用 LRU 算法选择键,进行淘汰 |
allkeys-lfu | 从所有的键中,使用 LFU 算法选择键,进行淘汰 |
补充:
1、LRU(Least Recently Used,最近最少使用)
标准 LRU
把所有的数据组成一个链表,表头和表尾分别表示 MRU端 和 LRU 端(即 最常使用端 和 最少使用端),新增的数据(或 刚被访问的数据)会被移动到 MRU 端,当链表的空间被占满时,它会删除 LRU 端的数据。
近似 LRU
记录每个数据的最近一次访问的时间戳,当 内存超出限制 时,随机采样 N 个 key,淘汰最旧的 key。(可以通过 maxmemory_samples 设置采样个数,默认值为 5)
2、LFU(Least Frequently Used,最少频率使用)
在 LRU 的基础上,为每个数据增加了一个计数器,来统计该数据的访问次数,当 内存超出限制 时,首先会将 访问次数最低 的数据淘汰出去,如果两个数据的访问次数相同,则将 访问时间更早 的数据淘汰出去。
4、Redis 的过期策略
惰性删除
客户端访问一个 key 的时候,Redis 会先检查它的过期时间,如果发现过期就立刻删除这个 key 。
定期删除
Redis 会将设置了过期时间的 key 放到一个独立的字典中,并对该字典进行每秒 10 次的过期扫描,过期扫描不会遍历字典中所有的 key,而是采用了一个种简单的贪心策略:
- 从过期字典中随机选择 20 个 key;
- 删除这 20 个 key 中已过期的 key;
- 如果过期 key 的比例超过 25%,则重复步骤 1 。
5、缓存穿透、击穿、雪崩
5.1、缓存穿透
问题描述:
客户端查询根本不存在的数据,使得请求知道存储层,导致其负载过大,甚至宕机。出现这个情况的原因,可能是业务层误将缓存和库中的数据删除了,也可能是有人恶意攻击,专门访问库中不存在的数据。
解决方案:
缓存空对象
存储层未命中后,仍然将空值存入缓存层,客户端再次访问数据时,缓存层会直接返回空值。
布隆过滤器
将数据存入布隆过滤器,访问缓存之前以过滤器拦截,若请求的数据不存在,则直接返回空值。
扩展:布隆过滤器
- 核心组成
- 一个大型的位数组
- 若干个不一样的哈希函数,每个哈希函数都能将哈希值算的比较均匀
- 工作原理
- 添加 key 时,每个哈希函数都利用这个 key 计算出一个哈希值,再根据哈希值计算一个位置,并将位数组中这个位置的值设置为 1;
- 询问 key 时,每个哈希函数都利用这个 key 计算出一个哈希值,再根据哈希值计算一个位置,并对比这些哈希函数在位数组中对应位置的数值。
- 如果这几个位置中,有一个位置的值都是 0,就说明这个布隆过滤器中,不存在这个 key;
- 如果这几个位置中,所有位置的值都是 1,就说明这个布隆过滤器中,极有可能存在这个 key(存在一定的错误率)。
5.2、缓存击穿
问题描述:
一份热点数据,它的访问量非常大,在其缓存失效的瞬间,大量请求知道存储层,导致服务奔溃。
解决方案:
永不过期
热点数据不设置过期时间,或者 为每个数据设置逻辑过期时间,当发现该数据逻辑过期时,使用单独的线程重建缓存。
加互斥锁
对数据的访问加互斥锁,当一个线程访问数据时,其它线程只能等待,这个线程访问过后,缓存中的数据将被重建,届时其它线程就可以直接从缓存中取值。
5.3、缓存雪崩
问题描述:
在某一时刻,缓存层无法继续提供服务,导致所有的请求知道存储层,造成数据库宕机,可能是缓存中有大量数据同时过期,也可能是 Redis 节点发生故障,导致大量请求无法得到处理。
解决方案:
避免数据同时过期
设置过期时间时,附加一个随机数,避免大量的 key 同时过期。
启动降级和熔断措施
在发生雪崩时,若应用访问的不是核心数据,则直接返回预定义信息 / 空值 / 错误信息,或者 在发生雪崩时,对于访问缓存接口的请求,客户端并不会把请求发给 Redis,而是直接返回。
构建高可用的 Redis 服务
采用哨兵或集群模式,部署多个 Redis 实例,个别节点宕机,依然可以保持服务的整体可用。
6、缓存与数据库的双写一致性
4 种同步策略:
- 先更新缓存,再更新数据库;
- 先更新数据库,再更新缓存;
- 先删除缓存,再更新数据库;
- 先更新数据库,再删除缓存(常用)。
扩展:延时双删
- 删除缓存;
- 更新数据库;
- sleep N 毫秒;
- 再次删除缓存。