详解Redis中的List类型

本系列将和大家分享redis分布式缓存,本章主要简单介绍下redis中的list类型,以及如何使用redis解决博客数据分页、生产者消费者模型和发布订阅等问题。

redis list的实现为一个双向链表,即可以支持反向查找和遍历,更方便操作,不过带来了部分额外的内存开销,redis内部的很多实现,包括发送缓冲队列等也都是用这个数据结构。

list类型主要用于队列和栈,先进先出,后进先出等。

存储形式:key–linklist<value>

首先先给大家show一波redis中与list类型相关的api:

using system;
using system.collections.generic;
using servicestack.redis;
namespace tianya.redis.service
{
/// <summary>
/// redis list的实现为一个双向链表,即可以支持反向查找和遍历,更方便操作,不过带来了部分额外的内存开销,
/// redis内部的很多实现,包括发送缓冲队列等也都是用这个数据结构。 
/// </summary>
public class redislistservice : redisbase
{
#region queue队列(先进先出)
/// <summary>
/// 入队
/// </summary>
/// <param name="listid">集合id</param>
/// <param name="value">入队的值</param>
public void enqueueitemonlist(string listid, string value)
{
base._redisclient.enqueueitemonlist(listid, value);
}
/// <summary>
/// 出队
/// </summary>
/// <param name="listid">集合id</param>
/// <returns>出队的值</returns>
public string dequeueitemfromlist(string listid)
{
return base._redisclient.dequeueitemfromlist(listid);
}
/// <summary>
/// 出队(阻塞)
/// </summary>
/// <param name="listid">集合id</param>
/// <param name="timeout">阻塞时间(超时时间)</param>
/// <returns>出队的值</returns>
public string blockingdequeueitemfromlist(string listid, timespan? timeout)
{
return base._redisclient.blockingdequeueitemfromlist(listid, timeout);
}
/// <summary>
/// 从多个list中出队(阻塞)
/// </summary>
/// <param name="listids">集合id</param>
/// <param name="timeout">阻塞时间(超时时间)</param>
/// <returns>返回出队的 listid & item</returns>
public itemref blockingdequeueitemfromlists(string[] listids, timespan? timeout)
{
return base._redisclient.blockingdequeueitemfromlists(listids, timeout);
}
#endregion queue队列(先进先出)
#region stack栈(后进先出)
/// <summary>
/// 入栈
/// </summary>
/// <param name="listid">集合id</param>
/// <param name="value">入栈的值</param>
public void pushitemtolist(string listid, string value)
{
base._redisclient.pushitemtolist(listid, value);
}
/// <summary>
/// 入栈,并设置过期时间
/// </summary>
/// <param name="listid">集合id</param>
/// <param name="value">入栈的值</param>
/// <param name="expireat">过期时间</param>
public void pushitemtolist(string listid, string value, datetime expireat)
{
base._redisclient.pushitemtolist(listid, value);
base._redisclient.expireentryat(listid, expireat);
}
/// <summary>
/// 入栈,并设置过期时间
/// </summary>
/// <param name="listid">集合id</param>
/// <param name="value">入栈的值</param>
/// <param name="expirein">过期时间</param>
public void pushitemtolist(string listid, string value, timespan expirein)
{
base._redisclient.pushitemtolist(listid, value);
base._redisclient.expireentryin(listid, expirein);
}
/// <summary>
/// 出栈
/// </summary>
/// <param name="listid">集合id</param>
/// <returns>出栈的值</returns>
public string popitemfromlist(string listid)
{
return base._redisclient.popitemfromlist(listid);
}
/// <summary>
/// 出栈(阻塞)
/// </summary>
/// <param name="listid">集合id</param>
/// <param name="timeout">阻塞时间(超时时间)</param>
/// <returns>出栈的值</returns>
public string blockingpopitemfromlist(string listid, timespan? timeout)
{
return base._redisclient.blockingpopitemfromlist(listid, timeout);
}
/// <summary>
/// 从多个list中出栈一个值(阻塞)
/// </summary>
/// <param name="listids">集合id</param>
/// <param name="timeout">阻塞时间(超时时间)</param>
/// <returns>返回出栈的 listid & item</returns>
public itemref blockingpopitemfromlists(string[] listids, timespan? timeout)
{
return base._redisclient.blockingpopitemfromlists(listids, timeout);
}
/// <summary>
/// 从fromlistid集合出栈并入栈到tolistid集合
/// </summary>
/// <param name="fromlistid">出栈集合id</param>
/// <param name="tolistid">入栈集合id</param>
/// <returns>返回移动的值</returns>
public string popandpushitembetweenlists(string fromlistid, string tolistid)
{
return base._redisclient.popandpushitembetweenlists(fromlistid, tolistid);
}
/// <summary>
/// 从fromlistid集合出栈并入栈到tolistid集合(阻塞)
/// </summary>
/// <param name="fromlistid">出栈集合id</param>
/// <param name="tolistid">入栈集合id</param>
/// <param name="timeout">阻塞时间(超时时间)</param>
/// <returns>返回移动的值</returns>
public string blockingpopandpushitembetweenlists(string fromlistid, string tolistid, timespan? timeout)
{
return base._redisclient.blockingpopandpushitembetweenlists(fromlistid, tolistid, timeout);
}
#endregion stack栈(后进先出)
#region 赋值
/// <summary>
/// 向list头部添加value值
/// </summary>
public void prependitemtolist(string listid, string value)
{
base._redisclient.prependitemtolist(listid, value);
}
/// <summary>
/// 向list头部添加value值,并设置过期时间
/// </summary> 
public void prependitemtolist(string listid, string value, datetime expireat)
{
base._redisclient.prependitemtolist(listid, value);
base._redisclient.expireentryat(listid, expireat);
}
/// <summary>
/// 向list头部添加value值,并设置过期时间
/// </summary>  
public void prependitemtolist(string listid, string value, timespan expirein)
{
base._redisclient.prependitemtolist(listid, value);
base._redisclient.expireentryin(listid, expirein);
}
/// <summary>
/// 向list中添加value值
/// </summary>  
public void additemtolist(string listid, string value)
{
base._redisclient.additemtolist(listid, value);
}
/// <summary>
/// 向list中添加value值,并设置过期时间
/// </summary> 
public void additemtolist(string listid, string value, datetime expireat)
{
base._redisclient.additemtolist(listid, value);
base._redisclient.expireentryat(listid, expireat);
}
/// <summary>
/// 向list中添加value值,并设置过期时间
/// </summary> 
public void additemtolist(string listid, string value, timespan expirein)
{
base._redisclient.additemtolist(listid, value);
base._redisclient.expireentryin(listid, expirein);
}
/// <summary>
/// 向list中添加多个value值
/// </summary> 
public void addrangetolist(string listid, list<string> values)
{
base._redisclient.addrangetolist(listid, values);
}
/// <summary>
/// 向list中添加多个value值,并设置过期时间
/// </summary> 
public void addrangetolist(string listid, list<string> values, datetime expireat)
{
base._redisclient.addrangetolist(listid, values);
base._redisclient.expireentryat(listid, expireat);
}
/// <summary>
/// 向list中添加多个value值,并设置过期时间
/// </summary> 
public void addrangetolist(string listid, list<string> values, timespan expirein)
{
base._redisclient.addrangetolist(listid, values);
base._redisclient.expireentryin(listid, expirein);
}
#endregion 赋值
#region 获取值
/// <summary>
/// 获取指定list中包含的数据数量
/// </summary> 
public long getlistcount(string listid)
{
return base._redisclient.getlistcount(listid);
}
/// <summary>
/// 获取指定list中包含的所有数据集合
/// </summary> 
public list<string> getallitemsfromlist(string listid)
{
return base._redisclient.getallitemsfromlist(listid);
}
/// <summary>
/// 获取指定list中下标从startingfrom到endingat的值集合
/// </summary> 
public list<string> getrangefromlist(string listid, int startingfrom, int endingat)
{
return base._redisclient.getrangefromlist(listid, startingfrom, endingat);
}
#endregion 获取值
#region 删除
/// <summary>
/// 移除指定list中,listid/value,与参数相同的值,并返回移除的数量
/// </summary> 
public long removeitemfromlist(string listid, string value)
{
return base._redisclient.removeitemfromlist(listid, value);
}
/// <summary>
/// 从指定list的尾部移除一个数据,并返回移除的数据
/// </summary> 
public string removeendfromlist(string listid)
{
return base._redisclient.removeendfromlist(listid);
}
/// <summary>
/// 从指定list的头部移除一个数据,并返回移除的数据
/// </summary> 
public string removestartfromlist(string listid)
{
return base._redisclient.removestartfromlist(listid);
}
#endregion 删除
#region 其它
/// <summary>
/// 清理数据,保持list长度
/// </summary>
/// <param name="listid">集合id</param>
/// <param name="keepstartingfrom">保留起点</param>
/// <param name="keependingat">保留终点</param>
public void trimlist(string listid, int keepstartingfrom, int keependingat)
{
base._redisclient.trimlist(listid, keepstartingfrom, keependingat);
}
#endregion 其它
#region 发布订阅
/// <summary>
/// 发布
/// </summary>
/// <param name="channel">频道</param>
/// <param name="message">消息</param>
public void publish(string channel, string message)
{
base._redisclient.publishmessage(channel, message);
}
/// <summary>
/// 订阅
/// </summary>
/// <param name="channel">频道</param>
/// <param name="actiononmessage"></param>
public void subscribe(string channel, action<string, string, iredissubscription> actiononmessage)
{
var subscription = base._redisclient.createsubscription();
subscription.onsubscribe = c =>
{
console.writeline($"订阅频道{c}");
console.writeline();
};
//取消订阅
subscription.onunsubscribe = c =>
{
console.writeline($"取消订阅 {c}");
console.writeline();
};
subscription.onmessage += (c, s) =>
{
actiononmessage(c, s, subscription);
};
console.writeline($"开始启动监听 {channel}");
subscription.subscribetochannels(channel); //blocking
}
/// <summary>
/// 取消订阅
/// </summary>
/// <param name="channel">频道</param>
public void unsubscribefromchannels(string channel)
{
var subscription = base._redisclient.createsubscription();
subscription.unsubscribefromchannels(channel);
}
#endregion 发布订阅
}
}

使用如下:

/// <summary>
/// redis list的实现为一个双向链表,即可以支持反向查找和遍历,更方便操作,不过带来了部分额外的内存开销,
/// redis内部的很多实现,包括发送缓冲队列等也都是用这个数据结构。 
/// 队列/栈/生产者消费者模型/发布订阅
/// </summary>
public static void showlist()
{
using (redislistservice service = new redislistservice())
{
service.flushall();
service.additemtolist("article", "张三");
service.additemtolist("article", "李四");
service.additemtolist("article", "王五");
service.prependitemtolist("article", "赵六");
service.prependitemtolist("article", "钱七");
var result1 = service.getallitemsfromlist("article"); //一次性获取所有的数据
var result2 = service.getrangefromlist("article", 0, 3); //可以按照添加顺序自动排序,而且可以分页获取
console.writeline($"result1={jsonconvert.serializeobject(result1)}");
console.writeline($"result2={jsonconvert.serializeobject(result2)}");
console.writeline("=====================================================");
//栈:后进先出
service.flushall();
service.pushitemtolist("article", "张三"); //入栈
service.pushitemtolist("article", "李四");
service.pushitemtolist("article", "王五");
service.pushitemtolist("article", "赵六");
service.pushitemtolist("article", "钱七");
for (int i = 0; i < 5; i++)
{
console.writeline(service.popitemfromlist("article")); //出栈
}
console.writeline("=====================================================");
//队列:先进先出,生产者消费者模型 
//msmq---rabbitmq---zeromq---redislist 学习成本、技术成本
service.flushall();
service.enqueueitemonlist("article", "张三"); //入队
service.enqueueitemonlist("article", "李四");
service.enqueueitemonlist("article", "王五");
service.enqueueitemonlist("article", "赵六");
service.enqueueitemonlist("article", "钱七");
for (int i = 0; i < 5; i++)
{
console.writeline(service.dequeueitemfromlist("article")); //出队
}
//分布式缓存,多服务器都可以访问到,多个生产者,多个消费者,任何产品只被消费一次
}
}

