Redis进阶学习笔记

知识分子没文化
2024-03-07 / 0 评论 / 3,112 阅读 / 3,391 字数 / 正在检测是否收录...

目录:

Redis 官网:Redis - The Real-time Data Platform

一、Redis 的基础知识

1.1、什么是 Redis

Redis(REmote DIctionary Server)是一个开源的、基于内存的、键值对(Key-Value)数据库,它支持多种数据结构,并提供原子操作、事务、Lua脚本、发布订阅等高级特性。

Redis 支持的基础数据类型有五种:

数据类型 介绍 底层实现 特点
String(字符串) 最基础、最常用的数据类型 SDS(简单动态字符串) 可以存储:字符串、整数、浮点数、二进制数据
单个数据最大支持 512MB
支持原子操作(如自增、自减)
List(列表) 本质是一个双向链表 压缩列表 / 双向链表 有序、可重复
支持从两端插入/弹出
操作时间复杂度:头尾 O(1)
Hash(哈希) 类似于 Map<String, String> 压缩列表 / 哈希表 存储键值对集合(field-value)
适合存储对象
支持单字段读写
Set(集合) 无序、不 哈希表 / 整数集合 自动去重
支持集合运算(交集、并集、差集)
ZSet(Sorted Set,有序集合) 在 Set 基础上增加了一个 score(分数) 用来排序 跳跃表 + 哈希表 元素唯一,但 score 可重复
按 score 自动排序(底层是跳跃表 + 哈希表)
查询效率高(范围查询)

还有几种基于基础数据类型的扩展类型:

数据类型 介绍 特点
Bitmap(位图) 基于 String 的按位操作 每一位是 0 或 1
非常节省空间(适合大规模布尔状态)
HyperLogLog 用于基数统计(去重计数)的概率算法 占用极小内存(≈12KB)
有一定误差(约 0.81%)
Geospatial(地理位置) 用于存储地理位置信息 基于 Sorted Set 实现
支持距离计算、范围搜索
Stream(流,Redis 5+) 类似消息队列,但功能更强 支持消费组(Consumer Group)
支持消息持久化
可回溯消息

1.2、为什么要有 Redis

传统关系型数据库(如 MySQL)擅长复杂查询和事务,但磁盘 I/O 导致高并发下存在延迟高、吞吐低的问题,因此数据库的读写性能往往会成为瓶颈。

而加入 Redis 作为高性能缓存,将使用频率高的数据(热点数据)放在内存中,需要时可以直接通过请求 Redis 获取,只有当 Redis 获取不到时才会查询数据库,这种方式可以降低数据库的压力,同时还显著提升系统的响应速度和吞吐量。

Redis 的主要优势包括:

  • 基于内存:所有数据存储在内存中,读写也基于内存操作,避免了磁盘 I/O,因此速度极快,这是最根本的原因;

  • 单线程模型(核心I/O线程)

    • 准确来讲,Redis 不是完全单线程,Redis 6.0 之后,网络I/O、持久化会使用多线程,但命令执行依然是单线程
    • 在进行读写操作时,单线程可以避免多线程的上下文切换和竞争(锁竞争、死锁)带来的额外开销,反而提升了处理速度,此时的瓶颈不在CPU,而在内存和网络;
    • 并且由于单线程没有了复杂的同步机制,因此也不易出错;
  • I/O多路复用机制:让单线程能同时监听多个 socket 连接,当某个连接有数据请求时,才去处理。类似于一个服务员同时服务很多桌客人,谁举手就去谁那。使用了epoll等高效机制;

  • 高效的数据结构:底层针对不同场景设计了极致优化的数据结构。

二、内存管理与淘汰机制

Redis 是内存数据库,随着应用的运行,如果不清理,Redis 中的 Key 会不断增加,最终内存耗尽导致 OOM(Out Of Memery)。

针对于内存溢出问题,Redis 通过内存过期策略预防内存溢出,而当内存使用达到 maxmemory 上限导致新 Key 无法写入时,Redis 使用内存淘汰机制淘汰旧数据为新数据腾出空间。

2.1、内存过期策略

