目录:
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-lru 或 allkeys-random,那么即使某个 Key 还没到过期时间,也可能被提前淘汰,使用 volatile-lru 或 volatile-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 等框架:当数据库压力过大时,快速熔断,返回兜底数据(“系统繁忙,请稍后重试”或缓存的旧数据)
评论 (0)