go語言使用redis實現異步任務

目前go語言有一些比較可靠的異步隊列的開源組件比如asynq、Machinery等,但是組件一個有學習成本,二是出現問題的時候比較頭疼,排查起來比較費時間還要分析源碼,所以自己比較傾向於寫一個輕量級。

此過程使用的是redis的list,左進右出,一般生產者使用lpush壓入數據,消費者調用rpop取出數據。這個具體看自己的需求,如果任務是可以做到冪等操作的,可以使用lrange+ltrim替代lpop,在處理成功後調用ltrim,這樣可做到至少處理一次。

首先在應用啓動的時候初始話加載redis連接

package initialization

import (
	"context"
	"fmt"
	"log"
	"time"

	"github.com/go-redis/redis/v8"
)

var RedisClient *redis.Client

type RedisOptoins struct {
	Host     string `yaml:"host" json:"host"`
	Port     int    `yaml:"port" json:"port"`
	DB       int    `yaml:"db" json:"db"`
	PoolSize int    `yaml:"pool_size" json:"pool_size"`
	MinIdle  int    `yaml:"min_idle" json:"min_idle"`
}

func NewRedisClient() *redis.Client {
	redis_config := Config.Redis
	RedisClient = redis.NewClient(&redis.Options{
		Addr:         fmt.Sprintf("%s:%d", redis_config.Host, redis_config.Port),
		PoolSize:     redis_config.PoolSize,
		MinIdleConns: redis_config.MinIdle,
		DB:           redis_config.DB,
		PoolTimeout:  time.Second * 5,
		WriteTimeout: time.Second * 5,

		IdleCheckFrequency: 60 * time.Second,  //閒置連接檢查的週期,默認爲1分鐘,-1表示不做週期性檢查,只在客戶端獲取連接時對閒置連接進行處理。
		IdleTimeout:        5 * time.Minute,   //閒置超時,默認5分鐘,-1表示取消閒置超時檢查
		MaxConnAge:         600 * time.Second, //連接存活時長,從創建開始計時,超過指定時長則關閉連接,默認爲0,即不關閉存活時長較長的連接

		//命令執行失敗時的重試策略
		MaxRetries:      1,                      // 命令執行失敗時,最多重試多少次,默認爲0即不重試
		MinRetryBackoff: 8 * time.Millisecond,   //每次計算重試間隔時間的下限,默認8毫秒,-1表示取消間隔
		MaxRetryBackoff: 512 * time.Millisecond, //每次計算重試間隔時間的上限,默認512毫秒,-1表示取消間隔
	})
	var ctx = context.Background()
	pong, err := RedisClient.Ping(ctx).Result()
	if err != nil {
		fmt.Println("redis 連接失敗:", pong, err)
		log.Panicf("redis connect failed: %v", err)
	}
	return RedisClient
}

  

然後將任務寫入redis

if err := initialization.RedisClient.LPush(ctx, string(QueueName), string(queueValue)).Err(); err != nil {
	return err
}

  

然後啓動一個異步監聽

func ListenQueue(queueName string) {
	// 確保即使某個代碼執行異常,整個程序不會panic中斷
	defer func() {
		err := recover()
		if err != nil {
			log.Println("程序遭遇嚴重異常!!!!")
			ListenQueue(queueName)
		}
	}()
	for {
		ctx := initialization.NewTraceIDContext()                                               // 每個業務啓動創建一個trace_id
		values, err := initialization.RedisClient.BRPop(ctx, 5*time.Second, queueName).Result() // 設置一個5秒的超時時間
		if err == redis.Nil {                                                                   // 查詢不到數據
			time.Sleep(1 * time.Second)
			continue
		}
		if err != nil { // 查詢出錯,則每隔10秒重新查詢
			time.Sleep(10 * time.Second)
			continue
		}

		initialization.WithContext(ctx).Info("消費到數據:", values)
		if len(values) != 2 {
			initialization.WithContext(ctx).Errorf("數據格式不正確,需要長度爲2,但實際長度%d", len(values))
			continue
		}

		// TODO 消費操作,values是數組,但理論上values的默認長度是1
		QueueExecute(ctx, queueName, values[1]) // 實際執行任務處理的方法
	}
}

  

 用這種方式,需要注意,超時時間要稍微長一些,因爲超時時間短了,會觸發重試,一旦重試可能會造成隊列裏面的數據丟失

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章