Docker安装部署RabbitMQ
查询RabbitMQ镜像
docker search rabbitmq:management
注:此处是获取management版本的,这种镜像才会有管理界面,如果是获取last版本的没有管理界面
拉取RabbitMQ镜像
docker pull rabbitmq:management
拉取成功之后接下来就是运行RabbitMQ镜像了
运行镜像
docker run -d -p 5672:5672 -p 15672:15672 --name rabbitmq rabbitmq:management
参数说明:
-d:后台运行容器,返回容器id-p:主机端口和容器端口之间的映射,格式为:主机端口:容器端口--name:指定容器运行的名称
镜像运行成功之后就可以在主机上打开管理界面了,管理界面的地址为:http://主机Ip:15762,初次登录的账号和密码都是guest
登录成功的界面如下所示:
RabbitMQ的应用
RabbitMQ支持五种消息模型,不同的模型就代表者不同的消息规则,不同的应用场景;这篇文章RabbitMQ介绍对于RabbitMQ的模型已经有了一个简单的介绍,下面主要是RabbitMQ的应用
简单模型(Hello World)
简单模型就是只有一个生产者,一个队列和一个消费者,下面是使用go实现的代码示例:
send.go
package main
import (
"github.com/streadway/amqp"
"log"
)
func main(){
conn, err := amqp.Dial("amqp://guest:guest@localhost:5672/")
failOnError(err, "Failed to connect to RabbitMQ")
defer conn.Close()
ch, err := conn.Channel()
failOnError(err, "Failed to open a channel")
defer ch.Close()
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!"
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")
log.Printf(" [x] Sent %s\n", body)
}
func failOnError(err error, msg string) {
if err != nil {
log.Printf("%s: %s", msg, err)
}
}
receive.go
package main
import (
"github.com/streadway/amqp"
"log"
)
func main(){
conn, err := amqp.Dial("amqp://guest:guest@localhost:5672/")
failOnError(err, "Failed to connect to RabbitMQ")
defer conn.Close()
ch, err := conn.Channel()
failOnError(err, "Failed to open a channel")
defer ch.Close()
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")
msgs, err := ch.Consume(
q.Name, // queue
"", // consumer
true, // auto-ack
false, // exclusive
false, // no-local
false, // no-wait
nil, // args
)
failOnError(err, "Failed to register a consumer")
var forever chan struct{}
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
}
func failOnError(err error, msg string) {
if err != nil {
log.Printf("%s: %s", msg, err)
}
}
注:简单模型当中,发送消息是直接发送到队列当中,所以在send.go中发送消息时的routingkey=queueName
模型图:
Worker模型
Worker模型和简单模型的代码几乎是相同的,因为在worker模型中也是只有一个生产者,但是有多个消费者,所以只需要将消费者的代码产生多个实例就好了
模型图:
Worker模型与简单模型不同之处在于出现了多个消费者,那么就需要有一个消息分发策略了,默认的消息分发策略就是轮询,这种模型下多个消费者消费的消息个数都是差不多的
但是还有一种分发策略为Fair Dispatch(公平分发),这种分发需要指定一个Prefetch(预获取值),这个值保证了该消费者一次只能消费固定数量的消息,只有消费的这些消息消费完成才会继续获得消息
代码示例如下:
send.go
package main
import (
"log"
"os"
"strings"
amqp "github.com/rabbitmq/amqp091-go"
)
func failOnError(err error, msg string) {
if err != nil {
log.Panicf("%s: %s", msg, err)
}
}
func main() {
conn, err := amqp.Dial("amqp://guest:guest@localhost:5672/")
failOnError(err, "Failed to connect to RabbitMQ")
defer conn.Close()
ch, err := conn.Channel()
failOnError(err, "Failed to open a channel")
defer ch.Close()
q, err := ch.QueueDeclare(
"task_queue", // name
true, // durable
false, // delete when unused
false, // exclusive
false, // no-wait
nil, // arguments
)
failOnError(err, "Failed to declare a queue")
body := bodyFrom(os.Args)
err = ch.Publish(
"", // exchange
q.Name, // routing key
false, // mandatory
false,
amqp.Publishing{
DeliveryMode: amqp.Persistent,
ContentType: "text/plain",
Body: []byte(body),
})
failOnError(err, "Failed to publish a message")
log.Printf(" [x] Sent %s", 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
}
receive.go
package main
import (
"bytes"
"log"
"time"
amqp "github.com/rabbitmq/amqp091-go"
)
func failOnError(err error, msg string) {
if err != nil {
log.Panicf("%s: %s", msg, err)
}
}
func main() {
conn, err := amqp.Dial("amqp://guest:guest@localhost:5672/")
failOnError(err, "Failed to connect to RabbitMQ")
defer conn.Close()
ch, err := conn.Channel()
failOnError(err, "Failed to open a channel")
defer ch.Close()
q, err := ch.QueueDeclare(
"task_queue", // name
true, // durable
false, // delete when unused
false, // exclusive
false, // no-wait
nil, // arguments
)
failOnError(err, "Failed to declare a queue")
// 通过此处指定 prefetch 的值
err = ch.Qos(
1, // prefetch count
0, // prefetch size
false, // global
)
failOnError(err, "Failed to set QoS")
msgs, err := ch.Consume(
q.Name, // queue
"", // consumer
false, // auto-ack
false, // exclusive
false, // no-local
false, // no-wait
nil, // args
)
failOnError(err, "Failed to register a consumer")
var forever chan struct{}
go func() {
for d := range msgs {
log.Printf("Received a message: %s", d.Body)
dotCount := bytes.Count(d.Body, []byte("."))
t := time.Duration(dotCount)
time.Sleep(t * time.Second)
log.Printf("Done")
d.Ack(false)
}
}()
log.Printf(" [*] Waiting for messages. To exit press CTRL+C")
<-forever
}
参数说明:
prefetch count:指定一次消费的消息个数prefetch size:指定一次消费的消息大小global:如果为true,则该规则应用到当前连接所有channel上的所有Consumer种
发布/订阅(Public/Subscribe)
前面的模型都是直接将消息发送到消息队列,而在发布订阅模型中引入了一个新的组件:交换机(Exchange),消息首先会被发送到交换机,然会由交换机再发送到消息队列当中,模型图如下:
那为什么需要这个交换机呢?交换机是如何知道将消息发送哪个队列呢
首先,如果不使用交换机那么当需要将一个消息发送给多个队列时,你就需要将一条消息发送多次,并且每增加一个队列时都需要再重新改代码,这样对扩展不友好;而使用交换机就可以一次将一条消息同时发送到多个队列当中,同时也可以兼容新增的应用和队列
其次,交换机是根据一个RoutingKey来匹配消息队列的,匹配成功之后就会将消息发送到对应的消息队列当中,如下面图所示:
在RabbitMQ中提供了四种交换机类型,分别是:direct,topic,header,fanout;下面来首先来介绍fanout
fanout exchange
这种类型比较简单,它是广播所有的消息到所有绑定到这个exchange的消息队列中,下面是具体的代码示例:
sender.go
package main
import (
"log"
"os"
"strings"
"github.com/streadway/amqp"
)
func failOnError(err error, msg string) {
if err != nil {
log.Panicf("%s: %s", msg, err)
}
}
func main() {
conn, err := amqp.Dial("amqp://guest:guest@localhost:5672/")
failOnError(err, "Failed to connect to RabbitMQ")
defer conn.Close()
ch, err := conn.Channel()
failOnError(err, "Failed to open a channel")
defer ch.Close()
err = ch.ExchangeDeclare(
"logs", // name
"fanout", // type
true, // durable
false, // auto-deleted
false, // internal
false, // no-wait
nil, // arguments
)
failOnError(err, "Failed to declare an exchange")
body := bodyFrom(os.Args)
err = ch.Publish(
"logs", // exchange
"", // routing key
false, // mandatory
false, // immediate
amqp.Publishing{
ContentType: "text/plain",
Body: []byte(body),
})
failOnError(err, "Failed to publish a message")
log.Printf(" [x] Sent %s", 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
}
receive.go
package main
import (
"log"
"github.com/streadway/amqp"
)
func failOnError(err error, msg string) {
if err != nil {
log.Panicf("%s: %s", msg, err)
}
}
func main() {
conn, err := amqp.Dial("amqp://guest:guest@localhost:5672/")
failOnError(err, "Failed to connect to RabbitMQ")
defer conn.Close()
ch, err := conn.Channel()
failOnError(err, "Failed to open a channel")
defer ch.Close()
err = ch.ExchangeDeclare(
"logs", // name
"fanout", // type
true, // durable
false, // auto-deleted
false, // internal
false, // no-wait
nil, // arguments
)
failOnError(err, "Failed to declare an exchange")
q, err := ch.QueueDeclare(
"", // name
false, // durable
false, // delete when unused
true, // exclusive
false, // no-wait
nil, // arguments
)
failOnError(err, "Failed to declare a queue")
err = ch.QueueBind(
q.Name, // queue name
"", // routing key
"logs", // exchange
false,
nil,
)
failOnError(err, "Failed to bind a queue")
msgs, err := ch.Consume(
q.Name, // queue
"", // consumer
true, // auto-ack
false, // exclusive
false, // no-local
false, // no-wait
nil, // args
)
failOnError(err, "Failed to register a consumer")
var forever chan struct{}
go func() {
for d := range msgs {
log.Printf(" [x] %s", d.Body)
}
}()
log.Printf(" [*] Waiting for logs. To exit press CTRL+C")
<-forever
}
direct exchange
前面的fanout exchange会将消息广播到所有绑定到它的队列上面,但是如果我们不想广播消息,而是只发送消息到指定的消息队列呢
那么此时就可以使用direct exchange了,direct exchange就是指定一个RoutingKey,然会交换机只会将消息发送到指定的消息队列当中
注:为了避免routingkey混淆,我们将Channel.Publish参数中的routingKey称之为BindingKey
下面是使用direct exchange的go代码示例:
send.go
package main
import (
"log"
"os"
"strings"
"github.com/streadway/amqp"
)
func failOnError(err error, msg string) {
if err != nil {
log.Panicf("%s: %s", msg, err)
}
}
func main() {
conn, err := amqp.Dial("amqp://guest:guest@localhost:5672/")
failOnError(err, "Failed to connect to RabbitMQ")
defer conn.Close()
ch, err := conn.Channel()
failOnError(err, "Failed to open a channel")
defer ch.Close()
err = ch.ExchangeDeclare(
"logs_direct", // name
"direct", // type
true, // durable
false, // auto-deleted
false, // internal
false, // no-wait
nil, // arguments
)
failOnError(err, "Failed to declare an exchange")
body := bodyFrom(os.Args)
err = ch.Publish(
"logs_direct", // exchange
severityFrom(os.Args), // routing key
false, // mandatory
false, // immediate
amqp.Publishing{
ContentType: "text/plain",
Body: []byte(body),
})
failOnError(err, "Failed to publish a message")
log.Printf(" [x] Sent %s", body)
}
func bodyFrom(args []string) string {
var s string
if (len(args) < 3) || os.Args[2] == "" {
s = "hello"
} else {
s = strings.Join(args[2:], " ")
}
return s
}
func severityFrom(args []string) string {
var s string
if (len(args) < 2) || os.Args[1] == "" {
s = "info"
} else {
s = os.Args[1]
}
return s
}
receive.go
package main
import (
"log"
"os"
"github.com/streadway/amqp"
)
func failOnError(err error, msg string) {
if err != nil {
log.Panicf("%s: %s", msg, err)
}
}
func main() {
conn, err := amqp.Dial("amqp://guest:guest@localhost:5672/")
failOnError(err, "Failed to connect to RabbitMQ")
defer conn.Close()
ch, err := conn.Channel()
failOnError(err, "Failed to open a channel")
defer ch.Close()
err = ch.ExchangeDeclare(
"logs_direct", // name
"direct", // type
true, // durable
false, // auto-deleted
false, // internal
false, // no-wait
nil, // arguments
)
failOnError(err, "Failed to declare an exchange")
q, err := ch.QueueDeclare(
"", // name
false, // durable
false, // delete when unused
true, // exclusive
false, // no-wait
nil, // arguments
)
failOnError(err, "Failed to declare a queue")
if len(os.Args) < 2 {
log.Printf("Usage: %s [info] [warning] [error]", os.Args[0])
os.Exit(0)
}
for _, s := range os.Args[1:] {
log.Printf("Binding queue %s to exchange %s with routing key %s",
q.Name, "logs_direct", s)
err = ch.QueueBind(
q.Name, // queue name
s, // routing key
"logs_direct", // exchange
false,
nil)
failOnError(err, "Failed to bind a queue")
}
msgs, err := ch.Consume(
q.Name, // queue
"", // consumer
true, // auto ack
false, // exclusive
false, // no local
false, // no wait
nil, // args
)
failOnError(err, "Failed to register a consumer")
var forever chan struct{}
go func() {
for d := range msgs {
log.Printf(" [x] %s", d.Body)
}
}()
log.Printf(" [*] Waiting for logs. To exit press CTRL+C")
<-forever
}
topic exchange
前面已经介绍过两种交换机模型了,一种是广播,一种是单播,那有没有组播呢?下面介绍的topic exchange就是实现组播的功能了
topic exchange提供了两个特殊的字符:*和#,用来匹配多个队列,从而将消息发送到多个队列当中
*:星号可以匹配一个单词,比如:log.*可以匹配log.err,log.info等,但是不能匹配log.err.app#:井号可以匹配零个或多个单词,比如:log.#可以匹配log.err,log.info同时也可以匹配log.err.app
下面是使用go语言实现的代码示例:
sender.go
package main
import (
"log"
"os"
"strings"
"github.com/streadway/amqp"
)
func failOnError(err error, msg string) {
if err != nil {
log.Panicf("%s: %s", msg, err)
}
}
func main() {
conn, err := amqp.Dial("amqp://guest:guest@localhost:5672/")
failOnError(err, "Failed to connect to RabbitMQ")
defer conn.Close()
ch, err := conn.Channel()
failOnError(err, "Failed to open a channel")
defer ch.Close()
err = ch.ExchangeDeclare(
"logs_topic", // name
"topic", // type
true, // durable
false, // auto-deleted
false, // internal
false, // no-wait
nil, // arguments
)
failOnError(err, "Failed to declare an exchange")
body := bodyFrom(os.Args)
err = ch.Publish(
"logs_topic", // exchange
severityFrom(os.Args), // routing key
false, // mandatory
false, // immediate
amqp.Publishing{
ContentType: "text/plain",
Body: []byte(body),
})
failOnError(err, "Failed to publish a message")
log.Printf(" [x] Sent %s", body)
}
func bodyFrom(args []string) string {
var s string
if (len(args) < 3) || os.Args[2] == "" {
s = "hello"
} else {
s = strings.Join(args[2:], " ")
}
return s
}
func severityFrom(args []string) string {
var s string
if (len(args) < 2) || os.Args[1] == "" {
s = "anonymous.info"
} else {
s = os.Args[1]
}
return s
}
receive.go
package main
import (
"log"
"os"
"github.com/streadway/amqp"
)
func failOnError(err error, msg string) {
if err != nil {
log.Panicf("%s: %s", msg, err)
}
}
func main() {
conn, err := amqp.Dial("amqp://guest:guest@localhost:5672/")
failOnError(err, "Failed to connect to RabbitMQ")
defer conn.Close()
ch, err := conn.Channel()
failOnError(err, "Failed to open a channel")
defer ch.Close()
err = ch.ExchangeDeclare(
"logs_topic", // name
"topic", // type
true, // durable
false, // auto-deleted
false, // internal
false, // no-wait
nil, // arguments
)
failOnError(err, "Failed to declare an exchange")
q, err := ch.QueueDeclare(
"", // name
false, // durable
false, // delete when unused
true, // exclusive
false, // no-wait
nil, // arguments
)
failOnError(err, "Failed to declare a queue")
if len(os.Args) < 2 {
log.Printf("Usage: %s [binding_key]...", os.Args[0])
os.Exit(0)
}
for _, s := range os.Args[1:] {
log.Printf("Binding queue %s to exchange %s with routing key %s",
q.Name, "logs_topic", s)
err = ch.QueueBind(
q.Name, // queue name
s, // routing key
"logs_topic", // exchange
false,
nil)
failOnError(err, "Failed to bind a queue")
}
msgs, err := ch.Consume(
q.Name, // queue
"", // consumer
true, // auto ack
false, // exclusive
false, // no local
false, // no wait
nil, // args
)
failOnError(err, "Failed to register a consumer")
var forever chan struct{}
go func() {
for d := range msgs {
log.Printf(" [x] %s", d.Body)
}
}()
log.Printf(" [*] Waiting for logs. To exit press CTRL+C")
<-forever
}
header exchange
header exchange和topic exchange非常相似,只不过它是通过header中的参数来路由消息的,而会自动忽略掉routingKey
在header exchange中,如果header中的值和绑定的值相互匹配,那么就会被消息发送到这个队列当中去了;在队列和交换机绑定中提供了一个参数为x-match,它有两个值分别是:all,any,这个参数表明是否header中所有的值都匹配还是最少匹配一个
all:表示所有header中的key-val都必须匹配any:表示所有header中最少匹配一个
下面是使用go实现的代码示例:
send.go
package main
import (
"github.com/streadway/amqp"
"time"
)
func main() {
conn, _ := amqp.Dial("amqp://user:password@localhost:ip/vhost")
defer conn.Close()
ch, _ := conn.Channel()
defer ch.Close()
body := "Hello World! " + time.Now().Format("2006-01-02 15:04:05")
ch.ExchangeDeclare("logs", amqp.ExchangeHeaders, true, false, false, false, nil)
ch.Publish(
"logs", // exchange
"log.err", // routing key
false, // mandatory
false, // immediate
amqp.Publishing{
ContentType: "text/plain",
Body: []byte(body),
Headers: amqp.Table{"x-match": "any", "mail": "test@qq.com", "author": "sendi"}, // 头部信息 any:匹配一个即可 all:全部匹配
//Expiration: "3000",
})
}
receive.go
package main
import (
"github.com/streadway/amqp"
"log"
)
func main() {
conn, _ := amqp.Dial("amqp://user:password@localhost:ip/vhost")
defer conn.Close()
ch, _ := conn.Channel()
defer ch.Close()
ch.QueueDeclare("log.app", true, false, true, false, nil)
ch.ExchangeDeclare("logs", amqp.ExchangeHeaders, true, false, false, false, nil)
ch.QueueBind(
"log.app", // queue name
"", // 此处即使写了 routingkey 也会被自动忽略
"logs", // exchange
false,
amqp.Table{"mail": "test@qq.com"}, // 头部信息 any:匹配一个即可 all:全部匹配
)
//监听
msgs, _ := ch.Consume(
"log.app", // queue name
"", // consumer
true, // auto-ack
false, // exclusive
false, // no-local
false, // no-wait
nil, // args
)
forever := make(chan bool)
go func() {
for d := range msgs {
//println("tset")
log.Printf(" [x] %s", d.Body)
}
}()
<-forever
}