Redis 通过为 Key 设置过期时间(TTL)来管理内存中的数据生命周期。当 Key 的过期时间到达时,Redis 会自动删除该键及其对应的值,以避免旧数据的无限累计。

内存过期策略主要包括:

  • 定时删除:为每个设置过期时间的 Key 维护一个定时器,当过期时间到达时立即删除。但这种方式会消耗大量的CPU资源,比较影响性能。
  • 惰性删除:在访问 Key 时才检查其是否过期,如果过期则删除。这种方式不会额外消耗CPU资源,但可能导致内存中存在大量过期的 Key,占用内存空间。

需要说明的是,Redis 并不是在 Key 到期的一瞬间就立刻将其从内存中删除,而是采用了定时删除与惰性删除两种策略的组合:

  • Redis 内部开了一个定时任务,默认每段时间(比如 100ms)运行一次;
  • 从设置了过期时间(TTL)的 Key 中随机抽取 20 个数据进行检查;
  • 如果这 20 个 Key 中过期的 Key 占比超过 25%(即超过 5 个),Redis 会认为当前内存中过期 Key 较多,不会等待下一个 100ms,而是立即重复上一个步骤继续抽查、继续清理,直到过期的 Key 占比低于 25%。

内存过期策略也有可能会导致一个问题,如果同一时刻大量数据过期或者 Redis 无法提供服务,客户端请求从 Redis 中获取不到已过期的数据就会查询数据库,从而使数据库请求激增,压力过大造成数据库服务器宕机,即缓存雪崩

所以要避免大量数据在同一时刻过期,解决办法是在固定过期时间上增加一个随机偏移量,同时对于热点数据,不设置过期时间,使其达到“物理”上的永不过期,可以避免缓存击穿问题。

2.2、内存淘汰机制

当内存使用达到 maxmemory 上限,且新数据写入时,触发淘汰策略,根据配置的内存淘汰策略删除部分键值对以释放内存。

那么应该选择哪些数据淘汰,大致的思想有两种:

  • LRU(Least Recently Used)是按照最近最少使用原则来筛选数据,即最不常用的数据会被筛选出来;

  • LFU(Least Frequently Used)是 Redis4 新增的淘汰策略,它根据 key 的最近访问频率进行淘汰,即访问次数最少的数据会被筛选出来。

再结合淘汰数据的范围(全部 Key 或设置了过期时间的 Key),那么综合考虑下来内存淘汰策略就有八种:

策略 描述 适用场景
noeviction 默认策略。不淘汰,写请求直接返回错误(如SET失败) 数据绝不能丢的场景,如作为数据库
allkeys-random 从所有key中随机淘汰 缓存数据访问频率相近
allkeys-lru 从所有数据中淘汰最近最少使用(LRU)的key 通用缓存,希望保留最可能被再次访问的数据
allkeys-lfu(Redis4.0) 从所有数据中淘汰使用频率最低(LFU)的key 通用缓存,希望保留经常被访问的数据
volatile-ttl 从设置了过期时间的key中,淘汰将要过期的key 通用缓存,希望优先淘汰快过期的数据
volatile-random 从设置了过期时间的key中,随机淘汰key 缓存数据访问频率相近
volatile-lru 从设置了过期时间的数据中淘汰最近最少使用(LRU)的key 通用缓存,希望保留最可能被再次访问的数据
volatile-lfu(Redis4.0) 从设置了过期时间的数据中淘汰使用频率最低(LFU)的key 通用缓存,希望保留经常被访问的数据,比LRU更精准

结合内存过期策略来说,如果此时设置的淘汰策略是 allkeys-lruallkeys-random,那么即使某个 Key 还没到过期时间,也可能被提前淘汰,使用 volatile-lruvolatile-ttl 策略,即只针对有过期时间的 Key 进行淘汰,就可以解决这个问题。

三、持久化

对于 Redis 来说,由于数据都在内存中,如果发生断电或进程崩溃时就必然面临数据丢失的风险,因此有了数据的持久化,以便在 Redis 重启后能恢复数据

Redis 支持的持久化方式有三种:RDB 持久化AOF 持久化RDB-AOF 混合持久化

