Redis缓存系统常见应用场景
哪吒 2020/1/1
点击勘误issues (opens new window),哪吒感谢大家的阅读

# Redis缓存系统常见应用场景
# 📦 一、Redis 缓存系统常见应用场景
场景 | 示例 |
---|---|
热门商品缓存 | 电商首页、详情页秒开 |
订单信息缓存 | 避免频繁访问数据库 |
登录态存储 | Token、Session 缓存 |
接口幂等校验 | 基于 Redis 保证请求唯一 |
防刷频控 | 限制单位时间内的请求次数 |
# 🔥 二、热点缓存(高频数据缓存)
# ✅ 目标:
避免对数据库频繁查询相同数据,使用 Redis 缓存住热点。
# 示例代码:
public Product getProduct(Long productId) {
String cacheKey = "product:" + productId;
String json = redisTemplate.opsForValue().get(cacheKey);
if (StringUtils.hasText(json)) {
return JSON.parseObject(json, Product.class);
}
// 查询数据库
Product product = productMapper.selectById(productId);
if (product != null) {
redisTemplate.opsForValue().set(cacheKey, JSON.toJSONString(product), 10, TimeUnit.MINUTES);
}
return product;
}
# 🚫 三、缓存穿透(查询不存在的数据)
# ✅ 场景:
恶意请求大量不存在的 key,缓存未命中,数据库压力骤增。
# ✅ 解决方案:
# ✅ 1. 布隆过滤器(Bloom Filter)
- 将所有合法 ID 加入布隆过滤器,非法请求直接拦截。
if (!bloomFilter.contains(id)) {
return null; // 无效请求
}
# ✅ 2. 缓存空值
- 将查询结果为
null
也缓存一段时间,避免频繁打数据库。
if (product == null) {
redisTemplate.opsForValue().set(cacheKey, "", 2, TimeUnit.MINUTES);
}
# 🧊 四、缓存雪崩(大面积缓存同时过期)
# ✅ 场景:
缓存集中在某一时刻过期,导致短时间大量请求打到数据库。
# ✅ 解决方案:
方法 | 描述 |
---|---|
缓存过期加随机 | 给过期时间增加随机值,避免同时失效 |
数据预热 | 启动时或定时预加载关键数据到缓存 |
多级缓存 | 本地缓存 + 分布式缓存(如 Guava + Redis) |
// 设置过期时间随机值 10-15 分钟
long timeout = 10 + new Random().nextInt(5);
redisTemplate.opsForValue().set(key, value, timeout, TimeUnit.MINUTES);
# 🔐 五、缓存击穿(单个热点数据失效)
# ✅ 场景:
某个高并发访问的 key 刚好失效,大量请求同时打到数据库。
# ✅ 解决方案:
# ✅ 1. 分布式锁保护(推荐使用 Redisson)
RLock lock = redissonClient.getLock("lock:product:" + id);
if (lock.tryLock()) {
try {
// 查询数据库并写入缓存
} finally {
lock.unlock();
}
} else {
// 其他线程睡眠等待缓存
Thread.sleep(50);
return redisTemplate.opsForValue().get(cacheKey);
}
# ✅ 2. 本地互斥锁(Guava / Caffeine 本地缓存配合)
# 🧠 六、封装模板(伪代码)
可以将缓存逻辑封装成统一的工具类或注解方式(配合 AOP):
public <T> T queryWithCache(String keyPrefix, Long id, Function<Long, T> dbFallback) {
String key = keyPrefix + id;
String json = redis.get(key);
if (StringUtils.hasText(json)) {
return parse(json);
}
// 加锁防击穿
RLock lock = redisson.getLock("lock:" + key);
lock.lock();
try {
// 二次检查缓存
json = redis.get(key);
if (StringUtils.hasText(json)) {
return parse(json);
}
// 查数据库
T data = dbFallback.apply(id);
if (data == null) {
redis.set(key, "", 2, TimeUnit.MINUTES); // 缓存空值
} else {
redis.set(key, toJson(data), 10, TimeUnit.MINUTES);
}
return data;
} finally {
lock.unlock();
}
}
# ✅ 七、建议的技术选型
技术组件 | 推荐工具 |
---|---|
缓存客户端 | Spring Data Redis + Lettuce |
分布式锁 | Redisson |
本地缓存 | Caffeine (支持淘汰 + 异步刷新) |
布隆过滤器 | Guava BloomFilter / RedisBitmap 实现 |
可视化监控 | RedisInsight 、Prometheus + Grafana |
# 📌 八、附加功能推荐
- 定时刷新热点缓存(配合异步线程池)
- 缓存预加载、预热机制
- 缓存版本号控制(缓存双写一致性)
- 接口缓存(AOP + 注解)
# 🧠 一、IOC(控制反转,Inversion of Control)
# ✅ 作用:
把对象的创建和依赖关系交给 Spring 容器统一管理,而不是由我们手动 new
。
# ✅ 理解方式:
原来我们是自己控制对象的创建,现在是反转给容器来帮我们创建和注入。
# ✅ 举例:
@Component
public class UserService {
@Autowired
private OrderService orderService;
}
# ✅ 本质:
- IoC 容器会扫描
@Component
注解的类,将其放入容器 - 然后通过
@Autowired
自动注入所需依赖
# ⚙️ 二、AOP(面向切面编程,Aspect Oriented Programming)
# ✅ 作用:
将与业务无关的通用逻辑(如日志、事务、权限)从业务代码中抽离出来,形成切面统一管理。
# ✅ 应用场景:
- 日志打印
- 方法调用监控
- 权限校验
- 接口防刷
- 异常统一处理
# ✅ 示例代码:
@Aspect
@Component
public class LogAspect {
@Before("execution(* com.example.service.*.*(..))")
public void logBefore(JoinPoint joinPoint) {
System.out.println("调用方法:" + joinPoint.getSignature().getName());
}
}
# 🧩 三、事务管理(Transaction Management)
# ✅ 作用:
保证数据库操作的一致性、隔离性等事务特性。
# ✅ 使用方式:
只需加上一个注解即可实现事务的开启和回滚。
@Service
public class OrderService {
@Transactional
public void createOrder() {
// 操作订单表
// 操作库存表
// 如果出错自动回滚
}
}
# ✅ 事务传播机制(核心内容):
REQUIRED
:默认,当前有事务就加入,否则新建一个REQUIRES_NEW
:强制新建事务NESTED
:嵌套事务(可以独立回滚)
# 📦 总结:Spring 三大特性作用表
特性 | 简称 | 功能 |
---|---|---|
控制反转 | IoC | 统一管理 Bean 的生命周期和依赖注入 |
面向切面编程 | AOP | 抽离日志、事务、权限等公共逻辑 |
声明式事务 | Tx | 让数据库操作更安全、可控 |
Spring 中的事务功能(@Transactional)
# ✅ 一、事务生效的必要条件
条件 | 说明 |
---|---|
1. 方法必须被 Spring 容器管理的类调用 | 即类上必须被 @Service 、@Component 、@Repository 标注 |
2. 方法必须通过 代理对象 调用 | Spring 事务是基于 AOP 实现的,需要经过代理 |
3. 方法必须是 public 访问级别 | 因为 Spring 默认使用 JDK 或 CGLIB 动态代理,私有方法无法被代理拦截 |
4. 有真正的 数据库操作,并被连接管理 | Spring 的事务本质是对数据库连接的事务控制 |
5. 抛出的异常类型需匹配默认策略 | 默认只回滚 运行时异常(RuntimeException)及其子类 |
# ❌ 二、事务失效的常见情况(重点!)
# 🧨 场景 1:方法内部自调用,事务不生效
@Transactional
public void methodA() {
methodB(); // ⚠️ 同类内部调用,不会经过代理,事务失效
}
@Transactional
public void methodB() {
// 数据库操作
}
✅ 解决方式:使用代理对象调用,如通过 AopContext.currentProxy() 或将方法拆到新类中。
# 🧨 场景 2:方法不是 public
,事务不生效
@Transactional
void createOrder() {
// ⚠️ 非 public 方法,不会被代理,事务失效
}
✅ 正确写法:
@Transactional
public void createOrder() {
// 生效 ✅
}
# 🧨 场景 3:异常被 catch 了,事务不会回滚
@Transactional
public void process() {
try {
// 这里会报错
int a = 1 / 0;
} catch (Exception e) {
log.error("异常", e);
// ⚠️ 捕获了异常,事务不会自动回滚
}
}
✅ 正确处理方式:
- 抛出异常让事务感知
- 或手动触发回滚:
catch (Exception e) {
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
# 🧨 场景 4:抛出的是检查异常,事务不会回滚
默认 Spring 只对 RuntimeException 及其子类 自动回滚。
@Transactional
public void save() throws Exception { // 抛出的是 checked exception
throw new Exception("checked 异常"); // ⚠️ 默认不会回滚
}
✅ 正确做法:
@Transactional(rollbackFor = Exception.class) // 手动指定回滚策略
public void save() throws Exception {
throw new Exception("checked 异常");
}
# 🧨 场景 5:数据库未开启事务支持 或 非数据库操作
例如:
- 操作 Redis、MQ,不受事务控制
- 使用了数据库但某些 ORM 配置没有启用事务(如 MyBatis 没有开启事务管理器)
✅ 正确做法:检查是否使用了 DataSourceTransactionManager
,以及操作是否真正落到了数据库连接上。
# 🧠 三、事务失效的图解总结
失效原因 | 说明 |
---|---|
自调用 | 未经过代理,不会触发事务切面 |
非 public 方法 | Spring AOP 无法代理 |
异常被 catch | 事务未感知到异常 |
抛出 checked exception | 默认不会回滚 |
不操作数据库 | 没有真正事务 |
Redis、MQ 操作 | 不受数据库事务控制 |
# 🎯 四、如何确保事务生效?
@Transactional
一般加在public
的业务方法上保证方法是通过 代理对象 调用的(不是 this.xx())
明确异常抛出并让 Spring 能感知(或者加
rollbackFor
)日志调试建议打开:
logging: level: org.springframework.transaction: DEBUG