分布式缓存

YangeIT大约 49 分钟高级服务框架分布式缓存Redis高级Redis持久化RDB持久化AOF持久化Redis主从Redis哨兵Redis分片集群

学习目标

  1. Redis持久化
  2. Redis主从
  3. Redis哨兵
  4. Redis分片集群

分布式缓存

单机Redis存在的问题

基于Redis集群解决单机Redis存在的问题 单机的Redis存在四大问题:

image-20210725144240631
image-20210725144240631

1.Redis持久化

1.1.RDB持久化 🍐

RDB持久化

Redis有两种持久化方案:

  • RDB持久化
  • AOF持久化
image
image

RDB全称Redis Database Backup file(Redis数据备份文件),也被叫做Redis数据快照

简单来说就是把内存中的所有数据都记录到磁盘中。当Redis实例故障重启后,从磁盘读取快照文件,恢复数据。

快照文件称为RDB文件,默认是保存在当前运行目录。

小结

面试题:RDB方式bgsave的基本流程?

  • fork主进程得到一个子进程,共享内存空间
  • 子进程读取内存数据并写入新的RDB文件
  • 用新RDB文件替换旧的RDB文件

面试题:RDB会在什么时候执行?save 60 1000代表什么含义?

  • 默认是服务停止时
  • 代表60秒内至少执行1000次修改则触发RDB

面试题:RDB的缺点?

  • RDB执行间隔时间长,两次RDB之间写入数据有丢失的风险
  • fork子进程、压缩、写出RDB文件都比较耗时

1.2.AOF持久化

AOF持久化

AOF原理

AOF全称为Append Only File(追加文件)。Redis处理的每一个写命令都会记录在AOF文件,可以看做是命令日志文件。

image-20210725151543640
image-20210725151543640

记录指令,以及指令的长度 如$3表示长度为3

总结

面试题:RDB与AOF的区别

RDB和AOF各有自己的优缺点,如果对数据安全性要求较高,在实际开发中往往会结合两者来使用。

image-20210725151940515
image-20210725151940515

2.Redis主从

2.1.搭建主从架构

搭建主从架构

单节点Redis的并发能力是有上限的,要进一步提高Redis的并发能力,就需要搭建主从集群,实现读写分离。

image-20210725152037611
image-20210725152037611

具体搭建流程参考课前资料:https://b11et3un53m.feishu.cn/wiki/Jck7w4GBSia4sukQn1vc9s3anMfopen in new window

image-20210725152052501
image-20210725152052501

2.2.主从数据同步原理 🍐

主从数据同步原理

1.全量同步

主从第一次建立连接时,会执行全量同步,将master节点的所有数据都拷贝给slave节点,流程:

image-20210725152222497
image-20210725152222497

这里有一个问题,master如何得知salve是第一次来连接呢??

有几个概念,可以作为判断依据:

  • Replication Id:简称replid,是数据集的标记,id一致则说明是同一数据集。每一个master都有唯一的replid,slave则会继承master节点的replid
  • offset:偏移量,随着记录在repl_baklog中的数据增多而逐渐增大。slave完成同步时也会记录当前同步的offset。如果slave的offset小于master的offset,说明slave数据落后于master,需要更新。

因此slave做数据同步,必须向master声明自己的replication id 和offset,master才可以判断到底需要同步哪些数据。

因为slave原本也是一个master,有自己的replid和offset,当第一次变成slave,与master建立连接时,发送的replid和offset是自己的replid和offset。

master判断发现slave发送来的replid与自己的不一致,说明这是一个全新的slave,就知道要做全量同步了。

master会将自己的replid和offset都发送给这个slave,slave保存这些信息。以后slave的replid就与master一致了。

因此,master判断一个节点是否是第一次同步的依据,就是看replid是否一致

如图:

image-20210725152700914
image-20210725152700914

完整流程描述:

  • slave节点请求增量同步
  • master节点判断replid,发现不一致,拒绝增量同步
  • master将完整内存数据生成RDB,发送RDB到slave
  • slave清空本地数据,加载master的RDB
  • master将RDB期间的命令记录在repl_baklog,并持续将log中的命令发送给slave
  • slave执行接收到的命令,保持与master之间的同步
image
image

2.3.主从同步优化 🍐

主从同步优化

主从同步可以保证主从数据的一致性,非常重要。