3.1、RDB 持久化

RDB(Redis Database)是 Redis 默认采用的持久化方式,它的原理是 Redis 在后台 fork(复制)一个子进程,子进程将该时间的内存数据生成快照文件写入到磁盘中,创建一个经过压缩的 “.rdb” 二进制文件,存储各个数据库的键值对数据等信息。

RDB 持久化的触发方式有两种:

  • 手动触发:通过 SAVE 或 BGSAVE 命令触发 RDB 持久化操作,创建“.rdb”文件;
  • 自动触发:通过配置选项,让服务器在满足指定条件时自动执行 BGSAVE 命令。

RDB 持久化的优缺点:

  • 优点:RDB生成紧凑压缩的二进制文件,体积小,恢复数据的速度非常快;
  • 缺点:RDB 持久化没办法做到实时,BGSAVE 每次运行都要执行 fork 操作创建子进程,属于重量级操作,不宜频繁执行。

3.2、AOF 持久化

AOF 持久化的原理是记录了每次写入命令到 “.aof” 格式的文本文件,类似MySQL的binlog,当 Redis 重启时再重新执行 AOF 文件中的命令来恢复数据到内存中。

AOF的工作流程包括:命令写入(append)、文件同步(sync)、文件重写(rewrite)、重启加载(load)。

需要控制命令同步到硬盘的频率,即多久将命令写入到 AOF 文件中一次,就是同步策略 (appendfsync):

  • appendfsync always:每次写操作后立即同步到磁盘。最安全,但性能最差;
  • appendfsync everysec:每秒同步一次。折中方案,默认推荐,最多丢1秒数据,性能和安全平衡最好;
  • appendfsync no:由操作系统决定同步时机。最快,但最不安全,但可能丢大量数据。

AOF 重写(Rewrite)机制:为了解决文件越来越大的问题,Redis会自动(或手动 BGREWRITEAOF)在后台重写 AOF 文件。只保留“当前数据集的最小命令集合”,去掉冗余操作(比如多次 SET 同一个 Key,只留最后一次)。重写操作由子进程完成,不会阻塞主进程。

AOF 持久化的优缺点:

  • 优点:

    • 数据安全性高,最多丢失一秒的数据;
    • 生成的 “.aof” 格式的日志文件可读,便于调试和修复。
  • 缺点:

    • 文件通常比RDB大,恢复数据慢(因为需要逐条重新执行命令);

    • 对性能有一定影响(尤其是 always 策略)。

3.3、RDB-AOF 混合持久化(Redis 4.0)

RDB 持久化和 AOF 持久化都有各自鲜明的优缺点,因此从 Redis4.0 开始引入集成二者优点的持久化方式:RDB-AOF 混合持久化,这种方式的原理是将内存数据先以 RDB 格式写入一个快照到 AOF 文件头部,后续再把写命令追加到快照后面,恢复时先恢复数据的快照,再执行后面的追加命令。

与 AOF 持久化类似,RDB-AOF 混合持久化也具备同步策略重写机制

优势:兼顾了 RDB 持久化的加载速度和 AOF 持久化的数据安全,是 Redis 生产环境的首选方案。

四、Redis集群方案

4.1、单机模式(Standalone)

一台服务器上运行一个Redis实例(redis-server)。数据全部放在一台机器的内存里,所有读写操作都经过这个实例,没有任何备份或扩展。

优点:部署简单,适合开发、测试、小型项目;且性能最好,因为没有网络复制开销

缺点:机器宕机、Redis进程崩溃,都会导致服务完全不可用;数据量超过单机内存或 QPS 过高,就扛不住。

4.2、主从复制(Replication)

一个主节点(Master)负责处理写请求,多个从节点(Replica / Slave)实时复制主节点(Master)的数据,只负责处理读请求。整个集群的数据流向是单向的:Master -> Slave。

优点:结构简单,配置容易;从节点分担主节点读压力,极大提升 QPS

缺点:所有写操作都依赖主节点,写性能上限受限于主节点,无法水平扩展写能力;主节点故障需要人工干预切换,无法自动故障转移;异步复制可能导致轻微数据不一致

