Redis 並發競爭key問題如何解決?

  • 2019 年 11 月 10 日
  • 筆記

1. 問題描述

並發競爭key這個問題簡單講就是:

同時有多個客戶端去set一個key。

示例場景 1

例如有多個請求一起去對某個商品減庫存,通常操作流程是:

  • 取出當前庫存值
  • 計算新庫存值
  • 寫入新庫存值

假設當前庫存值為 20,現在有2個連接都要減 5,結果庫存值應該是 10 才對,但存在下面這種情況:

示例場景 2

比如有3個請求有序的修改某個key,按正常順序的話,數據版本應該是 1->2->3,最後應該是 3

但如果第二個請求由於網絡原因遲到了,數據版本就變為了 1->3->2,最後值為 2,出問題了。

2. 解決方案

2.1 樂觀鎖

樂觀鎖適用於大家一起搶着改同一個key,對修改順序沒有要求的場景。

watch 命令可以方便的實現樂觀鎖。

需要注意的是,如果你的 redis 使用了數據分片的方式,那麼這個方法就不適用了。

watch 命令會監視給定的每一個key,當 exec 時如果監視的任一個key自從調用watch後發生過變化,則整個事務會回滾,不執行任何動作。

2.2 分佈式鎖

適合分佈式環境,不用關心 redis 是否為分片集群模式。

在業務層進行控制,操作 redis 之前,先去申請一個分佈式鎖,拿到鎖的才能操作。

分佈式鎖的實現方式很多,比如 ZooKeeper、Redis 等。

2.3 時間戳

適合有序需求場景,例如 A 需要把 key 設置為 a,然後 B 設置為 bC 再設置為 c,最後的值應該是 c

這時就可以考慮使用時間戳的方式:

A => set key1 {a 11:01} B => set key1 {b 11:02} C => set key1 {c 11:03}

就是在寫入時保存一個時間戳,寫入前先比較自己的時間戳是不是早於現有記錄的時間戳,如果早於,就不寫入了。

假設 B 先執行了,key1 的值為 {b 11:02},當A執行時,發現自己的時間戳11:01早於現有值,就不執行 set 操作了。

2.4 消息隊列

在並發量很大的情況下,可以通過消息隊列進行串行化處理。這在高並發場景中是一種很常見的解決方案。

3. 小結

「Redis 並發競爭」 問題就是高並發寫同一個key時導致的值錯誤。

常用的解決方法:

  • 樂觀鎖,注意不要在分片集群中使用
  • 分佈式鎖,適合分佈式系統環境
  • 時間戳,適合有序場景
  • 消息隊列,串行化處理