Redis 原子操作
文章目录
- 前言
- ✅ 一、什么是「原子操作」?
- 🔍 二、怎么判断一个操作是否原子?
- 🧪 三、项目中的原子 vs 非原子案例(秒杀系统)
- ✅ 原子性(OK)
- ❌ 非原子性(高风险)
- 💡 四、常见后端场景对比
- 🛠️ 五、你可以这样判断是否是原子操作:
- ✅ 建议你在实际开发中记住一句话:
前言
如何判断某个操作是原子性 or 非原子性”,这是所有后端工程师必须理解清楚的高并发设计核心问题。
✅ 一、什么是「原子操作」?
原子性(Atomicity) 指的是一个操作要么全部完成、要么完全不做,不存在中间状态。
✔️ 不可分割、不可被并发干扰。
🔍 二、怎么判断一个操作是否原子?
你在用的系统/工具 | 判断原子性的方法 |
---|---|
Redis | ✅ 单条命令是原子 ✅ Lua 脚本是原子 ⚠️ 多条 get + set 是非原子 |
SQL 数据库 | ✅ 一条语句是原子 ✅ 在事务( BEGIN...COMMIT )块中是原子 |
Kafka | ✅ 单条 produce 是原子⚠️ 多 topic 发送不是事务除非启用事务 |
JavaScript 函数 | ❌ 大多数异步 await 链都是非原子 |
多服务调用 | ❌ 多接口调用天生非原子 → 需要事务协调 / 补偿 |
🧪 三、项目中的原子 vs 非原子案例(秒杀系统)
✅ 原子性(OK)
// Redis Lua 脚本库存扣减:原子执行
const result = await redis.eval(`local stock = tonumber(redis.call('get', KEYS[1]))if stock <= 0 then return 0 endredis.call('decr', KEYS[1])return 1
`, 1, `seckill:stock:{${skuId}}`);
✔️ 这个 Lua 脚本是一个原子块,Redis 会在单线程中完整执行。
❌ 非原子性(高风险)
// 错误写法:并发读写,导致超卖
const stock = await redis.get(key);
if (stock > 0) {await redis.set(key, stock - 1);
}
⚠️ 两条命令之间可能有 1000 个请求插入 → 超卖。
💡 四、常见后端场景对比
场景 | 原子性 | 正确方案 |
---|---|---|
秒杀扣库存 + 创建订单 | ❌ | 拆成 2 步:Redis 原子扣库存 + Kafka 异步下单 |
SQL 中 update ... where ... | ✅ | 单条语句原子 |
多表操作(如扣钱+加余额) | ❌ | 使用事务 |
Redis watch + multi | ⚠️ | 不是真正原子,执行前可以被干扰 |
Kafka 多 topic 同时写入 | ❌ | 开启 Kafka 事务 |
Redis 脚本 eval | ✅ | 推荐使用 |
🛠️ 五、你可以这样判断是否是原子操作:
提问 | 回答 |
---|---|
有没有多个步骤? | 多步骤一般非原子 |
中间是否会 await ? | 会 await 就可能被打断 |
Redis 命令是否是组合逻辑? | 推荐写成 Lua 脚本 |
是否包含多个微服务调用? | 微服务间永远非原子,需要幂等+补偿 |
有没有使用数据库事务? | 有事务通常能保证原子性(需正确使用) |
✅ 建议你在实际开发中记住一句话:
✅ 一个操作,只要会被并发干扰,那它就不是原子操作,必须考虑加锁、脚本、队列或事务。