zoukankan      html  css  js  c++  java
  • 第三节: List类型的介绍、生产者消费者模式、发布订阅模式

    一. List类型基础

    1.介绍

      它是一个双向链表,支持左进、左出、右进、右出,所以它即可以充当队列使用,也可以充当栈使用。

    (1). 队列:先进先出, 可以利用List左进右出,或者右进左出(ListLeftPush和ListRightPop配合 、 ListRightPush和ListLeftPop配合)

    (2). 栈:先进后出,可以利用List左进左出,或者右进右出

    2. 常用指令Api

    3.常用Api

    (1). ListLeftPush:从左侧添加,返回集合总数

    (2). ListRightPush:从右侧添加,返回集合总数

    (3). ListLeftPop:从左侧取1个值,并删除

    (4). ListRightPop:从右侧取1个值,并删除

    (5). ListInsertBefore:指定的key指定value之前(左边)插入1个值

    (6). ListInsertAfter:指定的key指定value之后(右边)插入1个值

    (7). ListGetByIndex:获取key的指定索引对应的value值(从左往右算)

    (8). ListRange:获取key的所有value,数据类型得一致 (也可以获取指定索引之间的value值,带扩展)

    (9). ListLength:获取指定key的数据的个数

    (10). ListRemove:删除指定key对应的指定value值,返回删除的个数

    (11). ListRightPopLeftPush:从List1右侧取一个值加到List2左侧,返回的是右侧取出来的这个值

     代码分享:

     1             //1.从左侧添加
     2             //单个,返回集合总数
     3             db.ListLeftPush("group1", "你好1");
     4             db.ListLeftPush("group1", "你好2");
     5             db.ListLeftPush("group1", "你好3");
     6             //多个
     7             string[] dList1 = { "你好4", "你好5" };
     8             RedisValue[] redisValue = dList1.Select(u => (RedisValue)u).ToArray();
     9             var d1 = db.ListLeftPush("group1", redisValue);
    10 
    11             //2.从右侧添加
    12             db.ListRightPush("group1", "你好6");
    13 
    14             //3.从左侧取1个值,并删除
    15             //var v1=db.ListLeftPop("group1");
    16 
    17             //4.从右侧取1个值并删除
    18             //var v2 = db.ListRightPop("group1");
    19 
    20             //5. 在List的指定的key指定value之前(左边)插入1个值
    21             db.ListInsertBefore("group1", "你好3", "ypf001");
    22 
    23             //6. 在List的指定的key指定value之后(右边)插入1个值
    24             db.ListInsertAfter("group1", "你好3", "ypf002");
    25 
    26             //7. 获取key指定索引的值(从左往右算)
    27             var d2 = db.ListGetByIndex("group1", 0);
    28             var d3 = db.ListGetByIndex("group1", 2);
    29 
    30             //8. 获取key的所有数据,数据类型得一致
    31             var d4 = db.ListRange("group1").Select(u => (string)u).ToList();
    32             //获取key的前4条数据(从左往右)
    33             var d44 = db.ListRange("group1", 0, 3).Select(u => (string)u).ToList();
    34 
    35             //9.获取指定key的数据的个数
    36             long d5 = db.ListLength("group1");
    37 
    38             //10. 删除指定key对应的指定value值,返回删除的个数
    39             db.ListLeftPush("group1", "你好");
    40             db.ListLeftPush("group1", "你好");
    41             long d6 = db.ListRemove("group1", "你好");
    42 
    43             //11. 从List1右侧取一个值加到List2左侧,返回的是右侧取出来的这个值
    44             db.ListLeftPush("group2", "你好1");
    45             db.ListLeftPush("group2", "你好2");
    46             db.ListLeftPush("group2", "你好3");
    47             db.ListLeftPush("group3", "哈哈1");
    48             db.ListLeftPush("group3", "哈哈2");
    49             db.ListLeftPush("group3", "哈哈3");
    50             //从队列group2右侧取出来一个值放到group3中
    51             var d7 = db.ListRightPopLeftPush("group2", "group3");

    二. 案例分析

    (一). 消息队列 (生产者消费者模式)

    PS:生产者消费模式:可以是多个生产者,多个消费者,但是生成的数据按顺序进入队列,但是每个数据只能被一个消费者消费。

    1. 异步处理

      (1). 将同步业务:创建订单→增加积分→发送短信,改为创建订单后 存放到两个消息队列中(积分队列和短信队列),然后积分业务和短信业务分部去队列中读取,执行各自的业务

      (2). 注册成功发邮件通知:很多场景注册成功后要给用户发一封邮件提示,但这封邮件实时性要求并不是很高,而且发邮件一般是调用第三方接口进行发送,有时候可能会很慢或者故障了, 针对这种情况,借助队列采用生产者消费者模式非常适合。

    2. 应用解耦

      将原先订单系统和库存系统的强依赖关系,改为中间引入消息队列,这样二者都依赖消息队列做中介.

    3. 流量削锋

      秒杀服务,下单的用户加到队列中,然后开启另外一个线程从队列中读取进行下单,下单成功/失败 利用实时通讯技术通知客户端 或者 客户端主动刷新页面进行查看结果,这里要结合实际架构(单体or集群)分析秒杀情况,不能一概而论,详见后面秒杀章节。

    4. 即时通讯

      考虑到同时很多人发送,前端页面的渲染会有点吃不消,这里可以采用 群id 当做队列的key,发送的消息当做value,存入队列中,然后开启一个新的线程从里面读取, 可以一下获取20条,获取的同时并删除,如果队列为空,则休息几秒中,再次获取。

    针对群聊的代码分享

      以群聊为例,利用ListLeftPush方法,以“群id”当做key,以发送人id、发送内容、时间组合当做value,从左侧存储到队列;然后利用Core中的BackService类开启后台线程利用ListRightPop 进行读取,同时要在ConfigureService中进行注册。

    代码分享:

     1        /// <summary>
     2         /// 测试群聊页面
     3         /// (PS:不断刷新即可)
     4         /// </summary>
     5         /// <returns></returns>
     6         public IActionResult Index()
     7         {
     8             string userId = Guid.NewGuid().ToString("N");
     9             string msg = "哈哈" + new Random().Next(1, 10000);
    10             SendMessage(userId, msg);
    11             return View();
    12         }
    13 
    14         /// <summary>
    15         ///群聊发送消息接口 
    16         /// </summary>
    17         /// <param name="userId">用户id</param>
    18         /// <param name="msg">发送的内容</param>
    19         /// <returns></returns>
    20         public string SendMessage(string userId, string msg)
    21         {
    22             try
    23             {            
    24                 string groupName = "classParty";  //群名
    25                 string sendContent = $"{userId}_{msg}_{DateTime.Now}"; //内容
    26                 //存入队列
    27                 _redis.ListLeftPush(groupName, sendContent);
    28                 return "ok";
    29             }
    30             catch (Exception ex)
    31             {
    32                 return "error";
    33             }
    34         }

    后台服务及注册:

     1  public class SendService : BackgroundService
     2     {
     3         private readonly IDatabase _redis;
     4         public SendService(RedisHelp redisHelp)
     5         {
     6             _redis = redisHelp.GetDatabase();
     7         }
     8         protected override async Task ExecuteAsync(CancellationToken stoppingToken)
     9         {
    10             while (!stoppingToken.IsCancellationRequested)
    11             {
    12                 try
    13                 {
    14                     //实际情况,这里有几个群,开几个线程执行     
    15                     List<string> msgList = new List<string>();
    16                     Stopwatch stopwatch = new Stopwatch();
    17                     stopwatch.Start();
    18                     //要么没有更多待发消息立即发给客户端,要么累积满1秒钟待发消息后发送给客户端
    19                     while (true)
    20                     {                        
    21                         string msg = _redis.ListRightPop("classParty");
    22                         if (!string.IsNullOrEmpty(msg)) 
    23                         {
    24                             msgList.Add(msg);
    25                         }
    26                         else
    27                         {
    28                             await Task.Delay(500);
    29                         }
    30                         //满一段时间的消息向客户端发送
    31                         if (stopwatch.Elapsed>TimeSpan.FromSeconds(1))
    32                         {
    33                             stopwatch.Stop();
    34                             if (msgList.Any())
    35                             {
    36                                 //将这一批消息发送给客户端                             
    37                                 //需要重新滞空msgList
    38                             }
    39                         }
    40                     }                 
    41                 }
    42                 catch (Exception)
    43                 {
    44                     throw;
    45                 }
    46             }
    47         }
    48     }
    1  public void ConfigureServices(IServiceCollection services)
    2   {
    3             //注册后台服务
    4             services.AddHostedService<SendService>();
    5    }

    (二). 解决查询缓慢问题

      比如发帖网站,会有非常多的帖子,而且数量每日俱增,首页显示的是最新发布的10条帖子,显示的是:发帖人名称 和 发帖标题,如果从数据库中查询可能会非常慢,这时候可以把发帖人名称和

    发帖标题(包括帖子id),存到Redis队列中,这个时候利用 栈 的特性,ListGetByIndex:获取key指定索引的值(从左往右算), 获取前10条数据,用于显示,查看详情的时候,再根据帖子的id到数据库中查。

    三. 发布订阅模式

     1. 说明

      发布者发布一条消息,所有的订阅者都能收到。

    2. 案例背景

      以微博为例(或者微信的订阅号),博主A,博主B都关注了博主C、博主D,在博主A(或B)的版面,应该显示的是博主C和博主D发布的最新博文(最新发布的在最上面),换句话说博主C或者博主D每发一篇博文,都要推送给关注他们的博主A和博主B。

    3. 技术分析

    (1). 数据结构的设计:一个博主对应一个List链表,用来存储该博主应该显示的博文消息。 以博主的用户id作为key,博文消息的id作为value。

    (2). 博主C每发一条博文,就需要向关注他的粉丝(A和B)对应的链表中分别存储一下 该博文消息的id。

    (3). 博主D每发一条博文,就需要向关注他的粉丝(A和B)对应的链表中分别存储一下 该博文消息的id。

    (4).  博主A就可以到自己对应的链表中利用的特性,获取最新的n条博文消息id。

    PS: 拿到博文消息id了,剩下的就容易了,根据id去关系型数据中查内容就很快了,或者也可以将标题或者内容的前100字也存储到Redis中,便于页面显示(这样value的格式就是:博文id-博文标题-博文内容前100字)。

    !

    • 作       者 : Yaopengfei(姚鹏飞)
    • 博客地址 : http://www.cnblogs.com/yaopengfei/
    • 声     明1 : 本人才疏学浅,用郭德纲的话说“我是一个小学生”,如有错误,欢迎讨论,请勿谩骂^_^。
    • 声     明2 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。
     
  • 相关阅读:
    由ping百度引发的思考
    操作系统 | 概述
    操作系统导论第四章 | 抽象:进程
    汇编语言 | 定制键盘输入的处理过程
    细数 TS 中那些奇怪的符号
    vue 各种 import 引入
    display:table-cell实现水平垂直居中
    Jquery判断单选框是否选中和获取选中的值
    css整理 -- 右箭头,上下箭头,三角形、超出省略号代替
    JQuery操作select下拉框
  • 原文地址:https://www.cnblogs.com/yaopengfei/p/11940420.html
Copyright © 2011-2022 走看看