• 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
  • 相关阅读:
    Mac电脑maven安装与配置
    解决Mac OS X中IDEA启动慢以及debug卡死问题
    如何在Mac上启用root用户或更改root密码
    mac文本编辑器软件,五大适用于Mac修订的文本编辑器,nodepad++替代软件
    mac系统到10.14以上,navicat无法打开,一直显示已损坏解决办法
    mac苹果电脑AppleID注册或者登录appstore时提示:您没有完整填写表格,请输入您的出生年份的解决方法
    mac苹果电脑使用相关,开发环境配置指南(持续更新)
    bitmap to base64
    Multiple actions were found that match the request in Web Api
    vue get attribute value
  • 原文地址:https://www.cnblogs.com/peterzhang123/p/12310384.html
Copyright © 2020-2023  润新知