Go中使用Redis實現訊息佇列教程

banq發表於2024-03-22

Redis 和 Golang這對充滿活力的組合將徹底改變我們處理訊息系統的方式。

Redis 作為記憶體資料儲存以其速度和多功能性而聞名,它與 Golang(一種以其簡單性和高效能而聞名的語言)無縫協作,為構建彈性和可擴充套件的訊息基礎設施提供了引人注目的解決方案。

在本文中,我們將使用頂級訊息代理 Redis 在 Go 應用程式中實現釋出-訂閱模式。這種模式增強了可擴充套件性,支援跨節點的繁重非同步任務,並支援事件驅動架構、資料轉換等。我們將使用 Docker 來輕鬆管理和部署。本文假設您已經在系統中安裝了DockerGo,並且已經設定了 Go 專案。

設定 docker 和配置檔案
第一步是將 go-redis 庫合併到我們的主 Dockerfile 中,我們將透過包含命令來豐富它:RUN go get github.com/go-redis/redis。精煉後的 Dockerfile 將如下所示:

FROM golang:1.18-alpine

WORKDIR /redis_docker

# Copy everything from this project into the filesystem of the container.
COPY . .

# Obtain the package needed to run redis commands.
RUN go get github.com/go-redis/redis

RUN go mod tidy

# Compile the binary exe for our app.
RUN go build -o main .
# Start the application.
CMD [<font>"./main"]

由於 Redis 是以獨立服務的形式執行的,因此在我們的 docker-compose 檔案中建立一個 Redis 服務以將其無縫整合到我們的 Docker 環境中是非常必要的。在服務部分,我們將引入以下鍵值對來協調 Redis 服務和 Go 應用程式。

services:
  redis:
    container_name: <font>"redis"
    image: redis:alpine
    # Specify the redis.conf file to use
    command: redis-server /usr/local/etc/redis/redis.conf
    ports:
      -
"6379:6379"
  web:
    container_name:
"redisapi"
    build:
      # build the image using the Dockerfile we have in this project. Can use an image instead.
      context: .
    ports:
      -
"8080:8080"

顯示 redis.conf 檔案:

# Required
##########
# 將記憶體使用限制設定為指定的位元組數。
# 當達到記憶體限制時,Redis 將嘗試根據所選的驅逐策略(參見 maxmemory-policy)移除 鍵值。
# 根據所選的驅逐策略(請參閱 maxmemory-policy)刪除。
maxmemory 41943040

Redis Pub-sub 服務
Redis Pub-Sub是Publish-Subscribe的縮寫,是一種訊息傳遞模式,在這種模式下,訊息傳送者(釋出者)可以使用不同的通道向多個接收者(訂閱者)分發訊息,而無需在他們之間進行直接通訊,這些通道將監聽事件。

在我們的 Go 應用程式中,必須初始化 Redis 服務,以便無縫使用。請使用以下程式碼片段實現初始化:

import (
 <font>"errors"
 
"github.com/go-redis/redis"
)

type Redis struct {
 RedisClient redis.Client
}

func NewRedis( env Env) Redis {

 var client = redis.NewClient(&redis.Options{
 
// Container name + port since we are using docker<i>
  Addr:    
"redis:6379",
  Password: env.RedisPassword,
 })

 if client == nil {
    errors.New(
"Cannot run redis")
 }

 return Redis{
  RedisClient: *client,
 }
}

在前面介紹的 Redis 服務的基礎上,我們現在將構建一個釋出-訂閱機制。首先,我們將建立一個接受新上下文、待處理訊息和通道/佇列名稱作為引數的釋出者函式。該函式將負責將事件分派到指定的通道或佇列。

<font>// MessagePublisher 是一個通用的釋出器,用於釋出不同型別的訊息。<i>
type MessagePublisher struct {
 redisClient infrastructure.Redis
}

func NewMessagePublisher(redisClient infrastructure.Redis) *MessagePublisher {
 return &MessagePublisher{redisClient}
}

// PublishMessages 向  channels 釋出訊息。<i>
func (p *MessagePublisher) PublishMessages(ctx context.Context, message interface{}, queueName string) {

 serializedMessage, err := json.Marshal(message)
 if err != nil {
  log.Printf(
"[%s] Failed to serialize message: %v", queueName, err)
 }

 
// 使用釋出操作的上下文<i>
 err = p.redisClient.RedisClient.Publish(queueName, serializedMessage).Err()
 if err != nil {
  log.Printf(
"[%s] Failed to publish message: %v", queueName, err)
 }
}

上述釋出者可在專案的任何地方使用,具體如下:

<font>/*
 * 為 redis 釋出器建立新的上下文。
 * 需要建立新的上下文,就像我們使用應用程式的當前上下文一樣、
 * 如果應用程式關閉,它就會關閉,但我們不想關閉 redis 釋出子服務。
*/
<i>
 ctx, cancel := context.WithCancel(context.Background())
 defer cancel()

 
