Redis 研究筆記

Overview

參考: https://redis.io/

  • open source

  • in-memory data store

  • cache

  • streaming engine

  • message broker

  • Clustering / High availability

  • TLS/SSL 連線

Use cases

  • Real-time data store 即時數據存儲

  • Caching & session storage 緩存和會話存儲

  • Streaming & messaging 串流媒體和訊息傳遞

小評: Redis 支援多種資料結構,不同功能述求用不同資料結構來實現。


以功能面談 Redis

Redis vs Redis Stack

(來自AI)

關於Redis和Redis Stack的主要區別,可以這樣對比:

  1. 開源程度

    • Redis是完全開源的資料庫軟件。

    • Redis Stack在Redis的基礎上開發,是部分開源的。

  2. 功能

    • Redis提供基礎的鍵值對資料庫功能。單一產品。

    • Redis Stack在Redis功能上擴充了模塊、管理工具等。包含 Redis、Kubernetes、Elasticsearch 和 Prometheus。

  3. 資料結構

    • Redis有字符串、哈希、列表、集合等資料結構。

    • Redis Stack添加了更多像數據流、搜索引擎等的資料結構。提供高可用性、安全性和可擴展性。可用於構建和管理複雜的應用程式。

  4. 商業支持

    • Redis主要由開源社區驅動開發。

    • Redis Stack提供商業支持和服務。

  5. 運行環境

    • Redis可在各種環境中運行。

    • Redis Stack主要面向企業級產品環境。

總結來說,Redis Stack在Redis的基礎上做了擴展和商業化,提供更豐富的資料結構、功能和支援,更適合企業級產品使用。但Redis本身仍是開源免費的核心資料庫軟件。

Redis 和 Redis Stack 的首次發布時間如下:

  • Redis:

    • 最初版本:2009年

    • 穩定版1.0:2010年1月

  • Redis Stack:

    • 最初發布:2021年11月

可以看到,Redis已有10多年的開發歷史,是一個相當成熟和穩定的開源資料庫。

而Redis Stack則是在2021年底由Redis Labs公司基於Redis開發的新產品線和品牌。它在開源Redis的基礎上增加了更多的商業功能和服務。

所以Redis Stack可以看作是Redis的商業化版本或分支,目的是在企業級生產環境中提供更強大的Redis應用方案。它們都以開源Redis資料庫為基礎,但Redis Stack提供了更多高級功能。

Redis vs Redis Stack

前述提到的 Redis Use Case: Caching & session storage、 Streaming & messaging 用 Redis 版本就可實現。

About Redis Stack https://redis.io/docs/about/about-stack/


Redis license

官網說明: https://redis.io/docs/about/license/

Open Source 授權採 SSPL (Server Side Public License)。

只要用於商用就很模糊。

SSPL 授權並不被開源團體認可。


以產品面談 Redis

Redis 現在主要產品。

  • Redis

    • 基本功能。前述提到的 Redis Use Case: Caching & session storage、 Streaming & messaging 用此版本就可實現。

    • 可在 Docker Hub 找到官方版本並安裝。

    • 單機非商用可免費使用。

    • 不確定商用是否要付費?

  • Redis Stack

    • 更多進階功能,GRAPH、JSON, Search and Query, Time Series, Probabilistic. 等等。

    • 包含 RedisInsight ,Redis 的 GUI 管理工具。

    • 官方建議用於開發環境。

    • 可在 Docker Hub 找到官方版本並安裝。

    • 單機非商用可免費使用。

    • 不確定商用是否要付費?

  • Redis Stack Server

    • 功能與 Redis Stack 相同不過少了 RedisInsight 管理工具。

    • 官方建議用於正式環境。

    • 可在 Docker Hub 找到官方版本並安裝。

    • 單機非商用可免費使用。

    • 不確定商用是否要付費?

    • 是否支援 cluster 尚不確定?

  • Redis Enterprise Cloud / 企業版


Redis 部署結構

(來自AI)

