★ Redis24篇集合
1 先導
我們在《Redis系列14:使用List實現訊息佇列》這一篇中詳細討論瞭如何使用List實現訊息佇列,但同時也看到很多侷限性,比如:
- 不支援訊息確認機制,沒有很好的ACK應答
- 不支援訊息回溯,無法排查問題和做訊息分析
- List按照FIFO機制執行,所以存在訊息堆積的風險。
- 查詢效率低,作為線性結構,List中定位一個資料需要進行遍歷,O(N)的時間複雜度
- 不存在消費組(Consumer Group)的概念,無法實現多個消費者組成分組進行消費
2 關於Stream
Redis Stream是Redis 5.0版本中引入的一種新的資料結構,它主要用於高效地處理流式資料,特別適用於訊息佇列、日誌記錄和實時資料分析等場景。
以下是對Redis Stream的 主要特徵:
1. 資料結構:Redis Stream是一個由有序訊息組成的日誌資料結構,每個訊息都有一個全域性唯一的ID,確保訊息的順序性和可追蹤性。
2. 訊息ID:訊息的ID由兩部分組成,分別是毫秒級時間戳和序列號。這種設計確保了訊息ID的單調遞增性,即新訊息的ID總是大於舊訊息的ID。
3. 消費者組:Redis Stream支援消費者組的概念,允許多個消費者以組的形式訂閱Stream,並且每個訊息只會被組內的一個消費者處理,避免了訊息的重複消費。
以及主要優勢:
1. 持久化儲存:Stream中的訊息可以被持久化儲存,確保資料不會丟失,即使在Redis伺服器重啟後也能恢復訊息。
2. 有序性:訊息按照產生順序生成訊息ID, 被新增到Stream中,並且可以按照指定的條件檢索訊息,保證了訊息的有序性。
3. 多播與分組消費:支援多個消費者同時消費同一流中的訊息,並且可以將消費者組織成消費組,實現訊息的分組消費。
4. 訊息確認機制:消費者可以透過XACK命令確認是否成功消費訊息,保證訊息至少背消費一次,確保訊息不會被重複處理。
5. 阻塞讀取:消費者可以選擇阻塞讀取模式,當沒有新訊息時,消費者會等待直至新訊息到達。
6. 訊息可回溯: 方便補數、特殊資料處理, 以及問題回溯查詢
3 主要命令
1. XADD:向Stream中新增訊息。如果指定的Stream不存在,則會自動建立。
2. XREAD:以阻塞/非阻塞方式獲取Stream中的訊息列表。
3. XREADGROUP:從消費者組中讀取訊息,支援阻塞讀取。
4. XACK:確認消費者已經成功處理了訊息。
5. XGROUP:用於管理消費者組,包括建立、設定ID、銷燬消費者組等操作。
6. XPENDING:查詢消費者組中的待處理訊息。
3.1 XADD 訊息記錄
XADD命令用於向Redis Stream(流)資料結構中新增訊息。
3.1.1 XADD 命令的基本語法
XADD stream_name [MAXLEN maxlen] [ID id] field1 value1 [field2 value2 ...]
1. stream_name:指定要新增訊息的Stream的名字。
2. MAXLEN maxlen:可選引數,用於限制Stream的最大長度。當Stream的長度達到maxlen時,舊的訊息會被自動刪除。
3. ID id:可選引數,用於指定訊息的ID。如果不指定該引數,Redis會自動生成一個唯一的ID。
4. field1 value1 [field2 value2 ...]:訊息的欄位和值,訊息的內容以key-value的形式存在。
XADD命令的一個重要用途是實現訊息釋出功能,釋出者可以使用XADD命令向Stream中新增訊息。
3.1.2 XADD 示例
假設我們有一個名為userinfo_stream
的Stream,並希望向其中新增一個包含sensor_id
和temperature
欄位的訊息,我們可以使用以下命令:
XADD userinfo_stream * user_name brand age 18
在這個例子中,*
表示讓Redis自動生成一個唯一的訊息ID。訊息包含兩個欄位:username
和age
,它們的值分別是brand
和18
。所以這邊記錄了一個使用者資訊,姓名為brand
, 年齡18
歲。
3.1.3 有啥需要注意的呢
- 如果指定的Stream不存在,XADD命令會建立一個新的Stream。
- 訊息的ID是唯一的,並且Redis會保證Stream中訊息的ID是單調遞增的。如果指定了ID,則新訊息的ID必須大於Stream中現有的所有訊息的ID。
- 使用MAXLEN引數可以限制Stream的大小,這在處理大量訊息時非常有用,可以避免Stream佔用過多的記憶體或磁碟空間。
3.2 XREAD 訊息消費
即將訊息從佇列中讀取出來(消費)
3.2.1 XREAD 命令的基本語法
XREAD命令的基本語法如下:
XREAD [COUNT count] [BLOCK milliseconds] STREAMS key [key ...] ID [ID ...]
1. COUNT count:這是一個可選引數,用於指定一次讀取的最大訊息數量。如果不指定,預設為1。
2. BLOCK milliseconds:這也是一個可選引數,用於指定阻塞的時間(以毫秒為單位)。如果指定了阻塞時間,並且當前沒有可消費的訊息,客戶端將在指定的時間內阻塞等待。如果不設定該引數或設定為0,則命令將立即返回,無論是否有可消費的訊息。
3. STREAMS key [key ...] ID [ID ...]:這部分指定了要消費的流(Streams)和對應的起始訊息ID。可以一次指定多個流和對應的起始ID。
XREAD命令的工作機制
1. 讀取指定ID之後的訊息:XREAD命令會返回指定ID之後的訊息(不包含指定ID的訊息本身)。如果沒有指定ID,或者指定的ID不存在於流中,那麼命令將從流的開始或結束處讀取訊息,具體取決於ID的值(如“0-0”表示從流的開始處讀取,“$”表示從流的當前最大ID處讀取)。
2. 阻塞讀取:當設定了BLOCK引數後,如果當前沒有可消費的訊息,客戶端將進入阻塞狀態,直到有新的訊息到達或阻塞時間超時。這種機制非常適合實現消費者等待生產者產生新訊息的場景。
3. 支援多個流:XREAD命令支援同時從多個流中讀取訊息,只需在命令中指定多個流和對應的起始ID即可。
3.2.2 XREAD 示例
假設我們有一個名為userinfo_stream
的流,並且想要從該流中讀取訊息。以下是一些示例:
1. 非阻塞讀取最新訊息:
XREAD COUNT 1 STREAMS userinfo_stream $
這條命令會嘗試從userinfo_stream
流中讀取最新的訊息(如果有的話)。$
是一個特殊ID,表示流的當前最大ID。
2. 阻塞讀取最新訊息:
XREAD COUNT 1 BLOCK 1000 STREAMS userinfo_stream $
這條命令會阻塞1000毫秒,等待userinfo_stream
流中出現新的訊息。如果在1000毫秒內有新訊息到達,則命令會返回該訊息;否則,命令將超時並返回nil。
3. 從特定ID開始讀取:
XREAD COUNT 2 STREAMS userinfo_stream 1722159931000-0
1) 1) "userinfo_stream"
2) 1) 1) "1722159931000-0"
2) 1) "user_name"
2) "brand"
3) "age"
4) "18"
這條命令會從userinfo_stream
流中讀取ID大於或等於1722159931000-0
的訊息,最多返回資料。
3.2.3 需要注意啥呢?
1. 訊息ID的唯一性:在Redis Streams中,每個訊息都有一個全域性唯一的訊息ID,這個訊息ID由兩部分組成:時間戳和序列號。時間戳表示訊息被新增到流中的時間,序列號表示在同一時間戳內新增的訊息的順序。
2. 消費者組:雖然XREAD命令本身不直接涉及消費者組的概念,但Redis Streams還支援消費者組模式,允許一組消費者協作消費同一流中的訊息。在消費者組模式下,通常會使用XREADGROUP命令而不是XREAD命令來讀取訊息。
3. 效能考慮:XREAD命令在讀取大量訊息時可能會消耗較多的CPU和記憶體資源。因此,在實際應用中需要根據實際情況合理設定COUNT引數的值,避免一次性讀取過多訊息導致效能問題。
3.3 Consumer Group 消費組模式
典型的多播模式,在實時性要求比較高的場景,如果你想加快對訊息的處理。那這是一個不錯的選擇,我們讓佇列在邏輯上進行分割槽,用不同的消費組來隔離消費。所以:
消費者組允許多個消費者(client 或 process)協同處理同一個流(Stream)中的訊息。每個消費者組維護自己的消費偏移量(即已處理訊息的位置),以支援消費者之間的負載均衡和容錯。
3.3.1 建立消費者組
使用 XGROUP CREATE 命令建立消費者組。
# stream_name:佇列名稱
# consumer_group:消費者組
# msgIdStartIndex:訊息Id開始位置
# msgIdStartIndex:訊息Id結束位置
# $ 表示從流的當前末尾(即最新訊息)開始建立消費者組。如果流不存在,MKSTREAM 選項將自動建立流
XGROUP CREATE stream_name consumer_group msgIdStartIndex-msgIdStartIndex
# 或者
XGROUP CREATE stream_name consumer_group $ MKSTREAM
下面是具體實現示例,為佇列 userinfo_stream 建立了消費組1(consumer_group1)和 消費組2(consumer_group2):
> xgroup create userinfo_stream consumer_group1 0-0
OK
> xgroup create userinfo_stream consumer_group2 0-0
OK
3.3.2 讀取訊息
消費者可以透過 XREADGROUP
命令從消費者組中讀取訊息。XREADGROUP
命令不僅讀取訊息,還會更新消費者組中的消費者狀態,即標記哪些訊息已被讀取。
# group_name: 消費者群組名
# consumer_name: 消費者名稱
# COUNT number: count 消費個數
# BLOCK ms: 表示如果流中沒有新訊息,則命令將阻塞最多 xx 毫秒,0則無限阻塞
# stream_name: 佇列名稱
# id: 訊息消費ID
# []:代表可選引數
# `>`:放在命令引數的最後面,表示從尚未被消費的訊息開始讀取;
XREADGROUP GROUP group_name consumer_name [COUNT number] [BLOCK ms] STREAMS stream_name [stream ...] id [id ...]
# 或者
XREADGROUP GROUP group_name consumer_name COUNT 1 BLOCK 2000 STREAMS stream_name >
下面是具體實現示例,消費組 consumer_group1 的消費者 consumer1 從 userinfo_stream 中以阻塞的方式讀取一條訊息:
XREADGROUP GROUP consumer_group1 consumer1 COUNT 1 BLOCK 0 STREAMS userinfo_stream >
1) 1) "userinfo_stream"
2) 1) 1) "1722159931000-0"
2) 1) "user_name"
2) "brand"
3) "age"
4) "18"
3.3.3 確認訊息
處理完訊息後,消費者需要傳送 XACK 命令來確認訊息。這告訴 Redis 這條訊息已經被成功處理,並且可以從消費者組的待處理訊息列表中移除
# stream_name: 佇列名稱
# group_name: 消費者群組名
# <message-id> 是要確認的訊息的 ID。
XACK stream_name group_name <message-id>
# ACK 確認兩條訊息
XACK userinfo_stream consumer_group1 1722159931000-0 1722159932000-0
(integer) 2
3.3.4 PLE:訊息可靠性保障
PEL(Pending Entries List)記錄了當前被消費者讀取但尚未確認(ACK)的訊息。這些訊息在消費者成功處理併傳送ACK命令之前,會一直保留在PEL中。如果消費者崩潰或未能及時傳送ACK命令,Redis將確保這些訊息能夠被重新分配給其他消費者進行處理,從而實現訊息的可靠傳遞。
XPENDING stream_name group_name
以下的例子中,我們檢視 userinfo_stream
中的 消費組 consumer_group1
中各個消費者已讀取但未確認的訊息資訊。
XPENDING userinfo_stream consumer_group1
1) (integer) 2 # 未確認訊息條數
2) "1722159931000-0"
3) "1722159932000-0"
詳細的stream操作見官網文件:https://redis.io/docs/data-types/streams-tutorial/
4 使用Golang實現Stream佇列能力
4.1 先安裝go-redis/redis庫
> go get github.com/go-redis/redis/v8
go: downloading github.com/go-redis/redis v6.15.9+incompatible
go: downloading github.com/go-redis/redis/v8 v8.11.5
go: downloading github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f
go: downloading github.com/cespare/xxhash/v2 v2.1.2
go: added github.com/cespare/xxhash/v2 v2.1.2
go: added github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f
go: added github.com/go-redis/redis/v8 v8.11.5
注意:這裡的v8是庫的版本號,你可以根據實際情況進行調整
邏輯實現
package main
import (
"context"
"fmt"
"log"
"time"
"github.com/go-redis/redis/v8"
)
func main() {
// 連線到Redis
rdb := redis.NewClient(&redis.Options{
Addr: "localhost:6379", // Redis地址
Password: "", // 密碼(如果有的話)
DB: 0, // 使用預設DB
})
ctx := context.Background()
// 建立Stream
_, err := rdb.XAdd(ctx, &redis.XAddArgs{
Stream: "mystream",
Values: map[string]interface{}{
"field1": "value1",
"field2": "value2",
},
}).Result()
if err != nil {
log.Fatalf("Failed to add message to stream: %v", err)
}
// 建立Consumer Group
_, err = rdb.XGroupCreate(ctx, "mystream", "mygroup", "$").Result()
if err != nil && err != redis.Nil {
log.Fatalf("Failed to create consumer group: %v", err)
}
// 消費者讀取訊息
go func() {
for {
msgs, err := rdb.XReadGroup(ctx, &redis.XReadGroupArgs{
Group: "mygroup",
Consumer: "myconsumer",
Streams: []string{"mystream", ">"},
Count: 1,
Block: 1000, // 阻塞1000毫秒
}).Result()
if err != nil {
if err == redis.Nil {
// 超時,沒有新訊息
continue
}
log.Fatalf("Failed to read from stream: %v", err)
}
for _, msg := range msgs[0].Messages {
fmt.Printf("Received: %s %s\n", msg.ID, msg.Values)
// 確認訊息
_, err = rdb.XAck(ctx, "mystream", "mygroup", msg.ID).Result()
if err != nil {
log.Fatalf("Failed to ack message: %v", err)
}
}
}
}()
// 模擬生產者繼續傳送訊息
for i := 0; i < 5; i++ {
_, err := rdb.XAdd(ctx, &redis.XAddArgs{
Stream: "mystream",
Values: map[string]interface{}{
"field1": fmt.Sprintf("value%d", i+1),
"field2": "another value",
},
MaxLen: 100,
Approximate: true,
}).Result()
if err != nil {
log.Fatalf("Failed to add message to stream: %v", err)
}
time.Sleep(2 * time.Second) // 模擬生產間隔
}
// 注意:在實際應用中,主goroutine通常不會立即退出,而是會等待某些觸發條件
5 應用場景
1. 訊息佇列:Redis Stream可以作為訊息佇列使用,支援訊息的釋出、訂閱和消費。
2. 日誌記錄:將日誌資訊寫入Redis Stream,方便後續的查詢和分析。
3. 實時資料分析:結合Redis的其他資料結構(如Sorted Set、Hash等),對Stream中的資料進行實時分析。
6 總結
Redis Stream是Redis在訊息佇列和流式資料處理領域的一個重要補充,它提供了簡單但功能強大的資料流處理能力,為開發者提供了更多的選擇和靈活性。相對List,Stream的優勢如下:
- 支援訊息確認機制(ACK應答確認)
- 支援訊息回溯,方便排查問題和做訊息分析
- 存在消費組(Consumer Group)的概念,可以進行分組消費和批次消費,可以負載多個消費例項