Redis-学习记录

 一、缓存

1. 缓存穿透、击穿与雪崩

缓存穿透

  • 定义:查询不存在的数据,导致每次请求都会查询数据库。
  • 解决方案
    • null值缓存
      • 优点:实现简单。
      • 缺点:内存消耗较大,尤其是当大量无效key存在时。
    • 布隆过滤器
      • 优点:内存占用少。
      • 缺点:可能存在误判(假阳性),但通过增加数组大小可以降低误判率。

缓存击穿

  • 定义:某个热点key过期后,大量并发请求直接打到数据库,导致数据库压力过大。
  • 解决方案
    • 互斥锁
      • 优点:保证数据一致性。
      • 缺点:性能较低,因为同一时间只能有一个线程获取锁并查询数据库。
    • 逻辑过期
      • 优点:性能好,避免所有请求同时访问数据库。
      • 缺点:用户体验较差,数据一致性不高。

缓存雪崩

  • 定义:大量key在同一时间段内过期或Redis崩溃,直接请求数据库导致数据库压力骤增。
  • 解决方案
    1. 给key添加随机TTL值:避免大量key同时过期。
    2. 利用Redis集群:提高系统的可用性。
    3. 缓存业务降级限流:在极端情况下限制流量,保护数据库。
    4. 多级缓存:使用多级缓存机制,如本地缓存+分布式缓存。

2. 双写一致

先删缓存还是先删数据库?

  • 读操作
    • 缓存命中则直接返回。
    • 否则查询数据库,写入缓存,并设置超时时间。
  • 写操作
    • 延迟双删:首先删除缓存,然后写数据库,延时一段时间再次删除缓存以确保数据更新。
      • 缺点:延时时间难以精确控制,可能导致脏数据。
    • 互斥锁:保证强一致性,但性能较低。
    • 共享锁/排他锁:用于控制并发读写。
    • 异步通知:例如使用Canal监听数据库变化并更新缓存,减少代码侵入。

3. Redis 数据持久化

RDB(快照)持久化

  • 原理:定期将内存中的数据快照保存到磁盘文件中。
  • Copy-On-Write:在写操作时复制一份新数据,避免脏数据影响。

AOF(Append Only File)持久化

  • 原理:记录每个写操作的日志,故障恢复时通过重放日志恢复数据。
  • 优势:提供更细粒度的数据恢复能力,适合需要高数据完整性的场景。

4.数据过期策略

惰性删除

  • 工作方式:在访问key时检测是否过期,如果过期则删除。
  • 优缺点
    • 优点:CPU友好。
    • 缺点:内存不友好,可能导致大量过期key长期占用内存。

定时删除

  • 工作方式:后台定时任务检查并删除过期key,分为slow和fast模式。
    • Slow模式:默认每秒运行10次,每次执行不超过25ms。
    • Fast模式:频率更高,间隔小于2ms,每次执行不超过1ms。
  • 优缺点
    • 优点:CPU和内存友好。
    • 缺点:执行时间和频率不确定,可能影响内存使用效率。

大多数情况下两者配合使用。

5. 数据淘汰策略

Redis提供了八种数据淘汰策略来应对内存不足的情况:

  • Noeviction:不淘汰任何数据,写操作失败。
  • Allkeys-lru:从所有key中选择最近最少使用的key进行淘汰。
  • Allkeys-random:随机淘汰key。
  • Volatile-lru:仅对设置了过期时间的key采用LRU算法淘汰。
  • Volatile-ttl:根据剩余生存时间(TTL)排序,优先淘汰TTL小的key。
  • Volatile-random:从设置了过期时间的key中随机淘汰。
  • Allkeys-lfu:基于LFU(Least Frequently Used)算法淘汰所有key中最不常用的。
  • Volatile-lfu:基于LFU算法淘汰设置了过期时间的key中最不常用的。

这些策略可以根据具体的应用场景灵活配置,以达到最佳的内存管理和应用性能平衡。

 

分布式

1. Redis 分布式锁的基础实现

1.1 底层命令:SETNX

  • SETNX 是 Redis 提供的一个原子性操作,全称是 SET if Not eXists。
  • 它的作用是:只有当键不存在时,才会设置键值对。

1.2 获取锁

SET lock value NX EX 10

  • NX:表示只有在键 lock 不存在时才设置。
  • EX 10:设置键的过期时间为 10 秒,防止死锁。
  • value:通常是一个唯一标识符(如 UUID),用于确保释放锁时的安全性。

