一.多執行緒下引起的超賣問題呈現
1.1.我先初始化庫存數量為1、訂單數量為0
1.2.開啟3個執行緒去執行業務
業務為:判斷如果說庫存數量大於0,則庫存減1,訂單數量加1
結果為:庫存為-2,訂單數量為3
原因:如下圖所示,這是因為分別有6個指令(3個庫存減1指令,3個訂單數量加1指令)在redis伺服器端執行導致的。
namespace MengLin.Shopping.Redis.LuaScript { public class SecKillOriginal { static SecKillOriginal() { using (RedisClient client = new RedisClient("127.0.0.1", 6379)) { //刪除當前資料庫中的所有Key, 預設刪除的是db0 client.FlushDb(); //刪除所有資料庫中的key client.FlushAll(); //初始化庫存數量為1和訂單數量為0 client.Set("inventoryNum", 1); client.Set("orderNum", 0); } } public static void Show() { for (int i = 0; i < 3; i++) { Task.Run(() => { using (RedisClient client = new RedisClient("127.0.0.1", 6379)) { int inventoryNum = client.Get<int>("inventoryNum"); //如果庫存數量大於0 if (inventoryNum > 0) { //給庫存數量-1 var inventoryNum2 = client.Decr("inventoryNum"); Console.WriteLine($"給庫存數量-1後的數量-inventoryNum: {inventoryNum2}"); //給訂單數量+1 var orderNum = client.Incr("orderNum"); Console.WriteLine($"給訂單數量+1後的數量-orderNum: {orderNum}"); } else { Console.WriteLine($"搶購失敗: 原因是因為沒有庫存"); } } }); } } } }
二.使用Lua指令碼解決多執行緒下超賣的問題以及為什麼
2.1.修改後的程式碼如下
結果為:如下圖所示,庫存為0、訂單數量為1,並沒有出現超賣的問題且有2個執行緒搶不到。
namespace MengLin.Shopping.Redis.LuaScript { public class SecKillLua { /// <summary> /// 使用Lua指令碼解決多執行緒下變賣的問題 /// </summary> static SecKillLua() { using (RedisClient client = new RedisClient("127.0.0.1", 6379)) { //刪除當前資料庫中的所有Key, 預設刪除的是db0 client.FlushDb(); //刪除所有資料庫中的key client.FlushAll(); //初始化庫存數量為1和訂單數量為0 client.Set("inventoryNum", 1); client.Set("orderNum", 0); } } public static void Show() { for (int i = 0; i < 3; i++) { Task.Run(() => { using (RedisClient client = new RedisClient("127.0.0.1", 6379)) { //如果庫存數量大於0,則給庫存數量-1,給訂單數量+1 var lua = @"local count = redis.call('get',KEYS[1]) if(tonumber(count)>0) then --return count redis.call('INCR',ARGV[1]) return redis.call('DECR',KEYS[1]) else return -99 end"; Console.WriteLine(client.ExecLuaAsString(lua, keys: new[] { "inventoryNum" }, args: new[] { "orderNum" })); } }); } } } }
三.為什麼使用Lua指令碼就能解決多執行緒下的超賣問題呢?
是因為Lua指令碼把3個指令,分別是:判斷庫存數量是否大於0、庫存減1、訂單數量加1,這3個指令打包放在一起執行了且不能分割,相當於組裝成了原子指令,所以避免了超賣問題。
在redis中我們儘量使用原子指令從而避免一些並行的問題。