最近在做小程序后端时,用 Redis 解决了几个典型问题:缓存 access_token、记录用户订阅状态、分布式锁防止并发。借此机会,我总结了 Redis 在业务中最常用的几种模式,希望对大家有帮助。相关代码,可以参考wx小程序实战
一、为什么选择 Redis?
首先 Redis 是一款基于内存的键值存储系统,速度快、数据结构丰富、支持持久化。在需要高性能缓存 、分布式锁 、计数器 、消息队列 等场景下,Redis 几乎是首选。
我将以 Spring Boot + RedisTemplate 为例,展示几种常见用法。

二、用法一:缓存(String 类型 + 过期时间)
场景
微信小程序的 access_token 有效期为 2 小时(7200 秒),且每日获取次数有限(2000 次)。必须缓存起来,避免频繁调用。
实现
java
// 获取 token
String token = redisTemplate.opsForValue().get("WECHAT_ACCESS_TOKEN");
if (token == null) {
token = fetchFromWechat();
redisTemplate.opsForValue().set("WECHAT_ACCESS_TOKEN", token, 7000, TimeUnit.SECONDS);
}
关键点
-
设置 过期时间(略小于微信的有效期,提前刷新)
-
配合 分布式锁防止缓存击穿(见用法四)
三、用法二:存储业务状态(String 类型 + 过期时间)
场景
用户订阅消息后,需要在后端记录订阅关系,30 天后自动失效(与微信授权有效期对齐)。
实现
java
String key = "SUBSCRIBE:" + templateId + ":" + openId;
redisTemplate.opsForValue().set(key, "1", 30, TimeUnit.DAYS);
// 检查是否订阅
Boolean exists = redisTemplate.hasKey(key);
扩展
-
可以用
Hash存储一个用户订阅的多个模板:hset user:openId templateId 1 -
但简单场景下 String 更直观
四、用法三:计数器(自增/自减)
场景
限制用户每天发送意见的次数(防刷)。例如每个村民每天最多提交 3 条意见。
实现
java
String key = "OPINION:LIMIT:" + openId + ":" + LocalDate.now();
Long count = redisTemplate.opsForValue().increment(key);
if (count == 1) {
// 设置过期时间为当天结束
redisTemplate.expire(key, 1, TimeUnit.DAYS);
}
if (count > 3) {
return Result.error("今日提交次数已达上限");
}
其他应用
-
统计点赞数、阅读数
-
接口限流(滑动窗口)
五、用法四:分布式锁(String + setIfAbsent + Lua)
场景
多实例部署时,缓存失效瞬间多个线程同时请求微信 access_token ,导致 token 被覆盖或超限。需要分布式锁保证只有一个线程去刷新。
实现
java
String lockKey = "WECHAT:ACCESS_TOKEN_LOCK";
String lockValue = UUID.randomUUID().toString();
Boolean locked = redisTemplate.opsForValue()
.setIfAbsent(lockKey, lockValue, 5, TimeUnit.SECONDS);
if (locked) {
try {
// 双重检查
String token = redisTemplate.opsForValue().get("WECHAT_ACCESS_TOKEN");
if (token == null) {
token = fetchFromWechat();
redisTemplate.opsForValue().set("WECHAT_ACCESS_TOKEN", token, 7000, TimeUnit.SECONDS);
}
return token;
} finally {
// 释放锁:Lua 脚本保证原子性,只释放自己加的锁
String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
redisTemplate.execute(new DefaultRedisScript<>(script, Long.class),
Collections.singletonList(lockKey), lockValue);
}
} else {
// 睡 100ms 后重试
Thread.sleep(100);
return getAccessToken();
}
注意
-
锁必须有过期时间,防止死锁
-
释放锁时必须验证 value,防止误删他人的锁
-
使用 Lua 脚本保证 get + del 的原子性
六、用法五:消息队列(List / PubSub)
场景
异步处理耗时任务,例如发送订阅消息后需要记录日志,但不影响主流程响应。
用 List 实现简单队列
java
// 生产者:入队
redisTemplate.opsForList().rightPush("MSG:QUEUE", message);
// 消费者:阻塞弹出(配合 while 循环或定时任务)
String message = redisTemplate.opsForList().leftPop("MSG:QUEUE", 10, TimeUnit.SECONDS);
用 Pub/Sub 实现广播
java
// 发布
redisTemplate.convertAndSend("CHANNEL:OPINION", "新意见");
// 订阅(需实现 MessageListener)
@Bean
public MessageListenerAdapter listenerAdapter(RedisReceiver receiver) {
return new MessageListenerAdapter(receiver, "onMessage");
}
对比
| 方式 | 特点 | 适用场景 |
|---|---|---|
| List | 点对点,支持阻塞等待 | 简单任务队列 |
| Pub/Sub | 广播,消息即发即失(不持久化) | 实时通知、聊天 |
| Stream(5.0+) | 持久化、消费组 | 可靠消息队列 |
七、用法六:Set / SortedSet 实现标签和排行榜
Set 应用:用户标签系统
java
// 给用户打标签
redisTemplate.opsForSet().add("TAG:GRID_MEMEBER", openId1, openId2);
// 判断是否网格员
Boolean isGrid = redisTemplate.opsForSet().isMember("TAG:GRID_MEMEBER", openId);
SortedSet 应用:排行榜
java
// 增加积分
redisTemplate.opsForZSet().incrementScore("RANK:OPINION", openId, 1);
// 获取前三名
Set<String> top3 = redisTemplate.opsForZSet().reverseRange("RANK:OPINION", 0, 2);
八、总结
| 数据结构 | 典型用法 | 你的项目中的应用 |
|---|---|---|
| String | 缓存、计数器、分布式锁 | access_token、订阅状态、限流 |
| Hash | 存储对象字段 | (可扩展)用户配置 |
| List | 消息队列 | 异步任务 |
| Set | 标签、去重 | 用户角色标识 |
| SortedSet | 排行榜、延迟队列 | 积分排行 |
| Pub/Sub | 实时广播 | 通知推送 |
ok,结束。