1.3 释放锁

DEL lock

  • 删除键以释放锁。
  • 问题
    • 如果锁的过期时间到了,但业务逻辑尚未执行完,可能会导致误删其他线程的锁。
    • 因此,释放锁时需要验证 value 是否与当前线程设置的值一致。

改进后的释放锁逻辑:

if redis.call("GET", KEYS[1]) == ARGV[1] then    
    return redis.call("DEL", KEYS[1])
else    
    return 0
end

如果Redis中指定的键的值等于给定的值,则删除该键,否则不执行任何操作。 

  • 使用 Lua 脚本保证原子性。

1.4 不可重入性

  • Redis 原生锁不支持重入性。
  • 如果同一个线程尝试多次获取锁,会导致死锁。

2 Redis 集群:

2.1 Redis 主从复制

关键概念

1. replid(复制 ID)

  • 每个主节点都有一个唯一的 replid,用于标识主节点的数据版本。
  • 从节点在首次同步时会保存主节点的 replid,用于后续的增量同步。
  • 如果主节点重启,会生成一个新的 replid,此时从节点无法进行增量同步,必须触发全量同步。

2. offset(偏移量)

  • offset 表示主节点写入的命令序列号。
  • 每次主节点执行写操作时,offset 都会递增。
  • 从节点通过 offset 来记录自己已经同步到的位置。

3. 复制缓冲区

  • 主节点维护一个固定大小的复制缓冲区,用于存储最近的写操作日志。
  • 当从节点断开连接后重新连接时,如果从节点的 offset 在复制缓冲区范围内,主节点可以直接发送缺失的日志,而无需进行全量同步。

全量同步(Full Resynchronization)

触发场景

  • 当主节点和从节点第一次建立连接时。
  • 从节点请求同步,但主节点判断 replid 不一致时(比如从节点从未与当前主节点同步过,或者主节点重启后生成了新的 replid)。

同步流程

请求同步

  • 从节点向主节点发送 PSYNC <replid> <offset> 请求。
  • 如果主节点发现 replid 不匹配或从节点没有提供有效的 replid,则触发全量同步。

生成 RDB 文件

  • 主节点生成一个 RDB 快照文件(包含当前所有数据的状态),并将该文件发送给从节点。

清空从节点数据

  • 从节点在接收到 RDB 文件后,会清空自己的现有数据。

加载 RDB 文件

  • 从节点加载主节点发送的 RDB 文件,恢复到主节点的数据状态。

发送缓冲区日志

  • 在生成 RDB 文件的过程中,主节点可能会继续接收写操作。
  •  主节点将这些写操作记录在复制缓冲区中,并在 RDB 文件传输完成后,将这些日志发送给从节点执行。

完成同步

  • 从节点执行主节点发送的日志,保证与主节点的数据完全一致。

增量同步(Partial Resynchronization)

触发场景

  • 当从节点已经与主节点完成过一次全量同步后,网络中断或其他原因导致从节点需要重新同步。 
  • 主节点判断从节点的 replid 和 offset 是有效的(即从节点之前已经与当前主节点同步过)。

同步流程

请求同步

  • 从节点向主节点发送 PSYNC <replid> <offset> 请求。
  • 主节点检查 replid 是否一致,以及 offset 是否在复制缓冲区范围内。

判断是否支持增量同步

  • 如果 replid 一致且 offset 在复制缓冲区范围内,则主节点支持增量同步。
  • 如果不满足条件(例如 replid 不一致或 offset 超出范围),则退回到全量同步。

发送缺失的日志

  • 主节点计算从节点缺失的日志范围:主节点的 offset - 从节点的 offset。
  • 将这部分缺失的日志发送给从节点。

从节点执行日志

  • 从节点接收到缺失的日志后,依次执行这些命令,追赶上主节点的数据状态。

2.2 Redis Sentinel 和 Redis Cluster

Redis Sentinel(哨兵模式)

  • 功能:监控 Redis 主从节点的状态,自动进行故障转移。
  • 脑裂问题:通过多数派决策机制解决,即只有当大多数哨兵认为主节点不可用时,才会触发故障转移。

