解決思路
從讀到寫這段時間的數據不一致問題,根源在於用戶並行(個人認為並發是時間概念,並行是空間概念),
要解決這個問題,需要讓用戶串行,單個用戶原子性。鎖 說它可以做到。
鎖只有一個目的,就是把並行變為串行,但是上鎖的方式 五花八門。
1. Java應用內存鎖
Java中自帶很多內存鎖,synchronize,各種Lock,但是優惠券服務多機部署,內存鎖無法滿足需求;
2. Mysql數據庫鎖
優惠券服務使用MySql(一個寫節點),innodb存儲引擎,innodb 支持 行鎖。
利用innodb的行鎖機制,可以使用兩種方式實現用戶領券的原子性:
第一種,讀取之前上鎖, 更新之后解鎖
select ... from table where ... for update;
update table set ....
優點: 簡單明了; 缺點: select 和 update 之間處理 出異常或應用異常終止 會產生死鎖。
第二中,利用update 鎖行機制,加上where 條件 判斷數據,也是讀取前上鎖,更新后解鎖。
update table set .... where ....
優點:簡單明了; 缺點: 效率不高
另外更新操作直接命中數據庫會對數據庫產生很大的壓力,所以數據庫鎖無法滿足搶券業務;
3. Redis分布式內存鎖
優惠券服務使用單節點Redis,Redis 支持setnx命令。
利用setnx命令,可以在應用中自建鎖及維護鎖的生命周期。
基本思路是領券前將優惠券的key通過 setnx 命令寫進 redis,成功則之后便執行后續的三次讀取 比較 和更新,
最后 del 命令刪除優惠券的key。
優點:邏輯簡單,實現簡單,total_got,user_got,user_today_got 三個值 存哪里不受任何限制。
缺點:不太可靠,setnx 成功后,應用出現異常,沒有執行最后的del , 會產生死鎖;也可以在 setnx 后再
設置一個過期時間,是的,這是一個辦法,只需要保證過期時間大於 接口的最大執行時間。
另外,也可以使用 官方推薦的 分布式Redis鎖 開源實現 Redisson。
以redis為例: