在软件开发中,消息队列(MQ)常用于实现异步通信、解耦合、以及提高系统的伸缩性和可靠性。以下是一个简单的 Go 语言示例,展示了 使用 MQ 和 不使用 MQ 的区别。
场景:
假设我们有一个需要处理的任务,比如发送通知。我们有两种方式:
- 不使用 MQ:直接在同一进程中同步处理任务。
- 使用 MQ:通过消息队列异步地发送任务,消息队列可以让任务在后台处理。
不使用 MQ(同步处理任务)
在不使用消息队列的情况下,程序会同步处理任务,即在发送通知时,任务需要在主程序中等待完成。
代码示例(不使用 MQ)
package main
import (
"fmt"
"time"
)
// 发送通知的函数(同步)
func sendNotification(message string) {
// 模拟任务处理时间
time.Sleep(2 * time.Second)
fmt.Println("通知已发送:", message)
}
func main() {
fmt.Println("开始发送通知")
// 直接同步处理任务
sendNotification("任务1")
sendNotification("任务2")
sendNotification("任务3")
fmt.Println("所有通知已发送")
}
使用 MQ(异步处理任务)
在使用消息队列的情况下,任务被发送到消息队列,然后由消费者(独立的处理程序)异步处理。这种方式提高了系统的伸缩性,因为生产者和消费者可以独立工作,不需要同步等待。
假设我们使用 RabbitMQ 作为消息队列。你需要安装 Go 的 amqp
包来与 RabbitMQ 进行交互。
go get github.com/streadway/amqp
代码示例(使用 RabbitMQ 作为 MQ)
生产者(将任务发送到 MQ)
package main
import (
"fmt"
"log"
"github.com/streadway/amqp"
)
// 发送消息到队列
func sendMessageToQueue(message string) {
// 连接到 RabbitMQ
conn, err := amqp.Dial("amqp://guest:guest@localhost:5672/")
if err != nil {
log.Fatalf("连接 RabbitMQ 失败: %s", err)
}
defer conn.Close()
// 创建通道
ch, err := conn.Channel()
if err != nil {
log.Fatalf("创建通道失败: %s", err)
}
defer ch.Close()
// 声明一个队列
q, err := ch.QueueDeclare(
"notificationQueue", // 队列名称
false, // 是否持久化
false, // 是否自动删除
false, // 是否排他
false, // 是否阻塞
nil, // 额外参数
)
if err != nil {
log.Fatalf("声明队列失败: %s", err)
}
// 发送消息到队列
err = ch.Publish(
"", // 交换机
q.Name, // 队列名称
false, // 是否等待确认
false, // 是否永久存储
amqp.Publishing{
ContentType: "text/plain",
Body: []byte(message),
})
if err != nil {
log.Fatalf("发送消息失败: %s", err)
}
fmt.Println("已将消息发送到队列:", message)
}
func main() {
// 模拟任务发送
sendMessageToQueue("任务1")
sendMessageToQueue("任务2")
sendMessageToQueue("任务3")
}
消费者(从 MQ 获取任务并处理)
package main
import (
"fmt"
"log"
"github.com/streadway/amqp"
"time"
)
// 从队列获取并处理任务
func consumeMessageFromQueue() {
// 连接到 RabbitMQ
conn, err := amqp.Dial("amqp://guest:guest@localhost:5672/")
if err != nil {
log.Fatalf("连接 RabbitMQ 失败: %s", err)
}
defer conn.Close()
// 创建通道
ch, err := conn.Channel()
if err != nil {
log.Fatalf("创建通道失败: %s", err)
}
defer ch.Close()
// 声明一个队列
q, err := ch.QueueDeclare(
"notificationQueue", // 队列名称
false, // 是否持久化
false, // 是否自动删除
false, // 是否排他
false, // 是否阻塞
nil, // 额外参数
)
if err != nil {
log.Fatalf("声明队列失败: %s", err)
}
// 获取队列中的消息
msgs, err := ch.Consume(
q.Name, // 队列名称
"", // 消费者标签
true, // 自动确认
false, // 排他
false, // 非持久
false, // 阻塞
nil, // 额外参数
)
if err != nil {
log.Fatalf("消费队列失败: %s", err)
}
// 处理接收到的消息
for msg := range msgs {
fmt.Println("正在处理通知:", string(msg.Body))
// 模拟处理时间
time.Sleep(2 * time.Second)
fmt.Println("通知已处理:", string(msg.Body))
}
}
func main() {
// 启动消费者
consumeMessageFromQueue()
}
总结
-
不使用 MQ(同步):
- 所有的任务(如发送通知)都在主程序内同步处理,导致程序必须等待每个任务完成后才能继续执行其他任务。适用于任务量少或不需要异步处理的场景。
-
使用 MQ(异步):
- 使用 RabbitMQ 或其他消息队列,生产者将任务发送到消息队列,消费者从队列中取任务并异步处理。这样生产者和消费者解耦,任务处理更加灵活且可扩展,适用于高并发或需要任务异步处理的场景。
性能和可靠性:
- 使用 MQ 可以提高系统的可靠性和伸缩性,特别是在处理大量任务时。通过消息队列的异步处理,系统的吞吐量能够大幅提升。
- 在没有 MQ 的情况下,任务的处理会被同步阻塞,可能导致性能瓶颈。
运行环境
在使用 RabbitMQ 的代码示例中,你需要确保本地有 RabbitMQ 服务在运行。你可以通过 Docker 启动 RabbitMQ 服务:
docker run -d --name rabbitmq -p 5672:5672 -p 15672:15672 rabbitmq:management
docker ps 显示镜像
http://x.x.x.x:
以下是简单的rabbitmq客户端的操作