学习资源
安装
docker run -it --rm --name rabbitmq -p 5672:5672 -p 15672:15672 rabbitmq:3.9-management
教程
HelloWorld
安装 go get github.com/streadway/amqp
发送者代码
package main
import (
"github.com/streadway/amqp"
"log"
)
func failOnError(err error, msg string) {
if err != nil {
log.Fatalf("%s: %s", msg, err)
}
}
func main() {
// 1. 尝试连接RabbitMQ,建立连接
// 该连接抽象了套接字连接,并为我们处理协议版本协商和认证等。
conn, err := amqp.Dial("amqp://guest:guest@localhost:5672/")
failOnError(err, "Failed to connect to RabbitMQ")
// 2. 接下来,我们创建一个通道,大多数API都是用过该通道操作的。
ch, err := conn.Channel()
failOnError(err, "Failed to open a channel")
//通道关闭时,会自动释放所有的资源。
defer ch.Close()
// 3. 声明消息要发送到的队列
q, err := ch.QueueDeclare(
"hello", // name
false, // durable
false, // delete when unused
false, // exclusive
false, // no-wait
nil, // arguments
)
failOnError(err, "Failed to declare a queue")
body := "Hello World!"
// 4.将消息发布到声明的队列
err = ch.Publish(
"", // exchange
q.Name, // routing key
false, // mandatory
false, // immediate
amqp.Publishing{
ContentType: "text/plain",
Body: []byte(body),
})
failOnError(err, "Failed to publish a message")
}
接收者代码
package main
import (
"github.com/streadway/amqp"
"log"
)
func failOnError1(err error, msg string) {
if err != nil {
log.Fatalf("%s: %s", msg, err)
}
}
func main() {
// 1.建立连接
conn, err := amqp.Dial("amqp://guest:guest@localhost:5672/")
failOnError1(err, "Failed to connect to RabbitMQ")
defer conn.Close()
// 2.创建通道
ch, err := conn.Channel()
failOnError1(err, "Failed to open a channel")
defer ch.Close()
// 3.声明一个队列
q, err := ch.QueueDeclare(
"hello", // name
false, // durable
false, // delete when unused
false, // exclusive
false, // no-wait
nil, // arguments
)
failOnError1(err, "Failed to declare a queue")
// 4.获取接收消息的Delivery通道
msgs, err := ch.Consume(
q.Name, // queue
"", // consumer
true, // auto-ack
false, // exclusive
false, // no-local
false, // no-wait
nil, // args
)
failOnError1(err, "Failed to register a consumer")
//创建通道
forever := make(chan bool)
go func() {
for d := range msgs {
log.Printf("Received a message: %s", d.Body)
}
}()
log.Printf(" [*] Waiting for messages. To exit press CTRL+C")
<-forever
}
结果:
总结:
- 使用者建立一个队列,将消息发送到rabbitmq这个客户端这里,然后断开连接。
- 接收者也建立一个队列(要与发送者匹配),然后接受信息,然后断开连接。
- 通过这个例子,rabbitmq就像一个临时存储消息一个工具。
工作队列
**工作队列(又称任务队列)**的主要思想是为了避免等待一些占用大量资源、时间的操作。相反,我们安排任务异步地同时或在当前任务之后完成。我们将任务封装为消息并将其发送到队列,在后台运行的工作进程将取出消息并最终执行任务。当你运行多个工作进程时,任务将在他们之间共享。
通俗的话来讲就是当有工作来了立即给一个反应,工作异步进行中。
简单工作队列
任务代码如下:
package main
import (
"fmt"
"github.com/streadway/amqp"
"os"
"strings"
)
func main() {
//1.尝试连接rabbitmq,建立连接
conn, err := amqp.Dial("amqp://guest:guest@localhost:5672/")
if err != nil {
fmt.Printf("connect to RabbitMQ failed, err:%v\n", err)
return
}
//开启之后,要记得关闭连接
defer conn.Close()
//2.创建一个channel
ch, err := conn.Channel()
if err != nil {
fmt.Printf("open a channel failed, failed, err:%v\n", err)
return
}
//开启之后,要记得关闭channel
defer ch.Close()
//3.声明一个队列
q, err := ch.QueueDeclare(
"task_queue", // name
true, // 持久的
false, // delete when unused
false, // 独有的
false, // no-wait
nil, // arguments
)
if err != nil {
fmt.Printf("declare queue failed, err:%v\n", err)
return
}
//以上就是准备工作,下面就是发送消息了
//4.将消息发布到声明的队列中
body := bodyFrom(os.Args) // 从参数中获取要发送的消息正文
err = ch.Publish(
"", // exchange
q.Name, // routing key
false, // mandatory
false, // immediate
amqp.Publishing{
DeliveryMode: amqp.Persistent,
ContentType: "text/plain",
Body: []byte(body),
})
if err != nil {
fmt.Printf("publish message failed, err:%v\n", err)
return
}
fmt.Printf("[x] sent %s\n", body)
}
func bodyFrom(args []string) string {
var s string
if (len(args) < 2) || os.Args[1] == "" {
s = "hello"
} else {
s = strings.Join(args[1:], " ")
}
return s
}
工人代码如下:
package main
import (
"bytes"
"fmt"
"github.com/streadway/amqp"
"time"
)
func main() {
//1.与rabbitmq建立连接
conn, err := amqp.Dial("amqp://guest:guest@localhost:5672/")
if err != nil {
fmt.Printf("connect to RabbitMQ failed, err:%v\n", err)
return
}
defer conn.Close()
//2.创建一个channel
ch, err := conn.Channel()
if err != nil {
fmt.Printf("create channel failed, err:%v\n", err)
return
}
defer ch.Close()
//3.建立一个队列,获取消息
q, err := ch.QueueDeclare(
"task_queue", // name
true, // 声明为持久队列
false, // delete when unused
false, // exclusive
false, // no-wait
nil, // arguments
)
if err != nil {
fmt.Printf("create queue failed, err:%v\n", err)
return
}
//4.消费消息
msgs, err := ch.Consume(
q.Name, // queue
"", // consumer
true, // auto-ack
false, // exclusive
false, // no-local
false, // no-wait
nil, // args
)
if err != nil {
fmt.Printf("consume failed, err:%v\n", err)
return
}
forever := make(chan bool)
go func() {
for d := range msgs {
fmt.Printf("Received a message: %s\n", d.Body)
//数有几个.
dot_count := bytes.Count(d.Body, []byte("."))
t := time.Duration(dot_count)
//模拟耗时
time.Sleep(t * time.Second)
fmt.Printf("Done")
}
}()
fmt.Printf(" [*] Waiting for messages. To exit press CTRL+C\n")
<-forever
}
当有消息时候,工作者会从通道里面接受消息,开启一个线程去处理任务,这里是模拟的耗时。
优点:
- 能够并行的处理队列。当存在很多任务时候,可以添加工作者来进行缓解。
当建立了多个工作者时候,RabbitMQ会按顺序得把消息发送给每个消费者(consumer)。平均每个消费者都会收到同等数量得消息。这种发送消息得方式叫做——轮询(round-robin)。
消息确认
场景:
当处理一个比较耗时得任务的时候,你也许想知道消费者(consumers)是否运行到一半就挂掉。
函数参数解析
-
channel.queueDeclare
| 参数名 | 参数类型 | 解释 | | --- | --- | --- | | name | string | 队列名称 | | durable | bool | 是否持久化,队列的声明默认是存放到内存中的,如果rabbitmq重启会丢失,如果想重启之后还存在就要使队列持久化,保存到Erlang自带的Mnesia数据库中,当rabbitmq重启之后会读取该数据库 | | autoDelete | bool | 是否自动删除队列,当最后一个消费者断开连接之后队列是否自动被删除,可以通过RabbitMQ Management,查看某个队列的消费者数量,当consumers = 0时队列就会自动删除 | | exclusive | bool | 是否排外的,有两个作用,
1:当连接关闭时该队列是否会自动删除;
2:该队列是否是私有的private,如果不是排外的,可以使用两个消费者都访问同一个队列,没有任何问题,如果是排外的,会对当前队列加锁,其他通道channel是不能访问的,如果强制访问会报异常;
一般等于true的话用于一个队列只能有一个消费者来消费的场景 | | no-wait | bool | 是否等待服务器返回 | | arguments | map[string]interface{} | 设置队列的其他一些参数,如 x-rnessage-ttl 、x-expires 、x-rnax-length 、x-rnax-length-bytes、 x-dead-letter-exchange、 x-deadletter-routing-key 、 x-rnax-priority 等。 | -
ch.Publish
| 参数名 | 参数类型 | 解释 | | --- | --- | --- | | exchange | string | 交换机 | | routing key | string | 路由键,#匹配0个或多个单词,*匹配一个单词,在topic exchange做消息转发用 | | mandatory | bool | true:如果exchange根据自身类型和消息routeKey无法找到一个符合条件的queue,那么会调用basic.return方法将消息返还给生产者。
false:出现上述情形broker会直接将消息扔掉 | | immediate | bool | true:如果exchange在将消息route到queue(s)时发现对应的queue上没有消费者,那么这条消息不会放入队列中。当与消息routeKey关联的所有queue(一个或多个)都没有消费者时,该消息会通过basic.return方法返还给生产者。 | | msg | | 消息内容 | -
ch.Consume
| 参数名 | 参数类型 | 解释 | | --- | --- | --- | | queue | string | 建立的队列名 | | consumer | string | 消费者 | | auto-ack | bool | 是否自动ack,如果不自动ack,需要使用channel.ack、channel.nack、channel.basicReject 进行消息应答 | | exclusive | bool | | | no-local | bool | | | no-wait | bool | 是否等待服务器返回 | | args | | |