一. 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.从左侧添加
//单个,返回集合总数
db.ListLeftPush("group1", "你好1");
db.ListLeftPush("group1", "你好2");
db.ListLeftPush("group1", "你好3");
//多个
string[] dList1 = { "你好4", "你好5" };
RedisValue[] redisValue = dList1.Select(u => (RedisValue)u).ToArray();
var d1 = db.ListLeftPush("group1", redisValue); //2.从右侧添加
db.ListRightPush("group1", "你好6"); //3.从左侧取1个值,并删除
//var v1=db.ListLeftPop("group1"); //4.从右侧取1个值并删除
//var v2 = db.ListRightPop("group1"); //5. 在List的指定的key指定value之前(左边)插入1个值
db.ListInsertBefore("group1", "你好3", "ypf001"); //6. 在List的指定的key指定value之后(右边)插入1个值
db.ListInsertAfter("group1", "你好3", "ypf002"); //7. 获取key指定索引的值(从左往右算)
var d2 = db.ListGetByIndex("group1", );
var d3 = db.ListGetByIndex("group1", ); //8. 获取key的所有数据,数据类型得一致
var d4 = db.ListRange("group1").Select(u => (string)u).ToList();
//获取key的前4条数据(从左往右)
var d44 = db.ListRange("group1", , ).Select(u => (string)u).ToList(); //9.获取指定key的数据的个数
long d5 = db.ListLength("group1"); //10. 删除指定key对应的指定value值,返回删除的个数
db.ListLeftPush("group1", "你好");
db.ListLeftPush("group1", "你好");
long d6 = db.ListRemove("group1", "你好"); //11. 从List1右侧取一个值加到List2左侧,返回的是右侧取出来的这个值
db.ListLeftPush("group2", "你好1");
db.ListLeftPush("group2", "你好2");
db.ListLeftPush("group2", "你好3");
db.ListLeftPush("group3", "哈哈1");
db.ListLeftPush("group3", "哈哈2");
db.ListLeftPush("group3", "哈哈3");
//从队列group2右侧取出来一个值放到group3中
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中进行注册。

代码分享:

        /// <summary>
/// 测试群聊页面
/// (PS:不断刷新即可)
/// </summary>
/// <returns></returns>
public IActionResult Index()
{
string userId = Guid.NewGuid().ToString("N");
string msg = "哈哈" + new Random().Next(, );
SendMessage(userId, msg);
return View();
} /// <summary>
///群聊发送消息接口
/// </summary>
/// <param name="userId">用户id</param>
/// <param name="msg">发送的内容</param>
/// <returns></returns>
public string SendMessage(string userId, string msg)
{
try
{
string groupName = "classParty"; //群名
string sendContent = $"{userId}_{msg}_{DateTime.Now}"; //内容
//存入队列
_redis.ListLeftPush(groupName, sendContent);
return "ok";
}
catch (Exception ex)
{
return "error";
}
}

后台服务及注册:

  public class SendService : BackgroundService
{
private readonly IDatabase _redis;
public SendService(RedisHelp redisHelp)
{
_redis = redisHelp.GetDatabase();
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while (!stoppingToken.IsCancellationRequested)
{
try
{
//实际情况,这里有几个群,开几个线程执行
List<string> msgList = new List<string>();
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
//要么没有更多待发消息立即发给客户端,要么累积满1秒钟待发消息后发送给客户端
while (true)
{
string msg = _redis.ListRightPop("classParty");
if (!string.IsNullOrEmpty(msg))
{
msgList.Add(msg);
}
else
{
await Task.Delay();
}
//满一段时间的消息向客户端发送
if (stopwatch.Elapsed>TimeSpan.FromSeconds())
{
stopwatch.Stop();
if (msgList.Any())
{
//将这一批消息发送给客户端
//需要重新滞空msgList
}
}
}
}
catch (Exception)
{
throw;
}
}
}
}
  public void ConfigureServices(IServiceCollection services)
{
//注册后台服务
services.AddHostedService<SendService>();
}

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

  比如发帖网站,会有非常多的帖子,而且数量每日俱增,首页显示的是最新发布的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 : 原创博客请在转载时保留原文链接或在文章开头加上本人博客地址,否则保留追究法律责任的权利。
 

