mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4
3009 words
8 minutes
Redis浅析
2024-06-09

Redis基础#

redis是非关系型数据库 key-value存储系统 Redis的读写操作为纯内存的

常见数据类型#

  • String: 最基本的数据类型 缓存热点数据 分布式锁 计数器常用 缓存session token。

  • Hash: 是一个 String 类型的 field-value(键值对) 的映射表,特别适合用于存储对象,后续操作的时候,你可以直接修改这个对象中的某些字段的值 常用来用户信息 购物车 订单信息。 结构如下图所示。 Redis Hash示意图

  • List: 双向链表 如图所示 常用来 消息队列 聊天记录 评论列表。 Redis 双向链表示意图

  • Set: Set 类型是一种无序集合,集合中的元素没有先后顺序但都唯一,常用于点赞 收藏 关注 去重。 set set示意图

  • zSet: 集合(有序,不重复)销量排行榜 积分排行榜 热搜榜。 zSetset zSet示意图

Redis为纯内存操作 读写均发生在内存上 ,访问速度是纳秒 普通数据库为硬盘存储读写为毫秒 无法直接用Redis当主数据,因为内存成本太高,,且仍有丢失数据的风险

Redis持久化机制#

Redis 是基于内存的数据库,如果服务器宕机或者断电,内存中的数据会全部丢失。为了保证数据安全,Redis 提供了两种持久化方案:

  • RDB(Redis DataBase)
  • AOF(Append Only File)

生产环境中通常会同时开启 RDB 和 AOF,以兼顾恢复速度和数据安全。


RDB(快照持久化)#

RDB 本质上就是在某个时间点将 Redis 内存中的数据生成一份快照文件(dump.rdb)保存到磁盘中。当 Redis 重启时,可以直接加载该快照文件恢复数据。

Redis内存
生成快照
dump.rdb
Redis重启
恢复数据

RDB 的核心思想是保存某一时刻的数据状态,而不是记录每一次数据变更。

RDB 优点#

RDB 文件采用压缩后的二进制格式存储,相较于 AOF 文件体积更小,非常适合用于数据备份和灾难恢复。

同时,RDB 恢复速度非常快。Redis 在启动时只需要解析快照文件并恢复数据,而不需要重新执行大量命令,因此在大数据量场景下恢复效率明显优于 AOF。

另外,RDB 对 Redis 运行性能影响较小。执行持久化时 Redis 会通过 fork 创建子进程,由子进程负责生成 RDB 文件,主线程仍然可以继续处理客户端请求。

在主从复制场景中,RDB 也是全量同步的重要基础。从节点可以直接加载主节点生成的 RDB 快照完成数据同步。

RDB 缺点#

RDB 最大的问题是可能出现数据丢失。

例如 Redis 每隔 5 分钟生成一次快照,如果在两次快照之间发生宕机,那么最近 5 分钟内的数据将无法恢复。

此外,当数据量较大时,fork 子进程会消耗较多 CPU 和内存资源,在极端情况下可能影响 Redis 的正常运行。


AOF(追加日志持久化)#

AOF(Append Only File)采用记录写命令的方式实现持久化。每当 Redis 执行写操作时,都会将对应命令追加到 AOF 文件中。

例如:

SET name Tom
SET age 18
DEL user:1

这些命令都会被写入 AOF 文件。

当 Redis 重启时,会重新执行 AOF 中记录的命令,从而恢复数据库状态。

写命令
AOF缓冲区
AOF文件
Redis重启
重新执行命令

AOF 优点#

AOF 的最大优势是数据安全性更高。

通过配置不同的刷盘策略,可以实现秒级持久化。其中 appendfsync everysec 是最常用的配置,通常最多只会丢失 1 秒的数据。

由于 AOF 保存的是完整的操作命令,因此具有较好的可读性和可维护性。开发人员可以直接查看日志内容,甚至在误执行 FLUSHALL 等危险命令后,通过删除对应日志记录来恢复数据。

另外,AOF 采用追加写入的方式,不存在随机写问题。即使日志文件因异常情况损坏,也可以通过 redis-check-aof 工具进行修复。

