這是一個建立於 的文章,其中的資訊可能已經有所發展或是發生改變。
雖然 rabbitmq 沒有延時隊列的功能,但是稍微變動一下也是可以實現的
實現延時隊列的基本要素
- 存在一個倒計時機制:Time To Live(TTL)
- 當到達時間點的時候會觸發一個發送訊息的事件:Dead Letter Exchanges(DLX)
$~~~~~~$基於第一點,我利用的是訊息存在到期時間這一特性, 訊息一旦到期就會變成dead letter
,可以讓單獨的訊息到期,也可以設定整個隊列訊息的到期時間
而rabbitmq
會有限取兩個值的最小值
$~~~~~~$基於第二點,是用到了rabbitmq
的到期訊息處理機制:
. x-dead-letter-exchange
將到期的訊息發送到指定的 exchange
中
. x-dead-letter-routing-key
將到期的訊息發送到自定的 route
當中
在這裡例子當中,我使用的是 到期訊息+轉寄指定exchange
在 golang 中的實現
首先是消費者comsumer.go
package mainimport ( "log" "github.com/streadway/amqp")func failOnError(err error, msg string) { if err != nil { log.Fatalf("%s: %s", msg, err) }}func main() { // 建立連結 conn, err := amqp.Dial("amqp://guest:guest@localhost:5672/") failOnError(err, "Failed to connect to RabbitMQ") defer conn.Close() ch, err := conn.Channel() failOnError(err, "Failed to open a channel") defer ch.Close() // 聲明一個主要使用的 exchange err = ch.ExchangeDeclare( "logs", // name "fanout", // type true, // durable false, // auto-deleted false, // internal false, // no-wait nil, // arguments ) failOnError(err, "Failed to declare an exchange") // 聲明一個常規的隊列, 其實這個也沒必要聲明,因為 exchange 會預設綁定一個隊列 q, err := ch.QueueDeclare( "test_logs", // name false, // durable false, // delete when unused true, // exclusive false, // no-wait nil, // arguments ) failOnError(err, "Failed to declare a queue") /** * 注意,這裡是重點!!!!! * 聲明一個延時隊列, ß我們的延時訊息就是要發送到這裡 */ _, errDelay := ch.QueueDeclare( "test_delay", // name false, // durable false, // delete when unused true, // exclusive false, // no-wait amqp.Table{ // 當訊息到期時把訊息發送到 logs 這個 exchange "x-dead-letter-exchange":"logs", }, // arguments ) failOnError(errDelay, "Failed to declare a delay_queue") err = ch.QueueBind( q.Name, // queue name, 這裡指的是 test_logs "", // routing key "logs", // exchange false, nil) failOnError(err, "Failed to bind a queue") // 這裡監聽的是 test_logs msgs, err := ch.Consume( q.Name, // queue name, 這裡指的是 test_logs "", // consumer true, // auto-ack false, // exclusive false, // no-local false, // no-wait nil, // args ) failOnError(err, "Failed to register a consumer") forever := make(chan bool) go func() { for d := range msgs { log.Printf(" [x] %s", d.Body) } }() log.Printf(" [*] Waiting for logs. To exit press CTRL+C") <-forever}
然後是生產者productor.go
package mainimport ( "log" "os" "strings" "github.com/streadway/amqp")func failOnError(err error, msg string) { if err != nil { log.Fatalf("%s: %s", msg, err) }}func main() { conn, err := amqp.Dial("amqp://guest:guest@localhost:5672/") failOnError(err, "Failed to connect to RabbitMQ") defer conn.Close() ch, err := conn.Channel() failOnError(err, "Failed to open a channel") defer ch.Close() body := bodyFrom(os.Args) // 將訊息發送到延時隊列上 err = ch.Publish( "", // exchange 這裡為空白則不選擇 exchange "test_delay", // routing key false, // mandatory false, // immediate amqp.Publishing{ ContentType: "text/plain", Body: []byte(body), Expiration: "5000", // 設定五秒的到期時間 }) failOnError(err, "Failed to publish a message") log.Printf(" [x] Sent %s", body)}func bodyFrom(args []string) string { var s string if (len(args) < 2) || os.Args[1] == "" { s = "hello" } else { s = strings.Join(args[1:], " ") } return s}
運行一下:
go run comsumer.gogo run productor.go
$~~~~~~$具體看代碼和注釋就行, 這裡的關鍵點就是將要延時的訊息發送到到期隊列當中, 然後監聽的是到期隊列轉寄到的 exchange 下的隊列
正常情況就是始終監聽一個隊列,然後把到期訊息發送到延時隊列中,當訊息到達時間後就把訊息發到正在監聽的隊列
一個自己寫的mq工具
部落格原文