可以从以下几个方面来优化Redis主从集群:

  • 在master中配置repl-diskless-sync yes启用无磁盘复制,避免全量同步时的磁盘IO。
  • Redis单节点上的内存占用不要太大,减少RDB导致的过多磁盘IO
  • 适当提高repl_baklog的大小,发现slave宕机时尽快实现故障恢复,尽可能避免全量同步
  • 限制一个master上的slave节点数量,如果实在是太多slave,则可以采用主-从-从链式结构,减少master压力

主从从架构图:

image-20210725154405899
image-20210725154405899

演示资料说明:

image
image

小结

面试题:简述全量同步和增量同步区别?

  • 全量同步:master将完整内存数据生成RDB,发送RDB到slave。后续命令则记录在repl_baklog,逐个发送给slave。
  • 增量同步:slave提交自己的offset到master,master获取repl_baklog中从offset之后的命令给slave

面试题:什么时候执行全量同步?

  • slave节点第一次连接master节点时
  • slave节点断开时间太久,repl_baklog中的offset已经被覆盖时

面试题:什么时候执行增量同步?

  • slave节点断开又恢复,并且在repl_baklog中能找到offset时

面试题:如果master的从节点太多,压力很大,有什么优化措施?

3.Redis哨兵

3.1.哨兵原理

哨兵原理

Redis提供了哨兵(Sentinel)机制来实现主从集群的自动故障恢复。

1.集群结构和作用

哨兵的结构如图:

image-20210725154528072
image-20210725154528072

哨兵的作用如下:

  • 监控:Sentinel 会不断检查您的master和slave是否按预期工作
  • 自动故障恢复:如果master故障,Sentinel会将一个slave提升为master。当故障实例恢复后也以新的master为主
  • 通知:Sentinel充当Redis客户端的服务发现来源,当集群发生故障转移时,会将最新信息推送给Redis的客户端

小结

  • Sentinel的三个作用是什么?

    • 监控
    • 故障转移
    • 通知
  • Sentinel如何判断一个redis实例是否健康?

    • 每隔1秒发送一次ping命令,如果超过一定时间没有相向则认为是主观下线
    • 如果大多数sentinel都认为实例主观下线,则判定服务下线
  • 故障转移步骤有哪些?

    • 首先选定一个slave作为新的master,执行slaveof no one
    • 然后让所有节点都执行slaveof 新master
    • 修改故障节点配置,添加slaveof 新master

3.2.搭建哨兵集群

搭建哨兵集群

具体搭建流程参考课前资料《Redis集群.md》

image-20210725155019276
image-20210725155019276

搭建哨兵集群在企业生产过程中,主要是运维人员的职责,本章节主要目的是理解哨兵机制的运行流程和特点,因此使用现有的资料,在win下快速搭建哨兵集群: 👈 👈

案例结构:一主二从三哨兵

image
image

启动流程:

  1. 先启动1主二从:6379 6380 6381
  2. 修改哨兵配置,下图2
  3. 然后启动3哨兵:26379,26380,26381 下图3图很大,建议,右击新开窗口查看

资料说明:

图1
图1
图2
图2

参考上述步骤,修改另外2个哨兵配置文件

图3
图3

总结

课堂作业

面试题:Sentinel的三个作用是什么?

  • 集群监控
  • 故障恢复
  • 状态通知🎤

面试题:Sentinel如何判断一个redis实例是否健康?

  • 每隔1秒发送一次ping命令,如果超过一定时间没有相向则认为是主观下线(sdown)
  • 如果大多数sentinel都认为实例主观下线,则判定服务客观下线(odown)

面试题:故障转移步骤有哪些?

  • 首先要在sentinel中选出一个leader,由leader执行failover
  • 选定一个slave作为新的master,执行slaveof noone,切换到master模式
  • 然后让所有节点都执行slaveof 新master
  • 修改故障节点配置,添加slaveof 新master

面试题:sentinel选举leader的依据是什么?

  • 票数超过sentinel节点数量1半
  • 票数超过quorum数量
  • 一般情况下最先发起failover的节点会当选

试题:sentinel从slave中选取master的依据是什么?

  • 首先会判断slave节点与master节点断开时间长短,如果超过down-after-milliseconds * 10则会排除该slave节点
  • 然后判断slave节点的slave-priority值,越小优先级越高,如果是0则永不参与选举(默认都是1)。
  • 如果slave-prority一样,则判断slave节点的offset值,越大说明数据越新,优先级越高
  • 最后是判断slave节点的run_id大小,越小优先级越高(通过info server可以查看run_id)。