Redis 的典型部署結構主要有以下幾種:

  1. 單節點部署 最簡單的部署方式,將Redis單個實例運行在一台服務器上。適用於測試和小型項目。

  2. 主從複製(Replication) 使用一個主節點進行讀寫,多個從節點從主節點同步數據,形成主從結構。可以實現讀寫分離,提高讀取速度。

  3. Sentinel (高可用) 使用Sentinel進行監控,當主節點故障時自動將從節點提升為主節點。實現高可用。

  4. 叢集(Cluster) 將數據分片到多個節點,每個節點負責部分數據。支援擴容。

  5. 多資料中心部署 跨多個資料中心部署Redis,同步數據。提高可用性和低延遲訪問。

  6. 雲服務 在公有雲上使用Redis雲服務,如AWS ElastiCache、阿里雲Redis等。

綜上所述,根據實際需求Redis可以部署為單節點或不同的分佈式結構,以實現擴展性、高可用性等目的。


Redis 資料結構

常用的 Redis 資料結構介紹

(來自網路)

Redis提供了好幾種資料結構,比較常用到的是String、Hash、List、Set、SortedSet這5種,另外還有特殊用途的HyperLogLog及Geo。

  • String 是最簡單的Key/Value結構

  • Hash 是一種Map結構,以.Net來說就像是HashTable,一個Key底下可以存放N個Key/Value

  • List 跟.Net裡的List不一樣,是類似Queue或Stack的結構,以加入List的順序排序

  • Set 則是一種沒有排序的集合

  • SortedSet 是加上排序功能的Set,但跟List不一樣的是排序依給定的權重值來排

  • HyperLogLog 我個人覺得它有點不算是結構,應該算是一種演算法,用在個數估算用的

  • Geo 顧名思義,這是放經緯度的,可以拿來算2點之間的距離或是做跟地圖相關的應用

  • Bitmaps,Redis bitmap 实现了对二进制串的操作,可以对字符串的位进行操作。

推論 Redis Stack 提供的功能都是為特殊雲端應用進行存取加速的,暫時不研究 Redis Stack 提供的資料結構。


安裝 / Docker

可單獨安裝在 Linux, MacOS 。

現在不支援在 Windows 單獨安裝,硬要裝的話也要基於 WSL 模擬 linux 執行環境再單獨安裝,這可以用在個人開發環境。

若要安裝在 Windows 建議還是安裝在 Docker 上。官方有在 Docker Hub 提供 Redis, Redis Stack, Redis Stack Server 三個版本。

參考文件


管理介面 redis-cli

Redis CLI

https://redis.io/docs/connect/cli/

Ping / Pong
Get / Set

管理介面 Redisinsight

Quick start using RedisInsight

https://redis.io/docs/interact/programmability/triggers-and-functions/quick_start_ri/


程式開發 C# / NRedisStack

現在支援的程式語言:

C#/.NET、Go、Java、Node.js、Python

參考文件

程式開發 - 連線、基本指令與參數

using NRedisStack;
using NRedisStack.RedisStackCommands;
using StackExchange.Redis;

//## for Redis-Stack-Server,有支援帳密連線。
using var redis = ConnectionMultiplexer.Connect(new ConfigurationOptions
{
  EndPoints = { { "localhost", 6379 } },
  User = "default", // More info <https://redis.io/docs/management/security/acl/>
  Password = "mypassword", // use your Redis password
  Ssl = false,
  SslProtocols = System.Security.Authentication.SslProtocols.Tls12,
  AllowAdmin = true, // 啟動管理員模式。才可抓取 INFO 主機狀態值。
});

// 或
// for Redis,不需帳密就可連線
using var redis = ConnectionMultiplexer.Connect("localhost:6379", options => {
  options.AllowAdmin = true; // 啟動管理員模式。才可抓取 INFO 主機狀態值。
});

IDatabase db = redis.GetDatabase();

// String 結構 ---------------------------------------
db.StringSet("foo", "我出運了", TimeSpan.FromSeconds(3));
//db.StringSet("baz", 9876543210.123456789m); // 不支援 deciaml

for (int i = 1; i <= 5; i++)
{
  var value = db.StringGet("foo");
  Console.WriteLine(value.HasValue ? value : "nil");

  // 取值 也 取時效
  var fooExpires = db.StringGetWithExpiry("foo");
  Console.WriteLine($"{fooExpires.Value} | {fooExpires.Expiry}");

  // 取值 並 重設時效
  var fooExpires2 = db.StringGetSetExpiry("foo", TimeSpan.FromSeconds(3));
  Console.WriteLine(fooExpires2.HasValue ? fooExpires2 : "nil");

  // 移除
  bool del_ret = db.KeyDelete("foo");
  Console.WriteLine(del_ret);

  System.Threading.SpinWait.SpinUntil(() => false, 1000);
}