运行结果如下所示:

下面我们就来看下如何使用上面的api来解决一些具体的问题:

一、博客数据分页

应用场景:

  博客网站每天新增的随笔和文章可能都是几千几万的,表里面是几千万数据。首页要展示最新的随笔,还有前20页是很多人访问的。

  这种情况下如果首页分页数据每次都去查询数据库,那么就会有很大的性能问题。

解决方案:

  每次写入数据库的时候,把 id_标题 写入到redis的list中(后面搞个trimlist,只要最近的200个)。

  这样的话用户每次刷页面就不需要去访问数据库了,直接读取redis中的数据。

  第一页(当然也可以是前几页)的时候可以不体现总记录数,只拿最新数据展示,这样就能避免访问数据库了。

还有一种就是水平分表了,数据存到redis的时候可以保存 id_表名称_标题

使用list主要是解决数据量大,变化快的数据分页问题。

二八原则:80%的访问集中在20%的数据,list里面只用保存大概的量就够用了。

using tianya.redis.service;
namespace myredis.scene
{
/// <summary>
/// 博客数据分页
/// 
/// 应用场景:
///  博客网站每天新增的随笔和文章可能都是几千几万的,表里面是几千万数据。首页要展示最新的随笔,还有前20页是很多人访问的。
///  这种情况下如果首页分页数据每次都去查询数据库,那么就会有很大的性能问题。
/// 
/// 解决方案:
///  每次写入数据库的时候,把 id_标题 写入到redis的list中(后面搞个trimlist,只要最近的200个)。
///  这样的话用户每次刷页面就不需要去访问数据库了,直接读取redis中的数据。
///  第一页(当然也可以是前几页)的时候可以不体现总记录数,只拿最新数据展示,这样就能避免访问数据库了。
/// 
/// 还有一种就是水平分表了,数据存到redis的时候可以保存 id_表名称_标题
/// 
/// 使用list主要是解决数据量大,变化快的数据分页问题。
/// 二八原则:80%的访问集中在20%的数据,list里面只用保存大概的量就够用了。
/// </summary>
public class blogpagelist
{
public static void show()
{
using (redislistservice service = new redislistservice())
{
service.additemtolist("newblog", "10001_ioc容器的实现原理");
service.additemtolist("newblog", "10002_aop面向切面编程");
service.additemtolist("newblog", "10003_行为型设计模式");
service.additemtolist("newblog", "10004_结构型设计模式");
service.additemtolist("newblog", "10005_创建型设计模式");
service.additemtolist("newblog", "10006_gc垃圾回收");
service.trimlist("newblog", 0, 200); //保留最新的201个(一个list最多只能存放2的32次方-1个)
var result1 = service.getrangefromlist("newblog", 0, 9); //第一页
var result2 = service.getrangefromlist("newblog", 10, 19); //第二页
var result3 = service.getrangefromlist("newblog", 20, 29); //第三页
}
}
}
}

