Redis分布式锁,CMS高并发场景防重复提交
在CMS高并发场景中,用户快速点击或网络重试常导致同一请求被多次提交,产生重复订单或脏数据。
Redis分布式锁利用其原子操作,能有效拦截并发重复请求。
本文从零开始,教你用原生命令和Redisson框架两种方式实现防重,并给出完整的验证与避坑步骤。
准备条件
- 一台已安装Redis的服务器(本地或远程均可),推荐Redis 5.0以上版本。
- 开发环境:Java项目(示例使用Spring Boot),PHP或其他语言逻辑类似。
- 能连接Redis的客户端,如redis-cli或项目依赖的Jedis/Lettuce。
- 对CMS中需要防重的接口路径有清晰认识,例如提交文章、发布评论、下单等。
用原生Redis命令实现锁
核心思路:利用SET key value NX PX milliseconds命令,仅当key不存在时才能设置成功,并自动过期避免死锁。
加锁步骤
- 生成唯一value(如UUID),用于标识锁的持有者。
- 执行命令:
SET lock:submit:12345 uuid NX PX 10000
其中lock:submit:12345是锁键(建议用业务前缀+用户ID或请求ID),uuid是随机值,NX表示不存在才设置,PX 10000表示10秒后自动释放。
- 若返回
OK,表示加锁成功,继续执行业务逻辑;返回nil则加锁失败,直接返回“请勿重复提交”。
释放锁步骤
不能简单DEL,否则可能误删他人锁。必须用Lua脚本保证原子性:
if redis.call("get", KEYS[1]) == ARGV[1] then
return redis.call("del", KEYS[1])
else
return 0
end
在代码中调用该脚本,传入锁键和之前设置的uuid。
注意:业务执行时间若超过锁过期时间,锁会自动释放,其他请求可能乘虚而入。
建议根据业务耗时合理设置PX,或在业务内部续期(复杂场景推荐Redisson)。
Redisson更省心的锁方案
Redisson是Redis的Java客户端,提供了看门狗自动续期、可重入锁等特性,适合生产环境。
引入依赖(Maven):
org.redisson
redisson-spring-boot-starter
3.23.0
配置连接(application.yml):
spring:
redis:
host: 127.0.0.1
port: 6379
加锁代码示例:
@Autowired
private RedissonClient redissonClient;
public void submitArticle(Long articleId) {
String lockKey = "lock:article:" + articleId;
RLock lock = redissonClient.getLock(lockKey);
try {
// 尝试加锁,最多等待3秒,锁自动释放时间10秒
boolean locked = lock.tryLock(3, 10, TimeUnit.SECONDS);
if (!locked) {
throw new RuntimeException("操作过于频繁,请稍后再试");
}
// 执行业务逻辑
doSubmit(articleId);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
看门狗:若未指定锁释放时间(tryLock不带leaseTime),Redisson默认加锁30秒,并启动后台线程每10秒续期,直到业务结束。
这能有效避免业务未完成锁提前释放。
避坑指南
- 锁粒度要细:不要用全局锁,应针对具体资源(如用户ID+操作类型)加锁,否则并发性能急剧下降。
- 锁超时时间:设置太短会导致业务未完成锁被释放;太长则故障时影响恢复。建议根据压测结果调整。
- Redis单点故障:若Redis宕机,所有锁失效。生产环境建议使用Redis哨兵或集群模式,并考虑Redisson的联锁(RedLock)实现。
- 不要用DEL直接删锁:必须验证value再删除,避免误解锁。
- 异常处理:确保
unlock在finally中执行,防止死锁。
效果验证
- 启动两个终端,同时用
ab或wrk压测同一接口,观察是否只有一个请求成功。 - 查看Redis中锁键是否存在:
redis-cli keys 'lock:*',正常业务结束后键应消失。 - 模拟业务超时:在业务中sleep超过锁过期时间,观察第二个请求是否被拦截。
- 检查日志:加锁失败的请求应返回友好提示,无重复数据写入。
常见疑问
锁过期了业务还没执行完怎么办?
使用Redisson的看门狗机制自动续期,或手动在业务中定期重置过期时间。
Redis主从切换时锁会丢失吗?
可能丢失。主从异步复制下,主节点故障时锁可能未同步到从节点。对一致性要求极高的场景,考虑RedLock或改用ZooKeeper。
能否用SETNX+Lua替代Redisson?
可以,但需要自行处理续期、可重入等逻辑,维护成本较高。Redisson更推荐。
按照以上步骤,你就能在CMS高并发场景中有效防止重复提交。
建议先在测试环境验证锁行为,再逐步上线。
遇到异常时,优先检查锁键、过期时间和释放逻辑。