# Redis 实战
# 缓存更新策略
| 内存淘汰 | 超时剔除 | 主动更新 | |
|---|---|---|---|
| 说明 | 不用自己维护,利用 Redis 的内存淘汰机制,当内存不足时自动淘汰部分数据,下次查询时更新缓存 | 给缓存添加 TTL 时间,到期后自动删除缓存,下次更新时更新缓存 | 编写业务逻辑,在修改数据库时,更新缓存 |
| 一致性 | 差 | 一般 | 好 |
| 维护成本 | 无 | 低 | 高 |
业务场景 :
- 低一致性需求:使用内存淘汰机制,例如店铺类型的查询缓存
- 高一致性需求:主动更新,并以超时剔除作为兜底方案。例如店铺详情查询的缓存
# 主动更新策略
- Cache Aside Pattern : 由缓存的调用者,在更新数据库的同时更新缓存
- Read/Write Through Pattern : 缓存与服务器整合为一个服务,由服务来维护一致性。调用者调用该服务,,无需关心缓存一致性的问题
- Write Behind Cacheing Pattern : 调用者只操作缓存,由其它线程异步的将缓存数据持久化到数据库,保证最终一致
** 最佳实践方案 : **
- 低一致性需求:使用 Redis 自带的内存淘汰机制
- 高一致性需求:主动更新,并以超时剔除作为兜底方案
- 读操作 :
- 缓存命中直接返回
- 缓存未命中则查询数据库,并写入缓存,设定超时时间
- 写操作 :
- 先写数据库,然后再删除缓存
- 要确保数据库与缓存操作的原子性
- 读操作 :
先删缓存,再操作数据库

先操作数据库,再删除缓存

# 缓存穿透
缓存穿透是指客户端请求的数据再缓存中和数据库中都不存在,这样缓存永远不会生效,这些请求都会打到数据库
常见的解决方案有两种 :
- 缓存空对象
- 优点:实现简单,维护方便
- 缺点 :
- 额外的内存消耗
- 可能造成短期的不一致
- 布隆过滤
- 优点:内存占用较少,没有多余 key
- 缺点 :
- 实现复杂
- 存在误判可能

# 缓存雪崩
缓存雪崩是指在同一时段大量的缓存 key 同时失效或者 Redis 服务宕机,导致大量请求到达数据库,带来巨大压力
解决方案 :
- 给不同的 key 的 TTL 添加随机值
- 利用 Redis 集群提高服务的可用性
- 给缓存业务添加降级限流策略
- 给业务添加多级缓存

# 缓存击穿
缓存击穿问题也叫热点 Key 问题,就是一个被高并发访问并且缓存重建业务较复杂的 key 突然失效了,无数的请求访问会在一瞬间给数据库带来巨大的冲击
常见的解决方案有两种 :
- 互斥锁
- 逻辑过期

# 互斥锁

# 逻辑删除

| 解决方案 | 优点 | 缺点 |
|---|---|---|
| 互斥锁 | 没有额外的内存消耗,保证一致性,实现简单 | 线程需要等待,性能受影响,可能有死锁风险 |
| 逻辑过期 | 线程无需等待,性能较好 | 不保证一致性,有额外内存损耗,实现复杂 |
# 全局 ID 生成器
为了增加 ID 的安全性,我们可以不直接使用 Redis 自增的数值,而是拼接一些其他信息 :

ID 的组成部分 :
- 符号位 : 1bit, 永远为 0
- 时间戳 : 31bit, 以秒为单位,可以使用 69 年
- 序列号 : 32bit, 秒内的计数器,支持每秒产生 个不同 ID
# 超卖问题
超卖问题是典型的多线程安全问题,针对这一问题的常见解决方案就是加锁
悲观锁
认为线程安全问题一定会发生,因此在操作数据之前先获取锁,确保线程串行执行
- 例如 Synchronized, Lock 都属于悲观锁
乐观锁
认为线程安全不一定会发生,因此不加锁,只是在更新数据时去判断有没有其他线程对数据进行了更改
- 如果没有修改则认为是安全的,自己才会更新数据
- 如果已经被其他线程修改说明发生了线程安全问题,此时可以重试或异常
# 乐观锁
版本号法
数据库维护一个 version 信息,每次更改数据之前判断 version 是否与获取到的 version 一致
CAS 法
每次更改数据前判断数据是否与之前或独到的一致
# 分布式锁的实现
| MySQL | Redis | Zookeeper | |
|---|---|---|---|
| 互斥 | 利用 mysql 本身的互斥锁机制 | 利用 setnx 这样的互斥命令 | 利用节点的唯一性和有序性实现互斥 |
| 高可用 | 好 | 好 | 好 |
| 高性能 | 一般 | 好 | 一般 |
| 安全性 | 断开连接,自动释放锁 | 利用锁超时时间,到期释放 | 临时节点,断开连接自动释放 |
# 基于 Redis 的分布式锁
实现分布式锁需要实现的两个基本方法 :
获取锁 :
互斥:确保有一个线程获取锁
非阻塞:尝试一次,成功返回 true, 失败返回 false
1
SET lock thread1 NX EX 10
释放锁 :
手动释放
超时释放:获取锁时添加一个超时时间
1
DEL key
# Redis 消息队列
消息队列,字面意思就是存放消息的队列。最简单的消息队列包括 3 个角色
- 消息队列:储存和管理消息,也被称为消息代理 (Message Broker)
- 生产者:发送消息到消息队列
- 消费者:从消息队列获取消息并处理消息
Redis 提供了三种不同的方式来实现消息队列 :
- List 结构:基于 List 结构模型模拟消息队列
- PubSub : 基本的点对点消息模型
- Stream : 比较完善的消息队列模型

