StackExchange.Redis 之 hash 類型示例


StackExchange.Redis 的組件封裝示例網上有很多,自行百度搜索即可。

這里只演示如何使用Hash類型操作數據:

1             // 在 hash 中存入或修改一個值  並設置order_hashkey有效期1分鍾,過期自動刪除;null為不過期
2             stopwatch.Start();
3             var isok = RedisCacheHelper.Instance.HashSet("order_hashkey", "order_hashfield", "10", TimeSpan.FromMinutes(1));
4             stopwatch.Stop();
5             Console.WriteLine("在hash中存入一個值消耗時間:" + stopwatch.ElapsedMilliseconds.ToString());  

1             //判斷該字段是否存在 hash 中
2             var isexists = RedisCacheHelper.Instance.HashExists("order_hashkey", "order_hashfield");
3             Console.WriteLine("判斷該字段是否存在hash中:" + isexists);  //返回 True 或 False

             //從hash中移除指定字段
             var isdel = RedisCacheHelper.Instance.HashDelete("order_hashkey", "order_hashfield");
             Console.WriteLine("從hash中移除指定字段:" + isdel);  //返回 True 或 False
            //從hash中遞增  默認按1遞增
            var getincre = RedisCacheHelper.Instance.HashIncrement("order_hashkey", "order_hashfield");
            Console.WriteLine("從hash中遞增:" + getincre);  //返遞增后的值 11

            //從hash中遞減  默認按1遞減
            var getdecre = RedisCacheHelper.Instance.HashDecrement("order_hashkey", "order_hashfield");
            Console.WriteLine("從hash中遞減:" + getdecre);  //返遞減后的值  10

            //保存一個字符串類型集合
            string[] strarray = { "1", "2", "3", "4", "5" };
            RedisCacheHelper.Instance.HashSetList("orderlist_hashkey", strarray, m => { return "hashfield_" + m.ToString(); }, new TimeSpan(0, 0, 50));

            //從數據庫獲取10條數據
            stopwatch.Start();
            var getlist = TestRedis.GetOrderFormAll(10);
            stopwatch.Stop();
            Console.WriteLine("從數據庫獲取10條數據消耗時間:" + stopwatch.ElapsedMilliseconds.ToString());

            //保存多個對象集合 非序列化
            stopwatch.Start();
            RedisCacheHelper.Instance.HashSetObjectList("objlist_hashkey", getlist, r => { return r.ordercode.ToString(); }, 1);
            stopwatch.Stop();
            Console.WriteLine("將10條數據存入HashRedis 消耗時間:" + stopwatch.ElapsedMilliseconds.ToString());

 

            //保存或修改一個hash對象(序列化) 對key設置過期時間有時候起作用,有時候不起作用
            stopwatch.Start();
            RedisCacheHelper.Instance.HashSet<OrderForm>("orderform_hashkey", "orderform_hashfield", getlist.FirstOrDefault(), new TimeSpan(0, 0, 10), 2);
            stopwatch.Stop();
            Console.WriteLine("保存或修改一個hash對象(序列化)消耗時間:" + stopwatch.ElapsedMilliseconds.ToString());


            //保存Hash對象集合 序列化   對key設置過期時間可以起作用
            stopwatch.Start();
            RedisCacheHelper.Instance.HashSet<OrderForm>("orderformlist_hashkey", getlist, m=> { return m.ordercode.ToString(); }, new TimeSpan(0, 0, 30), 3);
            stopwatch.Stop();
            Console.WriteLine("保存Hash對象集合(序列化)消耗時間:" + stopwatch.ElapsedMilliseconds.ToString());

 

            //從Hash Redis獲取某一條訂單信息  反序列化
            stopwatch.Start();
            var getorderinfo = RedisCacheHelper.Instance.HashGet<OrderForm>("orderform_hashkey", "orderform_hashfield", 2);
            stopwatch.Stop();
            Console.WriteLine("從Hash Redis獲取某一條訂單信息消耗時間:" + stopwatch.ElapsedMilliseconds.ToString());
            Console.WriteLine(getorderinfo.ordercode);
            Console.WriteLine(getorderinfo.Area);
            Console.WriteLine(getorderinfo.City);
            Console.WriteLine(getorderinfo.Province);
            Console.WriteLine(getorderinfo.PostTime);

            //根據hashkey 獲取所有的值  反序列化
            stopwatch.Start();
            var getorderlist = RedisCacheHelper.Instance.HashGetAll<OrderForm>("orderformlist_hashkey", 3);
            stopwatch.Stop();
            Console.WriteLine("根據hashkey 獲取所有的值消耗時間:" + stopwatch.ElapsedMilliseconds.ToString());
            foreach (var item in getorderlist)
            {
                Console.WriteLine("獲取反序列化得到的ordercode: " + item.ordercode);
            }

            //根據hashkey獲取單個字段hashField的值
            stopwatch.Start();
            var getvalue = RedisCacheHelper.Instance.HashGet("objlist_hashkey1", "City", 1);
            stopwatch.Stop();
            Console.WriteLine("獲取單個字段hashField的值消耗時間:" + stopwatch.ElapsedMilliseconds.ToString());
            Console.WriteLine("獲取City: " + getvalue);

            Console.WriteLine("------------------");
            Console.WriteLine();

            //獲取多個字段hashField的值
            List<RedisValue> fieldlist = new List<RedisValue>() { "ordercode", "Province", "City", "Area" };
            stopwatch.Start();
            var getlist = RedisCacheHelper.Instance.HashGet("objlist_hashkey1", fieldlist.ToArray(), 1);
            stopwatch.Stop();
            Console.WriteLine("獲取多個字段hashField的值消耗時間:" + stopwatch.ElapsedMilliseconds.ToString());
            foreach (var item in getlist)
            {
                Console.WriteLine("獲取到的值: " + item);
            }

            //根據hashkey返回所有的HashFiled
            stopwatch.Start();
            var getkeylist = RedisCacheHelper.Instance.HashKeys("objlist_hashkey1", 1);
            stopwatch.Stop();
            Console.WriteLine("消耗時間:" + stopwatch.ElapsedMilliseconds.ToString());
            foreach (var item in getkeylist)
            {
                Console.WriteLine("獲取到的HashFiled: " + item);
            }


            //根據hashkey返回所有HashFiled值
            stopwatch.Start();
            var getvaluelist = RedisCacheHelper.Instance.HashValues("objlist_hashkey1", 1);
            stopwatch.Stop();
            Console.WriteLine("消耗時間:" + stopwatch.ElapsedMilliseconds.ToString());
            foreach (var item in getvaluelist)
            {
                Console.WriteLine("獲取到的HashFiled值: " + item);
            }

            //模擬並發場景 使用HashDecrement遞減功能  使用分布式鎖,但是線程線程排隊處理,有可能照成Redis超時