4.3、哨兵模式(Redis Sentinel)

哨兵模式基于主从模式扩展,主要解决主从模式中主节点(Master)宕机无法自动恢复的问题,以实现高可用。

在主从架构的基础上,引入一组独立的 Sentinel(哨兵)节点。Sentinel 不处理业务读写,只负责心跳检测主从节点,当主节点故障(客观下线),哨兵通过 Raft 协议投票选出一个新的主节点,并通知客户端和其他从节点。

主观下线与客观下线:一个 Sentinel 认为节点挂了是主观下线,半数以上 Sentinel 认为挂了才是客观下线,避免网络误判。

哨兵节点的数量至少为 3 个,且为奇数个,以避免脑裂问题,总体架构推荐至少 3 Sentinel + 1 Master + 2+ Replica。

优点:保留了主从模式的优点,依然支持读写分离,从节点分担读压力;实现了故障自动检测和切换

缺点:本质上还是主从模式,写性能上限受限于主节点,依然无法水平扩展写能力;且主从切换过程中,Redis 会短暂不可用

4.4、Redis Cluster

Redis Cluster 是 Redis 官方提供的分布式解决方案,自 Redis 3.0 引入,旨在通过分片(Sharding)技术将数据分布在多个主节点上,解决单机写瓶颈和内存容量瓶颈。

Redis Cluster 将整个数据集分成 16384 个哈希槽(Hash Slot)。每个键通过 CRC16(key) % 16384 计算后,决定落在哪个槽位。集群中存在多个主节点(Master),每个主节点负责一段连续的槽位区间(如 0-5000、5001-10000、10001-16383),并且可挂载一个或多个从节点(Replica)作为数据备份实现高可用。节点之间通过 Gossip 协议相互通信,自动发现新节点、传播故障信息、协调槽位迁移。客户端连接任意一个节点,若请求的数据不在当前节点,节点会返回正确的目标节点地址(MOVED 重定向),智能客户端根据新的目标节点地址自动跳转到正确的节点。

优点

  • 数据容量水平扩展:总数据量不再受限于单机内存,集群总容量随节点增加而线性增长
  • 写性能水平扩展:写操作分散到多个主节点,增加节点即可线性提升写吞吐量,彻底突破单机写瓶颈
  • 内置高可用:主节点故障自动转移,无需人工干预,比哨兵 Sentinel 更快
  • 故障隔离更好:单个节点故障只影响部分数据,只影响它负责的槽位,其他数据仍可正常读写

缺点

  • 复杂度高:搭建、监控、运维更麻烦(需要处理 slot 迁移、热点 slot 等)
  • 客户端要求高:必须用支持Cluster的客户端(Redisson、go-redis等),否则需要处理MOVED重定向
  • 批量操作受限:不支持跨节点的事务、和多 Key 操作(除非 Key 带有相同的 {hashtag},强制将其分配到同一槽位)
  • 资源消耗更高:至少6个节点起步
  • 数据倾斜风险:热点key可能集中在某个节点,需要优化key设计

五、应用场景

5.1 数据库

Redis 可以作为主数据库使用,适用于对性能要求高、数据量相对较小的场景,如会话存储、实时排行榜等。

5.2 系统缓存

Redis 最常见的用途是作为缓存层,通过缓存热点数据,可以减少对数据库的查询次数,减轻数据库负担,显著提高应用访问速度。

5.3 消息队列

Redis 的列表和发布/订阅模式可以用于实现消息队列:

  • 通过列表(List)的 LPUSH 和 RPOP 命令可以实现简单的消息队列功能
  • 通过发布/订阅模式可以实现更复杂的消息分发和订阅功能

5.4 计数器和排行榜

Redis 的原子操作和有序集合(ZSet)非常适合实现计数器和排行榜功能,通过为每个元素关联一个分数,可以方便地实现排序和统计功能,如网站访问统计、游戏积分榜等。

5.5 分布式锁

Redis 的原子性操作可以用于实现分布式锁。通过 SETNX 命令可以尝试获取锁,如果获取成功则执行临界区代码;执行完毕后通过 DEL 命令释放锁,保证在分布式环境下的互斥访问。为了避免死锁,还可以为锁设置过期时间。

