redis使用Lua腳本解決多線程下的超賣問題及原因解析
一.多線程下引起的超賣問題呈現(xiàn)
1.1.我先初始化庫存數(shù)量為1、訂單數(shù)量為0
1.2.開啟3個(gè)線程去執(zhí)行業(yè)務(wù)
業(yè)務(wù)為:判斷如果說庫存數(shù)量大于0,則庫存減1,訂單數(shù)量加1
結(jié)果為:庫存為-2,訂單數(shù)量為3
原因:如下圖所示,這是因?yàn)榉謩e有6個(gè)指令(3個(gè)庫存減1指令,3個(gè)訂單數(shù)量加1指令)在redis服務(wù)端執(zhí)行導(dǎo)致的。
namespace MengLin.Shopping.Redis.LuaScript { public class SecKillOriginal { static SecKillOriginal() { using (RedisClient client = new RedisClient("127.0.0.1", 6379)) { //刪除當(dāng)前數(shù)據(jù)庫中的所有Key, 默認(rèn)刪除的是db0 client.FlushDb(); //刪除所有數(shù)據(jù)庫中的key client.FlushAll(); //初始化庫存數(shù)量為1和訂單數(shù)量為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"); //如果庫存數(shù)量大于0 if (inventoryNum > 0) { //給庫存數(shù)量-1 var inventoryNum2 = client.Decr("inventoryNum"); Console.WriteLine($"給庫存數(shù)量-1后的數(shù)量-inventoryNum: {inventoryNum2}"); //給訂單數(shù)量+1 var orderNum = client.Incr("orderNum"); Console.WriteLine($"給訂單數(shù)量+1后的數(shù)量-orderNum: {orderNum}"); } else { Console.WriteLine($"搶購(gòu)失敗: 原因是因?yàn)闆]有庫存"); } } }); } } } }
二.使用Lua腳本解決多線程下超賣的問題以及為什么
2.1.修改后的代碼如下
結(jié)果為:如下圖所示,庫存為0、訂單數(shù)量為1,并沒有出現(xiàn)超賣的問題且有2個(gè)線程搶不到。
namespace MengLin.Shopping.Redis.LuaScript { public class SecKillLua { /// <summary> /// 使用Lua腳本解決多線程下變賣的問題 /// </summary> static SecKillLua() { using (RedisClient client = new RedisClient("127.0.0.1", 6379)) { //刪除當(dāng)前數(shù)據(jù)庫中的所有Key, 默認(rèn)刪除的是db0 client.FlushDb(); //刪除所有數(shù)據(jù)庫中的key client.FlushAll(); //初始化庫存數(shù)量為1和訂單數(shù)量為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)) { //如果庫存數(shù)量大于0,則給庫存數(shù)量-1,給訂單數(shù)量+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腳本就能解決多線程下的超賣問題呢?
是因?yàn)長(zhǎng)ua腳本把3個(gè)指令,分別是:判斷庫存數(shù)量是否大于0、庫存減1、訂單數(shù)量加1,這3個(gè)指令打包放在一起執(zhí)行了且不能分割,相當(dāng)于組裝成了原子指令,所以避免了超賣問題。
在redis中我們盡量使用原子指令從而避免一些并發(fā)的問題。
到此這篇關(guān)于redis使用Lua腳本解決多線程下的超賣問題以及為什么的文章就介紹到這了,更多相關(guān)redis多線程超賣內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
基于Redis無序集合如何實(shí)現(xiàn)禁止多端登錄功能
這篇文章主要給你大家介紹了關(guān)于基于Redis無序集合如何實(shí)現(xiàn)禁止多端登錄功能的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2018-12-12redis數(shù)據(jù)一致性之延時(shí)雙刪策略詳解
在使用redis時(shí),需要保持redis和數(shù)據(jù)庫數(shù)據(jù)的一致性,最流行的解決方案之一就是延時(shí)雙刪策略,今天我們就來詳細(xì)刨析一下,需要的朋友可以參考下2023-09-09Redis 對(duì)過期數(shù)據(jù)的處理方法
這篇文章主要介紹了Redis 對(duì)過期數(shù)據(jù)的處理,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2020-10-10Govern Service 基于 Redis 的服務(wù)治理平臺(tái)安裝過程詳解
Govern Service 是一個(gè)輕量級(jí)、低成本的服務(wù)注冊(cè)、服務(wù)發(fā)現(xiàn)、 配置服務(wù) SDK,通過使用現(xiàn)有基礎(chǔ)設(shè)施中的 Redis 不用給運(yùn)維部署帶來額外的成本與負(fù)擔(dān),接下來通過本文給大家分享Govern Service 基于 Redis 的服務(wù)治理平臺(tái)的相關(guān)知識(shí),感興趣的朋友一起看看吧2021-05-05redis-cli登錄遠(yuǎn)程redis服務(wù)并批量導(dǎo)入數(shù)據(jù)
本文主要介紹了redis-cli登錄遠(yuǎn)程redis服務(wù)并批量導(dǎo)入數(shù)據(jù),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2023-10-10Redis?Lua腳本實(shí)現(xiàn)ip限流示例
這篇文章主要介紹了Redis?Lua腳本實(shí)現(xiàn)ip限流示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-07-07