二、生产者消费者模型

分布式缓存,多服务器都可以访问到,多个生产者,多个消费者,任何产品只被消费一次。(使用队列实现)

其中一个(或多个)程序写入,另外一个(或多个)程序读取消费。按照时间顺序,数据失败了还可以放回去下次重试。

下面我们来看个例子:

demo中添加了2个控制台应用程序,分别模拟生产者和消费者:

using system;
using tianya.redis.service;
namespace tianya.producer
{
/// <summary>
/// 模拟生产者
/// </summary>
class program
{
static void main(string[] args)
{
console.writeline("生产者程序启动了。。。");
using (redislistservice service = new redislistservice())
{
console.writeline("开始生产test产品");
for (int i = 1; i <= 20; i++)
{
service.enqueueitemonlist("test", $"产品test{i}");
}
console.writeline("开始生产task产品");
for (int i = 1; i <= 20; i++)
{
service.enqueueitemonlist("task", $"产品task{i}");
}
console.writeline("模拟生产结束");
while (true)
{
console.writeline("************请输入数据************");
string testtask = console.readline();
service.enqueueitemonlist("test", testtask);
}
}
}
}
}
using system;
using system.threading;
using tianya.redis.service;
namespace tianya.consumer
{
/// <summary>
/// 模拟消费者
/// </summary>
class program
{
static void main(string[] args)
{
console.writeline("消费者程序启动了。。。");
using (redislistservice service = new redislistservice())
{
while (true)
{
var result = service.blockingdequeueitemfromlists(new string[] { "test", "task" }, timespan.fromhours(1));
thread.sleep(100);
console.writeline($"消费者消费了 {result.id} {result.item}");
}
}
}
}
}