3.3.RedisTemplate连接哨兵集群 选学,自学

RedisTemplate连接哨兵集群

在Sentinel集群监管下的Redis主从集群,其节点会因为自动故障转移而发生变化,Redis的客户端必须感知这种变化,及时更新连接信息。Spring的RedisTemplate底层利用lettuce实现了节点的感知和自动切换。

下面,我们通过一个测试来实现RedisTemplate集成哨兵机制。

代码操作

3.3.1.导入Demo工程

首先,我们引入课前资料提供的Demo工程:

image-20210725155124958
image-20210725155124958

4.Redis分片集群

4.1.搭建分片集群

搭建分片集群

image
image

主从和哨兵可以解决高可用、高并发读的问题。但是依然有两个问题没有解决:

  • 海量数据存储问题
  • 高并发写的问题

使用分片集群可以解决上述问题,如图:

image-20210725155747294
image-20210725155747294

分片集群特征:

  • 集群中有多个master,每个master保存不同分片数据 ,解决海量数据存储问题
  • 每个master都可以有多个slave节点 ,确保高可用
  • master之间通过ping监测彼此健康状态 ,类似哨兵作用
  • 客户端请求可以访问集群任意节点,最终都会被转发到数据所在节点

总结

课堂作业

  1. 面试题:分片集群特征有哪些?🎤

4.2.散列插槽

散列插槽

Redis插槽(Redis Slots)是用于分片的概念,它在Redis集群中用于将数据均匀分散到多个Redis节点上。每个插槽对应一个特定的数据范围,Redis使用哈希槽(Hash Slot)算法来确定键(Key)应该存储在哪个插槽中。

Redis的插槽机制允许横向扩展和分布式数据存储,以提高性能和可扩展性。

每个Redis集群节点都负责维护一部分插槽,并负责处理与这些插槽相关的键。这使得Redis集群能够处理大量的数据,而不会因单一节点的容量限制而导致性能下降。

关于Redis插槽的一些要点:

  • Redis插槽的数量是固定的,默认有16384个插槽(0-16383)。
  • 插槽的分配是自动的,Redis会尝试平均分配插槽到可用的节点上。
  • 插槽的分配信息可以使用CLUSTER SLOTS命令查看。
  • Redis的插槽机制允许在集群中添加或删除节点,以进行横向扩展或缩减。
  • 添加或删除插槽的数量通常需要对Redis集群进行重新分片,这可能需要一些操作。

关于增加插槽的数量,Redis的插槽数量是在集群初始化时固定的,通常情况下不建议更改插槽的数量,因为它会涉及到对整个集群的重新分片操作,可能导致数据的迁移和集群的不稳定。

总结

Redis如何判断某个key应该在哪个实例?

  • 将16384个插槽分配到不同的实例
  • 根据key的有效部分计算哈希值,对16384取余
  • 余数作为插槽,寻找插槽所在实例即可

如何将同一类数据固定的保存在同一个Redis实例?

  • 这一类数据使用相同的有效部分,例如key都以{typeId}为前缀 image

4.3.集群伸缩选学,可以自行学习哦

集群伸缩

redis-cli --cluster提供了很多操作集群的命令,可以通过下面方式查看:

image-20210725160138290
image-20210725160138290

比如,添加节点的命令:

image-20210725160448139
image-20210725160448139

--cluster-slave 身份定了 -- cluster-master_id 是谁的奴隶

代码操作

接下来在win11的系统下操作

2.创建新的redis实例

  1. 查看当前实例
redis-cli.exe -p 6380 cluster nodes
image
image

2.创建一个将任何一个文件夹复制一下 6386,该问:

image
image
  1. 修改配置文件:
image
image

配置文件名字,也改成6386

4.编写启动脚本---启动

image
image
image
image

目前还只有6个节点

4.4.故障转移

故障转移

集群初识状态是这样的:

image
image

其中6380,6386,6381,6382都是master,我们计划让6382宕机。

代码操作

1.自动故障转移

当集群中有一个master宕机会发生什么呢?

直接停止一个redis实例,例如6382:

直接差叼窗口 image

1)首先是该实例与其它实例失去连接

2)然后是疑似宕机:

image
image

3)最后是确定下线,自动提升一个slave为新的master:

image
image

4)当6382再次启动,就会变为一个slave节点了:

image
image

4.5.RedisTemplate访问分片集群 选学,自行学习

RedisTemplate访问分片集群

RedisTemplate底层同样基于lettuce实现了分片集群的支持,而使用的步骤与哨兵模式基本一致:

  • 1)引入redis的starter依赖
  • 2)配置分片集群地址
  • 3)配置读写分离

与哨兵模式相比,其中只有分片集群的配置方式略有差异,如下:

spring:
  redis:
    cluster:
      nodes: # 配置分片集群的地址
        - 192.168.150.101:7001
        - 192.168.150.101:7002
        - 192.168.150.101:7003
        - 192.168.150.101:8001
        - 192.168.150.101:8002
        - 192.168.150.101:8003

在项目的启动类中,添加一个新的bean:

@Bean
public LettuceClientConfigurationBuilderCustomizer clientConfigurationBuilderCustomizer(){
    return clientConfigurationBuilder -> clientConfigurationBuilder.readFrom(ReadFrom.REPLICA_PREFERRED);
}

5.Redis 数据结构

RedisObject

我们常用的 Redis 数据类型有 5 种,分别是:

  • String
  • List
  • Set
  • SortedSet
  • Hash

还有一些高级数据类型,比如 Bitmap、HyperLogLog、GEO 等,其底层都是基于上述 5 种基本数据类型。因此在 Redis 的源码中,其实只有 5 种数据类型。

5.1 RedisObject

不管是任何一种数据类型,最终都会封装为 RedisObject 格式,它是一种结构体,C 语言中的一种结构,可以理解为 Java 中的类。

结构大概是这样的:

可以看到整个结构体中并不包含真实的数据,仅仅是对象头信息,内存占用的大小为 4+4+24+32+64 = 128bit

也就是 16 字节,然后指针 ptr 指针指向的才是真实数据存储的内存地址。所以 RedisObject 的内存开销是很大的。

属性中的 encoding 就是当前对象底层采用的数据结构编码方式,可选的有 11 种之多:

##### 编号##### 编码方式##### 说明
0OBJ_ENCODING_RAWraw 编码动态字符串
1OBJ_ENCODING_INTlong 类型的整数的字符串
2OBJ_ENCODING_HThash 表(也叫 dict)
3OBJ_ENCODING_ZIPMAP已废弃
4OBJ_ENCODING_LINKEDLIST双端链表
5OBJ_ENCODING_ZIPLIST压缩列表
6OBJ_ENCODING_INTSET整数集合
7OBJ_ENCODING_SKIPLIST跳表
8OBJ_ENCODING_EMBSTRembstr 编码的动态字符串
9OBJ_ENCODING_QUICKLIST快速列表
10OBJ_ENCODING_STREAMStream 流
11OBJ_ENCODING_LISTPACK紧凑列表

Redis 中的 5 种不同的数据类型采用的底层数据结构和编码方式如下:

##### 数据类型##### 编码方式
STRINGintembstrraw
LISTLinkedList和ZipList(3.2 以前)、QuickList(3.2 以后)
SETintsetHT
ZSETZipList(7.0 以前)、Listpack(7.0 以后)、HTSkipList
HASHZipList(7.0 以前)、Listpack(7.0 以后)、HT

这些数据类型比较复杂,我们重点讲解几个面试会问的,其它的大家可以查看黑马程序员发布的 Redis 专业课程

总结

课堂作业

  1. 为什么Redis中使用SkipList(跳表)?🎤
  2. 在 Redis 的源码中,有几种数据类型?

6.Redis 内存回收

内存回收

前言

Redis 之所以性能强,最主要的原因就是基于内存存储。然而单节点的 Redis 其内存大小不宜过大,会影响持久化或主从同步性能。

我们可以通过修改 redis.conf 文件,添加下面的配置来配置 Redis 的最大内存:

maxmemory 1gb

当内存达到上限,就无法存储更多数据了。因此,Redis 内部会有两套内存回收的策略:

  • 内存过期策略
  • 内存淘汰策略

6.1.内存过期处理

存入 Redis 中的数据可以配置过期时间,到期后再次访问会发现这些数据都不存在了,也就是被过期清理了。

1️⃣ 过期命令