Redis Cluster

  • 功能:提供高可用性和水平扩展能力,通过分片(sharding)将数据分布到多个节点上,使用16384个哈希槽分配实例,依据key查询。
  • 特点
    • 每个节点负责一部分数据,支持自动故障转移。
    • 支持多主多从架构,提高系统的可靠性和性能。
    • 提供了一致性哈希算法来分配数据,减少数据迁移带来的影响。
    • 互相监测以确保可用

3. Redisson 分布式锁

  • Redisson 是一个基于 Redis 的 Java 客户端,提供了分布式锁的高级实现。

3.1 Watch Dog 续期

  • Redisson 提供了自动续期功能(Watch Dog),避免因锁超时而导致业务未完成的情况。
  • 当锁即将过期时,Watch Dog 会自动延长锁的有效期。

工作原理:

  • 默认情况下,Redisson 锁的有效期为 30 秒。
  • 如果业务逻辑未完成,Watch Dog 每隔 10 秒会自动续期一次,将锁的有效期重新设置为 30 秒。
  • 当业务逻辑完成后,手动释放锁时,Watch Dog 停止续期。

3.2 可重入性

  • Redisson 支持可重入锁。
  • 同一线程可以多次获取同一把锁,而不会死锁。

实现原理:

  • 使用 Redis 的 Hash 数据结构存储锁信息。

键值对格式:

{  
    "lock": 
    {    
        "thread-id": 12345,
        "count": 2  
    }
}
  • thread-id:记录持有锁的线程 ID。
  • count:记录重入次数。
  • 每次获取锁时,count 自增;每次释放锁时,count 自减。当 count 为 0 时,删除锁。

3.3 主从一致性

  • Redis 单实例的分布式锁存在主从一致性问题。
  • 如果主节点宕机,从节点可能尚未同步最新的锁状态,导致数据不一致。

解决方案:

  • 使用 Redis Sentinel 或 Redis Cluster 高可用架构。
  • 在 Redisson 中,可以通过配置哨兵模式或集群模式来解决主从一致性问题。

3.4 Redlock 算法

  • Redlock 是 Redis 官方提出的一种分布式锁算法,旨在提高锁的可靠性。

核心思想:

  • 在多个独立的 Redis 实例上创建锁。
  • 只有当客户端成功在大多数实例(n/2+1)上获取锁时,才算真正获取到锁。

流程:

  • 客户端依次向多个 Redis 实例请求锁。
  • 记录每个实例的响应时间和锁的获取结果。
  • 如果成功获取锁的实例数超过半数,且总耗时小于锁的有效时间,则认为获取锁成功。
  • 如果失败,释放已获取的锁,并重试。

优点:

  • 提高了锁的可靠性和可用性。
  • 即使部分 Redis 实例宕机,仍能正常工作。

缺点:

  • 实现复杂。
  • 性能较低(需要与多个实例交互)。
  • 运维成本高(需要维护多个 Redis 实例)。

4. 对比总结

特性

Redis 原生锁

Redisson

Redlock

实现复杂度

简单

中等

复杂

性能

较高

较低

可靠性

较低(主从一致性问题)

较高

可重入性

不支持

支持

不支持

续期机制

手动

自动(Watch Dog)

手动

适用场景

简单业务、单实例 Redis

复杂业务、高并发场景

高可靠性要求、多实例 Redis

5. 注意事项

锁的过期时间

  • 设置合理的锁过期时间,避免死锁。
  • 使用 Watch Dog 自动续期时,需确保业务逻辑能够在合理时间内完成。

主从一致性

  • 单实例 Redis 存在主从一致性问题,建议使用 Redis Sentinel 或 Redis Cluster。

Redlock 的使用

  • Redlock 适用于高可靠性场景,但在高并发环境下性能较差,需权衡利弊。

安全性

  • 释放锁时务必验证 value,避免误删其他线程的锁。

小结

  • Redis 原生锁:简单易用,适合轻量级场景,但存在不可重入性和主从一致性问题。
  • Redisson:功能强大,支持可重入性、自动续期和主从一致性,适合复杂业务场景。
  • Redlock:高可靠性,但实现复杂、性能较低,适合极端高可用性需求。

同步方式优缺点对比

特性

全量同步

增量同步

适用场景

初次同步、replid 不一致、offset 超出范围

网络中断后重新连接、数据变化较小

性能消耗

高(需要生成 RDB 文件并传输)

低(仅传输缺失的日志)

数据一致性

强(从节点清空数据后重新加载)

