異步消息隊列
- 使用的數據結構: list
- 主要實現: 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"])
}
- 問題:
- 空隊列: 如果隊列爲空,客戶端會不斷pop空輪詢,這樣拉高客戶端的cpu和服務器redis的qps
可以sleep一下
- 隊列延遲問題: 使用阻塞讀blpop/brpop,在隊列沒有數據的時候就會進入休眠狀態
- 空閒斷開問題: 線程一直阻塞在哪裏,Redis 的客戶端連接就成了閒置連接,閒置過久,服務器一般會主動斷開連接,減少閒置資源佔用。這個時候 blpop/brpop 會拋出異常來。要對其進行錯誤處理
- 空隊列: 如果隊列爲空,客戶端會不斷pop空輪詢,這樣拉高客戶端的cpu和服務器redis的qps
- 缺點: 不能保證消息可靠(沒有 ack 保證)
延遲消息隊列
- 使用的數據結構: zset
- 主要實現: 將消息序列化成一個字符串作 爲 zset 的 value,這個消息的到期處理時間作爲 score,然後用多個線程輪詢 zset 獲取到期 的任務進行處理.
- 問題
- 保障可用性,萬一掛了一個線程還有其它線程可以繼續處理。
- 併發爭搶任務,確保任務不能被多次執行。
- 實現: 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()
}