六、缓存相关问题

在高并发系统中,使用 Redis 做缓存时,最容易引发系统故障的三大“杀手”就是缓存穿透缓存击穿缓存雪崩

6.1、缓存穿透

客户端请求的数据在 Redis 中不存在(缓存未命中),同时在数据库中也不存在。每次请求都会直接打到数据库。如果是恶意构造的大量无效请求(比如 userId = -1、随机长字符串、已删除的商品 ID),数据库瞬间就被打崩。

核心特点

  • 查询的是“根本不存在”的数据。
  • 缓存和数据库都无法命中,请求每次都穿透。
  • 极易被恶意攻击利用(也叫恶意穿透)。

常见场景

  • 接口未做参数校验,攻击者用不存在的 ID 疯狂刷接口。
  • 查询已下架或从未存在过的商品 / 用户详情。

解决方式

  • 接口层参数校验(第一道防线):对请求参数做基础合法性检查(格式、长度等),提前过滤无效请求
  • 缓存空对象(空值缓存)(最常用、简单有效):当数据库返回 null/空时,也在 Redis 中缓存一个空结果,并设置较短的过期时间(建议 30 秒 ~ 5 分钟,避免长期占用内存)
  • 布隆过滤器(Bloom Filter):在 Redis 前加一层布隆过滤器,提前判断该 Key 是否可能存在,如果肯定不存在,直接返回 null,不查 Redis 和数据库,如果可能存在,再走正常缓存流程。

6.2、缓存击穿

某个高并发热点 Key 的缓存突然过期(或被删除),而此时有大量并发请求同时到达。所有请求发现缓存 miss,同时去查数据库,导致数据库瞬时压力暴增,甚至宕机。

核心特点

  • 问题发生在单个热点 Key 过期瞬间
  • 查询的是真实存在非常热门的数据。
  • 影响虽然是单个 Key,但并发量极大,破坏力强。

常见场景

  • 秒杀商品的库存/详情 Key 刚好过期。
  • 热门新闻、明星话题、爆款商品的缓存到期。
  • 大促期间某个热卖品的缓存失效。

解决方式(核心:只让一个请求去查数据库,其他等待或用旧数据):

  • 缓存未命中时,先尝试获取分布式锁。只有拿到锁的线程才允许查询数据库,其他线程等待或短暂重试。

  • 不依赖 Redis 的原生 expire,而是把“过期时间”字段存到 Value 里(例如 JSON 中加一个 expireTime)。用户请求时,如果发现逻辑已过期,后台异步线程去刷新数据,用户先返回旧数据

  • 热点数据永不过期 + 后台刷新 对极重要热点 Key 不设过期时间,或设很长 TTL,由定时任务/消息队列后台定期刷新

6.3、缓存雪崩

大量缓存 Key 在同一时间段内集体过期(或 Redis 实例宕机),导致海量请求同时穿透到数据库,数据库压力骤增,甚至引发连锁反应导致系统雪崩。

核心特点

  • 影响的是大量 Key(而非单个)。
  • 通常发生在同一时刻大量 Key 同时失效。
  • 后果最严重,可能导致整个服务不可用。

常见场景

  • 所有 Key 统一设置相同过期时间(如全设 30 分钟)。
  • 大促活动结束,大量活动缓存同时到期。
  • Redis 重启或故障(无持久化时,所有缓存瞬间丢失)。

解决方式

  • 最简单、最有效:设置过期时间时加上随机偏移,避免大量 Key 同时到期
  • 缓存预热 + 永不过期策略:提前把热点数据加载到缓存;核心数据可永不过期,由后台异步更新
  • 多级缓存:第一层用 Caffeine/Guava 本地缓存挡住大部分请求,Redis 作为二级,数据库作为最后防线
  • 熔断降级 + 限流 + 快速失败:使用 Sentinel / Hystrix / Resilience4j 等框架:当数据库压力过大时,快速熔断,返回兜底数据(“系统繁忙,请稍后重试”或缓存的旧数据)
3

评论 (0)

取消