【RabbitMQ #4】 | Work 任务模型

一、Work Queues 是什么

Work Queues 任务模型:多个消费者绑定同一个队列,共同消费队列中的消息。 场景:消息量较大,单个消费者处理不过来,启动多个消费者分担任务,提升消息处理速度。

核心特点

  1. 多个消费者绑定到一个队列,可以加快消息处理速度
  2. 同一条消息只会被一个消费者处理,不会重复消费
  3. 默认策略:轮询分发。MQ 按顺序轮流把消息发给消费者,不管消费者处理快慢。
    • 例:消费者 1 拿到 1、3、5...;消费者 2 拿到 2、4、6...,消息均分
  4. 优化策略:设置 prefetch = 1(底层 basicQos)
    • 控制消费者预取消息数量:处理完一条、ACK 确认后,才获取下一条消息
    • 实现能者多劳:处理快的消费者拿到更多消息,解决轮询模式下慢消费者消息堆积问题

适用场景:消息发送速度超过单个消费者处理上限,出现消息堆积。此时给队列绑定多个消费者并行消费。

二、实验需求

模拟 WorkQueue,一个队列绑定多个消费者:

  1. RabbitMQ 网页控制台创建队列:work.queue
  2. 生产者 publisher:1 秒内发送 50 条消息到 work.queue,每发一条休眠 20ms
  3. 消费者 consumer:两个监听者同时监听 work.queue
    • 消费者 1:处理速度快,每秒处理 50 条
    • 消费者 2:处理速度慢,每秒处理 5 条

三、代码实战(Go + streadway/amqp)

1. 生产者代码(发送 50 条消息)

Go 复制代码
package main

import (
	"fmt"
	"log"
	"time"

	"github.com/streadway/amqp"
)

func failOnError(err error, msg string) {
	if err != nil {
		log.Fatalf("%s: %s", msg, err)
	}
}

func main() {
	// 连接RabbitMQ
	conn, err := amqp.Dial("amqp://guest:guest@localhost:5672/")
	failOnError(err, "连接RabbitMQ失败")
	defer conn.Close()

	ch, err := conn.Channel()
	failOnError(err, "打开通道失败")
	defer ch.Close()

	queueName := "work.queue"
	// 声明队列(不存在则创建)
	_, err = ch.QueueDeclare(
		queueName,
		false,
		false,
		false,
		false,
		nil,
	)
	failOnError(err, "声明队列失败")

	// 循环发送50条消息,每次发送休眠20ms
	for i := 1; i <= 50; i++ {
		msg := fmt.Sprintf("hello, worker, message_%d", i)
		err = ch.Publish(
			"",         // 默认交换机
			queueName,  // 队列名称
			false,
			false,
			amqp.Publishing{
				ContentType: "text/plain",
				Body:        []byte(msg),
			},
		)
		failOnError(err, "发送消息失败")
		fmt.Printf("发送消息: %s\n", msg)
		time.Sleep(20 * time.Millisecond)
	}
	fmt.Println("50条消息全部发送完成")
}

2. 消费者代码(双消费者 + prefetch=1 + 手动 ACK)

关键点:使用 ch.Qos(1,0,false) 设置 prefetch=1;必须关闭自动 ACK autoAck=false,处理完成手动调用 d.Ack(false)。

Go 复制代码
package main

import (
	"fmt"
	"log"
	"os"
	"time"

	"github.com/streadway/amqp"
)

func failOnError(err error, msg string) {
	if err != nil {
		log.Fatalf("%s: %s", msg, err)
	}
}

// 消费者1:处理快,每秒50条(几乎无休眠)
func consumeWorkQueue1(ch *amqp.Channel) {
	msgs, err := ch.Consume(
		"work.queue",
		"",
		false, // autoAck=false 关闭自动确认,必须手动ACK
		false,
		false,
		false,
		nil,
	)
	failOnError(err, "注册消费者1失败")

	go func() {
		for d := range msgs {
			fmt.Printf("消费者1 收到 work.queue消息:【%s】\n", d.Body)
			// 模拟处理:几乎不耗时
			time.Sleep(20 * time.Millisecond)
			d.Ack(false) // 手动确认消息处理完成
		}
	}()
	fmt.Println("消费者1 已启动")
}

// 消费者2:处理慢,每秒5条(模拟耗时任务),输出到stderr红色
func consumeWorkQueue2(ch *amqp.Channel) {
	msgs, err := ch.Consume(
		"work.queue",
		"",
		false, // autoAck=false
		false,
		false,
		false,
		nil,
	)
	failOnError(err, "注册消费者2失败")

	go func() {
		for d := range msgs {
			fmt.Fprintf(os.Stderr, "消费者2 收到 work.queue消息:【%s】\n", d.Body)
			// 模拟慢任务,处理耗时久
			time.Sleep(200 * time.Millisecond)
			d.Ack(false) // 手动确认
		}
	}()
	fmt.Println("消费者2 已启动")
}

func main() {
	conn, err := amqp.Dial("amqp://guest:guest@localhost:5672/")
	failOnError(err, "连接RabbitMQ失败")
	defer conn.Close()

	ch, err := conn.Channel()
	failOnError(err, "创建channel失败")
	defer ch.Close()

	// =========核心配置 prefetch=1 =========
	err = ch.Qos(
		1,     // prefetchCount:预取1条消息
		0,     // prefetchSize:0代表不限制消息字节大小
		false, // global=false:仅当前channel生效
	)
	failOnError(err, "设置Qos prefetch失败")

	// 声明队列
	_, err = ch.QueueDeclare(
		"work.queue",
		false,
		false,
		false,
		false,
		nil,
	)
	failOnError(err, "声明队列失败")

	// 启动两个消费者协程
	go consumeWorkQueue1(ch)
	go consumeWorkQueue2(ch)

	// 阻塞常驻消费
	select {}
}

四、两种分发模式对比

模式 1:默认轮询(不设置 prefetch)

MQ 预先把消息轮流下发给消费者。

  • 消息均分:消费者 1 拿 1、3、5...;消费者 2 拿 2、4、6...
  • 缺点:不管消费者处理快慢,消息提前分配。慢消费者堆积大量未处理消息,快消费者空闲。

模式 2:prefetch=1 + 手动 ACK(能者多劳)

MQ 每次只给消费者下发 1 条消息。消费者处理完成、ACK 确认之后,MQ 才会推送下一条消息。

  • 处理速度快的消费者可以持续领取消息,拿到更多任务
  • 解决慢消费者消息积压问题,充分利用消费者算力

五、总结

  • Work 模型:多个消费者绑定同一个队列,并行消费,提升处理速度。
  • 消息唯一性:一条消息只会被一个消费者消费一次。
  • prefetch 机制:控制预取消息数量,配合手动 ACK 实现能者多劳。
  • 使用场景:消息生产速度大于单个消费者处理能力,出现消息堆积。
相关推荐
程序猿乐锅1 小时前
【黑马点评 | 第七篇】Redis 分布式锁的两种实现
java·数据库·redis·分布式·spring·缓存
千里码aicood3 小时前
基于Hadoop的机票价格波动分析系统的设计与实现
大数据·hadoop·分布式
Thomas.Sir6 天前
第21课:PyTorch|GPU多卡训练与分布式训练基础【让多卡并行成为你的加速引擎】
人工智能·pytorch·分布式
Cicada1286 天前
库存消息消费的正确性设计——从幂等窗口到批量流水线
分布式·系统架构
Francek Chen7 天前
【大数据处理与分析】数据仓库Hive:04 数据仓库Hive概述
大数据·数据仓库·hive·hadoop·分布式
Gl�ria7 天前
Hadoop/YARN 集群缩容:下线DN节点
大数据·hadoop·分布式
吉甫作诵7 天前
Kafka 集群安装与运维实战:消费组排查、Offset 重置与副本重分配
大数据·运维·分布式·kafka·消息队列
imDwAaY7 天前
消息队列四大核心问题:顺序性、幂等性、可靠性与一致性
学习·kafka·rabbitmq