强(基于日志追加更新)

网络带宽占用

I/O

Redis 以其高性能和灵活性在现代应用开发中扮演着重要角色。它的设计充分利用了内存操作的高效性,并通过独特的网络模型实现了高效的 I/O 处理。以下是关于 Redis 的内存操作、线程模型以及其网络模型的详细解析。

1. 内存操作与单线程

内存操作

  • Redis 是一个内存数据库,所有的数据都存储在内存中,这使得它能够提供极高的读写速度。
  • 由于内存访问速度远高于磁盘,Redis 可以快速地处理大量请求。

单线程模型

  • Redis 采用单线程来处理客户端请求,这意味着在同一时间点上只有一个任务被执行,从而避免了多线程编程中的并发问题(如死锁、竞态条件等)。
  • 单线程模式下的 Redis 非常适合执行短小且快速的操作,因为这些操作不会阻塞其他请求的处理。

2. I/O 多路复用

I/O 多路复用是一种允许单个线程同时监听多个文件描述符的技术,适用于高并发场景下提高服务器性能。

常见 I/O 模型

  • 阻塞 I/O:当尝试从 socket 读取数据时,如果当前没有可用的数据,则该线程会被阻塞,直到有数据可读为止。这种方式效率较低,因为它会导致线程等待。
  • 非阻塞 I/O:尝试读取数据时,如果没有数据立即返回错误,而不是等待。虽然这种方法可以避免线程被阻塞,但它需要不断轮询,导致 CPU 使用率增加。
  • I/O 多路复用:通过 select/poll/epoll 等机制,可以让一个线程同时监控多个 socket 的状态变化,只有当某个 socket 准备好进行读写操作时才触发相应的事件处理函数。这种机制可以显著提高系统吞吐量。

Select/Poll/Epoll

  • Select:支持同时监视多个文件描述符的状态变化,但存在最大文件描述符数量限制,且每次调用都需要遍历所有文件描述符,效率不高。
  • Poll:类似于 select,但是去除了文件描述符数量的限制,但在大并发情况下依然存在性能瓶颈。
  • Epoll:Linux 特有的 I/O 事件通知接口,相比于 select 和 poll,它更高效,尤其是在处理大量连接时表现尤为突出。

3. Redis 网络模型

单线程 + I/O 多路复用 + 事件派发

  • Redis 的早期版本采用了单线程模型结合 I/O 多路复用技术(通常使用 epoll),来实现高效的请求处理。
  • 这种模型的核心思想是利用单线程配合 I/O 多路复用来管理大量的网络连接,确保每个连接都能得到及时响应。
  • 当接收到客户端请求后,Redis 会将其分发给对应的事件处理器执行具体的命令逻辑。

多线程模型(Redis 6.0+)

  • 在 Redis 6.0 版本中引入了多线程 I/O 支持,旨在进一步提升性能,特别是在高并发环境下。
  • 网络多线程获取处理:多个线程负责接收和发送网络请求,这样可以并行化 I/O 操作,减少单一 I/O 线程成为瓶颈的可能性。
  • 本地单线程执行:尽管 I/O 操作被并行化,核心的数据处理仍然由主线程完成,保证了数据的一致性和安全性。
  • 网络多线程返回:经过处理后的响应再由多个 I/O 线程异步返回给客户端,提高了整体的响应速度。

总结:       

        Redis 的主从复制机制通过 replid 和 offset 实现了高效的数据同步,既支持初次同步时的全量传输,也支持断点续传式的增量同步,大大提升了系统的可用性和性能。结合 Redis Sentinel 和 Redis Cluster,可以构建高可用性和高性能的分布式系统。

        对于分布式锁,Redisson 提供了比原生 Redis 更强大的功能,包括可重入性、自动续期(Watch Dog)、以及对主从一致性的支持。Redlock 算法虽然提供了更高的可靠性,但在实现复杂度和性能上有一定的代价,适用于极端高可用性需求的场景。根据具体的业务需求选择合适的方案是至关重要的。

        Redis 的设计巧妙地结合了内存操作的高速度和单线程模型的安全性,通过 I/O 多路复用技术实现了高效的网络通信。随着 Redis 6.0 引入多线程 I/O,它能够在保持数据一致性的同时,进一步提升在高并发环境下的性能表现。对于开发者而言,理解 Redis 的这些特性有助于更好地优化应用程序的架构设计。

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值