java八股文之Redis
目录
Redis的作用
缓存篇
缓存穿透
触发原因
产生后果
解决方法
缓存击穿
触发原因
产生后果
解决方法
缓存雪崩
触发原因
产生后果
解决方案
双写一致性
一致性要求高的情况(强一致性)
允许延迟一致的情况(最终一致性)
持久化
RDB(Redis DataBase)持久化
AOF(Append Only File)持久化
混合持久化(RDB + AOF)
Redis数据过期策略
定期删除(定时扫描)
惰性删除(惰性清理)
内存淘汰机制(内存限制下的淘汰策略)
Redis 支持的淘汰策略
LRU与LFU区别
常用场景的淘汰策略选择
分布式锁篇
使用场景
使用原因
Redisson实现分布式锁
集群篇
主从复制
功能架构
工作机制
基本操作
哨兵模式
核心功能
功能架构
工作原理
配置操作
分片集群
架构及特点
工作原理
配置操作
Redis的作用
Redis 是一个开源的内存数据结构存储系统,通常用于以下几种场景和功能:
-
缓存:Redis 主要用于缓存数据,因为它的所有数据都存储在内存中,因此读取速度非常快。常用于加速数据库查询,减少数据库压力。
-
消息队列:Redis 支持发布/订阅(Pub/Sub)模式,可以用作消息队列。通过列表(List)、集合(Set)等数据结构实现任务队列系统,处理分布式任务。
-
分布式锁:Redis 提供了一种实现分布式锁的机制(如使用
SETNX
命令),常用于需要在分布式环境中进行同步的场景。 -
会话存储:在 Web 应用中,Redis 常用于存储用户会话数据,具有较高的读写性能,并且可以设置键的过期时间,适合存储短期数据。
-
数据持久化:虽然 Redis 主要是内存数据库,但它支持将数据异步地持久化到磁盘中(通过快照 RDB 或者追加日志 AOF)。
-
计数器和限流:Redis 可以快速地执行递增、递减等操作,因此常用于实现实时计数功能,如访问计数、点击量统计等,也可以用于限流机制中。
-
地理位置计算:Redis 还支持基于 GeoHash 的地理位置存储和计算功能,用于查找某个地理范围内的元素。
-
分布式数据共享:Redis 可以通过集群模式在多个节点上分布数据,支持高可用和数据自动分片,常用于分布式系统中的数据共享。
通过其多种数据结构(如字符串、列表、哈希、集合、有序集合等),Redis 在各种场景下都能灵活应用,并且具有极高的读写性能和可扩展性。
缓存篇
缓存穿透
缓存穿透 是指在查询过程中,客户端请求的数据既不在缓存中,也不存在于数据库中,导致每次请求都会绕过缓存,直接查询数据库。长期下来,大量无效请求会对数据库造成很大压力。
触发原因
- 恶意请求:客户端大量请求一些根本不存在的 key(例如随机生成的 key),导致这些请求无法命中缓存。
- 自然缺失:查询的数据在缓存中没有,并且数据库中也确实不存在,例如用户请求一个从未创建过的用户 ID。
产生后果
- 数据库压力过大:由于每次请求都绕过了缓存,直接查询数据库,特别是在大量无效请求的情况下,数据库的负载会急剧上升,可能导致宕机。
- 缓存命中率低:缓存的命中率会大幅下降,缓存的优势无法体现。
- 资源浪费:频繁地查询数据库不仅浪费资源,还可能影响其他正常查询的响应时间,导致系统性能下降。
解决方法
-
缓存空结果:当查询数据库后确认数据不存在时,可以将这个空结果缓存一段时间(例如设置一个短的过期时间),下次再查询相同 key 时,直接返回空值而不是再次查询数据库。
-
使用布隆过滤器:布隆过滤器是一种可以快速判断某个数据是否在数据库中的数据结构。如果请求的数据在布隆过滤器中不存在,直接返回而不查询数据库,这样可以有效防止大量请求查询数据库中的不存在数据。
-
参数合法性检查:对传入的请求参数进行校验,过滤掉明显不合理的请求,避免恶意或无效请求穿透缓存。
缓存击穿
缓存击穿 是指在高并发场景下,某个热点数据的缓存失效,并且在缓存重建之前,有大量请求同时到达该数据。这些请求由于缓存失效,会直接访问数据库,给数据库带来巨大的压力。
触发原因
- 热点数据失效:某个热点数据设置了缓存过期时间,当过期时间到达时,缓存中的数据被删除。
- 高并发访问:大量用户同时访问这条热点数据,而缓存已经失效,导致所有请求瞬间击中数据库。
产生后果
- 数据库负载急剧上升:在缓存失效后,由于所有请求直接打到数据库,数据库的处理能力会面临极大的压力,可能导致响应变慢甚至宕机。
- 性能下降:系统无法充分利用缓存的高效性,整个系统性能会受到较大影响,特别是在高并发访问的情况下,响应时间可能会显著延长。
- 缓存系统无效:缓存击穿使缓存暂时失去作用,极大削弱了缓存的性能优势。
解决方法
-
热点数据提前预热:在缓存过期之前,主动将热点数据重新加载到缓存中,避免缓存到期后有大量请求直接访问数据库。这可以通过定期刷新缓存或手动控制缓存失效时间来实现。
-
使用互斥锁:在高并发的环境下,可以通过分布式锁(如 Redis 的分布式锁机制)控制同一时刻只有一个请求能够重建缓存,其余的请求必须等待缓存重建完成后才能读取数据。这样可以有效防止大量请求同时查询数据库。
-
设置合理的过期时间:对于热点数据,可以设置较长的过期时间,或者动态调整过期时间,确保缓存数据在高峰期不会轻易失效。
-
缓存双层设计:可以设计多层缓存策略(如本地缓存和分布式缓存结合使用),即使分布式缓存失效时,仍能通过本地缓存减少数据库压力。
缓存雪崩
缓存雪崩 是指在短时间内,缓存中大量数据同时失效,导致大量请求直接访问数据库,造成数据库负载剧增,甚至可能引发系统崩溃。这通常发生在缓存数据的过期时间设置不当或其他外部因素(如缓存服务器宕机)导致缓存大面积失效的情况下。
触发原因
- 大量缓存同时过期:当大量缓存数据的过期时间设置得过于集中,某个时刻同时失效,导致原本依赖缓存的数据瞬间全部需要查询数据库。
- 缓存服务器宕机:如果使用单节点或集群缓存服务器时,缓存服务器宕机或者重启,所有缓存数据瞬间失效,所有请求会直接打到数据库。
- 网络波动或故障:缓存系统受到外部网络环境影响,导致无法正常访问缓存服务器,所有请求转向数据库。
产生后果
- 数据库压力过大:当缓存大量失效时,所有请求会直接访问数据库,造成数据库负载瞬间增大,可能导致数据库响应变慢甚至宕机。
- 系统响应延迟:由于缓存失效,大量请求无法从缓存中快速返回,系统的整体响应速度大幅降低,用户体验变差。
- 系统崩溃:在高并发场景下,缓存雪崩可能直接导致系统崩溃,严重影响系统可用性。
解决方案
-
缓存过期时间随机化:避免大量缓存数据在同一时刻失效,可以对缓存数据的过期时间设置一定的随机区间。比如设置 10-20 分钟的过期时间,随机分配具体的失效时间,防止集中失效。
-
缓存预热:在系统启动或大促活动之前,预先将热点数据加载到缓存中,避免大流量期间缓存数据同时过期导致雪崩。
-
分布式缓存设计:采用分布式缓存系统(如 Redis 集群、哨兵模式),保证单节点故障时,其他节点仍能正常工作,防止因为单点故障导致所有缓存失效。
-
降级策略:在缓存失效且数据库压力过大时,实施降级策略。例如,返回默认数据、部分数据或者提示用户稍后再试,来减少对数据库的访问压力。
-
限流:对访问数据库的请求进行限流控制,避免瞬时大量请求对数据库造成冲击,确保数据库能够承受高负载。
-
多级缓存:使用本地缓存和分布式缓存的双层缓存机制,即使分布式缓存暂时不可用,本地缓存仍然可以承担部分流量,减轻数据库压力。
双写一致性
在分布式系统中,为了提高读写性能,通常会使用缓存 + 数据库的双写架构。然而,这种架构会面临双写不一致性的问题,即数据库和缓存中的数据在更新时不同步。为了解决这个问题,Redis 常用于实现双写一致性。根据一致性要求的高低,可以分为两种情况:
一致性要求高的情况(强一致性)
这种场景要求数据更新后,缓存和数据库中的数据必须保持实时一致。常见的实现方法如下
先更新数据库,再删除缓存
- 更新数据库:先进行数据库的更新操作,确保数据库中的数据是最新的。
- 删除缓存:更新数据库后,立即删除缓存中的对应数据。
优点
-
保证数据的最终一致性。
-
避免了缓存和数据库的数据不同步问题。
缺点
- 可能出现并发问题:在高并发的场景下,可能会有以下情况:
- 读请求 A 先查询了旧缓存,然后缓存失效;
- 同时 写请求 B 更新了数据库并删除了缓存;
- 随后 读请求 A 再次访问数据库并重新缓存旧数据,导致缓存中的数据与数据库不一致。
- 需要额外考虑并发控制:可以通过加锁、消息队列、延时双删等方式解决此类问题。
优化措施
- 延时双删策略:在第一次删除缓存后,等待一小段时间(例如 500 毫秒)再执行一次删除操作,以应对并发写入时缓存数据不一致的问题。
允许延迟一致的情况(最终一致性)
对于一些一致性要求较低的场景,可以允许缓存和数据库在短时间内不一致,最终达到一致性。常见的实现方法有:
方案一:先更新数据库,再更新缓存
- 更新数据库:首先更新数据库中的数据,确保数据库是最新的。
- 更新缓存:然后将新数据写入缓存中。
优点
-
缓存中的数据可以及时更新,减小数据不一致的时间窗口。
缺点
- 缓存和数据库可能会短暂不一致:在数据库更新后,缓存更新之前的短时间内,可能会有查询操作读到旧缓存。
方案二:先删除缓存,再更新数据库
- 删除缓存:先删除缓存中的数据,避免查询到过期数据。
- 更新数据库:然后再更新数据库中的数据。
优点
-
在缓存删除的过程中,后续查询请求会直接查询数据库,避免读到旧的缓存数据。
缺点
- 可能出现短暂的缓存不命中问题:在缓存被删除但数据库尚未更新的这段时间内,所有读请求都会直接查询数据库,可能会导致数据库负载升高。
方案三:异步更新缓存(缓存延迟更新)
- 更新数据库:先更新数据库,确保数据准确。
- 异步更新缓存:通过消息队列或者后台线程异步更新缓存,避免对数据库的写操作影响缓存的及时性。
优点
-
适合对一致性要求不高的场景,减轻了同步更新的压力。
缺点
- 在数据库更新后到缓存更新前的这段时间内,数据不一致可能影响用户体验。
优化策略
- 消息队列:可以使用消息队列来异步保证缓存更新的顺序性和一致性,例如数据库更新后,向消息队列发送更新缓存的任务,消费者处理消息队列的任务以更新缓存。
- 加锁控制:可以使用 Redis 的分布式锁,确保同一时间只有一个线程能够同时操作缓存和数据库,避免并发问题。
持久化
Redis 是一个内存数据库,为了避免数据丢失,Redis 提供了多种持久化机制,将数据持久化到磁盘中,以便在重启或故障后恢复数据。Redis 支持两种主要的持久化方式:RDB持久化和AOF持久化。
RDB(Redis DataBase)持久化
RDB 是 Redis 的默认持久化方式,它会在指定的时间间隔内将内存中的数据快照保存到磁盘中。RDB 文件是 Redis 的二进制快照文件,它记录了某一时刻的数据状态。
RDB 工作原理:
- Redis 会定期将内存中的数据快照保存为 RDB 文件(可以手动触发或通过配置自动触发)。
- RDB 文件可以用来在 Redis 重启时恢复数据。
配置方式:
可以通过 redis.conf
文件配置 RDB 生成的频率。例如:
save 900 1 # 900秒(15分钟)内如果有1次写操作,则生成RDB文件
save 300 10 # 300秒(5分钟)内如果有10次写操作,则生成RDB文件
save 60 10000 # 60秒(1分钟)内如果有10000次写操作,则生成RDB文件
优点:
- 节省资源:RDB 是基于时间间隔来生成快照的,它不会实时保存数据,因此对 Redis 性能影响较小。
- 适合备份:RDB 文件是紧凑的二进制文件,适合用于数据备份,比如可以将这些文件保存到远程服务器。
缺点:
- 数据丢失风险:RDB 是间隔性地进行快照保存,可能会导致 Redis 意外宕机后丢失最近一次快照之后的数据。
- 磁盘开销大:在进行快照时,会将整个数据库的数据写入磁盘,磁盘 I/O 开销较大。
手动生成 RDB 文件:
可以通过 Redis 命令手动生成 RDB 文件:
BGSAVE # 在后台生成 RDB 快照
SAVE # 立即生成 RDB 快照(阻塞 Redis 服务器)
AOF(Append Only File)持久化
AOF 是 Redis 另一种持久化机制,它通过将每次写操作的命令以追加的方式写入文件,从而记录数据的变化。
AOF 工作原理:
- Redis 将每个写命令(如
SET
、LPUSH
等)记录到 AOF 文件中。 - 当 Redis 重启时,可以通过重放 AOF 文件中的命令来恢复数据。
配置方式:
在 redis.conf
中配置 AOF:
appendonly yes # 启用 AOF 持久化
appendfilename "appendonly.aof" # AOF 文件的文件名
AOF 有三种不同的同步策略,可以通过配置 appendfsync
参数控制写入磁盘的频率:
appendfsync always
:每次写操作都立即同步到磁盘,保证最强的数据安全性,但性能最差。appendfsync everysec
:每秒将数据同步到磁盘(Redis 默认设置),性能与数据安全性的折中。appendfsync no
:让操作系统决定何时将数据同步到磁盘,性能最好,但数据安全性最低。
优点:
- 数据安全性高:AOF 可以保证数据几乎不会丢失,特别是在使用
appendfsync always
时,数据可以做到实时持久化。 - 可读性高:AOF 文件以文本格式保存 Redis 命令,因此可读性高,便于恢复和分析数据。
缺点:
- 文件较大:相比 RDB,AOF 文件通常更大,因为它记录的是每个写操作。
- 重写性能开销:随着时间的推移,AOF 文件会越来越大,Redis 提供了自动重写机制来压缩文件,但重写过程中会带来一定的性能开销。
AOF 文件重写:
AOF 文件会随着写操作的增加变得越来越大,Redis 提供了 AOF 重写功能来压缩文件。重写不会阻塞 Redis 服务器,运行在后台:
BGREWRITEAOF # 手动触发 AOF 重写
混合持久化(RDB + AOF)
Redis 4.0 开始引入了混合持久化模式,即将 RDB 和 AOF 结合起来,在恢复数据时可以同时使用两者的优势。
- 在混合持久化下,Redis 会将 RDB 的快照和 AOF 中的增量日志一起写入到 AOF 文件中,既保留了 RDB 的快速恢复特性,也保留了 AOF 几乎无数据丢失的优势。
- 当 Redis 重启时,AOF 文件可以通过快照 + 命令增量的方式更快地恢复数据。
配置混合持久化:
可以在 redis.conf
中启用混合持久化:
aof-use-rdb-preamble yes # 开启混合持久化模式
Redis数据过期策略
Redis 提供了三种主要的过期策略来管理过期数据:
- 定期删除(定时扫描)
- 惰性删除(惰性清理)
定期删除(定时扫描)
Redis 会定期随机扫描一部分设置了过期时间的 key,并删除那些已经过期的 key。这个操作是由 Redis 的后台线程定期执行的,默认每 100 毫秒触发一次。
- 优点:定期删除策略能够防止过期 key 大量堆积,避免 Redis 内存被无效数据占用。
- 缺点:由于是定时扫描+随机抽取 key,可能会有一些过期 key 没有被及时删除,尤其是当过期 key 数量庞大时。
惰性删除(惰性清理)
惰性删除是指当客户端尝试访问某个 key 时,Redis 会检查该 key 是否已经过期,如果已经过期则将其删除。如果没有客户端访问,这些过期的数据会一直保留在内存中,直到被访问时才删除。
- 优点:惰性删除只在访问时检查,因此不会额外占用系统资源来定期扫描删除过期数据。
- 缺点:如果有大量过期数据没有被访问,内存中会保留无效数据,导致内存浪费。
内存淘汰机制(内存限制下的淘汰策略)
当 Redis 的内存达到配置的最大限制(通过配置 maxmemory
参数设置)时,Redis 会根据配置的淘汰策略,删除一些数据来释放内存。Redis 支持多种内存淘汰策略:
Redis 支持的淘汰策略
1 noeviction
- 描述:Redis 默认策略。如果内存不足以容纳新数据时,不会删除任何数据,而是直接返回错误(通常对写操作返回错误)。
- 适用场景:适用于只读或非常高写安全性场景,避免任何数据丢失。
- 缺点:当内存不足时,写操作将失败,可能影响业务的正常运行。
2 allkeys-lru
- 描述:在所有的 key 中,使用 LRU(Least Recently Used,最近最少使用)算法,删除最近最少被访问的 key。
- 适用场景:适用于缓存场景。经常访问的数据会保留在内存中,较少访问的数据会被淘汰。
- 优点:最常用的缓存淘汰策略,能够尽可能保持热点数据在内存中。
3 volatile-lru
- 描述:仅在设置了过期时间的 key 中,使用 LRU 算法淘汰最近最少被访问的 key。
- 适用场景:适用于缓存中只对带有过期时间的数据进行淘汰,确保持久数据不被误删。
- 优点:可以控制带有过期时间的数据的淘汰,保留没有过期时间的数据。
4 allkeys-random
- 描述:在所有 key 中,随机选择一些 key 进行删除。
- 适用场景:适用于不关心数据访问频率的场景,或需要确保高性能、低延迟的场景。
- 优点:实现简单,适合一些对访问频率不敏感的场景。
5 volatile-random
- 描述:仅在设置了过期时间的 key 中,随机选择一些 key 进行删除。
- 适用场景:适用于缓存系统,并且只希望淘汰带有过期时间的数据,而不删除持久存在的数据。
- 优点:适合那些有一定过期策略的缓存系统,确保有过期时间的数据被优先删除。
6 volatile-ttl
- 描述:仅对设置了过期时间的 key,根据 key 的 TTL(剩余存活时间)进行淘汰,优先删除 TTL 最短的 key。
- 适用场景:适用于需要优先淘汰即将过期的数据的场景。
- 优点:能够确保接近过期的 key 被优先淘汰,避免存活时间较长的 key 被过早删除。
7 volatile-lfu(Redis 4.0+)
- 描述:仅在设置了过期时间的 key 中,使用 LFU(Least Frequently Used,最不常使用)算法,淘汰使用频率最低的 key。
- 适用场景:适用于缓存系统中的数据频繁变化且访问频率有规律的场景。
- 优点:通过访问频率记录来淘汰冷数据,适合大规模缓存场景。
8 allkeys-lfu(Redis 4.0+)
- 描述:在所有 key 中,使用 LFU 算法,淘汰使用频率最低的 key。
- 适用场景:类似于 LRU,但考虑的是访问频率而不是最近访问时间,适合更复杂的缓存场景。
- 优点:更适合访问模式具有明显冷热分布的场景,能够较好地保留常用数据。
LRU与LFU区别
LRU(Least Recently Used):记录每个 key 的最后访问时间,最久没有访问的 key 会优先被淘汰。
LFU(Least Frequently Used):为每个 key 维护一个访问计数器,访问频率较低的 key 会被优先淘汰。
常用场景的淘汰策略选择
- 缓存场景:
allkeys-lru
和allkeys-lfu
是常用的策略,能够确保热点数据留在内存中。 - 短期任务缓存:
volatile-lru
或volatile-ttl
,确保设置过期时间的临时任务数据被优先淘汰。 - 安全性高的场景:
noeviction
,适用于对写入失败敏感的系统,确保不会因为内存不足导致意外删除数据。
分布式锁篇
Redis 分布式锁是一种基于 Redis 的分布式系统中的同步机制,用来解决分布式环境下多个进程或线程竞争同一资源的问题。分布式锁可以确保在多实例、多线程环境中,某一时刻只有一个客户端能够访问或操作某一共享资源,避免数据不一致或竞争条件的发生。
使用场景
-
库存扣减和订单创建:在电商系统中,多个用户同时购买同一商品时,分布式锁可以确保在高并发下,只有一个请求能成功扣减库存并创建订单,避免超卖或数据错乱。
-
定时任务调度:在分布式环境中,当多个节点都在执行定时任务时,分布式锁可以确保某一任务在任意时刻只能由一个节点执行,避免重复处理任务。
-
资源竞争管理:在分布式系统中,多个微服务可能会竞争使用某些共享资源(如数据库记录、缓存中的数据等),使用分布式锁可以保证资源不会被多个服务同时修改。
-
幂等操作控制:一些关键操作需要保证在多次调用时,只有第一次调用能够执行,后续调用不能重复操作,分布式锁可以实现这样的控制。
-
防止缓存击穿:当缓存数据失效时,多个请求同时发往数据库,可能导致数据库压力骤增。通过分布式锁,只有一个请求能够从数据库中获取数据并更新缓存,其他请求等待缓存更新。
使用原因
-
保证资源的独占访问:在分布式系统中,多个进程或节点可能会同时尝试修改同一资源,而分布式锁可以确保在任意时间只有一个节点能访问共享资源。
-
防止数据竞争和不一致性:如果多个节点同时访问或修改资源,可能导致数据不一致。通过分布式锁,多个节点的操作可以按照预期顺序执行,防止并发引发的异常。
-
简单高效的实现:Redis 的高性能和简单易用的接口,使其成为实现分布式锁的理想工具。Redis 提供的操作具有原子性,能够避免多个进程同时获取锁的竞态条件。
Redisson实现分布式锁
Redisson 是一个 Redis 的 Java 客户端,它不仅支持 Redis 的基本操作,还提供了许多高级功能,其中之一就是分布式锁的实现。Redisson 通过对 Redis 的封装,提供了更加简便和安全的分布式锁机制,实现了诸如重入锁、读写锁、公平锁等常见的锁模型,且通过 Redis 保障了分布式锁的性能和可靠性。
特性:
- 可重入锁:同一个线程可以多次获取锁。
- 高效性:依赖于 Redis 的高性能和原子性操作,Redisson 实现的分布式锁具有非常高的效率。
- 高可靠性:通过 Redis 的特性(持久化、主从复制、集群模式等),Redisson 提供了高可靠性。
- 自动续期:当线程持有锁并且操作时间超过锁的过期时间时,Redisson 会自动续期,防止由于超时导致锁失效的问题。
- 多种锁模型支持:提供了多种锁类型,如公平锁、读写锁、红锁(RedLock)等,适应不同场景。
实现原理:
1.获取锁: Redisson 使用 SET NX EX
命令来获取锁,NX
保证在 key 不存在时才能设置,EX
设置锁的过期时间,防止死锁。
示例:
SET lock_key unique_value NX EX 30
lock_key
:锁的唯一标识。unique_value
:用于标识持有锁的客户端(如 UUID),防止其他客户端误释放锁。NX
:保证键不存在时才会设置(确保互斥性)。PX 30000
:锁的过期时间,防止死锁(30 秒,死锁:若命令中途中断会导致无法释放锁)。
2.锁的续期: Redisson 通过内部的看门狗机制(Watchdog)自动续期。在默认情况下,如果锁的持有者仍然活跃,当锁的默认过期时间快要到达时,Redisson 会自动续期,延长锁的有效时间。
看门狗机制:
- Redisson 会启动一个后台线程,该线程会不断检查持有的锁,并在锁快要到期时,自动为锁续期,防止锁由于超时而被其他客户端抢占。
- 默认的锁过期时间是 30 秒,如果客户端没有显式指定过期时间,看门狗会每隔 10 秒续期。
3.释放锁: 当持有锁的线程完成任务后,需要显式释放锁。Redisson 使用 Lua 脚本来保证释放锁的原子性。它会先检查锁的值是否与当前线程对应的 unique_value
相同,只有相同才能删除锁,防止其他线程误删锁。
多种锁类型:
普通锁 (ReentrantLock): 可重入锁,即同一线程可以多次获取锁,每次加锁需要与解锁成对调用。
公平锁 (FairLock): 公平锁会按照请求的顺序来分配锁,先请求的线程会先获取到锁。这可以解决"饥饿"问题。
读写锁 (ReadWriteLock): Redisson 实现了读写锁,多个读操作可以同时进行,但写操作需要独占锁,并且写操作之间是互斥的。
红锁 (RedLock): 红锁(RedLock)是 Redis 的分布式锁算法,在多个 Redis 实例上实现锁的加锁与解锁操作,具有更高的容错性和安全性。Redisson 提供了对 RedLock 算法的支持,可以在多个 Redis 节点之间加锁,增强分布式锁的可靠性。
联锁 (MultiLock): 联锁是同时持有多个锁的一种机制,只有当多个锁都获取成功时,任务才能执行。
集群篇
Redis提供的集群方式有主从复制、哨兵模式、分片集群。
主从复制
功能架构
-
主节点(Master): 主节点是处理写操作的节点,所有的写操作(如
SET
、DEL
等)都只能发送到主节点。主节点会将这些操作的结果同步到从节点,从而保持数据的一致性。 -
从节点(Slave): 从节点是只读节点,它会从主节点复制数据,并根据主节点的变化进行更新。从节点可以接受查询操作,如
GET
,但不能执行写操作。多个从节点可以同时连接到一个主节点。
工作机制
全量复制:
- 从节点启动时,向主节点发送
PSYNC
命令请求同步数据。 - 主节点接收到请求后,会将整个数据集(RDB 文件)发送给从节点,从节点清空自身的数据并加载主节点发来的数据。
- 当从节点完成全量数据同步后,主节点会将从全量复制开始到当前期间的增量操作日志发送给从节点,从节点应用这些日志确保数据一致。
增量复制:
- 全量复制完成后,主节点会将每次执行的写操作记录下来,并以命令流的形式实时发送给从节点,从节点将这些写操作应用到自己的数据集中。
- 增量复制避免了每次从节点重启时都进行全量复制,提高了同步效率。
异步复制: Redis 主从复制是异步的,主节点不会等待从节点完成数据同步就继续处理其他请求。虽然 Redis 提供了一种“半同步”模式(主节点会将命令发送给从节点并等待确认),但在大多数场景下,复制是异步进行的。
基本操作
1.配置主从复制: 要配置 Redis 的主从复制,需要在从节点的配置文件中指定主节点的 IP 地址和端口,或通过 SLAVEOF
命令来设置从节点。
配置文件方式: 在从节点的 redis.conf
配置文件中添加以下配置:
replicaof <master-ip> <master-port>
命令方式: 在从节点中执行以下命令:
SLAVEOF <master-ip> <master-port>
2.取消复制: 如果希望从节点停止复制,可以使用 SLAVEOF NO ONE
命令,使从节点成为独立的主节点:
SLAVEOF NO ONE
查看复制状态: 可以通过 INFO replication
命令查看主节点和从节点的复制状态。该命令会显示主从复制的相关信息,如角色(role
)、连接的从节点数、复制延迟等。
INFO replication
哨兵模式
核心功能
-
监控(Monitoring): Sentinel 会监控主节点和从节点的运行状态,定期发送
PING
命令,检查节点是否可用。如果节点响应超时,Sentinel 会将其标记为下线(subjectively down
,简称sdown
)。多个 Sentinel 共同判断某个主节点不可用时,会将其标记为客观下线(objectively down
,简称odown
)。 -
故障转移(Failover): 当主节点被 Sentinel 判断为客观下线时,Sentinel 会选择一个从节点,执行故障转移,将该从节点提升为新的主节点,并让其他从节点开始复制新的主节点的数据。整个过程是自动完成的。
-
通知(Notification): Sentinel 可以通知客户端新的主节点地址,使客户端能够自动连接到新的主节点。这是通过 Redis Sentinel 客户端的自动化机制来完成的。
-
配置提供者(Configuration Provider): Redis Sentinel 也充当客户端连接的配置提供者。当客户端启动时,可以向 Sentinel 请求当前主节点的地址,以便自动化地获取正确的连接信息。
功能架构
哨兵模式的架构一般由多个 Sentinel 实例和 Redis 主从节点组成。哨兵本身也是 Redis 进程,多个 Sentinel 实例会相互通信,通过共识机制来做出可靠的主节点故障判断和转移决策。
- Sentinel 实例:负责监控 Redis 节点并执行自动故障转移。
- 主节点(Master):处理所有写操作,当主节点发生故障时,哨兵会执行故障转移。
- 从节点(Slave):复制主节点的数据,负责处理读请求,主节点故障时会被提升为新的主节点。
工作原理
-
主节点监控: 每个 Sentinel 会不断向 Redis 主节点和从节点发送
PING
命令,监控它们的状态。如果某个节点在指定时间内没有回应PING
请求,Sentinel 会将其标记为主观下线(sdown
),此时只代表这个 Sentinel 认为节点不可用。 -
客观下线判断: 如果有多个 Sentinel 实例同时认为主节点不可用,它们会通过投票机制达成共识,决定是否将主节点标记为客观下线(
odown
)。通常需要多数 Sentinel 认为主节点不可用时,才会执行后续的故障转移操作。 -
故障转移: 当主节点被标记为客观下线时,Sentinel 会选择一个合适的从节点,将其提升为新的主节点。选择从节点的标准通常是复制的进度、延迟等因素。故障转移步骤包括:
- 停止其他从节点复制旧的主节点。
- 将一个从节点提升为新的主节点。
- 配置其他从节点复制新的主节点。
-
通知客户端: Sentinel 将新的主节点信息通知给客户端,使客户端能够自动切换到新的主节点继续进行操作。
-
集群重配置: 故障转移完成后,Redis Sentinel 会自动更新集群中的其他从节点配置,让它们从新的主节点进行数据同步,从而保持整个集群的一致性和正常运行。
配置操作
1.配置文件示例:
以下是 Sentinel 配置文件的基本内容(sentinel.conf
):
port 26379
sentinel monitor mymaster 127.0.0.1 6379 2
sentinel down-after-milliseconds mymaster 5000
sentinel parallel-syncs mymaster 1
sentinel failover-timeout mymaster 15000
#port:哨兵监听的端口。
#sentinel monitor:定义监控的 Redis 主节点,mymaster 是该节点的名字,后面是主节点的 IP 和端口,以及判断主节点下线所需的最少 Sentinel 数量。
#sentinel down-after-milliseconds:定义 Sentinel 认为主节点下线所需的时间(毫秒)。
#sentinel parallel-syncs:在故障转移时,可以同时有多少个从节点与新主节点进行同步。
#sentinel failover-timeout:故障转移的超时时间。
2.启动 Sentinel:
redis-sentinel /path/to/sentinel.conf
3.通过命令行查看哨兵状态: 可以使用 redis-cli -p 26379
连接到哨兵实例,通过命令 SENTINEL masters
查看当前监控的主节点状态,SENTINEL slaves <master-name>
查看与某个主节点相关联的从节点信息。
分片集群
架构及特点
-
数据分片: Redis Cluster 使用一致性哈希的思想将数据分布到多个节点上。Redis Cluster 将所有的键空间划分为 16384 个槽(slots),每个节点负责一部分槽的存储。当客户端进行操作时,Redis Cluster 会根据键的哈希值将其映射到相应的槽,再定位到具体的节点执行操作。
-
无中心架构: Redis Cluster 没有中心节点,所有节点都平等并协作工作。这意味着没有单点故障问题,任何节点都可以处理客户端请求,并能够将请求转发到正确的节点上。
-
高可用性: 每个主节点可以有一个或多个从节点作为备份节点。如果主节点故障,Redis Cluster 会自动选举从节点来接替主节点的角色,实现故障转移,保证服务的高可用性。
-
自动故障转移: 如果某个主节点出现故障,集群会通过投票机制选择某个从节点作为新的主节点,保证集群的持续可用。这个过程是自动完成的,无需人工干预。
-
水平扩展: Redis Cluster 支持在线扩展节点,可以动态增加或减少集群中的节点数量,适应不同规模的应用需求。这使得集群能够灵活地应对大规模的数据存储和高并发访问。
-
客户端直连: Redis Cluster 的客户端可以直接连接集群中的任意节点,所有节点之间都知道彼此的状态。如果客户端连接的节点不存储所需的槽,它会将请求重定向到存储相应槽的节点,从而避免单点瓶颈。
工作原理
-
槽(Slots)分配: Redis Cluster 将整个键空间划分为 16384 个槽。每个键的哈希值会落入某个槽中,集群中的每个节点负责若干个槽。每当客户端请求某个键时,Redis 会根据该键的哈希值找到对应的槽,再确定该槽所在的节点。
-
数据读写流程:
- 写操作:客户端发送写请求时,Redis Cluster 会根据键的哈希值找到目标槽,并将写操作路由到该槽对应的主节点。如果客户端连接的节点不存储该槽的数据,节点会返回
MOVED
指令,告诉客户端正确的目标节点。 - 读操作:客户端发送读请求时,类似于写操作,Redis Cluster 根据哈希槽找到目标节点。如果主节点故障,客户端可以从从节点读取数据,但在某些情况下可能会出现短暂的不一致。
- 写操作:客户端发送写请求时,Redis Cluster 会根据键的哈希值找到目标槽,并将写操作路由到该槽对应的主节点。如果客户端连接的节点不存储该槽的数据,节点会返回
-
故障检测与故障转移: Redis Cluster 通过心跳机制(Gossip 协议)监控各个节点的健康状态。如果主节点不可用,集群会通过投票选举某个从节点作为新的主节点。整个过程是自动化的,客户端无需手动干预。
-
数据复制: Redis Cluster 中的每个主节点都会有一个或多个从节点作为备份。如果主节点崩溃,从节点会被提升为主节点,保证集群的高可用性。数据的同步是异步的,这意味着在故障转移过程中可能会丢失少量数据。
配置操作
Redis Cluster 的节点配置: 在 Redis 配置文件中,需要启用集群模式,并配置节点之间的通信信息。例如,在 redis.conf
中,添加如下配置:
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 15000
appendonly yes
#cluster-enabled:启用集群模式。
#cluster-config-file:指定保存节点状态的文件。
#cluster-node-timeout:节点通信超时时间,单位为毫秒。
#appendonly:开启 AOF 持久化,防止数据丢失。
创建 Redis Cluster: 创建 Redis Cluster 的典型步骤是先启动多个 Redis 实例,然后使用 redis-cli
创建集群。例如:
redis-cli --cluster create 192.168.1.1:6379 192.168.1.2:6379 192.168.1.3:6379 \
192.168.1.4:6379 192.168.1.5:6379 192.168.1.6:6379 --cluster-replicas 1
添加新节点到集群: 当需要扩展集群时,可以将新的节点添加到 Redis Cluster 中,Redis 会自动将一部分槽从现有节点迁移到新节点。使用命令:
redis-cli --cluster add-node <new-node-ip>:<port> <existing-node-ip>:<port>