Redis 中通过 expire 命令可以给 KEY 设置 TTL(过期时间),例如:

# 写入一条数据
set num 123
# 设置20秒过期时间
expire num 20

不过 set 命令本身也可以支持过期时间的设置:

# 写入一条数据并设置20s过期时间
set num EX 20

当过期时间到了以后,再去查询数据,会发现数据已经不存在。


2️⃣ 过期策略

那么问题来了:

  • Redis 如何判断一个 KEY 是否过期呢?
  • Redis 又是何时删除过期 KEY 的呢?

Redis 不管有多少种数据类型,本质是一个 KEY-VALUE 的键值型数据库,而这种键值映射底层正式基于 HashTable 来实现的,在 Redis 中叫做 Dict.

来看下 RedisDB 的底层源码:

typedef struct redisDb {
    dict _dict;                 /_ The keyspace for this DB , 也就是存放KEY和VALUE的哈希表*/
    dict *expires;              /* 同样是哈希表,但保存的是设置了TTL的KEY,及其到期时间*/
    dict *blocking_keys;        /* Keys with clients waiting for data (BLPOP)*/
    dict *ready_keys;           /* Blocked keys that received a PUSH */
    dict *watched_keys;         /* WATCHED keys for MULTI/EXEC CAS _/
__    int id;                     /_ Database ID, 0 ~ 15 _/
__    long long avg_ttl;          /_ Average TTL, just for stats _/
__    unsigned long expires_cursor; /_ Cursor of the active expire cycle. */
    list *defrag_later;         /* List of key names to attempt to defrag one by one, gradually. */
} redisDb;

现在回答第一个问题:

Redis 是何时删除过期 KEY 的呢?

Redis 并不会在 KEY 过期时立刻删除 KEY,因为要实现这样的效果就必须给每一个过期的 KEY 设置时钟,并监控这些 KEY 的过期状态。无论对 CPU 还是内存都会带来极大的负担。

Redis 的过期 KEY 删除策略有两种:

  • 惰性删除
  • 周期删除

惰性删除,顾明思议就是过期后不会立刻删除。那在什么时候删除呢? ❓

Redis 会在每次访问 KEY 的时候判断当前 KEY 有没有设置过期时间,如果有,过期时间是否已经到期。对应的源码如下:

// db.c
// 寻找要执行写操作的key
robj *lookupKeyWriteWithFlags(redisDb *db, robj *key, int flags) {
    // 检查key是否过期,如果过期则删除
    expireIfNeeded(db,key);
    return lookupKey(db,key,flags);
}

// 寻找要执行读操作的key
robj *lookupKeyReadWithFlags(redisDb *db, robj *key, int flags) {
    robj *val;
    // 检查key是否过期,如果过期则删除
    if (expireIfNeeded(db,key) == 1) {
        // 略 ...
    }
    val = lookupKey(db,key,flags);
    if (val == NULL)
        goto keymiss;
    server.stat_keyspace_hits++;
    return val;
}

周期删除:顾明思议是通过一个定时任务,周期性的抽样部分过期的 key,然后执行删除。

执行周期有两种:

  • **SLOW 模式:**Redis 会设置一个定时任务 serverCron(),按照 server.hz 的频率来执行过期 key 清理
  • **FAST 模式:**Redis 的每个事件循环前执行过期 key 清理(事件循环就是 NIO 事件处理的循环)。

SLOW 模式规则:

  • ① 执行频率受 server.hz 影响,默认为 10,即每秒执行 10 次,每个执行周期 100ms。
  • ② 执行清理耗时不超过一次执行周期的 25%,即 25ms.
  • ③ 逐个遍历 db,逐个遍历 db 中的 bucket,抽取 20 个 key 判断是否过期
  • ④ 如果没达到时间上限(25ms)并且过期 key 比例大于 10%,再进行一次抽样,否则结束

FAST 模式规则(过期 key 比例小于 10% 不执行):

  • ① 执行频率受 beforeSleep() 调用频率影响,但两次 FAST 模式间隔不低于 2ms
  • ② 执行清理耗时不超过 1ms
  • ③ 逐个遍历 db,逐个遍历 db 中的 bucket,抽取 20 个 key 判断是否过期
  • ④ 如果没达到时间上限(1ms)并且过期 key 比例大于 10%,再进行一次抽样,否则结束

6.缓存问题

缓存问题

前言

