feat(P1): MQ Worker 死信队列 — 重试上限3次后 Ack 移入 DLX
This commit is contained in:
53
backend/internal/middleware/rabbitmq/dlx.go
Normal file
53
backend/internal/middleware/rabbitmq/dlx.go
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
package rabbitmq
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
|
||||||
|
amqp "github.com/rabbitmq/amqp091-go"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
DLXExchange = "dlx.events"
|
||||||
|
MaxRetryCount = 3
|
||||||
|
)
|
||||||
|
|
||||||
|
// DeclareDLX 声明死信交换机和对应的死信队列
|
||||||
|
func DeclareDLX(ch *amqp.Channel, queueName string) error {
|
||||||
|
if ch == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if err := ch.ExchangeDeclare(
|
||||||
|
DLXExchange, "topic", true, false, false, false, nil,
|
||||||
|
); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
dlxQueue := queueName + ".dlx"
|
||||||
|
_, err := ch.QueueDeclare(
|
||||||
|
dlxQueue, true, false, false, false, nil,
|
||||||
|
)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := ch.QueueBind(dlxQueue, "#", DLXExchange, false, nil); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Printf("DLX ready: exchange=%s queue=%s", DLXExchange, dlxQueue)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRetryCount 从 AMQP x-death header 中提取当前消息已被重试的次数
|
||||||
|
func GetRetryCount(d amqp.Delivery) int {
|
||||||
|
deaths, ok := d.Headers["x-death"].([]interface{})
|
||||||
|
if !ok || len(deaths) == 0 {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
death, ok := deaths[0].(amqp.Table)
|
||||||
|
if !ok {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
count, ok := death["count"].(int64)
|
||||||
|
if !ok {
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
return int(count)
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@ import (
|
|||||||
"encoding/json"
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"feedsystem_video_go/internal/config"
|
"feedsystem_video_go/internal/config"
|
||||||
|
"log"
|
||||||
"strconv"
|
"strconv"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
@@ -73,19 +74,25 @@ func (r *RabbitMQ) DeclareTopic(exchange string, queue string, bindingKey string
|
|||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
false,
|
false,
|
||||||
nil,
|
amqp.Table{"x-dead-letter-exchange": DLXExchange},
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
return r.Ch.QueueBind(
|
if err := r.Ch.QueueBind(
|
||||||
q.Name,
|
q.Name,
|
||||||
bindingKey,
|
bindingKey,
|
||||||
exchange,
|
exchange,
|
||||||
false,
|
false,
|
||||||
nil,
|
nil,
|
||||||
)
|
); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if err := DeclareDLX(r.Ch, queue); err != nil {
|
||||||
|
log.Printf("DLX declare failed for %s: %v", queue, err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *RabbitMQ) PublishJSON(ctx context.Context, exchange string, routingKey string, payload any) error {
|
func (r *RabbitMQ) PublishJSON(ctx context.Context, exchange string, routingKey string, payload any) error {
|
||||||
|
|||||||
@@ -59,7 +59,13 @@ func (w *CommentWorker) Run(ctx context.Context) error {
|
|||||||
|
|
||||||
func (w *CommentWorker) handleDelivery(ctx context.Context, d amqp.Delivery) {
|
func (w *CommentWorker) handleDelivery(ctx context.Context, d amqp.Delivery) {
|
||||||
if err := w.process(ctx, d.Body); err != nil {
|
if err := w.process(ctx, d.Body); err != nil {
|
||||||
log.Printf("comment worker: failed to process message: %v", err)
|
retryCount := rabbitmq.GetRetryCount(d)
|
||||||
|
if retryCount >= rabbitmq.MaxRetryCount {
|
||||||
|
log.Printf("comment worker: max retries exceeded (%d), moving to DLX: %v", retryCount, err)
|
||||||
|
_ = d.Ack(false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Printf("comment worker: failed (retry %d/%d): %v", retryCount+1, rabbitmq.MaxRetryCount, err)
|
||||||
_ = d.Nack(false, true)
|
_ = d.Nack(false, true)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,7 +58,13 @@ func (w *LikeWorker) Run(ctx context.Context) error {
|
|||||||
|
|
||||||
func (w *LikeWorker) handleDelivery(ctx context.Context, d amqp.Delivery) {
|
func (w *LikeWorker) handleDelivery(ctx context.Context, d amqp.Delivery) {
|
||||||
if err := w.process(ctx, d.Body); err != nil {
|
if err := w.process(ctx, d.Body); err != nil {
|
||||||
log.Printf("like worker: failed to process message: %v", err)
|
retryCount := rabbitmq.GetRetryCount(d)
|
||||||
|
if retryCount >= rabbitmq.MaxRetryCount {
|
||||||
|
log.Printf("like worker: max retries exceeded (%d), moving to DLX: %v", retryCount, err)
|
||||||
|
_ = d.Ack(false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Printf("like worker: failed (retry %d/%d): %v", retryCount+1, rabbitmq.MaxRetryCount, err)
|
||||||
_ = d.Nack(false, true)
|
_ = d.Nack(false, true)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,7 +58,13 @@ func (w *PopularityWorker) Run(ctx context.Context) error {
|
|||||||
|
|
||||||
func (w *PopularityWorker) handleDelivery(ctx context.Context, d amqp.Delivery) {
|
func (w *PopularityWorker) handleDelivery(ctx context.Context, d amqp.Delivery) {
|
||||||
if err := w.process(ctx, d.Body); err != nil {
|
if err := w.process(ctx, d.Body); err != nil {
|
||||||
log.Printf("popularity worker: failed to process message: %v", err)
|
retryCount := rabbitmq.GetRetryCount(d)
|
||||||
|
if retryCount >= rabbitmq.MaxRetryCount {
|
||||||
|
log.Printf("popularity worker: max retries exceeded (%d), moving to DLX: %v", retryCount, err)
|
||||||
|
_ = d.Ack(false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Printf("popularity worker: failed (retry %d/%d): %v", retryCount+1, rabbitmq.MaxRetryCount, err)
|
||||||
_ = d.Nack(false, true)
|
_ = d.Nack(false, true)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,8 +58,13 @@ func (w *SocialWorker) Run(ctx context.Context) error {
|
|||||||
|
|
||||||
func (w *SocialWorker) handleDelivery(ctx context.Context, d amqp.Delivery) {
|
func (w *SocialWorker) handleDelivery(ctx context.Context, d amqp.Delivery) {
|
||||||
if err := w.process(ctx, d.Body); err != nil {
|
if err := w.process(ctx, d.Body); err != nil {
|
||||||
log.Printf("social worker: failed to process message: %v", err)
|
retryCount := rabbitmq.GetRetryCount(d)
|
||||||
// 重新入队,稍后重试
|
if retryCount >= rabbitmq.MaxRetryCount {
|
||||||
|
log.Printf("social worker: max retries exceeded (%d), moving to DLX: %v", retryCount, err)
|
||||||
|
_ = d.Ack(false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
log.Printf("social worker: failed (retry %d/%d): %v", retryCount+1, rabbitmq.MaxRetryCount, err)
|
||||||
_ = d.Nack(false, true)
|
_ = d.Nack(false, true)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user