RabbitMQ之work queue

本文介绍了使用RabbitMQ实现工作队列的工作原理,通过将资源密集型任务延迟执行,避免阻塞Web应用。文中展示了如何创建生产者发送消息到队列,以及启动消费者(worker)从队列中读取并处理消息。同时,讨论了消息确认、持久化、公平分发等关键特性,强调了手动确认和预取值在确保消息处理完整性和负载均衡中的作用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

work queue

主要思想: 避免立即执行资源密集型任务,而不得不等待它完成。安排任务在之后执行,我们将任务封装为消息并放送到队列。而后台的工作进程从消息队列中读取消息并执行任务。当有多个工作线程时,这些线程将一起处理这些任务。

这个概念在 Web 应用程序中特别有用,在这些应用程序中,无法在短暂的 HTTP 请求窗口中处理复杂的任务。

work queue适合在集群环境中做异步处理,能最大程度发挥每一台服务器的性能

img

生产者代码, 简单的往队列发送20条消息

package main

import (
	"fmt"
	"github.com/streadway/amqp"
	"log"
	"rabbit/utils"
)

func main() {
	ch, _ := utils.GetRabbitMQChannel()

	q, _ := ch.QueueDeclare(
		"hello", // name
		false,   // durable
		false,   // delete when unused
		false,   // exclusive
		false,   // no-wait
		nil,     // arguments
	)
	for i := 0; i < 20; i++ {
		msg := fmt.Sprintf("%d号消息", i+1)
		_ = ch.Publish(
			"",     // exchange
			q.Name, // routing key
			false,  // mandatory
			false,
			amqp.Publishing{
				DeliveryMode: amqp.Persistent,
				ContentType:  "text/plain",
				Body:         []byte(msg),
			})
		log.Printf(" Sent %s", msg)
	}
}

消费者代码, 开启三个goroutine作为worker从队列里读数据

package main

import (
	"github.com/streadway/amqp"
	"log"
	"rabbit/utils"
	"sync"
)

func main() {
	ch, _ := utils.GetRabbitMQChannel()
	q, _ := ch.QueueDeclare(
		"hello", // name
		false,   // durable
		false,   // delete when unused
		false,   // exclusive
		false,   // no-wait
		nil,     // arguments
	)
	var wg sync.WaitGroup
	wg.Add(3)
	for i := 0; i < 3; i++ {
		go work(i+1, q, ch, &wg)
	}
	wg.Wait()
}

func work(id int, q amqp.Queue, ch *amqp.Channel, wg *sync.WaitGroup) {
	defer wg.Done()
	msgs, _ := ch.Consume(
		q.Name, // queue
		"",     // consumer
		true,   // auto-ack
		false,  // exclusive
		false,  // no-local
		false,  // no-wait
		nil,    // args
	)
	forever := make(chan bool)
	go func() {
		for d := range msgs {
			log.Printf("[%d号worker]Received a message: %s", id, d.Body)
		}
	}()
	<-forever
}

运行结果

F:\go_workspace\mq\rabbit\work-queues\workers>go run worker.go
2022/04/18 22:10:12 [3号worker]Received a message: 1号消息
2022/04/18 22:10:12 [1号worker]Received a message: 2号消息
2022/04/18 22:10:12 [1号worker]Received a message: 5号消息
2022/04/18 22:10:12 [3号worker]Received a message: 4号消息
2022/04/18 22:10:12 [3号worker]Received a message: 7号消息
2022/04/18 22:10:12 [3号worker]Received a message: 10号消息
2022/04/18 22:10:12 [2号worker]Received a message: 3号消息
2022/04/18 22:10:12 [2号worker]Received a message: 6号消息
2022/04/18 22:10:12 [2号worker]Received a message: 9号消息
2022/04/18 22:10:12 [1号worker]Received a message: 8号消息
2022/04/18 22:10:12 [1号worker]Received a message: 11号消息
2022/04/18 22:10:12 [2号worker]Received a message: 12号消息
2022/04/18 22:10:12 [2号worker]Received a message: 15号消息
2022/04/18 22:10:12 [3号worker]Received a message: 13号消息
2022/04/18 22:10:12 [1号worker]Received a message: 14号消息
2022/04/18 22:10:12 [1号worker]Received a message: 17号消息
2022/04/18 22:10:12 [3号worker]Received a message: 16号消息
2022/04/18 22:10:12 [3号worker]Received a message: 19号消息
2022/04/18 22:10:12 [2号worker]Received a message: 18号消息
2022/04/18 22:10:12 [1号worker]Received a message: 20号消息

仔细观察发现,

消息号n%3 等于1的都是3号worker处理的;

消息号n%3 等于2的都是1号worker处理的;

消息号n%3 等于0的都是2号worker处理的。

就是轮询机制

消息确认[Message acknowledgment]

因为RabbitMQ一旦向消费者传递一条消息,便立即将消息标记为删除。消费者完成一个任务可能需要一段时间,如果其中一个消费者处理一个任务只完成了部分突然挂掉,这样会导致我们丢失正在处理的消息,以及后续发送给该消费者的消息。

消息应答的方法:

  1. 自动确认

    msgs, err := ch.Consume(
      q.Name, // queue
      "",     // consumer
      false,  // auto-ack
      false,  // exclusive
      false,  // no-local
      false,  // no-wait
      nil,    // args
    )
    
  2. 手动确认

    go func() {
        for d := range msgs {
            log.Printf("Received a message: %s", d.Body)
            d.Ack(false) //不批量确认
        }
    }()
    

推荐使用手动确认,并其不批量确认

消息自动重新入队

如果消费者由于某些原因失去连接(其通道已关闭,连接已关闭或TCP连接丢失), 导致消息未发送ACK确认,RabbitMQ将了解到消息未完全处理,并将其重新加入到队列中,等待其他消费者来处理。

消息持久化[message durability]

首先,我们需要确保队列能够在 RabbitMQ 节点重启后继续存在。

生产者和消费者的队列声明都需要设置durable: true

q, err := ch.QueueDeclare(
  "hello",      // name
  true,         // durable
  false,        // delete when unused
  false,        // exclusive
  false,        // no-wait
  nil,          // arguments
)

其次, 我们需要将我们的消息标记为持久 - 通过使用 amqp.Persistent 选项 amqp.Publishing 采取。

err = ch.Publish(
  "",           // exchange
  q.Name,       // routing key
  false,        // mandatory
  false,
  amqp.Publishing {
    DeliveryMode: amqp.Persistent,
    ContentType:  "text/plain",
    Body:         []byte(body),
})

注意:rabbitmq中的队列是不允许同名但属性不同的

公平分发[fair dispatch]

轮询分发并不算是公平的。例如在有两个worker的情况下,当所有奇数消息很重而偶数消息都很轻时,一个worker会一直很忙,另一个worker几乎不会做任何工作。

消费者添加代码

err = ch.Qos(
  1,     // prefetch count
  0,     // prefetch size
  false, // global
)
failOnError(err, "Failed to set QoS")

prefechCount = 1表示:在处理并确认前一个消息之前,不要向worker发送新消息。相反,它将把它分派给下一个不忙的worker。

发布确认

单个确认发布
批量确认
异步批量确认

### ThinkPHP 框架中的队列工作 #### 配置与初始化 在ThinkPHP框架中,为了使消息队列正常运作,需先完成必要的配置。对于基于Redis的消息队列,在`config/queue.php`文件内指定连接参数以及驱动名称为`redis`[^3]。 ```php return [ 'default' => env('QUEUE_DRIVER', 'sync'), 'connections' => [ 'sync' => [ 'type' => 'sync' ], 'beanstalkd' => [ 'type' => 'beanstalkd', 'host' => '127.0.0.1', 'port' => 11300, 'timeout' => 30, 'retry_time' => 3, 'tube' => 'think_default', ], 'swoole_table' => [ 'type' => 'table', 'host' => '', 'port' => '', 'table' => null, // swoole\Table 实例对象或者ID 'size' => 8192 * 16, 'key_size' => 128, 'data_size' => 1024, ], 'rabbitmq' => [ 'type' => 'rabbitmq', 'host' => 'localhost', 'port' => 5672, 'user' => 'guest', 'password' => 'guest', 'vhost' => '/', 'exchange_name' => 'think_queue_exchange', 'queue_name' => 'think_queue', 'routing_key' => '*', 'is_lazy' => false, 'ssl_options' => array( 'cafile' => '', 'local_cert' => '', 'local_key' => '', 'verify_peer' => true, ), ], 'redis' => [ 'type' => 'redis', 'host' => '127.0.0.1', 'port' => 6379, 'select' => 0, 'password' => null, 'persistent' => false, 'pool' => false, 'channel' => 'think', ] ], ]; ``` #### 启动与管理命令 当一切准备就绪之后,可以通过CLI工具启动监听器并处理任务: - `php think queue:listen`:持续运行监听来自不同通道的任务请求。 - `php think queue:work` :执行一次性的任务消费操作。 - 若要重启所有正在运行的工作进程,则应输入如下指令 `$ php think queue:restart$ php think queue:work` [^1]。 #### 常见问题解答 有时可能会遇到某些特定情况下的挑战,比如性能瓶颈或是错误调试困难等问题;针对这些问题有几种常规建议可供参考: - **优化性能**:如果发现系统响应速度变慢,考虑增加更多的消费者实例来分担负载压力,或者是调整数据库查询逻辑减少不必要的资源消耗。 - **排查故障**:面对程序抛出异常却难以定位原因的情况时,可以尝试开启详细的日志记录功能以便于后续分析具体位置发生的问题所在之处。 - **确保依赖项已安装**:确认已经通过Composer正确加载了所需的扩展包版本,并且服务器环境满足最低要求。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值