一.多線程下引起的超賣問題呈現
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中我們儘量使用原子指令從而避免一些併發的問題。