接下来我们使用.net core cli来启动2个消费者实例和1个生产者实例,运行结果如下所示:

像这种异步队列在项目中有什么价值呢?

ps:此处事务是一个很大问题,真实项目中需根据实际情况决定是否采用异步队列。

三、发布订阅

发布订阅:

  发布一个数据,全部的订阅者都能收到。

  观察者,一个数据源,多个接收者,只要订阅了就可以收到的,能被多个数据源共享。

  观察者模式:微信订阅号—群聊天—数据同步。。。

下面我们来看个小demo:

/// <summary>
/// 发布订阅
///  发布一个数据,全部的订阅者都能收到。
///  观察者,一个数据源,多个接收者,只要订阅了就可以收到的,能被多个数据源共享。
///  观察者模式:微信订阅号---群聊天---数据同步。。。
/// </summary>
public static void showpublishandsubscribe()
{
task.run(() =>
{
using (redislistservice service = new redislistservice())
{
service.subscribe("tianya", (c, message, iredissubscription) =>
{
console.writeline($"注册{1}{c}:{message},dosomething else");
if (message.equals("exit"))
iredissubscription.unsubscribefromchannels("tianya");
});//blocking
}
});
task.run(() =>
{
using (redislistservice service = new redislistservice())
{
service.subscribe("tianya", (c, message, iredissubscription) =>
{
console.writeline($"注册{2}{c}:{message},dosomething else");
if (message.equals("exit"))
iredissubscription.unsubscribefromchannels("tianya");
});//blocking
}
});
task.run(() =>
{
using (redislistservice service = new redislistservice())
{
service.subscribe("twelve", (c, message, iredissubscription) =>
{
console.writeline($"注册{3}{c}:{message},dosomething else");
if (message.equals("exit"))
iredissubscription.unsubscribefromchannels("twelve");
});//blocking
}
});
using (redislistservice service = new redislistservice())
{
thread.sleep(1000);
service.publish("tianya", "tianya1");
thread.sleep(1000);
service.publish("tianya", "tianya2");
thread.sleep(1000);
service.publish("tianya", "tianya3");
thread.sleep(1000);
service.publish("twelve", "twelve1");
thread.sleep(1000);
service.publish("twelve", "twelve2");
thread.sleep(1000);
service.publish("twelve", "twelve3");
thread.sleep(1000);
console.writeline("**********************************************");
thread.sleep(1000);
service.publish("tianya", "exit");
thread.sleep(1000);
service.publish("tianya", "tianya6");
thread.sleep(1000);
service.publish("tianya", "tianya7");
thread.sleep(1000);
service.publish("tianya", "tianya8");
thread.sleep(1000);
service.publish("twelve", "exit");
thread.sleep(1000);
service.publish("twelve", "twelve6");
thread.sleep(1000);
service.publish("twelve", "twelve7");
thread.sleep(1000);
service.publish("twelve", "twelve8");
thread.sleep(1000);
console.writeline("结束");
}
}

运行结果如下所示:

至此本文就全部介绍完了,如果觉得对您有所启发请记得点个赞哦!!!

demo源码:

链接: https://pan.baidu.com/s/1_kemctbf2it5plv7irxr5q 提取码: v4sr

此文由博主精心撰写转载请保留此原文链接:https://www.cnblogs.com/xyh9039/p/14022264.html

到此这篇关于详解redis中的list类型的文章就介绍到这了,更多相关redis list类型内容请搜索www.887551.com以前的文章或继续浏览下面的相关文章希望大家以后多多支持www.887551.com!

(0)
上一篇 2022年3月21日
下一篇 2022年3月21日

相关推荐