消息队列 | 青训营

73 阅读4分钟

基本概念

在分布式系统中用于异步通信的通信方式。它允许应用程序之间通过发送、接收和处理消息来实现解耦和并发处理。消息队列通过提供中间代理来确保消息的可靠传递,即使接收方不在线也能保存消息直到其处理完成。这种机制在高负载、异构系统集成以及需要削峰填谷的情况下尤其有用。一些常见的消息队列系统包括RabbitMQ、Apache Kafka和Amazon SQS。通过将消息发送者和接收者分离,消息队列提供了更强大、可扩展和可靠的通信方式,有助于构建可靠的分布式应用系统

基本原理

  1. 发布/订阅模型: 在发布/订阅模型中,消息发送者(发布者)将消息发布到一个主题(Topic),而多个消息接收者(订阅者)可以订阅该主题以接收消息。发布者和订阅者之间不直接交互,而是通过消息队列中间代理进行通信。这种模型支持一对多的消息广播,适用于广播通知等场景。
  2. 点对点模型: 在点对点模型中,消息发送者将消息发送到一个队列中,消息接收者从队列中获取消息并进行处理。每条消息只能被一个接收者消费,一旦消息被消费,它会从队列中删除。这种模型适用于任务分发和负载均衡场景。
  3. 消息代理: 消息队列使用一个中间代理,也称为消息代理或消息中间件,来存储和传递消息。代理负责维护主题、队列以及消息的发布、订阅和传递机制。代理通常提供持久化存储、消息分发、故障恢复等功能。
  4. 可靠性和持久性: 消息队列通常提供消息的可靠传递,确保消息在发送后即使发生中断或崩溃也不会丢失。持久性是通过将消息存储在持久化存储中实现的,以便在代理重启后仍然可用。
  5. 解耦和伸缩性: 消息队列可以将发送者和接收者解耦,使它们不需要直接知道对方的存在。这样可以降低系统组件之间的依赖,提高系统的可维护性和可扩展性。
  6. 削峰填谷: 消息队列允许在高负载时将请求排队,以便缓解系统负载压力。这种机制可以帮助平衡系统的处理能力,避免系统崩溃。

go与消息队列相结合

当谈论消息队列的实现和使用时,以下是一个简单的Go代码示例,用于演示如何使用 github.com/streadway/amqp 库与 RabbitMQ 消息队列进行交互。在这个示例中,我们将展示如何创建一个消息发布者和一个消息订阅者。

代码示例:消息发布者(Publisher)

package main

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

func main() {
	// 连接 RabbitMQ 服务器
	conn, err := amqp.Dial("amqp://guest:guest@localhost:5672/")
	if err != nil {
		log.Fatalf("Failed to connect to RabbitMQ: %v", err)
	}
	defer conn.Close()

	// 创建一个通道
	ch, err := conn.Channel()
	if err != nil {
		log.Fatalf("Failed to open a channel: %v", err)
	}
	defer ch.Close()

	// 声明一个队列
	q, err := ch.QueueDeclare(
		"hello", // 队列名称
		false,   // 是否持久化
		false,   // 是否自动删除
		false,   // 是否排他性
		false,   // 是否阻塞
		nil,     // 额外属性
	)
	if err != nil {
		log.Fatalf("Failed to declare a queue: %v", err)
	}

	// 发布消息
	body := "Hello, World!"
	err = ch.Publish(
		"",     // 交换机名称
		q.Name, // 队列名称
		false,  // 强制性
		false,  // 立即发布
		amqp.Publishing{
			ContentType: "text/plain",
			Body:        []byte(body),
		},
	)
	if err != nil {
		log.Fatalf("Failed to publish a message: %v", err)
	}

	fmt.Printf(" [x] Sent '%s'\n", body)
}

代码示例:消息订阅者(Subscriber)

package main

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

func main() {
	// 连接 RabbitMQ 服务器
	conn, err := amqp.Dial("amqp://guest:guest@localhost:5672/")
	if err != nil {
		log.Fatalf("Failed to connect to RabbitMQ: %v", err)
	}
	defer conn.Close()

	// 创建一个通道
	ch, err := conn.Channel()
	if err != nil {
		log.Fatalf("Failed to open a channel: %v", err)
	}
	defer ch.Close()

	// 声明一个队列
	q, err := ch.QueueDeclare(
		"hello", // 队列名称
		false,   // 是否持久化
		false,   // 是否自动删除
		false,   // 是否排他性
		false,   // 是否阻塞
		nil,     // 额外属性
	)
	if err != nil {
		log.Fatalf("Failed to declare a queue: %v", err)
	}

	// 获取消息
	msgs, err := ch.Consume(
		q.Name, // 队列名称
		"",     // 消费者名称
		true,   // 是否自动应答
		false,  // 是否排他性
		false,  // 是否阻塞
		false,  // 是否等待
		nil,    // 额外属性
	)
	if err != nil {
		log.Fatalf("Failed to register a consumer: %v", err)
	}

	forever := make(chan bool)

	// 处理接收到的消息
	go func() {
		for d := range msgs {
			fmt.Printf(" [x] Received %s\n", d.Body)
		}
	}()

	fmt.Println(" [*] Waiting for messages. To exit press CTRL+C")
	<-forever
}

发布者将消息发送到名为 "hello" 的队列,而订阅者从相同的队列中接收并处理消息