第三节: 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 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· DeepSeek如何颠覆传统软件测试?测试工程师会被淘汰吗?
2017-11-27 第三节:Action向View传值的四种方式(ViewData、ViewBag、TempData、Model)