Go RabbitMQ(四)消息路由
RabbitMQ_Routing
本节内容我们将对发布订阅增加一个特性:订阅子集。比如我们将一些危险的错误消息保存进硬盘中,同时在控制台仍然能够读取所有的消息
Bingings
上一节内容我们将队列跟交换器进行binging:
err = ch.QueueBind(
q.Name, // queue name
"", // routing key
"logs", // exchange
false,
nil)
一个binging是将交换器跟队列进行关联,可以简单理解为,绑定好的队列只会接收来自这个交换器的消息
Bindings能够携带一个额外的参数:routing_key,为了避免跟Channel.Publish中的参数混淆我们称这个routing_key为binding key。所以我们可以创建一个binding:
err = ch.QueueBind(
q.Name, // queue name
"black", // routing key
"logs", // exchange
false,
nil)
binding key的含义取决交换类型,像之前我们使用的fanout类型就完全忽略了binding key的价值
Direct exchange
上一节中我们的发布订阅模式是将所有的消息广播给消费者,接下来我们进行扩展:允许根据消息的严重性过滤消息。举个例子,对于严重性错误的消息我们直接写硬盘,对于一般的提醒消息或日志则不需要浪费硬盘空间
我们之前使用的fanout交换类型则不具备这些灵活操作,它只能够将消息不加过滤的进行广播。想要达到上面的灵活性我们使用direct交换模式来替代fanout,它能够将消息传递到binding key跟routing key完全匹配的队列
如上图所示,direct类型的交换器绑定了两个队列,第一个队列使用了绑定键是orange,第二个队列使用了两个绑定键,一个是black,另外一个是green
在这样的设置中,使用路由键orange发布到交换器的消息将被路由到队列Q1,使用路由键black或者green则会被路由到队列Q2,其他的消息则会被丢弃
我们也可以使用同一个绑定键来绑定交换器跟不同队列,在这种情况下,direct模式跟fanout有点相似,使用路由键black发布到交换器的消息将被路由到队列Q1跟Q2
接下来我们来实现以下配置的消息系统:
完整的代码如下所示:
emit_log_direct脚本
package main
import (
"fmt"
"log"
"os"
"strings"
"github.com/streadway/amqp"
)
func failOnError(err error, msg string) {
if err != nil {
log.Fatalf("%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_logs_direct脚本
package main
import (
"fmt"
"log"
"os"
"github.com/streadway/amqp"
)
func failOnError(err error, msg string) {
if err != nil {
log.Fatalf("%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 usused
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")
forever := make(chan bool)
go func() {
for d := range msgs {
log.Printf(" [x] %s", d.Body)
}
}()
log.Printf(" [*] Waiting for logs. To exit press CTRL+C")
<-forever
}
使用下面命令运行:
go run receive_logs_direct.go info warning error
go run receive_logs_direct.go warning error
go run emit_log_direct.go error "Run. Run. Or it will explode."