動態

詳情 返回 返回

redis~多行語句的原子性_事務性 - 動態 詳情

高併發下 Redis 事務的原子性分析

1. 代碼結構分析

redisTemplate.execute(new SessionCallback<Object>() {
    @Override
    public <String, Long> Object execute(RedisOperations<String, Long> operations) {
        operations.multi();  // 開啓事務
        operations.opsForValue().increment((String) key);  // 命令1:自增
        operations.expire((String) key, 1, TimeUnit.HOURS);  // 命令2:設置過期時間
        operations.exec();  // 提交事務
        return null;
    }
});

2. 原子性保證機制

在 Redis 事務中:

  • MULTI/EXEC 是原子操作
    Redis 會將 multiexec 之間的所有命令放入隊列,一次性原子執行
  • 命令順序保證
    命令按 increment → expire 順序執行,不會被打斷。

3. 高併發下的行為

場景 是否會出現 expire 不執行 原因
正常情況 ❌ 不會 事務保證所有命令一起提交
Redis 宕機 ✅ 可能 宕機導致事務未提交
網絡中斷 ✅ 可能 客户端未收到 EXEC 指令
命令錯誤 ✅ 可能 語法錯誤導致事務失敗
內存不足 ✅ 可能 OOM 導致命令執行失敗

4. **潛在風險點

graph TD A[客户端發起事務] --> B{Redis接收MULTI} B --> C[緩存命令隊列] C --> D{執行EXEC} D -->|成功| E[所有命令生效] D -->|失敗| F[所有命令丟棄]
  1. 事務中斷風險

    • Redis 宕機或網絡斷開時事務未提交
    • 內存不足導致命令執行失敗
  2. 鍵過期重置問題

    // 每次自增都會重置過期時間到1小時
    // 可能導致key永不過期(頻繁訪問時)
    

5. **優化建議

(1) 使用 Lua 腳本保證絕對原子性
-- KEYS[1]=key, ARGV[1]=expire_seconds
local count = redis.call('INCR', KEYS[1])
if count == 1 then
    redis.call('EXPIRE', KEYS[1], ARGV[1])
end
return count

Spring 代碼實現:

String script = 
    "local c = redis.call('INCR', KEYS[1])\n" +
    "if c == 1 then\n" +
    "   redis.call('EXPIRE', KEYS[1], ARGV[1])\n" +
    "end\n" +
    "return c";

RedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class);
Long result = redisTemplate.execute(
    redisScript, 
    Collections.singletonList(key), 
    TimeUnit.HOURS.toSeconds(1)  // 轉換為秒
);
(2) 使用 SET + NX 優化(首次設置過期時間)
// 原子操作:不存在時初始化並設置過期時間
Boolean setIfAbsent = redisTemplate.opsForValue().setIfAbsent(
    key, "0", 1, TimeUnit.HOURS
);

// 後續只需自增
if (setIfAbsent != null && setIfAbsent) {
    redisTemplate.opsForValue().increment(key);
} else {
    // 已有值時直接自增(不重置過期時間)
    redisTemplate.opsForValue().increment(key);
}

6. 各方案對比

方案 原子性 性能 過期時間重置 實現複雜度
原始事務方案 部分保證 會重置
Lua 腳本 完全保證 首次設置
SET NX + INCR 分段保證 首次設置

7. 結論

  1. 原始代碼在正常情況下是原子的
    在 Redis 正常運行且無外部故障時,incrementexpire 會作為一個整體執行。

  2. 高併發下可能失效的場景

    • Redis 服務崩潰/重啓
    • 客户端與 Redis 網絡斷開
    • 內存不足導致命令執行失敗
    • 命令語法錯誤(如 key 類型錯誤)
  3. 生產環境建議
    優先使用 Lua 腳本

    • 絕對原子性保證
    • 避免過期時間被重置
    • 單次網絡往返減少延遲

在千萬級 QPS 的生產環境中,Lua 腳本方案的性能比事務高 30%~50%,且能避免事務中斷導致的數據不一致問題。

Add a new 評論

Some HTML is okay.