zoukankan      html  css  js  c++  java
  • 第一节: Redis之String类型和Hash类型的介绍和案例应用

    一. String类型基础

    1.类型介绍

      典型的Key-Value集合,如果要存实体,需要序列化成字符串,获取的时候需要反序列化一下。

    2. 指令Api说明

    3.常用Api说明

    (1).StringSet:写入数据,如果数据已经存在,则覆盖;可以一次性存入1个key-value,也可以一次性存入多个Key-value集合,并且可以设置其过期时间。

    (2).StringGet:读取数据,可以一次性读取一个key的value,也可以一次性读取多个key对应的value的集合。

    (3).StringAppend:在原有值的基础上进行拼接追加.

    (4).StringLength:获取值的长度

    (5).StringIncrement:数值自增n,返回自增后的值

    (6).StringDecrement:数值自减n,返回自减后的值

    3.通用Api操作

    (1).Execute("FLUSHDB"):删除所有数据,类似SqlServer的truncate

    (2).KeyDelete:根据key删除数据,可以删除单个key,也可以删除多个key

    (3).KeyExists:判断key是否存在,也可以单个key或者多个key

    (4).KeyRename:重命名key

    (5).KeyExpire:设置对应key的的过期时间

    常用string类型Api代码:

     1             //1. 最简单的key-value的添加,如果该key已存在,则执行的是附加操作
     2             //可以设置过期时间哦
     3             bool a1 = db.StringSet("101", "keen");
     4 
     5             //2. 根据key获取值
     6             string data1 = db.StringGet("101");
     7 
     8             //3. 在原有的value上进行追加
     9             //在原有值的基础上追加,返回值是最终字符串的长度,如果没有这个key,则当做一个新的key进行添加
    10             long data2 = db.StringAppend("101", "Marren");
    11 
    12             //4. 获取值的长度
    13             long data3 = db.StringLength("101");
    14 
    16             //5. 数值自增/减,返回自增、自减后的值
    17             db.StringSet("102", 10);
    18             //自增2,可以自增负值
    19             var data4 = db.StringIncrement("102", 2);
    20             //自减5
    21             var data5 = db.StringDecrement("102", 5);
    22 
    24             //6. 插入实体和读取实体 (需要序列化和反序列化)
    25             //由于序列化的原因,肯定不如存到Hash里速度快
    26             UserInfor userInfor = new UserInfor()
    27             {
    28                 userName = "ypf",
    29                 userPwd = "123456",
    30                 userAge = 15
    31             };
    32             db.StringSet("userInfor_101", JsonConvert.SerializeObject(userInfor));
    33             UserInfor data6 = JsonConvert.DeserializeObject<UserInfor>(db.StringGet("userInfor_101"));
    34 
    35             //7. 一次性添加多个key-value集合
    36             Dictionary<string, string> dic = new Dictionary<string, string>();
    37             dic.Add("103", Guid.NewGuid().ToString("N"));
    38             dic.Add("104", Guid.NewGuid().ToString("N"));
    39             dic.Add("105", Guid.NewGuid().ToString("N"));
    40             dic.Add("106", Guid.NewGuid().ToString("N"));
    41             dic.Add("107", Guid.NewGuid().ToString("N"));
    42             dic.Add("108", Guid.NewGuid().ToString("N"));
    43             var keyValues = dic.Select(p => new KeyValuePair<RedisKey, RedisValue>(p.Key, p.Value)).ToArray();
    44             bool data7 = db.StringSet(keyValues);
    45 
    46             //8.获取多个key的 value值集合
    47             string[] keys = { "101", "102", "103" };
    48             RedisKey[] redisKeys = keys.Select(u => (RedisKey)u).ToArray();
    49             //此处如果是别的复杂类型要借助JsonConvert进行转换
    50             List<string> data8 = db.StringGet(redisKeys).Select(u => u.ToString()).ToList();

    通用Api代码: 

     1             //1. 删除所有数据
     2             db.Execute("FLUSHDB");
     3 
     4             //2. 删除单个key
     5             bool d1 = db.KeyDelete("101");  //删除成功,返回true
     6             bool d2 = db.KeyDelete("ffff"); //删除不存在的数据,返回false
     7 
     8             //3. 删除多个key
     9             string[] arry = { "102", "103", "104" };
    10             RedisKey[] keys = arry.Select(u => (RedisKey)u).ToArray();
    11             long d3 = db.KeyDelete(keys);     //返回的是删除成功的个数  
    12 
    13             //4. 判断key是否存在(不推荐使用,会有并发问题)
    14             bool d4 = db.KeyExists("102");
    15             bool d5 = db.KeyExists("105");
    16 
    17             //5. 重命名key
    18             bool d6 = db.KeyRename("108", "10086");
    19 
    20             //6. 设置key的过期时间(1分钟后自动销毁)
    21             bool d7 = db.KeyExpire("107", DateTime.Now.AddMinutes(1));

    二. String类型案例

    1. 普通的Key-Value缓存

      string类型最简单的一个应用就是Key-value缓存,value可以是简单string、int,也可以是序列化后的实体,可以替代Session进行存储,和其它缓存一样,也可以设置缓存的过期时间(常用的键设计:表名_id  ,如: UserInfor_001 )

    2. 秒杀-超卖问题

    (1).背景

      某商家拿出来100件iphone11在某天的0点以超低价开卖,势必有很多人等着购买. 简单分析一下业务逻辑:判断库存,库存>0,继续往后执行下单逻辑(比如:插入订单表、发货地址表记录等等); 否则提示顾客“商品已经被抢光”.

      看到这个需求,可能新手会直接操控关系型数据库,并没有采取一下措施,这样就会导致同一时间进来的顾客判断库存都>0, 购买成功的人数就>100了,也就是出现了超卖现象,对于商家而言, 我太难了!!

    常用的解决方案:

    A. 下单页面加Lock锁,会造成大量的客户等待卡死等现象. (注:只能锁住单进程,如果是分布式,多个IIS,需要引进分布式锁)

    B. 利用乐观锁, 会造成一种现象即使该用户是前100个进来的也没有买到,不合理,不适用

    C. 把下单的用户加到队列中,然后开启另外一个线程从队列中读取进行下单,下单业务执行完,再出队,下单成功/失败 利用实时通讯技术通知客户端 或者 客户端主动刷新页面进行查看结果.

    (此处需要区分是出队后,接着出队,还是出队后执行完下单业务才能出下一个对呢, 还要注意如果秒杀服务是个集群,无法保证原队列的顺序,且同样存在超买超卖问题)

    (2).利用Redis单线程的原理解决 

      事先将该商品的库存初始化到Redis中,然后利用StringDecrement自减1同时返回自减后的值,如果值>=0,表示有库存然后执行后面的下单逻辑,这里利用Redis很大程度的给关系型数据库减压了(不用查询sqlserver 判断库存了), 库存不足,直接返回库存不足。

    (单体Redis好用,集群Redis不适用,如果用集群的话,可以考虑用lua脚本把查库存和减库存写到一起,这样就可以用于集群了)

    代码分享:

     1  public static void CaseDemo1(IDatabase db)
     2         {
     3             //删除所有数据
     4             db.Execute("FLUSHDB");
     5             //事先初始化库存
     6             db.StringSet("order_Num", 10);
     7 
     8             List<Task> taskList = new List<Task>();
     9             for (int i = 0; i < 50; i++)   //模拟多个用户并发
    10             {
    11                 var task = Task.Run(() =>
    12                  {
    13                      try
    14                      {
    15                         //先自减,获取自减后的值
    16                         int order_Num = (int)db.StringDecrement("order_Num", 1);
    17                          if (order_Num >= 0)
    18                          {
    19                              //下面执行订单逻辑(这里不考虑业务出错的情况)
    20                              Task.Delay(2000);
    21                              Console.WriteLine("下单成功了");
    22                          }
    23                          else
    24                          {
    25                              Console.WriteLine("商品已经被抢光了");
    26                          }
    27 
    28                      }
    29                      catch (Exception ex)
    30                      {
    31                          Console.WriteLine(ex.Message);
    32                          throw;
    33                      }
    34                  });
    35                 taskList.Add(task);
    36             }
    37             Task.WaitAll(taskList.ToArray());
    38         }
    View Code

    PS:关于秒杀问题,详见后面单独的章节:

                        第六节:秒杀业务/超买超卖的几种解决思路

    3. 点击量、点赞量、访问量

    (1). 背景

      要统计一个网站的访问次数,一个ip一天只能点击一次。

    (2). 解决方案

      先判断是否存在该ip,如果不存在,以ip为key,value随意,存储到string类型中,同时利用StringIncrement对访问次数自增1。

     1         /// <summary>
     2         /// 访问量案例
     3         /// </summary>
     4         /// <returns></returns>
     5         public IActionResult Index()
     6         {
     7             //获取Ip,这里利用个随机数模拟ip效果
     8             var ip = Guid.NewGuid().ToString("N");
     9             if (!_redis.KeyExists(ip))
    10             {
    11                 //把该ip存进去,并且设置有效期为1天
    12                 _redis.StringSet(ip, "随意值", TimeSpan.FromDays(1));
    13                 //同时访问次数自增1
    14                 _redis.StringIncrement("fw_count", 1);
    15             }
    16             ViewBag.FwCount = _redis.StringGet("fw_count");
    17             return View();
    18         }

    总结: 

      String除了key-value当缓存外,主要是利用其原子性,围绕自增自减并返回当前值(计数器作用)来使用,比如:单个网站的点击量(访问量、收藏量),单个商品的秒杀等等。如果是某个类别下多个物品的计数,同时要获取物品的计数排名,则利用SortedSet来实现,比如:某个班级每个小孩的投票数并排序、某个栏目下每篇文章的阅读数并排序 等等。

     PS:String和SortedSet具有计数器功能,String是针对单个,Sorted是针对某个类别下的多个或每一个,并且实现排序功能。 Hash类型也能实现某个类别下多个物品的计数,但它不具有排序功能。

    三. Hash类型基础

    1.类型说明

      一个key,对应一个Key-Value集合, hashid -{key:value;key:value;key:value;}, 相当于value又是一个“键值对集合” 或者值是另外一个 Dictionary。

    2. 常用指令Api

    3.常用Api说明

    (1).HashSet:存储单个 hashid-key-value

    (2).HashGet:单个value的获取; 获取1个hashid对应的所有key集合; 获取1个hashid对应所有的key和value集合.

    (3).HashDelete:删除1个hashid-key; 删除1个hashid-多个key

    (4).HashIncrement:自增,返回自增后的值

    (5).HashDecrement:自减,返回自减后的值

    (6).HashExists:判断 hashid-key 是否存在,返回true和value

     代码分享:

     1             //1.添加
     2             db.HashSet("UserInfor_001", "name", "ypf");
     3             db.HashSet("UserInfor_001", "age", "27");
     4             db.HashSet("UserInfor_001", "sex1", "男1");
     5             db.HashSet("UserInfor_001", "sex2", "男2");
     6             db.HashSet("UserInfor_001", "sex3", "男3");
     7             db.HashSet("UserInfor_001", "sex4", "男4");
     8             db.HashSet("UserInfor_001", "name", "Marren");  //会覆盖上面的值
     9             //没找到一下把实体添加进去的方法
    10             UserInfor userInfor = new UserInfor()
    11             {
    12                 userName = "ypf",
    13                 userPwd = "123456",
    14                 userAge = 15
    15             };
    16 
    17             //2.获取
    18             //2.1 单个value的获取
    19             string age = db.HashGet("UserInfor_001", "age");
    20             string name = db.HashGet("UserInfor_001", "name");
    21             //2.2 获取1个hashid对应所有的key的集合(前提必须是同数据类型的)
    22             List<string> keyList = db.HashKeys("UserInfor_001").Select(u => (string)u).ToList();
    23             //2.3 获取hashid对应的所有key和value,必须保证该hashid对应的所有数据类型一致
    24             Dictionary<string, string> dic = new Dictionary<string, string>();
    25             foreach (var item in db.HashGetAll("UserInfor_001"))
    26             {
    27                 dic.Add(item.Name, item.Value);
    28             }
    29             //没法一下获取一个实体
    30 
    31             //3. 删除
    32             //单个key
    33             bool d1 = db.HashDelete("UserInfor_001", "name");
    34             //多个key
    35             string[] dataKeyArry = { "sex1", "sex2", "sex3" };
    36             RedisValue[] redisValueArry = dataKeyArry.Select(u => (RedisValue)u).ToArray();
    37             long deleteNum = db.HashDelete("UserInfor_001", redisValueArry);
    38 
    39             //4. 自增,自减, 返回自增或自减后的值
    40             db.HashSet("UserInfor_002", "age", 20);
    41             long d2 = db.HashIncrement("UserInfor_002", "age", 2); //自增2,返回值为22
    42             long d3 = db.HashDecrement("UserInfor_002", "age", 3); //自减3,返回值为19
    43 
    44             //5. 判断数据是否存在
    45             bool d4 = db.HashExists("UserInfor_002", "age");
    46             bool d5 = db.HashExists("UserInfor_002", "age2");

     4. 优缺点

    四. Hash类型案例

      Hash类型用于存储某个类别下多个物品的存储,也可以实现物品的计数器功能,但是和SortedSet相比,它不具有排序功能。

    1. 购物车

     分析:

      以用户id作为hashid,商品id作为key,商品数量作为value,利用自增和自减功能来实现增加商品数量和减少商品数量能。也可以删除商品,获取商品总数,获取购物车中所有商品。

    2. 存储群聊消息。

      存储群聊消息,比如:群名为hashid, 用户id当做key,内容作为value。 这样存储可以,但是取数据的时候必须一下全部取出来,不能根据时间取前n条。

     

    !

    • 作       者 : Yaopengfei(姚鹏飞)
    • 博客地址 : http://www.cnblogs.com/yaopengfei/
    • 声     明1 : 本人才疏学浅,用郭德纲的话说“我是一个小学生”,如有错误,欢迎讨论,请勿谩骂^_^。
    • 声     明2 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。
     
  • 相关阅读:
    【C#进阶系列】06 类型和成员基础
    纪中5日T1 1564. 旅游
    纪中17日T1 2321. 方程
    纪中17日T2 2322. capacitor
    纪中10日T1 2313. 动态仙人掌
    纪中14日听课小结 图论 最短路 二分图 差分约束
    一个抓猫的游戏 消遣GAME 持续更新中!
    洛谷P1464 Function  HDU P1579 Function Run Fun
    洛谷P1976 鸡蛋饼
    纪中12日T1 2307. 选择
  • 原文地址:https://www.cnblogs.com/yaopengfei/p/11912930.html
Copyright © 2011-2022 走看看