Redis 经常被用作缓存,而缓存在使用的过程中存在很多问题需要解决。例如:

  • 缓存的数据一致性问题
  • 缓存击穿
  • 缓存穿透
  • 缓存雪崩

6.1.缓存一致性

我们先看下目前企业用的最多的缓存模型。缓存的通用模型有三种:

  • Cache Aside:有缓存调用者自己维护数据库与缓存的一致性。即:

    • 查询时:命中则直接返回,未命中则查询数据库并写入缓存
    • 更新时:更新数据库并删除缓存,查询时自然会更新缓存
  • Read/Write Through:数据库自己维护一份缓存,底层实现对调用者透明。底层实现:

    • 查询时:命中则直接返回,未命中则查询数据库并写入缓存
    • 更新时:判断缓存是否存在,不存在直接更新数据库。存在则更新缓存,同步更新数据库
  • Write Behind Cahing:读写操作都直接操作缓存,由线程异步的将缓存数据同步到数据库

目前企业中使用最多的就是 Cache Aside 模式,因为实现起来非常简单。但缺点也很明显,就是无法保证数据库与缓存的强一致性。为什么呢?我们一起来分析一下。

Cache Aside 的写操作是要在更新数据库的同时删除缓存,那为什么不选择更新数据库的同时更新缓存,而是删除呢?

原因很简单,假如一段时间内无人查询,但是有多次更新,那这些更新都属于无效更新。采用删除方案也就是延迟更新,什么时候有人查询了,什么时候更新。

那到底是先更新数据库再删除缓存,还是先删除缓存再更新数据库呢?

现在假设有两个线程,一个来更新数据,一个来查询数据。我们分别分析两种策略的表现。

我们先分析策略 1,先更新数据库再删除缓存:

正常情况

异常情况

异常情况说明:

  • 线程 1 删除缓存后,还没来得及更新数据库,
  • 此时线程 2 来查询,发现缓存未命中,于是查询数据库,写入缓存。由于此时数据库尚未更新,查询的是旧数据。也就是说刚才的删除白删了,缓存又变成旧数据了。
  • 然后线程 1 更新数据库,此时数据库是新数据,缓存是旧数据

由于更新数据库的操作本身比较耗时,在期间有线程来查询数据库并更新缓存的概率非常高。因此不推荐这种方案。

再来看策略 2,先更新数据库再删除缓存:

正常情况

异常情况

异常情况说明:

  • 线程 1 查询缓存未命中,于是去查询数据库,查询到旧数据
  • 线程 1 将数据写入缓存之前,线程 2 来了,更新数据库,删除缓存
  • 线程 1 执行写入缓存的操作,写入旧数据

可以发现,异常状态发生的概率极为苛刻,线程 1 必须是查询数据库已经完成,但是缓存尚未写入之前。线程 2 要完成更新数据库同时删除缓存的两个操作。要知道线程 1 执行写缓存的速度在毫秒之间,速度非常快,在这么短的时间要完成数据库和缓存的操作,概率非常之低。

其他面试题

  • 初级级别:

    • 什么是Redis的持久化,为什么我们需要持久化?
    • 请解释Redis的主从架构,主节点和从节点各自承担什么角色?
    • 如何进行Redis的数据备份?
    • 什么是Redis的RDB快照持久化,它的优点和缺点是什么?
    • 什么是Redis的AOF持久化,它的优点和缺点是什么?
  • 中级级别:

    • 请解释Redis主从复制的同步原理,包括全同步和增量同步。
    • 什么是Redis哨兵机制,它的作用是什么,如何配置和使用?
    • 请讨论Redis的分片集群是如何工作的,有什么优势和挑战?
    • 什么是散列插槽(Hash Slot),它在Redis集群中的作用是什么?
    • 在Redis集群中,如何进行集群的伸缩(扩展或缩减集群规模)?
  • 高级级别:

    • Redis的RDB快照和AOF持久化在哪些场景下更适合使用,如何选择合适的持久化方式?
    • 在Redis主从复制中,如果主节点宕机,会如何处理,从节点如何晋升为主节点?
    • Redis哨兵机制的工作原理是什么,如何配置多个哨兵来实现高可用性?
    • Redis集群中的数据分片和散列插槽是如何实现的,有什么注意事项?
    • 请解释Redis的数据分片策略,例如一致性哈希和哈希槽分片。