AOF 缺点#

由于记录的是每一次写操作,因此 AOF 文件通常会比 RDB 文件更大。

Redis 启动时需要重新执行日志中的所有命令才能恢复数据,因此恢复速度通常慢于 RDB。

随着运行时间增长,AOF 文件会不断膨胀,需要定期执行 AOF Rewrite(重写)来压缩文件体积,这会带来额外的资源消耗。


RDB 与 AOF 对比#

对比项RDBAOF
持久化方式数据快照命令日志
文件大小
恢复速度较慢
数据安全性较低较高
可读性
性能影响略高
适用场景备份恢复数据持久化

总结#

RDB 适合用于数据备份和快速恢复,但存在一定的数据丢失风险;AOF 能够提供更高的数据安全性,但会占用更多存储空间且恢复速度较慢。

因此生产环境通常采用 RDB + AOF 混合持久化 的方式:

  • RDB 负责快速恢复;
  • AOF 负责降低数据丢失风险;

Redis 重启时会优先加载 AOF 文件,因为 AOF 中保存的数据通常更加完整。

Redis过期机制详解#

Redis过期时间存储#

Redis 并不会将过期时间存储在 Value 中,而是额外维护一个 expires 字典。

expires 字典的 Key 与主字典保持一致,Value 为毫秒级 Unix 时间戳。

当访问 Key 或执行定期删除任务时,Redis 会通过比较当前时间与 expires 中记录的时间戳判断 Key 是否已经过期。

这种设计既节省内存,又能高效完成过期检查。

Redis过期删除方式#

惰性删除#

当客户端访问Key时检查是否过期,未过期返回数据,已过期删除数据。 这样做

  1. 优点是节省cpu,只有在访问的时候才检查。
  2. 缺点是浪费内存,如果 Key 已经过期但一直没人访问,则一直存在内存中。

定期删除#

为了避免大量过期 Key 长期占用内存。edis 会启动后台任务: 每隔一段时间 -> 随机抽取部分Key -> 检查是否过期 -> 删除过期 Key

注意: Redis 并不会扫描所有 Key。 如果扫描全部:1000万 Key -> 全量扫描 -> Redis 阻塞 这是不可接受的因此Redis采用随机抽样策略。

内存淘汰策略#

当redis内存存储达到配置上限时使用的淘汰数据策略,共八种。

  1. noeviction:不淘汰任何数据,内存满了直接报错。
  2. volatile-ttl:有过期时间的Ke最接近过期时间的key优先删除。
  3. volatile-random:设置了过期时间的Key随即删除。
  4. volatile-lru:设置过期时间的Key中删除最近最少使用
  5. volatile-lfu: 从设置过期时间的Key中删除访问频率最低的数据。
  6. allkeys-random:所有Key随即删除。
  7. allkeys-lru:从所有Key中淘汰最近最少使用的数据。
  8. allkeys-lfu: 从所有Key中删除访问频率最低的数据。 内存淘汰示意图 需要注意Redis 的 LRU 并非严格实现,而是基于随机采样实现的近似 LRU,以降低维护成本和提升性能。

Redis缓存三大问题#

在高并发系统中,引入 Redis 可以极大降低数据库压力,提高系统响应速度。

但如果缓存设计不合理,仍然可能导致数据库压力骤增,甚至引发系统崩溃。

常见的缓存问题主要有三种:

  • 缓存穿透(Cache Penetration)
  • 缓存击穿(Cache Breakdown)
  • 缓存雪崩(Cache Avalanche)

一、缓存穿透#

什么是缓存穿透#

缓存穿透是指:

请求的数据既不存在 Redis 中,也不存在数据库中。

由于缓存和数据库都查询不到数据,因此每次请求都会直接访问数据库。

例如用户查询一个不存在的商品:

商品ID:99999999

查询流程:

用户请求
Redis不存在
MySQL不存在
返回空结果

如果恶意用户持续请求不存在的数据:

99999999
99999998
99999997
99999996
...

那么所有请求都会直接打到数据库。

最终可能导致数据库压力过大。