// Publish the message(context, message, queue Name)<i>
 redisPublisher.PublishMessages(ctx,
"Test Message", "Test")

接下來,讓我們利用 Redis 服務建立一個訂閱者。這個熟練的訂閱者會監聽 "測試 "通道上的事件,有效處理透過 PublishMessages 函式傳送的資料。

<font>//消費者是不同資訊型別的通用消費者<i>
type MessageConsumer struct {
 redisClient  infrastructure.Redis
 subscription *redis.PubSub
}

// NewMessageConsumer 建立 MessageConsumer 的新例項。<i>
func NewMessageConsumer(redis infrastructure.Redis) *MessageConsumer {
 return &MessageConsumer{
  redisClient: redis,
 }
}

//該函式接收陣列中的佇列名稱,並使用切換語句來執行佇列所需的邏輯<i>
func (c *MessageConsumer) ConsumerMessages(ctx context.Context, queueNames []string) {
 for _, queueName := range queueNames {
  switch queueName {
  case
"Test":
   
// We will handle the go routines in the custom function<i>
   go c.handleCustomType1Logic(ctx, queueName)
  default:
   log.Printf(
"[%s] Unsupported message type: %+v\n", queueName, queueName)
  }
 }
}

// handleCustomType1Logic 啟動一個 goroutine 來處理來自指定佇列的訊息。<i>
func (c *MessageConsumer) handleCustomType1Logic(ctx context.Context, queueName string) {

// 建立取消上下文,優雅地停止 goroutine<i>
 consumerCtx, cancel := context.WithCancel(context.Background())
 defer cancel()

 log.Printf(
"[%s] Consumer started listening...\n", queueName)

 
//訂閱指定的 Redis 頻道<i>
 c.subscription = c.redisClient.RedisClient.Subscribe(queueName)
 defer c.subscription.Close()

//獲取接收資訊的通道<i>
 channel := c.subscription.Channel()

 for {
  select {
// C如果取消主上下文以停止執行程式,則會被置疑<i>
  case <-consumerCtx.Done():
   log.Printf(
"[%s] Consumer stopped listening...\n", queueName)
   return
// 監聽頻道上的來電資訊<i>
  case msg := <-channel:
   var messageObj interface{}
// 反序列化報文有效載荷<i>
   err := json.Unmarshal([]byte(msg.Payload), &messageObj)
   if err != nil {
    log.Printf(
"[%s] Failed to deserialize message: %v", queueName, err)
    continue
   }

   
// 繼續你的邏輯:<i>

   fmt.Printf(
"[%s] Received message: %+v\n", queueName, messageObj)
  }
 }
}

引入 MessageConsumer 結構,該結構旨在處理各種型別的訊息,它連線到 Redis 並訂閱特定佇列。ConsumerMessages 方法由佇列名稱陣列驅動,為每個佇列執行定製的邏輯。例如,遇到 "Test "佇列會觸發一個併發例程,以處理與該訊息型別相關的自定義邏輯。

handleCustomType1Logic 方法訂閱 "測試 "佇列,處理傳入的訊息,並在資料上無縫執行自定義邏輯。您可以隨意新增自己的邏輯。

這種周到的訂閱者結構,強調明確的關注點分離和有效的錯誤處理,為在我們的 Go 應用程式中建立一個可擴充套件的、反應靈敏的訊息傳遞系統奠定了堅實的基礎。此外,在建立新的邏輯處理程式時,請務必使用選擇語句,以防止應用程式中的程式洩漏。

最終,我們的目標是讓訂閱者持續監控傳送到通道的事件。為了實現這一目標,我們將在 Go 應用程式啟動時設定通道。

lifecycle.Append(fx.Hook{
 OnStart: func(ctx context.Context) error {

  go func() {
   <font>// Add your queues here<i>
   consumer.ConsumerMessages(ctx, []string{
"Test"})

   if env.ServerPort ==
"" {
    _ = handler.Gin.Run()
   } else {
    _ = handler.Gin.Run(
":" + env.ServerPort)
   }
  }()

  return nil
 },
})

不要忘記在 OnStart 函式內的 ConsumerMessages 方法中向佇列陣列新增新佇列。

結論
總而言之,本文探討了 Redis 和 Golang 如何協同工作以實現更好的訊息傳遞。我們使用 Docker 來簡化設定。在我們的 Go 應用程式中開始工作時,我們確保它一直在監聽事件。我們建立的組織良好的訊息處理程式就像一個可靠的工作者,確保訊息得到順利處理。這說明了 Redis 和 Golang 如何以其簡潔性成為解決通訊難題和構建強大訊息系統的最佳搭檔。
 

相關文章