該示例會照成1個用戶搶多個
stopwatch.Start(); List<Task> taskList = new List<Task>(); for (int i = 1; i <= 100; i++) //模擬多個用戶同時請求 { var task = Task.Run(() => { try { //*************************開始**************** RedisValue token = Environment.MachineName; while (true) { if (RedisCacheHelper.Instance.LockAndDo("lock_key", token, () => { //在這里處理並發邏輯 //先獲取庫存數 var getvalues = (int)RedisCacheHelper.Instance.HashGet("order_hashkey", "order_hashfield"); if (getvalues > 0) { //先自減,獲取自減后的值 int order_Num = (int)RedisCacheHelper.Instance.HashDecrement("order_hashkey", "order_hashfield", null); if (order_Num >= 0) { //下面執行訂單邏輯(這里不考慮業務出錯的情況) Console.WriteLine("我搶到了,還剩余數量:" + order_Num); } else { Console.WriteLine("A商品已經被搶光了"); } return true; } else { Console.WriteLine("B商品已經被搶光了"); return true; } }, new TimeSpan(0, 0, 10))) { break; } } } catch (Exception ex) { Console.WriteLine(ex.Message); throw; } }); taskList.Add(task); } Task.WaitAll(taskList.ToArray()); stopwatch.Stop(); Console.WriteLine("模擬並發場景消耗時間:" + stopwatch.ElapsedMilliseconds.ToString());

