Redis隊列——消息隊列,延遲隊列

異步消息隊列

  1. 使用的數據結構: list
    在這裏插入圖片描述
  2. 主要實現: go實現簡單消息隊列
package main

import (
	"encoding/json"
	"fmt"
	"github.com/gomodule/redigo/redis"
	"os"
)

type Producer struct {
	// 生產者
}

func (p *Producer)publish(conn redis.Conn, listKey string, data string) (reply interface{}, err error){
	return conn.Do("lpush", listKey, data)
}

type Customer struct {
	// 消費者
}

func (c *Customer)putMessage(conn redis.Conn, listKey string) (interface{}, error) {
	return conn.Do("rpop", listKey)
}

func (c *Customer)getCount(conn redis.Conn, listKey string) (interface{}, error) {
	return conn.Do("llen", listKey)
}

func HandlecError(err error, when string)  {
	if err != nil{
		fmt.Println("error happen at", when)
		os.Exit(500)
	}else {
		fmt.Println("連接成功")
	}
}

func main()  {
	// 連接redis操作
	conn, err := redis.Dial("tcp","127.0.0.1:6379")
	HandlecError(err, "connect")
	defer func() {
		_ = conn.Close()
	}()

	producer := Producer{}
	personMap := make(map[string]interface{})
	personMap["name"]  = "hxh"
	personMap["work"] = "toDoSomething"
	bytes, _ := json.Marshal(personMap)
	_,_ = producer.publish(conn, "test_queue", string(bytes))

	customer := Customer{}
	num, _ := customer.getCount(conn, "test_queue")
	fmt.Println("隊列數量爲", num)

	values, err := redis.String(customer.putMessage(conn,"test_queue"))
	dataMap := make(map[string]interface{})	// 準備好map來裝
	_ = json.Unmarshal([]byte(values), &dataMap)
	fmt.Println(dataMap["work"])

}	
  1. 問題:
    • 空隊列: 如果隊列爲空,客戶端會不斷pop空輪詢,這樣拉高客戶端的cpu和服務器redis的qps

      可以sleep一下

    • 隊列延遲問題: 使用阻塞讀blpop/brpop,在隊列沒有數據的時候就會進入休眠狀態
    • 空閒斷開問題: 線程一直阻塞在哪裏,Redis 的客戶端連接就成了閒置連接,閒置過久,服務器一般會主動斷開連接,減少閒置資源佔用。這個時候 blpop/brpop 會拋出異常來。要對其進行錯誤處理
  2. 缺點: 不能保證消息可靠(沒有 ack 保證)

延遲消息隊列

  1. 使用的數據結構: zset
  2. 主要實現: 將消息序列化成一個字符串作 爲 zset 的 value,這個消息的到期處理時間作爲 score,然後用多個線程輪詢 zset 獲取到期 的任務進行處理.
  3. 問題
    • 保障可用性,萬一掛了一個線程還有其它線程可以繼續處理。
    • 併發爭搶任務,確保任務不能被多次執行。
  4. 實現: go實現簡單延遲隊列
package main

import (
	"encoding/json"
	"fmt"
	"github.com/gomodule/redigo/redis"
	uuid "github.com/satori/go.uuid"
	"os"
	"sync"
	"time"
)

type delayQueue struct {
	// 延遲隊列
}

func (d *delayQueue) publish(conn redis.Conn, zSetKey string, dataMap map[string]interface{}, time int64) (reply interface{}, err error) {
	// 生成唯一id,保證zset的每一個value都不一樣,time爲執行的時間戳
	dataMap["uuid"] = uuid.NewV4().String()
	bytes, _ := json.Marshal(dataMap)
	return conn.Do("zadd", zSetKey, time, string(bytes))
}

func (d *delayQueue) customer(conn redis.Conn,  zSetKey string) {
	for true {
		now := time.Now().Unix()
		data, err := redis.Strings(conn.Do("zrangebyscore", zSetKey, 0, now,  "limit", 0, 1))
		if err == nil &&  len(data) > 0 {
			res, delErr := conn.Do("zrem", "test-delay-queue", data[0])
			if res.(int64) >= 1 && delErr == nil {
				dataMap := make(map[string]interface{}) // 準備好map來裝
				_ = json.Unmarshal([]byte(data[0]), &dataMap)
				fmt.Println("任務是:", dataMap["work"])
			}else {
				fmt.Println(delErr)
			}
		}else {
			time.Sleep(time.Second * 10)
			continue
		}
	}
}

func HandlecError(err error, when string) {
	if err != nil {
		fmt.Println("error happen at", when)
		os.Exit(500)
	} else {
		fmt.Println("連接成功")
	}
}

func main() {
	var wg sync.WaitGroup

	// 連接redis操作
	conn, err := redis.Dial("tcp", "127.0.0.1:6379")
	HandlecError(err, "connect")
	defer func() {
		_ = conn.Close()
	}()

	delayQueue := delayQueue{}
	personMap := make(map[string]interface{})
	personMap["name"] = "hxh"
	personMap["work"] = "toDoSomething"
	_,_ = delayQueue.publish(conn, "test-delay-queue", personMap, time.Now().Unix())

	wg.Add(1)
	go func() {
		delayQueue.customer(conn, "test-delay-queue")
		wg.Done()
	}()
	wg.Wait()

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