1. 程式人生 > 資料庫 >詳解Redis中的List型別

詳解Redis中的List型別

本系列將和大家分享Redis分散式快取,本章主要簡單介紹下Redis中的List型別,以及如何使用Redis解決部落格資料分頁、生產者消費者模型和釋出訂閱等問題。

Redis List的實現為一個雙向連結串列,即可以支援反向查詢和遍歷,更方便操作,不過帶來了部分額外的記憶體開銷,Redis內部的很多實現,包括髮送緩衝佇列等也都是用這個資料結構。

List型別主要用於佇列和棧,先進先出,後進先出等。

儲存形式:key--LinkList<value>

詳解Redis中的List型別

首先先給大家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,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,DateTime expireAt)
  {
   base._redisClient.PrependItemToList(listId,expireAt);
  }

  /// <summary>
  /// 向list頭部新增value值,並設定過期時間
  /// </summary>  
  public void PrependItemToList(string listId,TimeSpan expireIn)
  {
   base._redisClient.PrependItemToList(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,DateTime expireAt)
  {
   base._redisClient.AddItemToList(listId,expireAt);
  }

  /// <summary>
  /// 向list中新增value值,並設定過期時間
  /// </summary> 
  public void AddItemToList(string listId,TimeSpan expireIn)
  {
   base._redisClient.AddItemToList(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,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",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")); //出隊
  }
  //分散式快取,多伺服器都可以訪問到,多個生產者,多個消費者,任何產品只被消費一次
 }
}

執行結果如下所示:

詳解Redis中的List型別

下面我們就來看下如何使用上面的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",200); //保留最新的201個(一個List最多隻能存放2的32次方-1個)
    var result1 = service.GetRangeFromList("newBlog",9); //第一頁
    var result2 = service.GetRangeFromList("newBlog",10,19); //第二頁
    var result3 = service.GetRangeFromList("newBlog",20,29); //第三頁
   }
  }
 }
}

二、生產者消費者模型

分散式快取,多伺服器都可以訪問到,多個生產者,多個消費者,任何產品只被消費一次。(使用佇列實現)

其中一個(或多個)程式寫入,另外一個(或多個)程式讀取消費。按照時間順序,資料失敗了還可以放回去下次重試。

下面我們來看個例子:

詳解Redis中的List型別

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個生產者例項,執行結果如下所示:

詳解Redis中的List型別

像這種非同步佇列在專案中有什麼價值呢?

詳解Redis中的List型別

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",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",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("結束");
 }
}

執行結果如下所示:

詳解Redis中的List型別

至此本文就全部介紹完了,如果覺得對您有所啟發請記得點個贊哦!!!

Demo原始碼:

連結: https://pan.baidu.com/s/1_kEMCtbf2iT5pLV7irxR5Q 提取碼: v4sr

此文由博主精心撰寫轉載請保留此原文連結:https://www.cnblogs.com/xyh9039/p/14022264.html

到此這篇關於詳解Redis中的List型別的文章就介紹到這了,更多相關Redis List型別內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!