【redis】-使用Lua腳本解決多線程下的超賣問題以及爲什麼?

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

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章