第三节: List类型的介绍、生产者消费者模式、发布订阅模式的更多相关文章

  1. 阶段5 3.微服务项目【学成在线】_day05 消息中间件RabbitMQ_8.RabbitMQ研究-工作模式-发布订阅模式-生产者

    Publish/subscribe:发布订阅模式 发布订阅模式: 1.每个消费者监听自己的队列. 2.生产者将消息发给broker,由交换机将消息转发到绑定此交换机的每个队列,每个绑定交换机的队列都将 ...

  2. 阶段5 3.微服务项目【学成在线】_day05 消息中间件RabbitMQ_9.RabbitMQ研究-工作模式-发布订阅模式-消费者

    消费者需要写两个消费者 定义邮件的类 复制以前的代码到邮件类里面进行修改 最上面 声明队列的名称和交换机的名称 监听修改为email 的队列的名称 手机短信接收端 复制一份email的接收端的代码 改 ...

  3. python使用rabbitMQ介绍三(发布订阅模式)

    一.模式介绍 在前面的例子中,消息直接发送到queue中. 现在介绍的模式,消息发送到exchange中,消费者把队列绑定到exchange上. 发布-订阅模式是把消息广播到每个消费者,每个消费者接收 ...

  4. 生产者-消费者模型在Hudi中的应用

    介绍 生产者-消费者模型用于解耦生产者与消费者,平衡两者之间的能力不平衡,该模型广泛应用于各个系统中,Hudi也使用了该模型控制对记录的处理,即记录会被生产者生产至队列中,然后由消费者从队列中消费,更 ...

  5. JMS消息传递类型特点介绍

    对于消息的传递有两种类型: 一种是点对点的,即一个生产者和一个消费者一一对应: 另一种是发布/ 订阅模式,即一个生产者产生消息并进行发送后,可以由多个消费者进 行接收. 特点介绍: 点到点模型点对点传 ...

  6. labview学习——生产者/消费者(数据)(事件)

    其主要的模型: 主要从以下几个方面理解: 1.可重入性 正常的labview是多线程设计语言,而我们在执行VI时的规则是通过VI的命名来分别调用实现的. 打开VI的Highlight调试工具,可以看出 ...

  7. 生产者消费者模型及Golang简单实现

    简介:介绍生产者消费者模型,及go简单实现的demo. 一.生产者消费者模型 生产者消费者模型:某个模块(函数等〉负责产生数据,这些数据由另一个模块来负责处理(此处的模块是广义的,可以是类.函数.协程 ...

  8. Java实现生产者消费者问题与读者写者问题

    摘要: Java实现生产者消费者问题与读者写者问题 1.生产者消费者问题 生产者消费者问题是研究多线程程序时绕不开的经典问题之一,它描述是有一块缓冲区作为仓库,生产者可以将产品放入仓库,消费者则可以从 ...

  9. 4.生产者 消费者模式的RabbitMQ

    1.生产者: using RabbitMQ.Client; using System; using System.Text; namespace Publisher1 { class Program ...

随机推荐

  1. 两种查看EFCore生成Sql语句的方法

    一.利用反射生成查询语句 该方法转载自:https://jhrs.com/2019/28488.html (略有修改) using Microsoft.EntityFrameworkCore.Quer ...

  2. SpringBoot(14)—注解装配Bean

    SpringBoot(14)-注解装配Bean SpringBoot装配Bean方式主要有两种 通过Java配置文件@Bean的方式定义Bean. 通过注解扫描的方式@Component/@Compo ...

  3. 使用ADO.NET实体数据模型

    前景:要操作的数据表必须添加主键(方式:进入数据库-->数据表名-->设计-->列名右键-->设置主键) 可在服务器资源管理器中查看是否设置了主键(主键会有一把钥匙的图样) 1 ...

  4. 资深程序员:学Python我推荐你用这几款编辑器

    Python使用什么编辑比较好,Python编辑器推荐 各位 Pythoner 好啊!在这个烦躁的时代,相聚就是缘分,很高兴各位 Pythoner 能相聚于此,希望接下来的路,我们一起走下去,使用 P ...

  5. JAVA语言的环境搭建

    1.下载JDK 下载地址 https://www.oracle.com/technetwork/java/javase/downloads/index.html 2.安装JDK 傻瓜式的安装,一直点击 ...

  6. Java 小游戏 - 井字棋 v1.0 (初步完成) (2018.4.16更新)

      井字棋游戏初步完成 实现功能:输入位置数据->打印棋盘->判断是否胜利->继续游戏/退出游戏 缺点:没有清屏函数   判断胜利方法太过无脑    package MYGAME; ...

  7. 初始HTML_表格

    Html表格 <!DOCTYPE html> <html> <head> <meta charset="utf-8"> <ti ...

  8. [b0025] vmware_桥接网路设置

    总结: 桥接 模式上外网比较简单. 缺点是每次重启虚拟机,分配的IP可能变化,导致 xshell  连接 时都要修改,很麻烦 桥接模式能不能配置静态IP  ? 1. 环境 物理机网络 情况,从外部自动 ...

  9. Mysql8.0主从复制搭建,shardingsphere+springboot+mybatis读写分离

    1.安装mysql8.0 首先需要在192.167.3.171上安装JDK. 下载mysql安装包,https://dev.mysql.com/downloads/,找到以下页面下载. 下载后放到li ...

  10. Shell命令-用户用户组管理之passwd、chage

    文件及内容处理 - passwd.chage 1. passwd:修改用户密码 passwd命令的功能说明 passwd命令用来更改使用者的密码 passwd命令的语法格式 passwd [-k] [ ...