搶購前庫存30個

 

 搶購后庫存0

 

針對上邊的分布式做搶購固然可以,但是相對的會犧牲一部分性能,還有一個更好的解決辦法,那就是Lua腳本:

 1             List<Task> taskList = new List<Task>();
 2             for (int i = 1; i <= 100; i++)   //模擬多個用戶同時請求
 3             {
 4                 var task = Task.Run(() =>
 5                 {
 6                     var getguid = Guid.NewGuid().ToString("N");   //模擬用戶名
 7                     var script = " local isexist= redis.call('hexists',@hid_1,@hfiled_1) " +    //檢查該用戶是否已經搶到
 8                                   " if tonumber(isexist) == 1 then " +   //1 表示已經存在   0表示不存在
 9                                   "   return isexist " +      //直接返回
10                                   " end " +
11                                   " local getstocknumber = redis.call('hget',@hid_2,@hfiled_2) " +    //獲取庫存剩余數量
12                                   " if tonumber(getstocknumber) > 0 then " +    //如果庫存>0
13                                   "   local decrtystock = redis.call('hincrby',@hid_2,@hfiled_2,@hvalue_2) " +    //減去1個庫存
14                                   "   local setuser = redis.call('hset',@hid_1,@hfiled_1,@hvalue_1) " +   //記錄搶到的用戶
15                                   "   if tonumber(setuser) ==1 then " +      //保存成功返回1     0失敗
16                                   "     redis.log(redis.LOG_WARNING,decrtystock) " +    //打印redis日志
17                                   "   end " +
18                                   "   return setuser " +         //返回1 表示true
19                                   " end ";
20 
21                     //參數
22                     object obj = new
23                     {
24                         @hid_1 = "orderSuccessList",
25                         @hfiled_1 = getguid.ToString(),
26                         @hvalue_1 = "1",
27                         @hid_2 = "order_hashkey",
28                         @hfiled_2 = "order_hashfield",
29                         @hvalue_2 = -1
30                     };
31 
32                     var getresult = RedisCacheHelper.Instance.HashOrderPut(script, obj);
33                     Console.WriteLine("返回值:" + getresult);
34 
35                 });
36                 taskList.Add(task);
37             }
38             Task.WaitAll(taskList.ToArray());

先設置庫存數量為10個

操作日志及剩余庫存數量

      

 記錄的用戶數據

 

 

應用場景:

我們簡單舉個實例來描述下 Hash 的應用場景,比如我們要存儲一個用戶信息對象數據,包含以下信息:

用戶 ID 為查找的 key,存儲的 value 用戶對象包含姓名,年齡,生日等信息,如果用普通的 key/value 結構來存儲,主要有以下 2 種存儲方式:

第一種方式將用戶 ID 作為查找 key, 把其他信息封裝成一個對象以序列化的方式存儲,這種方式的缺點是,增加了序列化 / 反序列化的開銷,並且在需要修改其中一項信息時,需要把整個對象取回,並且修改操作需要對並發進行保護,引入 CAS 等復雜問題。

第二種方法是這個用戶信息對象有多少成員就存成多少個 key-value 對兒,用用戶 ID+ 對應屬性的名稱作為唯一標識來取得對應屬性的值,雖然省去了序列化開銷和並發問題,但是用戶 ID 為重復存儲,如果存在大量這樣的數據,內存浪費還是非常可觀的。

那么 Redis 提供的 Hash 很好的解決了這個問題,Redis 的 Hash 實際是內部存儲的 Value 為一個 HashMap,並提供了直接存取這個 Map 成員的接口,如下圖:

也就是說,Key 仍然是用戶 ID, value 是一個 Map,這個 Map 的 key 是成員的屬性名,value 是屬性值,這樣對數據的修改和存取都可以直接通過其內部 Map 的 Key(Redis 里稱內部 Map 的 key 為 field), 也就是通過 key(用戶 ID) + field(屬性標簽) 就可以操作對應屬性數據了,既不需要重復存儲數據,也不會帶來序列化和並發修改控制的問題。很好的解決了問題。

這里同時需要注意,Redis 提供了接口 (hgetall) 可以直接取到全部的屬性數據, 但是如果內部 Map 的成員很多,那么涉及到遍歷整個內部 Map 的操作,由於 Redis 單線程模型的緣故,這個遍歷操作可能會比較耗時,而另其它客戶端的請求完全不響應,這點需要格外注意。

實現方式:

上面已經說到 Redis Hash 對應 Value 內部實際就是一個 HashMap,實際這里會有 2 種不同實現,這個 Hash 的成員比較少時 Redis 為了節省內存會采用類似一維數組的方式來緊湊存儲,而不會采用真正的 HashMap 結構,對應的 value redisObject 的 encoding 為 zipmap, 當成員數量增大時會自動轉成真正的 HashMap, 此時 encoding 為 ht。

 

