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 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。
     
  • 相关阅读:
    Yii2框架之旅(三)
    Yii2框架之旅(二)
    Redis本地集群搭建(5版本以上)
    Redis入门笔记
    Java如何使用elasticsearch进行模糊查询--转载
    springboot集成elasticsearch7.6.1,常用api调用,创建,查找,删除索引,crud,高亮。。。--转载
    SpringBoot整合Elasticsearch7.2.0的实现方法-转载
    Spring Webflux 入门 -转载
    java 视频流截屏,形成缩略图
    记录一下 spring boot 线程处理返回数据
  • 原文地址:https://www.cnblogs.com/yaopengfei/p/11912930.html
Copyright © 2011-2022 走看看