【RabbitMQ】 使用方法

60 阅读5分钟

学习资源

  1. www.liwenzhou.com/posts/Go/go…
  2. www.bilibili.com/video/BV1h4…
  3. blog.csdn.net/weixin_4530…

安装

参阅安装指南
使用Docker镜像

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
}

结果:
image.png
总结:

  1. 使用者建立一个队列,将消息发送到rabbitmq这个客户端这里,然后断开连接。
  2. 接收者也建立一个队列(要与发送者匹配),然后接受信息,然后断开连接。
  3. 通过这个例子,rabbitmq就像一个临时存储消息一个工具。

工作队列

image.png
**工作队列(又称任务队列)**的主要思想是为了避免等待一些占用大量资源、时间的操作。相反,我们安排任务异步地同时或在当前任务之后完成。我们将任务封装为消息并将其发送到队列,在后台运行的工作进程将取出消息并最终执行任务。当你运行多个工作进程时,任务将在他们之间共享。
通俗的话来讲就是当有工作来了立即给一个反应,工作异步进行中。

简单工作队列

任务代码如下:

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
}

当有消息时候,工作者会从通道里面接受消息,开启一个线程去处理任务,这里是模拟的耗时。
优点:

  1. 能够并行的处理队列。当存在很多任务时候,可以添加工作者来进行缓解。

当建立了多个工作者时候,RabbitMQ会按顺序得把消息发送给每个消费者(consumer)。平均每个消费者都会收到同等数量得消息。这种发送消息得方式叫做——轮询(round-robin)。

消息确认

场景:

当处理一个比较耗时得任务的时候,你也许想知道消费者(consumers)是否运行到一半就挂掉。

函数参数解析

  1. 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 等。 |

  2. 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 | | 消息内容 |

  3. 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 | | |