一. 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 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。