分散式任務 + 訊息佇列框架 go-queue
- 為什麼寫這個庫
- 應用場景有哪些
- 如何使用
- 總結
為什麼要寫這個庫?
在開始自研 go-queue
之前,針對以下我們調研目前的開源佇列方案:
beanstalkd
beanstalkd
有一些特殊好用功能:支援任務 priority、延時 (delay)、超時重發 (time-to-run) 和預留 (buried),能夠很好的支援分散式的後臺任務和定時任務處理。如下是 beanstalkd
基本部分:
-
job
:任務單元; -
tube
:任務佇列,儲存統一型別job
。producer 和 consumer 操作物件; -
producer
:job
生產者,通過 put 將 job 加入一個 tube; -
consumer
:job
消費者,通過 reserve/release/bury/delete 來獲取 job 或改變 job 的狀態;
很幸運的是官方提供了 go client:https://github.com/beanstalkd/go-beanstalk。
但是這對不熟悉 beanstalkd
操作的 go 開發者而言,需要學習成本。
kafka
類似基於 kafka
訊息佇列作為儲存的方案,儲存單元是訊息,如果要實現延時執行,可以想到的方案是以延時執行的時間作為 topic
,這樣在大型的訊息系統中,充斥大量一次性的 topic
(dq_1616324404788, dq_1616324417622
),當時間分散,會容易造成磁碟隨機寫的情況。
而且在 go 生態中,
同時考慮以下因素:
- 支援延時任務
- 高可用,保證資料不丟失
- 可擴充套件資源和效能
所以我們自己基於以上兩個基礎元件開發了 go-queue
:
- 基於
beanstalkd
開發了dq
,支援定時和延時操作。同時加入redis
保證消費唯一性。 - 基於
kafka
開發了kq
,簡化生產者和消費者的開發 API,同時在寫入 kafka 使用批量寫,節省 IO。
整體設計如下:
應用場景
首先在消費場景來說,一個是針對任務佇列,一個是訊息佇列。而兩者最大的區別:
- 任務是沒有順序約束;訊息需要;
- 任務在加入中,或者是等待中,可能存在狀態更新(或是取消);訊息則是單一的儲存即可;
所以在背後的基礎設施選型上,也是基於這種消費場景。
-
dq
:依賴於beanstalkd
,適合延時、定時任務執行; -
kq
:依賴於kafka
,適用於非同步、批量任務執行;
而從其中 dq
的 API 中也可以看出:
// 延遲任務執行
- dq.Delay(msg, delayTime);
// 定時任務執行
- dq.At(msg, atTime);
而在我們內部:
- 如果是 非同步訊息消費/推送 ,則會選擇使用
kq
:kq.Push(msg)
; - 如果是 15 分鐘提醒/ 明天中午傳送簡訊 等,則使用
dq
;
如何使用
分別介紹 dq
和 kq
的使用方式:
dq
// [Producer]
producer := dq.NewProducer([]dq.Beanstalk{
{
Endpoint: "localhost:11300",
Tube: "tube",
},
{
Endpoint: "localhost:11301",
Tube: "tube",
},
})
for i := 1000; i < 1005; i++ {
_, err := producer.Delay([]byte(strconv.Itoa(i)), time.Second*5)
if err != nil {
fmt.Println(err)
}
}
// [Consumer]
consumer := dq.NewConsumer(dq.DqConf{
Beanstalks: []dq.Beanstalk{
{
Endpoint: "localhost:11300",
Tube: "tube",
},
{
Endpoint: "localhost:11301",
Tube: "tube",
},
},
Redis: redis.RedisConf{
Host: "localhost:6379",
Type: redis.NodeType,
},
})
consumer.Consume(func(body []byte) {
// your consume logic
fmt.Println(string(body))
})
和普通的 生產者 - 消費者 模型類似,開發者也只需要關注以下:
- 開發者只需要關注自己的任務型別「延時/定時」
- 消費端的消費邏輯
kq
producer.go
:
// message structure
type message struct {
Key string `json:"key"`
Value string `json:"value"`
Payload string `json:"message"`
}
pusher := kq.NewPusher([]string{
"127.0.0.1:19092",
"127.0.0.1:19093",
"127.0.0.1:19094",
}, "kq")
ticker := time.NewTicker(time.Millisecond)
for round := 0; round < 3; round++ {
select {
case <-ticker.C:
count := rand.Intn(100)
// 準備訊息
m := message{
Key: strconv.FormatInt(time.Now().UnixNano(), 10),
Value: fmt.Sprintf("%d,%d", round, count),
Payload: fmt.Sprintf("%d,%d", round, count),
}
body, err := json.Marshal(m)
if err != nil {
log.Fatal(err)
}
fmt.Println(string(body))
// push to kafka broker
if err := pusher.Push(string(body)); err != nil {
log.Fatal(err)
}
}
}
config.yaml
:
Name: kq
Brokers:
- 127.0.0.1:19092
- 127.0.0.1:19092
- 127.0.0.1:19092
Group: adhoc
Topic: kq
Offset: first
Consumers: 1
consumer.go
:
var c kq.KqConf
conf.MustLoad("config.yaml", &c)
// WithHandle: 具體的處理msg的logic
// 這也是開發者需要根據自己的業務定製化
q := kq.MustNewQueue(c, kq.WithHandle(func(k, v string) error {
fmt.Printf("=> %s\n", v)
return nil
}))
defer q.Stop()
q.Start()
和 dq
不同的是:開發者不需要關注任務型別(在這裡也沒有任務的概念,傳遞的都是 message data
)。
其他操作和 dq
類似,只是將 業務處理函式 當成配置直接傳入消費者中。
總結
在我們目前的場景中,kq
大量使用在我們的非同步訊息服務;而延時任務,我們除了 dq
,還可以使用記憶體版的 TimingWheel「go-zero
生態元件」。
關於 go-queue
更多的設計和實現文章,可以持續關注我們。歡迎大家去關注和使用。
https://github.com/tal-tech/go-queue
https://github.com/tal-tech/go-zero
歡迎使用 go-zero 並 star 支援我們!
- 加微信實戰群請加微信(註明:實戰群):gocnio
相關文章
- 分散式訊息佇列分散式佇列
- 快速理解Kafka分散式訊息佇列框架Kafka分散式佇列框架
- 分散式訊息佇列RocketMQ--事務訊息--解決分散式事務的最佳實踐分散式佇列MQ
- 分散式服務(RPC)+分散式訊息佇列(MQ)面試題精選分散式RPC佇列MQ面試題
- [原始碼分析]並行分散式任務佇列 Celery 之 子程式處理訊息原始碼並行分散式佇列
- 分散式訊息佇列知識圖譜分散式佇列
- 老生常談——利用訊息佇列處理分散式事務佇列分散式
- 分散式訊息佇列:如何保證訊息的順序性分散式佇列
- 分散式之訊息佇列複習精講分散式佇列
- RabbitMQ訊息佇列(三):任務分發機制MQ佇列
- Redis 分散式鎖與任務佇列實戰Redis分散式佇列
- 分散式訊息佇列:如何保證訊息不被重複消費?(訊息佇列消費的冪等性)分散式佇列
- 基於訊息佇列(RabbitMQ)實現延遲任務佇列MQ
- Hatchet:Python中分散式、容錯任務佇列Python分散式佇列
- 為什麼分散式一定要有訊息佇列?分散式佇列
- 大型網站架構系列:分散式訊息佇列(一)網站架構分散式佇列
- 佇列Queue:任務間的訊息讀寫,安排起來~佇列
- 訊息佇列系列一:訊息佇列應用佇列
- 微服務02 Kafka訊息佇列, Dubbo, Springcloud微服務框架, Nacos微服務Kafka佇列SpringGCCloud框架
- Zookeeper和Curator-Framework實踐之:分散式訊息佇列Framework分散式佇列
- 訊息佇列佇列
- 總結:JavaScript非同步、事件迴圈與訊息佇列、微任務與巨集任務JavaScript非同步事件佇列
- 基於asyncio和redis的Python分散式任務佇列RedisPython分散式佇列
- 用 Redis 實現分散式鎖與實現任務佇列Redis分散式佇列
- RocketMQ 分散式事務訊息MQ分散式
- [原始碼分析] 分散式任務佇列 Celery 之 傳送Task & AMQP原始碼分散式佇列MQ
- 訊息佇列(MQ)佇列MQ
- Kafka訊息佇列Kafka佇列
- RabbitMQ訊息佇列MQ佇列
- kafka 訊息佇列Kafka佇列
- POSIX訊息佇列佇列
- 訊息佇列(一)佇列
- 訊息佇列(二)佇列
- 訊息佇列二佇列
- [Redis]訊息佇列Redis佇列
- [訊息佇列]rocketMQ佇列MQ
- [訊息佇列]RabbitMQ佇列MQ
- RabbitMQ 訊息佇列之佇列模型MQ佇列模型