本系列將和大家分享Redis分布式緩存,本章主要簡單介紹下Redis中的List類型,以及如何使用Redis解決博客數(shù)據(jù)分頁、生產(chǎn)者消費者模型和發(fā)布訂閱等問題。
Redis List的實現(xiàn)為一個雙向鏈表,即可以支持反向查找和遍歷,更方便操作,不過帶來了部分額外的內(nèi)存開銷,Redis內(nèi)部的很多實現(xiàn),包括發(fā)送緩沖隊列等也都是用這個數(shù)據(jù)結構。
List類型主要用于隊列和棧,先進先出,后進先出等。
存儲形式:key--LinkListvalue>
首先先給大家Show一波Redis中與List類型相關的API:
using System;
using System.Collections.Generic;
using ServiceStack.Redis;
namespace TianYa.Redis.Service
{
/// summary>
/// Redis List的實現(xiàn)為一個雙向鏈表,即可以支持反向查找和遍歷,更方便操作,不過帶來了部分額外的內(nèi)存開銷,
/// Redis內(nèi)部的很多實現(xiàn),包括發(fā)送緩沖隊列等也都是用這個數(shù)據(jù)結構。
/// /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, Liststring> values)
{
base._redisClient.AddRangeToList(listId, values);
}
/// summary>
/// 向list中添加多個value值,并設置過期時間
/// /summary>
public void AddRangeToList(string listId, Liststring> values, DateTime expireAt)
{
base._redisClient.AddRangeToList(listId, values);
base._redisClient.ExpireEntryAt(listId, expireAt);
}
/// summary>
/// 向list中添加多個value值,并設置過期時間
/// /summary>
public void AddRangeToList(string listId, Liststring> values, TimeSpan expireIn)
{
base._redisClient.AddRangeToList(listId, values);
base._redisClient.ExpireEntryIn(listId, expireIn);
}
#endregion 賦值
#region 獲取值
/// summary>
/// 獲取指定list中包含的數(shù)據(jù)數(shù)量
/// /summary>
public long GetListCount(string listId)
{
return base._redisClient.GetListCount(listId);
}
/// summary>
/// 獲取指定list中包含的所有數(shù)據(jù)集合
/// /summary>
public Liststring> GetAllItemsFromList(string listId)
{
return base._redisClient.GetAllItemsFromList(listId);
}
/// summary>
/// 獲取指定list中下標從startingFrom到endingAt的值集合
/// /summary>
public Liststring> GetRangeFromList(string listId, int startingFrom, int endingAt)
{
return base._redisClient.GetRangeFromList(listId, startingFrom, endingAt);
}
#endregion 獲取值
#region 刪除
/// summary>
/// 移除指定list中,listId/value,與參數(shù)相同的值,并返回移除的數(shù)量
/// /summary>
public long RemoveItemFromList(string listId, string value)
{
return base._redisClient.RemoveItemFromList(listId, value);
}
/// summary>
/// 從指定list的尾部移除一個數(shù)據(jù),并返回移除的數(shù)據(jù)
/// /summary>
public string RemoveEndFromList(string listId)
{
return base._redisClient.RemoveEndFromList(listId);
}
/// summary>
/// 從指定list的頭部移除一個數(shù)據(jù),并返回移除的數(shù)據(jù)
/// /summary>
public string RemoveStartFromList(string listId)
{
return base._redisClient.RemoveStartFromList(listId);
}
#endregion 刪除
#region 其它
/// summary>
/// 清理數(shù)據(jù),保持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 發(fā)布訂閱
/// summary>
/// 發(fā)布
/// /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, Actionstring, 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($"開始啟動監(jiān)聽 {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 發(fā)布訂閱
}
}
使用如下:
/// summary>
/// Redis List的實現(xiàn)為一個雙向鏈表,即可以支持反向查找和遍歷,更方便操作,不過帶來了部分額外的內(nèi)存開銷,
/// Redis內(nèi)部的很多實現(xiàn),包括發(fā)送緩沖隊列等也都是用這個數(shù)據(jù)結構。
/// 隊列/棧/生產(chǎn)者消費者模型/發(fā)布訂閱
/// /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"); //一次性獲取所有的數(shù)據(jù)
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("=====================================================");
//隊列:先進先出,生產(chǎn)者消費者模型
//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")); //出隊
}
//分布式緩存,多服務器都可以訪問到,多個生產(chǎn)者,多個消費者,任何產(chǎn)品只被消費一次
}
}
運行結果如下所示:
下面我們就來看下如何使用上面的API來解決一些具體的問題:
一、博客數(shù)據(jù)分頁
應用場景:
博客網(wǎng)站每天新增的隨筆和文章可能都是幾千幾萬的,表里面是幾千萬數(shù)據(jù)。首頁要展示最新的隨筆,還有前20頁是很多人訪問的。
這種情況下如果首頁分頁數(shù)據(jù)每次都去查詢數(shù)據(jù)庫,那么就會有很大的性能問題。
解決方案:
每次寫入數(shù)據(jù)庫的時候,把 ID_標題 寫入到Redis的List中(后面搞個TrimList,只要最近的200個)。
這樣的話用戶每次刷頁面就不需要去訪問數(shù)據(jù)庫了,直接讀取Redis中的數(shù)據(jù)。
第一頁(當然也可以是前幾頁)的時候可以不體現(xiàn)總記錄數(shù),只拿最新數(shù)據(jù)展示,這樣就能避免訪問數(shù)據(jù)庫了。
還有一種就是水平分表了,數(shù)據(jù)存到Redis的時候可以保存 ID_表名稱_標題
使用List主要是解決數(shù)據(jù)量大,變化快的數(shù)據(jù)分頁問題。
二八原則:80%的訪問集中在20%的數(shù)據(jù),List里面只用保存大概的量就夠用了。
using TianYa.Redis.Service;
namespace MyRedis.Scene
{
/// summary>
/// 博客數(shù)據(jù)分頁
///
/// 應用場景:
/// 博客網(wǎng)站每天新增的隨筆和文章可能都是幾千幾萬的,表里面是幾千萬數(shù)據(jù)。首頁要展示最新的隨筆,還有前20頁是很多人訪問的。
/// 這種情況下如果首頁分頁數(shù)據(jù)每次都去查詢數(shù)據(jù)庫,那么就會有很大的性能問題。
///
/// 解決方案:
/// 每次寫入數(shù)據(jù)庫的時候,把 ID_標題 寫入到Redis的List中(后面搞個TrimList,只要最近的200個)。
/// 這樣的話用戶每次刷頁面就不需要去訪問數(shù)據(jù)庫了,直接讀取Redis中的數(shù)據(jù)。
/// 第一頁(當然也可以是前幾頁)的時候可以不體現(xiàn)總記錄數(shù),只拿最新數(shù)據(jù)展示,這樣就能避免訪問數(shù)據(jù)庫了。
///
/// 還有一種就是水平分表了,數(shù)據(jù)存到Redis的時候可以保存 ID_表名稱_標題
///
/// 使用List主要是解決數(shù)據(jù)量大,變化快的數(shù)據(jù)分頁問題。
/// 二八原則:80%的訪問集中在20%的數(shù)據(jù),List里面只用保存大概的量就夠用了。
/// /summary>
public class BlogPageList
{
public static void Show()
{
using (RedisListService service = new RedisListService())
{
service.AddItemToList("newBlog", "10001_IOC容器的實現(xiàn)原理");
service.AddItemToList("newBlog", "10002_AOP面向切面編程");
service.AddItemToList("newBlog", "10003_行為型設計模式");
service.AddItemToList("newBlog", "10004_結構型設計模式");
service.AddItemToList("newBlog", "10005_創(chuàng)建型設計模式");
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); //第三頁
}
}
}
}
二、生產(chǎn)者消費者模型
分布式緩存,多服務器都可以訪問到,多個生產(chǎn)者,多個消費者,任何產(chǎn)品只被消費一次。(使用隊列實現(xiàn))
其中一個(或多個)程序寫入,另外一個(或多個)程序讀取消費。按照時間順序,數(shù)據(jù)失敗了還可以放回去下次重試。
下面我們來看個例子:
Demo中添加了2個控制臺應用程序,分別模擬生產(chǎn)者和消費者:
using System;
using TianYa.Redis.Service;
namespace TianYa.Producer
{
/// summary>
/// 模擬生產(chǎn)者
/// /summary>
class Program
{
static void Main(string[] args)
{
Console.WriteLine("生產(chǎn)者程序啟動了。。。");
using (RedisListService service = new RedisListService())
{
Console.WriteLine("開始生產(chǎn)test產(chǎn)品");
for (int i = 1; i = 20; i++)
{
service.EnqueueItemOnList("test", $"產(chǎn)品test{i}");
}
Console.WriteLine("開始生產(chǎn)task產(chǎn)品");
for (int i = 1; i = 20; i++)
{
service.EnqueueItemOnList("task", $"產(chǎn)品task{i}");
}
Console.WriteLine("模擬生產(chǎn)結束");
while (true)
{
Console.WriteLine("************請輸入數(shù)據(jù)************");
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個生產(chǎn)者實例,運行結果如下所示:
像這種異步隊列在項目中有什么價值呢?
PS:此處事務是一個很大問題,真實項目中需根據(jù)實際情況決定是否采用異步隊列。
三、發(fā)布訂閱
發(fā)布訂閱:
發(fā)布一個數(shù)據(jù),全部的訂閱者都能收到。
觀察者,一個數(shù)據(jù)源,多個接收者,只要訂閱了就可以收到的,能被多個數(shù)據(jù)源共享。
觀察者模式:微信訂閱號---群聊天---數(shù)據(jù)同步。。。
下面我們來看個小Demo:
/// summary>
/// 發(fā)布訂閱
/// 發(fā)布一個數(shù)據(jù),全部的訂閱者都能收到。
/// 觀察者,一個數(shù)據(jù)源,多個接收者,只要訂閱了就可以收到的,能被多個數(shù)據(jù)源共享。
/// 觀察者模式:微信訂閱號---群聊天---數(shù)據(jù)同步。。。
/// /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("結束");
}
}
運行結果如下所示:
至此本文就全部介紹完了,如果覺得對您有所啟發(fā)請記得點個贊哦?。。?/p>
Demo源碼:
鏈接: https://pan.baidu.com/s/1_kEMCtbf2iT5pLV7irxR5Q 提取碼: v4sr
此文由博主精心撰寫轉載請保留此原文鏈接:https://www.cnblogs.com/xyh9039/p/14022264.html
到此這篇關于詳解Redis中的List類型的文章就介紹到這了,更多相關Redis List類型內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
您可能感興趣的文章:- python3操作redis實現(xiàn)List列表實例
- Redis List列表的詳細介紹
- redis redisson 集合的使用案例(RList、Rset、RMap)
- Redis快速表、壓縮表和雙向鏈表(重點介紹quicklist)
- redis 獲取 list 中的所有元素操作
- Redis list 類型學習筆記與總結
- Redis教程(三):List數(shù)據(jù)類型
- 基于Redis的List實現(xiàn)特價商品列表功能