// Hash 結構, 二層結構 ---------------------------------------
var hash = new HashEntry[] {
  new HashEntry("name", "John"),
  new HashEntry("surname", "Smith"),
  new HashEntry("company", "Redis"),
  new HashEntry("age", "29"),
  new HashEntry("salary", "9876543210.123456789"),
};

db.HashSet("user-session:123", hash);

// 取 Hash 全部
var hashFields = db.HashGetAll("user-session:123");
Console.WriteLine(String.Join("; ", hashFields));

// 取 Hash  其中一個值
var hashValue = db.HashGet("user-session:123", "age");
Console.WriteLine(hashValue);

// Set 結構 ---------------------------------------
string mysetKey = "myset:123";
Console.WriteLine($"{db.KeyType(mysetKey)} | {db.KeyExists(mysetKey)}"); 

db.SetAdd(mysetKey, "我是字串", CommandFlags.FireAndForget);
db.SetAdd(mysetKey, 166888, CommandFlags.FireAndForget);
db.SetAdd(mysetKey, 889, CommandFlags.FireAndForget);

Console.WriteLine(db.SetPop(mysetKey)); // pop 一筆
Console.WriteLine(String.Join(", ", db.SetMembers(mysetKey))); // 列出全部

Console.WriteLine("§§ List (Queue/Stack) 結構 ---------------------------------------");
string mylistKey = "mylist:456";

db.ListLeftPush(mylistKey, "abc");
db.ListLeftPush(mylistKey, "走過路過別錯過。");

      
Console.WriteLine("§§ SortedSet 結構 ---------------------------------------");
string mysetKey2 = "myset:789";

db.SortedSetAdd(mysetKey2, "我是字串", 4);
db.SortedSetAdd(mysetKey2, 166888, 8);
db.SortedSetAdd(mysetKey2, 889, 6);

Console.WriteLine(db.SortedSetPop(mysetKey2)); // pop 一筆
Console.WriteLine(db.SortedSetPop(mysetKey2)); // pop 一筆
Console.WriteLine(db.SortedSetPop(mysetKey2)); // pop 一筆

Console.WriteLine("§§ Hyper Log Log 結構 ---------------------------------------");
// 估算大型數據集基數的場景,如:廣告點擊計數、唯一訪問者統計、按讚數等估算。

db.HyperLogLogAdd("likeCnt", "4");
db.HyperLogLogAdd("likeCnt", "5");
db.HyperLogLogAdd("likeCnt", "3");

Console.WriteLine($"likeCnt: {db.HyperLogLogLength("likeCnt")}"); // 按讚數

Console.WriteLine("§§ Geo 結構 -------------------------------------------");
db.GeoAdd("mygeokey", 111.11, 22.22, "某甲");
db.GeoAdd("mygeokey", 131.11, 52.22, "某乙");

var distance = db.GeoDistance("mygeokey", "某甲", "某乙");
Console.WriteLine($"{"某甲"}與{"某乙"}的距離有{distance}。");

計算 hit rate

// to connect
using var redis = ConnectionMultiplexer.Connect("localhost:6379", options => {
  options.AllowAdmin = true; // 啟動管理員模式。才可抓取 INFO 主機狀態值。
});

var statsInfo = redis.GetServer("localhost:6379").Info("Stats")
                     .SelectMany(m => m.Select(c => c))
                     .ToDictionary(c => c.Key, c => c.Value);

long hits = long.Parse(statsInfo["keyspace_hits"]);
long misses = long.Parse(statsInfo["keyspace_misses"]);
long total = hits + misses;

double hitRate = (double)hits * 100.0d / (double)(total);
Console.WriteLine($"Hit Rate → {hits} / {total} = {hitRate:#.00}%");

Dump reids server all INFO

// to connect
using var redis = ConnectionMultiplexer.Connect("localhost:6379", options => {
  options.AllowAdmin = true; // 啟動管理員模式。才可抓取 INFO 主機狀態值。
});

// get all INFO
var allInfos = redis.GetServer("localhost:6379").Info();

// dump
foreach (var info in allInfos)
  foreach (var prop in info)
    Console.WriteLine($"Dump redis info: {info.Key}: {prop.Key} => {prop.Value}");

完整程式碼

(EOF)

Last updated