文章目錄
- 前言
- ? 一、什么是「原子操作」?
- 🔍 二、怎么判斷一個操作是否原子?
- 🧪 三、項目中的原子 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 腳本 |
是否包含多個微服務調用? | 微服務間永遠非原子,需要冪等+補償 |
有沒有使用數據庫事務? | 有事務通常能保證原子性(需正確使用) |
? 建議你在實際開發中記住一句話:
? 一個操作,只要會被并發干擾,那它就不是原子操作,必須考慮加鎖、腳本、隊列或事務。