news 2026/10/10 9:19:35

AI 写的 Redis 分布式锁为什么会超卖:四个错误写法和正确实现

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
AI 写的 Redis 分布式锁为什么会超卖:四个错误写法和正确实现

秒杀功能要加个分布式锁。我让 AI 写,它给了一段这个:

publicbooleantryLock(Stringkey,longtimeoutSeconds){Booleansuccess=redisTemplate.opsForValue().setIfAbsent(key,"1",timeoutSeconds,TimeUnit.SECONDS);returnBoolean.TRUE.equals(success);}publicvoidunlock(Stringkey){redisTemplate.delete(key);}

写法看着挺标准。加锁、设过期、释放,一样不缺。

压测跑起来,库存超卖。

先看这套写法本身的问题

第一,value 是写死的"1"。

意味着所有客户端写进去的值都一样。那么释放锁的时候,谁都可以删。

场景:A 拿到锁,业务执行超时,锁自动过期了。B 拿到锁开始执行。A 这时候执行完了,走进unlock,把 B 的锁删了。C 立刻就能拿到锁进来。

三个线程同时在跑临界区。

第二,释放锁不判断持有者。

上面那个场景之所以能发生,就是因为delete(key)不检查这把锁是不是自己的。

第三,锁超时时间不一定够。

30 秒的锁,业务跑了 35 秒,锁先过期了。这时候另一台机器直接进来。

这个问题的难点在于:你没法准确预估业务耗时。今天 3 秒,明天数据库慢一下变 40 秒,你就中招了。

第四,也是最容易忽略的。上面那个写法里,加锁和设过期其实是原子的(setIfAbsent传了过期时间,Redis 内部是一条SET key value NX PX命令,没有原子性问题)。

但如果 AI 写成下面这样,就是真的非原子了:

// 危险:两步操作Booleansuccess=redisTemplate.opsForValue().setIfAbsent(key,"1");if(Boolean.TRUE.equals(success)){redisTemplate.expire(key,30,TimeUnit.SECONDS);// 单独一步}

这两行之间如果服务挂了,那把锁永远不会过期。后面所有请求全部阻塞。

这个 bug 平时测不出来,只在你最不想出事的时候出现。

正确的写法长什么样

基础版本:加锁带过期、value 带唯一标识、释放用 Lua 比对

privatestaticfinalStringUNLOCK_LUA="if redis.call('get', KEYS[1]) == ARGV[1] then "+" return redis.call('del', KEYS[1]) "+"else return 0 end";publicbooleantryLock(Stringkey,StringrequestId,longmillis){Booleanok=redisTemplate.opsForValue().setIfAbsent(key,requestId,millis,TimeUnit.MILLISECONDS);returnBoolean.TRUE.equals(ok);}publicbooleanunlock(Stringkey,StringrequestId){Longresult=redisTemplate.execute(newDefaultRedisScript<>(UNLOCK_LUA,Long.class),Collections.singletonList(key),requestId);returnresult!=null&&result>0;}

关键三点:

  1. requestId用 UUID,每次加锁都是唯一值
  2. 加锁一条命令,SET NX PX,原子
  3. 释放用 Lua,先比对再删,比对和删除在 Redis 端是一条命令

再往上一步:自动续期

上面这个版本还是没解决“业务比锁时间长”的问题。要彻底解决,得有个看门狗,在锁快过期的时候自动续上。

Redisson 干的就是这个:

RLocklock=redissonClient.getLock("order:"+orderId);try{lock.lock(30,TimeUnit.SECONDS);// 业务逻辑}finally{lock.unlock();}

它内部有个后台线程,每隔 10 秒(默认锁时间的 1/3)检查一次,业务还在跑就续期。

手写这个看门狗不是不行,但要处理的东西不少:续期线程的管理、客户端宕机的情况、续期失败怎么办。

我的建议是:能用 Redisson 就别手写。分布式锁这种基础设施,手写出来能跑通不难,跑对所有边界情况很难。

AI 为什么写不对这个

因为 AI 给你的不是“正确的并发代码”,是“看起来正确的代码”。

训练数据里分布式锁的文章,绝大多数是教学性质的。讲清楚“加锁、设过期、finally 释放”这三步就够了,没人会在教程里讲锁续期和持有者校验。

它给你的是一个能演示的版本,不是一个能上线的版本。

这两个之间的差距,就是超卖的库存数。

一句话

分布式锁这东西,最危险的状态不是“没加锁”,是“以为加了锁”。

没加锁你会老老实实做幂等、做库存校验。以为加了锁,你就把那些防线全撤了。

我把这段代码和修复版整理了一下,回头顺手查查你们项目里的锁,value 是不是写死的字符串。

你见过最离谱的分布式锁是怎么写的?说出来让我开开眼。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/10 9:18:34

Qt C++ 坦克大战实战:从 QGraphicsScene 到完整 35 关源码解析

简介&#xff1a;面向C课程设计或大作业场景的经典坦克大战游戏项目&#xff0c;基于Qt 5.14.1与gcc 7.3.0环境开发&#xff0c;使用Qt Creator 4.11.0进行构建调试&#xff0c;完整实现单人闯关玩法。游戏共有35关&#xff0c;每关需要击败20个敌方坦克&#xff1b;玩家每关拥…

作者头像 李华
网站建设 2026/10/10 9:18:23

山海鲸可视化 VS FineReport:渲染能力与报表能力,决定项目选型边界

数字化大屏项目落地到实际业务&#xff0c;终究绕不开一个核心问题&#xff1a;项目核心诉求到底是三维场景渲染展示&#xff0c;还是复杂业务报表输出。园区 IOC、工厂数字孪生、财务统计报表、监管报送驾驶舱&#xff0c;不同项目的核心目标完全不一样&#xff0c;而山海鲸可…

作者头像 李华
网站建设 2026/10/10 9:15:34

Twenty CRM 5.8万星背后:为AI Agent打造的可编程数据模型与集成实战

1. 从5.8万星说起&#xff1a;这个CRM到底戳中了什么痛点第一次在代码托管平台上刷到Twenty这个项目时&#xff0c;5.8万颗星这个数字确实让我停了一下。做企业软件这么多年&#xff0c;我太清楚CRM这个赛道有多拥挤了——从老牌巨头到各种轻量级SaaS&#xff0c;几乎每个团队都…

作者头像 李华
网站建设 2026/10/10 9:15:29

SAP ABAP CDS Service Consumption 深度解析,从数据模型到 OData、InA 与 SQL Service

在 ABAP 系统里完成一个 CDS View,并不代表这个数据模型已经能够被外部系统使用。 CDS View 更接近 ABAP 应用内部的数据语义模型。它可以被 ABAP SQL 查询,可以成为 RAP Business Object 的组成部分,也可以继续被其他 CDS View 组合。但当消费方离开 Application Server A…

作者头像 李华