大家好,我是大华! 在很多Java项目里,Redis就是个@Cacheable
,缓存个用户、商品,然后就没了。 但!Redis的本事远不止这点。 我在大厂搬砖的那段时间,用 Spring Boot+Redis 解决过一堆要命的问题。今天我就来分享10个真实又常用的Redis使用场景
1. 限流防崩:别让接口被刷爆
场景 公司搞促销,优惠券的接口被黄牛用脚本疯狂刷,每秒几千的请求,服务器差点炸了。正常用户根本抢不到,页面卡成PPT。
问题 系统扛不住,资源被耗尽,用户体验非常差。
解决方案 用Redis记录每个用户的请求次数,每分钟最多10次,超过直接拒绝。
- 用户每请求一次,Redis里的计数器+1。
- 如果是第一次请求,就给这个计数器设置一个60秒的过期时间。
- 如果计数超过10,就直接返回"请求太频繁"。
Java代码
java
public boolean isAllowed(String userId) {
String key = "rate_limit:" + userId;
// incr:自增1,如果key不存在,自动创建并设为1
Long count = redisTemplate.opsForValue().increment(key);
// 如果是第一次请求,设置60秒后自动过期
if (count == 1) {
redisTemplate.expire(key, 60, TimeUnit.SECONDS);
}
// 返回:是否允许(10次以内)
return count <= 10;
}
黄牛刷不动,系统稳了,用户能正常抢券。
2. 分布式锁:别让订单被重复创建
场景 有时候用户手一抖,点了两下支付按钮,结果生成了两条订单!用户投诉、财务对不上账。
问题 多个服务实例同时处理同一个订单,数据冲突。
解决方案 用Redis加个锁,确保同一时间只有一个请求能处理这个订单。
- 每次处理订单前,先尝试在Redis里创建一个key,比如
lock:order_123
。 - 如果创建成功(说明没人锁),就继续处理。
- 如果创建失败(说明别人锁了),就提示操作太频繁。
Java代码
java
public boolean tryLock(String orderId) {
String key = "lock:order:" + orderId;
// setIfAbsent:只有key不存在时才设置,相当于"抢锁"
Boolean success = redisTemplate.opsForValue()
.setIfAbsent(key, "1", 10, TimeUnit.SECONDS);
return Boolean.TRUE.equals(success);
}
注意
- 这个锁10秒后自动过期,防止死锁。
- 生产环境建议用Redisson,功能更全,比如可重入锁、看门狗自动续期。
3. 延时任务:订单超时自动关闭
场景 用户下单不付款,占着库存不放,别人想买也买不了。
传统做法 后台定时任务每分钟扫一遍数据库,查哪些订单超时了,再关闭。又慢又耗数据库资源。
Redis做法 用**ZSET(有序集合)**存订单,按超时时间戳排序。
- 下单时,把订单ID和超时时间戳(比如当前时间+30分钟)存到ZSET。
- 后台每隔1秒查一次:哪些订单的超时时间 <= 现在时间?
- 拿到这些订单,关闭它们,释放库存。
Java代码
java
// 下单时:把订单加入延时队列
public void addDelayOrder(String orderId) {
long expireTime = System.currentTimeMillis() + 30 * 60 * 1000; // 30分钟后
redisTemplate.opsForZSet().add("order_delay", orderId, expireTime);
}
// 后台任务:每秒检查一次
@Scheduled(fixedRate = 1000)
public void checkExpiredOrders() {
long now = System.currentTimeMillis();
// 查出所有已超时的订单
Set<String> expiredOrders = redisTemplate.opsForZSet()
.rangeByScore("order_delay", 0, now);
if (expiredOrders != null) {
for (String orderId : expiredOrders) {
closeOrder(orderId); // 关闭订单逻辑
redisTemplate.opsForZSet().remove("order_delay", orderId); // 从队列移除
}
}
}
比扫库快多了,资源占用少,延迟基本在1秒内。
4. 热点数据统计:实时排行榜
场景 搞活动,要实时显示今日下单最多的10个用户。
问题 用MySQL实时聚合统计,每次查询都慢,页面卡顿。
Redis做法
用ZSET,每个用户是成员,下单次数是分数。
- 用户每下一单,就在排行榜里给他的分数+1。
- 前端请求时,直接从Redis拿TOP10。
Java代码
java
// 用户下单后,排行榜+1
public void addOrderToRank(String userId) {
redisTemplate.opsForZSet().incrementScore("today_rank", userId, 1);
}
// 获取TOP10
public Set<ZSetOperations.TypedTuple<String>> getTop10() {
return redisTemplate.opsForZSet()
.reverseRangeWithScores("today_rank", 0, 9);
}
排行榜秒刷新,运营小姐姐天天夸我。
5. 消息队列:异步处理,太香了!
场景
用户注册后要发邮件、发短信、打标签、推数据到数仓......如果全在注册流程里同步做,页面会卡顿!
问题
核心流程被非核心任务拖慢,用户体验差。
Redis做法
用List当简易消息队列。
- 注册成功后,把"用户注册成功"这个事件扔进Redis的List里。
- 另外起一个后台线程,不断从List里取事件,异步处理发短信等。
Java代码
java
// 注册成功后,发消息
public void onUserRegistered(String userId) {
redisTemplate.opsForList().leftPush("user_event_queue", "register:" + userId);
}
// 后台Worker:异步处理
@Scheduled(fixedRate = 100)
public void processEvents() {
// rightPop:从List右边取一条消息,最多等1秒
String event = redisTemplate.opsForList()
.rightPop("user_event_queue", 1, TimeUnit.SECONDS);
if (event != null) {
handleEvent(event); // 异步处理发短信、打标签等
}
}
注册秒完成,用户体验起飞。消息还能持久化,不怕丢。
PS:第5个真的太香了,简单、快、稳,小公司完全够用。
6. 全局唯一ID生成
场景
分布式系统,多个服务都要生成订单号,怕重复。
MySQL自增? 不行,跨库不好搞。
Redis做法
用INCR
命令,每次调用自动+1,返回唯一数字。
Java代码
java
public String generateOrderId() {
// 每次调用,自动+1
Long id = redisTemplate.opsForValue().increment("order_id_seq");
// 生成类似:ORDER_20240512123456_1001
return "ORDER_" + System.currentTimeMillis() + "_" + id;
}
优点:简单粗暴,全局唯一,性能好。
7. 用户在线状态
场景
做IM或社交功能,要显示"张三在线"。
传统做法
数据库存状态,频繁更新,IO爆炸。
Redis做法
用户上线,Redis里存个key,30秒过期。客户端每20秒发个心跳,刷新这个key。
原理
Redis自动过期机制,不用手动删。key存在就是在线,没了就是离线。
Java代码
java
// 用户上线或心跳
public void updateOnlineStatus(String userId) {
redisTemplate.opsForValue()
.set("online:" + userId, "1", 30, TimeUnit.SECONDS);
}
// 判断是否在线
public boolean isOnline(String userId) {
return Boolean.TRUE.equals(redisTemplate.hasKey("online:" + userId));
}
几百万人在线,Redis内存才占几G,查状态毫秒级。
8. 防重提交:别让用户重复点赞
场景
用户狂点"点赞",数据库写入多条,统计全乱了。
Redis做法
用SET
记录用户对内容的操作。
- 用户点赞时,先检查
like:user123:article456
这个key是否存在。 - 如果存在,说明点过,拒绝。
- 如果不存在,就创建key,设置1小时过期。
Java代码
java
public boolean like(String userId, String articleId) {
String key = "like:" + userId + ":" + articleId;
Boolean exists = redisTemplate.hasKey(key);
if (Boolean.TRUE.equals(exists)) {
return false; // 已点赞
}
// 设置1小时过期
redisTemplate.opsForValue().set(key, "1", 1, TimeUnit.HOURS);
return true;
}
点赞去重,数据干净,再也不用半夜修数据了。
9. 临时会话存储
场景
用户登录后,把token和用户信息存哪?
存数据库? 慢!
存Cookie? 不安全!
Redis做法
登录成功后,把用户信息存到Redis,key是token,设置2小时过期。
Java代码
java
// 登录成功
public String login(User user) {
String token = UUID.randomUUID().toString();
String userJson = JSON.toJSONString(user);
redisTemplate.opsForValue()
.set("session:" + token, userJson, 2, TimeUnit.HOURS);
return token;
}
// 拦截器验证
public User getUserFromToken(String token) {
String userJson = redisTemplate.opsForValue().get("session:" + token);
return userJson != null ? JSON.parseObject(userJson, User.class) : null;
}
优点:快、安全、可集中管理(比如主动踢下线)。
10. 秒杀库存预减
场景
做秒杀,库存100件,怕超卖。
难点
MySQL扣库存并发高,容易出错。
Redis做法
提前把库存load到Redis。
- 用
DECR
命令原子性地减库存。 - 如果返回值 >=0,说明还有库存,可以下单。
- 如果返回负数,库存不足。
Java代码
java
public boolean seckill(String goodsId, String userId) {
String key = "seckill:stock:" + goodsId;
// 原子性减1,返回减后的值
Long left = redisTemplate.opsForValue().decrement(key);
if (left >= 0) {
// 库存够,创建订单
createOrder(goodsId, userId);
return true;
}
return false;
}
注意:记得异步同步到数据库,防止Redis挂了丢数据。
总结
- 限流
- 分布式锁
- 消息队列
- 排行榜
- 延时任务
- 全局ID
- 在线状态
- 防重
- 会话管理
- 库存预减
当然Redis也不是万能的,适合数据量不大但访问频繁的场景。如果数据量特别大,还是得上专业解决方案。 工具不在多,用对才是王道。
我是大华,专注分享前后端开发的实战笔记。关注我,少走弯路,一起进步!
📌往期精彩
《写给小公司前端的 UI 规范:别让页面丑得自己都看不下去》
《只会写 Mapper 就敢说会 MyBatis?面试官:原理都没懂》