解决方案#

方案一:缓存空对象#

当数据库查询结果为空时,也写入缓存。

例如:

key:user:99999
value:null
expire:5分钟

下次请求时:

用户请求
Redis命中null
直接返回

避免再次访问数据库。

优点:

  • 实现简单
  • 效果明显

缺点:

  • 会占用部分缓存空间

方案二:布隆过滤器#

布隆过滤器用于判断数据是否可能存在。

查询流程:

请求
布隆过滤器
不存在
直接返回

如果布隆过滤器判断不存在:

直接拦截

不会访问 Redis 和 MySQL。

适用于:

  • 用户系统
  • 商品系统
  • 大数据量场景

二、缓存击穿#

什么是缓存击穿#

缓存击穿指:

某个热点数据突然失效,大量请求同时访问数据库。

特点:

  • 数据存在
  • 请求量极高
  • 某一时刻缓存过期

例如:

商品ID:1001

平时:

10000请求
Redis

缓存过期瞬间:

10000请求
MySQL

数据库压力瞬间暴增。


解决方案#

方案一:互斥锁#

第一个线程查询数据库并重建缓存。

其他线程等待。

请求
获取锁
查询数据库
写入缓存
释放锁

示例:

if(redis.get(key)==null){
if(lock.tryLock()){
Object data=queryDB();
redis.set(key,data);
lock.unlock();
}
}

优点:

  • 实现简单
  • 使用广泛

缺点:

  • 存在线程等待

方案二:逻辑过期#

不给缓存设置真实过期时间。

缓存结构:

{
"data":{},
"expireTime":"2026-06-23 10:00:00"
}

查询流程:

读取缓存
判断逻辑时间
过期
异步更新缓存

用户仍然能获得旧数据。

适用于:

  • 商品详情
  • 首页数据
  • 热门榜单

三、缓存雪崩#

什么是缓存雪崩#

缓存雪崩指:

大量缓存同时失效,导致大量请求直接访问数据库。

例如:

100万个Key

全部设置:

TTL = 30分钟

30分钟后:

全部过期
请求全部打向MySQL

数据库可能直接崩溃。


产生原因#

大量缓存同时过期#

key1 30分钟
key2 30分钟
key3 30分钟
key4 30分钟

统一失效。


Redis服务宕机#

Redis挂掉
缓存全部失效
请求直达数据库

解决方案#

方案一:TTL随机化#

不要统一设置过期时间。

错误方式:

redis.set(key,data,30,MINUTES);

推荐方式:

redis.set(
key,
data,
30 + RandomUtil.randomInt(10),
MINUTES
);

效果:

30分钟
34分钟
36分钟
39分钟

避免同时失效。


方案二:多级缓存#

浏览器缓存
Nginx缓存
Redis缓存
MySQL

降低数据库压力。


方案三:Redis集群#

采用:

  • 主从复制
  • Sentinel哨兵
  • Cluster集群

避免单点故障。


方案四:服务降级与限流#

当 Redis 异常时:

拒绝部分请求

例如:

  • Sentinel
  • Nacos
  • Gateway限流

保护数据库。


三大问题对比#

问题数据是否存在缓存状态风险
缓存穿透不存在不存在数据库被恶意攻击
缓存击穿存在热点Key失效瞬时高并发压垮数据库
缓存雪崩存在大量Key失效数据库整体崩溃

总结#

缓存穿透、缓存击穿和缓存雪崩本质上都是由于缓存失效导致数据库压力激增的问题。

对于实际项目来说,通常采用以下组合方案:

  • 缓存空对象 + 布隆过滤器解决缓存穿透;
  • 互斥锁 + 逻辑过期解决缓存击穿;
  • TTL随机化 + Redis集群 + 限流降级解决缓存雪崩。

这是目前互联网项目中最常见的一套缓存保护方案。

Share

If this article helped you, please share it with others!

Redis浅析
https://mizuki.mysqil.com/posts/redis/
Author
梦幻晨风
Published at
2024-06-09
License
CC BY-NC-SA 4.0

Some information may be outdated

Table of Contents