# 基于 List 结构模型模拟消息队列
优点 :
- 利用 Redis 存储,不受限于 JVM 内存上限
- 基于 Redis 的持久化机制,数据安全性有保证
- 可以满足消息有序性
缺点 :
- 无法避免消息丢失
- 只支持单消费者 (无法实现一个消息被多个消费者使用,因为一旦 pop 之后该队列元素就删除了)
# 基于 PubSub 的消息队列
PubSub (发布订阅) 是 Redis2.0 版本引入的消息传递模型。顾名思义,消费者可以订阅一个或多个 channel, 生产者向对应 channel 发送消息后,所有订阅者都能收到相关消息
- subscribe channel [channel] : 订阅一个或多个评到
- publish channel msg : 向一个频道发送消息
- psubscribe pattern [pattern] : 订阅与 pattern 格式匹配的所有频道
优点 :
- 采用发布订阅模型,支持多生产,多消费
缺点 :
- 不支持数据持久化
- 无法避免消息丢失
- 消息堆积有上限,超出时数据丢失
# 基于 Stream 的消息队列
Stream 是 Redis5.0 引入的一种新数据模型,可以实现一个功能非常完善的消息队列
Stream 类型消息队列的 xread 命令特点 :
- 消息可回溯
- 一个消息可以被多个消费者读取
- 可以阻塞读取
- 有消息漏读的风险
# 消费者组
消费者组 (Consumer Group) : 将多个消费者划分到一个组中,监听同一个队列。具备以下特点 :
- 消息分流:队列中的消息会分流给组内的不同消费者,而不是重复消费,从而加快消息处理的速度
- 消费者会围护一个标识,记录最后一个被处理的消息,哪怕消费者宕机重启,还会从标识之后读取消息。确保每一个消息都会被消费
- 消费者获取消息后,消息处于 pending 状态,并存入一个 pending-list 中,当处理完成后需要通过 xack 来确认消息,标记消息为已处理,才会从 pending-list 中移除
1 | XGROUP CREATE KEY groupName ID [MKSTREAM] |
- key : 队列名称
- groupName : 消费者组名称
- ID : 起始 ID 标识,$ 代表队列中最后一个消息,0 则代表队列中第一个消息
- MKSTREAM : 队列不存在时自动创建按队列
1 | XREADGROUP GROUP group consumer [COUNT count] [BLOCK millseconds] [NOACK] STREAMS key [key ...] ID [ID ...] |
- group : 消费组名称
- consumer : 消费者名称
- count : 本次查询的最大数量
- BLOCK milliseconds : 当没有消息时最长等待时间
- NOACK : 无需手动 ACK, 获取到消息后自动确认
- STREAMS key : 指定队列名称
- ID : 获取消息的起始 ID :
- “>” : 从下一个未消费的消息开始
- 其他:根据指定 id 从 pending-list 中获取已消费但未确认的消息,例如 0, 是从 pending-list 中的第一个消息开始
stream 类型队列消息的 xreadgroup 命令特点 :
- 消息可回溯
- 可以多消费者争抢消息,加快消费速度
- 可以阻塞读取
- 没有消息漏读的风险
- 有消息确认机制,保证消息至少被消费一次
# Redis 消息队列
| d | List | PubSub | Stream |
|---|---|---|---|
| 消息持久化 | 支持 | 不支持 | 支持 |
| 阻塞读取 | 支持 | 支持 | 支持 |
| 消息堆积处理 | 受限于内存空间,可以利用多消费者加快处理 | 受限于消费者缓冲区 | 受限于队列长度,可以利用消费者组提高消费速度,减少堆积 |
| 消息确认机制 | 不支持 | 不支持 | 支持 |
| 消息回溯 | 不支持 | 不支持 | 支持 |