這里附上RedisHelp

        /// <summary>
        /// 判斷該字段是否存在 hash 中
        /// </summary>
        /// <param name="redisKey"></param>
        /// <param name="hashField"></param>
        /// <returns></returns>
        public bool HashExists(string redisKey, string hashField, int db = -1)
        {
            try
            {
                _db = GetDatabase(db);
                return _db.HashExists(redisKey, hashField);
            }
            catch (Exception)
            {
                throw;
            }
        }

        /// <summary>
        /// 從 hash 中移除指定字段
        /// </summary>
        /// <param name="redisKey"></param>
        /// <param name="hashField"></param>
        /// <returns></returns>
        public bool HashDelete(string redisKey, string hashField, int db = -1)
        {
            try
            {
                _db = GetDatabase(db);
                return _db.HashDelete(redisKey, hashField);
            }
            catch (Exception)
            {
                throw;
            }
        }

        /// <summary>
        /// 從 hash 中移除多個指定字段
        /// </summary>
        /// <param name="redisKey"></param>
        /// <param name="hashField"></param>
        /// <returns></returns>
        public long HashDelete(string redisKey, IEnumerable<RedisValue> hashField, int db = -1)
        {
            try
            {
                _db = GetDatabase(db);
                return _db.HashDelete(redisKey, hashField.ToArray());
            }
            catch (Exception)
            {
                throw;
            }
        }

        /// <summary>
        /// 遞增  默認按1遞增  可用於計數
        /// </summary>
        /// <param name="key"></param>
        /// <param name="span"></param>
        /// <param name="db"></param>
        /// <returns></returns>
        public long HashIncrement(string redisKey, string hashField, TimeSpan? span = null, int db = -1)
        {
            try
            {
                _db = GetDatabase(db);
                var result = _db.HashIncrement(redisKey, hashField);
                //設置過期時間
                if (span != null)
                {
                    this.KeyExpire(redisKey, span);
                }
                return result;
            }
            catch (Exception)
            {

                throw;
            }
        }

        /// <summary>
        /// 遞減  默認按1遞減   可用於搶購類的案例
        /// </summary>
        /// <param name="key"></param>
        /// <param name="span"></param>
        /// <param name="db"></param>
        /// <returns></returns>
        public long HashDecrement(string redisKey, string hashField, TimeSpan? span = null, int db = -1)
        {
            try
            {
                _db = GetDatabase(db);
                var result = _db.HashDecrement(redisKey, hashField);
                //設置過期時間
                if (span != null)
                {
                    this.KeyExpire(redisKey, span);
                }
                return result;
            }
            catch (Exception)
            {
                throw;
            }
        }


        /// <summary>
        /// 在 hash 中保存或修改一個值   字符類型
        /// set or update the HashValue for string key 
        /// </summary>
        /// <param name="redisKey"></param>
        /// <param name="hashField"></param>
        /// <param name="value"></param>
        /// <param name="span">過期時間,可空</param>
        /// <returns></returns>
        public bool HashSet(string redisKey, string hashField, string value, TimeSpan? span = null, int db = -1)
        {
            try
            {
                _db = GetDatabase(db);
                if (span != null)
                {
                    //設置過期時間
                    this.KeyExpire(redisKey, span);
                }
                return _db.HashSet(redisKey, hashField, value);
            }
            catch (Exception)
            {
                throw;
            }
        }

        /// <summary>
        /// 保存一個字符串類型集合  
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="redisKey">Redis Key</param>
        /// <param name="list">數據集合</param>
        /// <param name="getFiledKey">字段key</param>
        public void HashSetList(string redisKey, IEnumerable<string> list, Func<string, string> getFiledKey, TimeSpan? span = null, int db = -1)
        {
            try
            {
                _db = GetDatabase(db);
                List<HashEntry> listHashEntry = new List<HashEntry>();
                foreach (var item in list)
                {
                    listHashEntry.Add(new HashEntry(getFiledKey(item), item));
                }
                _db.HashSet(redisKey, listHashEntry.ToArray());

                if (span != null)
                {
                    //設置過期時間
                    this.KeyExpire(redisKey, span);
                }
            }
            catch (Exception ex)
            {
                throw;
            }
        }


        /// <summary>
        /// 保存多個對象集合  非序列化
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="tableName">表名前綴</param>
        /// <param name="list">數據集合</param>
        /// <param name="getModelId">字段key</param>
        public void HashSetObjectList<T>(string tableName, IEnumerable<T> list, Func<T, string> getModelId, int db = -1) where T : class
        {
            try
            {
                _db = GetDatabase(db);
                foreach (var item in list)
                {
                    List<HashEntry> listHashEntry = new List<HashEntry>();
                    Type t = item.GetType();//獲得該類的Type
                    foreach (PropertyInfo pi in t.GetProperties())
                    {
                        string name = pi.Name; //獲得屬性的名字,后面就可以根據名字判斷來進行些自己想要的操作
                        var value = pi.GetValue(item, null);  //用pi.GetValue獲得值
                        listHashEntry.Add(new HashEntry(name, value.ToString()));
                    }
                    _db.HashSet(tableName + getModelId(item), listHashEntry.ToArray());
                }
            }
            catch (Exception ex)
            {
                throw;
            }
        }


        /// <summary>
        /// 保存或修改一個hash對象(序列化)
        /// </summary>
        /// <param name="redisKey"></param>
        /// <param name="hashField"></param>
        /// <param name="value"></param>
        /// <returns></returns>
        public bool HashSet<T>(string redisKey, string hashField, T value, TimeSpan? span = null, int db = -1) where T : class
        {
            try
            {
                _db = GetDatabase(db);
                var json = JsonConvert.SerializeObject(value);
                if (span != null)
                {
                    //設置過期時間
                    this.KeyExpire(redisKey, span);
                }
                return _db.HashSet(redisKey, hashField, json);
            }
            catch (Exception)
            {
                throw;
            }
        }

        /// <summary>
        /// 保存Hash對象集合 序列化
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="redisKey">Redis Key</param>
        /// <param name="list">數據集合</param>
        /// <param name="getModelId"></param>
        public void HashSet<T>(string redisKey, IEnumerable<T> list, Func<T, string> getModelId, TimeSpan? span = null, int db = -1) where T : class
        {
            try
            {
                _db = GetDatabase(db);
                List<HashEntry> listHashEntry = new List<HashEntry>();
                foreach (var item in list)
                {
                    string json = JsonConvert.SerializeObject(item);
                    listHashEntry.Add(new HashEntry(getModelId(item), json));
                }
                _db.HashSet(redisKey, listHashEntry.ToArray());

                if (span != null)
                {
                    //設置過期時間
                    this.KeyExpire(redisKey, span);
                }
            }
            catch (Exception ex)
            {
                throw;
            }
        }

        /// <summary>
        /// 從 hash 中獲取對象(反序列化)
        /// </summary>
        /// <param name="redisKey"></param>
        /// <param name="hashField"></param>
        /// <returns></returns>
        public T HashGet<T>(string redisKey, string hashField, int db = -1) where T : class
        {
            try
            {
                _db = GetDatabase(db);
                return JsonConvert.DeserializeObject<T>(_db.HashGet(redisKey, hashField));
            }
            catch (Exception)
            {
                throw;
            }
        }

        /// <summary>
        ///  根據hashkey獲取所有的值  序列化
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="redisKey"></param>
        /// <param name="db"></param>
        /// <returns></returns>
        public List<T> HashGetAll<T>(string redisKey, int db = -1) where T : class
        {
            List<T> result = new List<T>();
            try
            {
                _db = GetDatabase(db);
                HashEntry[] arr = _db.HashGetAll(redisKey);
                foreach (var item in arr)
                {
                    if (!item.Value.IsNullOrEmpty)
                    {
                        var t = JsonConvert.DeserializeObject<T>(item.Value);
                        if (t != null)
                        {
                            result.Add(t);
                        }
                    }
                }
            }
            catch (Exception)
            {
                throw;
            }
            return result;
        }


        /// <summary>
        ///  根據hashkey獲取所有的值  非序列化
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="redisKey"></param>
        /// <param name="db"></param>
        /// <returns></returns>
        public IEnumerable<Dictionary<RedisValue, RedisValue>> HashGetAll(IEnumerable<string> redisKey, int db = -1)
        {
            List<Dictionary<RedisValue, RedisValue>> diclist = new List<Dictionary<RedisValue, RedisValue>>();
            try
            {
                _db = GetDatabase(db);
                foreach (var item in redisKey)
                {
                    HashEntry[] arr = _db.HashGetAll(item);
                    foreach (var he in arr)
                    {
                        Dictionary<RedisValue, RedisValue> dic = new Dictionary<RedisValue, RedisValue>();
                        if (!he.Value.IsNullOrEmpty)
                        {
                            dic.Add(he.Name, he.Value);
                        }
                        diclist.Add(dic);
                    }
                }
            }
            catch (Exception)
            {
                throw;
            }
            return diclist;
        }


        /// <summary>
        /// 根據hashkey獲取單個字段hashField的值
        /// </summary>
        /// <param name="redisKey"></param>
        /// <param name="hashField"></param>
        /// <returns></returns>
        public RedisValue HashGet(string redisKey, string hashField, int db = -1)
        {
            try
            {
                _db = GetDatabase(db);
                return _db.HashGet(redisKey, hashField);
            }
            catch (Exception)
            {
                throw;
            }
        }

        /// <summary>
        /// 根據hashkey獲取多個字段hashField的值
        /// </summary>
        /// <param name="redisKey"></param>
        /// <param name="hashField"></param>
        /// <returns></returns>
        public RedisValue[] HashGet(string redisKey, RedisValue[] hashField, int db = -1)
        {
            _db = GetDatabase(db);
            return _db.HashGet(redisKey, hashField);
        }

        /// <summary>
        /// 根據hashkey返回所有的HashFiled
        /// </summary>
        /// <param name="redisKey"></param>
        /// <returns></returns>
        public IEnumerable<RedisValue> HashKeys(string redisKey, int db = -1)
        {
            _db = GetDatabase(db);
            return _db.HashKeys(redisKey);
        }

        /// <summary>
        /// 根據hashkey返回所有HashValue值
        /// </summary>
        /// <param name="redisKey"></param>
        /// <returns></returns>
        public RedisValue[] HashValues(string redisKey, int db = -1)
        {
            _db = GetDatabase(db);
            return _db.HashValues(redisKey);
        }
View Code


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM