go实战之探索消息队列-RabbitMQ(2)

132 阅读4分钟

引言

最近在学习消息队列,但是视频大多数是以java为主的进行简绍。关于go的很少且类似的文章也很少。因此想记录自己的慢慢摸索的。让其它go开发者少走点坑。今天也是记录的第二天

正文

work工作模式

轮询消费(公平分发)

生产者

package main

import (
	"fmt"
	"log"
	"math/rand"
	"time"

	"github.com/streadway/amqp"
)

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

func producer(ch *amqp.Channel) {
	q, err := ch.QueueDeclare(
		"task_queue", // 队列名称
		true,         // 持久化
		false,        // 删除未使用的队列
		false,        // 独占队列
		false,        // 阻塞
		nil,          // 额外参数
	)
	failOnError(err, "Failed to declare a queue")

	for i := 0; i < 10; i++ {
		body := fmt.Sprintf("Task %d", i)
		err = ch.Publish(
			"",     // 交换机
			q.Name, // 队列名称
			false,  // 强制
			false,  // 立即
			amqp.Publishing{
				DeliveryMode: amqp.Persistent, // 消息持久化
				ContentType:  "text/plain",
				Body:         []byte(body),
			})
		log.Printf(" [x] Sent %s", body)
		failOnError(err, "Failed to publish a message")
		time.Sleep(time.Duration(rand.Intn(3)) * time.Second)
	}
}





消费者

func consumer(ch *amqp.Channel, id int) {
	q, err := ch.QueueDeclare(
		"task_queue", // 队列名称
		true,         // 持久化
		false,        // 删除未使用的队列
		false,        // 独占队列
		false,        // 阻塞
		nil,          // 额外参数
	)
	failOnError(err, "创建队列失败")

	msgs, err := ch.Consume(
		q.Name, // 队列名称
		"",     // 消费者标识
		true,  // 自动应答
		false,  // 独占
		false,  // 无等待
		false,  // 额外参数
	)
	failOnError(err, "消费失败")

	for msg := range msgs {
		log.Printf("Worker %d received: %s", id, msg.Body)
		// 模拟任务处理
		time.Sleep(time.Duration(rand.Intn(5)) * time.Second)
		log.Printf("Worker %d completed: %s", id, msg.Body)
		// msg.Ack(false) // 手动确认消息处理完成,如果消费者设置了自动应答可以不需要手动,但是在生产环境下一般是进行手动ack
	}
}

入口函数


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

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

	go producer(ch)

	for i := 1; i <= 2; i++ {
		go consumer(ch, i)
	}

	// 保持程序运行
	select {}
}

目录结构

image.png

启动两个消费者

image.png

投递消息(生产

image.png

查看两个消费者

消费者1

image.png

消费者2

image.png

可以看出消费者1和消费者2是进行轮询的消费

轮询消费使用场景

轮询消费是一种常见的消息队列模式,适用于一些特定的场景,尤其是在多个消费者之间平均分配负载和确保任务公平处理的情况下。

  1. 任务分发和负载均衡: 当有多个消费者可以处理相同类型的任务时,轮询消费是一种有效的方式,可以确保每个消费者都有机会处理任务,从而实现负载均衡

  2. 并行处理: 如果有多个处理器可以同时处理任务,并且希望任务在这些处理器之间平均分配,轮询消费可以实现并行处理。

  3. 任务类型相同: 当多个消费者可以处理相同类型的任务时,轮询消费是一个合适的选择。每个消费者都可以从队列中获取任务并进行处理。

  4. 公平分发: 一定程度上的公平分发

不公平分发

怎么才能实现不公平分发呢?什么是不公平分发?

在rabbitmq里不公平分发是指干得多就多干(能者多劳)通过调整消费者的预取计数来实现。

  1. 调整消费者的预取计数: 在消费者开始处理之前,通过调用 ch.Qos() 方法来设置预取计数。预取计数表示每个消费者一次可以从队列中预取的消息数量。设置较高的预取计数将使某些消费者能够获取更多的任务。
  2. 确保手动确认消息处理: 如果您希望实现不公平分发,消费者需要手动确认消息的处理。这样,消费者可以在处理完一定数量的任务后再确认消息,从而影响队列中任务的分发情况。

生成者代码不变

消费者发生改变

实现不公平分发的步骤:

func consumer(ch *amqp.Channel, id int) {
    q, err := ch.QueueDeclare(
        "task_queue", // 队列名称
        true,         // 持久化
        false,        // 删除未使用的队列
        false,        // 独占队列
        false,        // 阻塞
        nil,          // 额外参数
    )
    failOnError(err, "创建队列失败")
    // 设置预取计数,每次预取2个消息
    _ = ch.Qos(2, 0, false)


    msgs, err := ch.Consume(
        q.Name, // 队列名称
        "",     // 消费者标识
        false,  // 自动应答
        false,  // 独占
        false,  // 无等待
        false,  // 额外参数
    )
    failOnError(err, "消费失败")

    for msg := range msgs {
        log.Printf("Worker %d received: %s", id, msg.Body)
        // 模拟业务耗时
        time.Sleep(time.Duration(rand.Intn(5)) * time.Second)
        log.Printf("Worker %d completed: %s", id, msg.Body)
        msg.Ack(false) // 手动确认消息处理完成
    }
}

这里也需要像轮询一样启动两个消费者,这里就不叙述了。

消费者1(由于消费者一每次消费需要5s)才消费了两条消息

image.png

消费者2(每次消费需要1s)消费了10条消息

image.png

总结

工作模式(Worker Queue)是一种使用消息队列来协调任务处理的模式。在工作模式中,任务由生产者发送到队列中,然后由多个消费者并行地从队列中获取任务并进行处理。两种常见的任务分发方式是轮询方法和不公平分发。

轮询方法

  1. 定义:轮询是一种任务分发方法,其中消息队列将任务依次分发给不同的消费者,确保每个消费者都有机会处理任务。消费者之间进行轮流获取任务,从而平均分摊任务负载。
  2. 优点:简单、公平,确保每个消费者都能获得任务并参与处理。
  3. 适用场景:适用于简单的任务分发场景,不需要特别的负载均衡和任务优先级控制。

不公平分发

  1. 定义:不公平分发是一种在处理任务时,对某些消费者分配更多任务,而对其他消费者分配较少任务的方法。通过调整预取计数,可以实现某些消费者能够获取更多任务,导致不公平的分发。
  2. 优点:可用于某些场景下,例如对某些高性能消费者分配更多任务。
  3. 适用场景:适用于需要有选择地调整任务分发,使某些消费者能够处理更多任务的场景。

综上所述个人认为基本上不用到work模式,经典白雪。