消息队列MQ-Rabbitmq

Rabbitmq简介

AMQP,即Advanced Message Queuing Protocol,高级消息队列协议,是应用层协议的一个开放标准,为面向消息的中间件设计。消息中间件主要用于组件之间的解耦,消息的发送者无需知道消息使用者的存在,反之亦然。 AMQP的主要特征是面向消息、队列、路由(包括点对点和发布/订阅)、可靠性、安全。 RabbitMQ是一个开源的AMQP实现,服务器端用Erlang语言编写,支持多种客户端,如:Python、Ruby、.NET、Java、JMS、C、PHP、ActionScript、XMPP、STOMP等,支持AJAX。用于在分布式系统中存储转发消息,在易用性、扩展性、高可用性等方面表现不俗。 下面将重点介绍RabbitMQ中的一些基础概念,了解了这些概念,是使用好RabbitMQ的基础。

Rabbitmq基础概念及架构

基础组成:

  • 队列Queue
  • Exchange
  • Bingding
  • bingding key
  • routing key
  • exchange type

消息机制

  • 消息确认机制 Message Acknowledgment
  • 消息持久化 Message durability
  • 消息分配 Prefetch count

基础组件

队列Queue

Queue(队列)是RabbitMQ的内部对象,用于存储消息,用下图表示。

FIHmnV3yUzkunbEH.png!thumbnail

RabbitMQ中的消息都只能存储在Queue中,生产者(下图中的P)生产消息并最终投递到Queue中,消费者(下图中的C)可以从Queue中获取消息并消费。

 

SAi5PKQWcEAQRLnF.png!thumbnail

多个消费者可以订阅同一个Queue,这时Queue中的消息会被平均分摊给多个消费者

行处理,而不是每个消费者都收到所有的消息并处理。 

 

pdCKtv2ketQlLVt4.png!thumbnail

Exchange

我们看到生产者将消息投递到Queue中,实际上这在RabbitMQ中这种事情永远都不会发生。实际的情况是,生产者将消息发送到Exchange(交换器,下图中的X),由Exchange将消息路由到一个或多个Queue中(或者丢弃)。 

FLlbWjl8busOkjTO.png!thumbnail

Exchange是按照什么逻辑将消息路由到Queue的?RabbitMQ中的Exchange有四种类型,不同的类型有着不同的路由策略。而exchange如何来识别到消息应该属于那种策略,然后根据路由的策略,分发到具体哪个队列里Queue里面。这部分就需要通过bingding,bingding key(队列),routing key(消息)来绑定识别消息及路由策略的匹配。

Binding

RabbitMQ中通过Binding将Exchange与Queue关联起来,这样RabbitMQ就知道如何正确地将消息路由到指定的Queue了。在创建bingding的时候,需要制定exchange,queue。bingding的主要目的,就是为了让exchange中的消息有地可去。而每一

 

AisbO4lr4Y4qmVym.png!thumbnail

Binding key

在绑定(Binding)Exchange与Queue的同时,一般会指定一个binding key;消费者将消息发送给Exchange时,一般会指定一个routing key;当binding key与routing key相匹配时,消息将会被路由到对应的Queue中。在绑定多个Queue到同一个Exchange的时候,这些Binding允许使用相同的binding key。 binding key 并不是在所有情况下都生效,它依赖于Exchange Type,比如fanout类型的Exchange就会无视binding key,而是将消息路由到所有绑定到该Exchange的Queue。

PS:针对同种策略的Exchange,可根据bingding key的不同,exchange来路由到不同的Queue中。

routing key

生产者在将消息发送给Exchange的时候,一般会指定一个routing key,来指定这个消息的路由规则,而这个routing key需要与Exchange Type及binding key联合使用才能最终生效。 在Exchange Type与binding key固定的情况下(在正常使用时一般这些内容都是固定配置好的),我们的生产者就可以在发送消息给Exchange时,通过指定routing key来决定消息流向哪里。 RabbitMQ为routing key设定的长度限制为255 bytes。

Exchange Types

RabbitMQ常用的Exchange Type有fanout、direct、topic、headers这四种,分别对应的是全部队列,完全匹配,主题匹配,消息包头。

fanout

fanout类型的Exchange路由规则非常简单,它会把所有发送到该Exchange的消息路由到所有与它绑定的Queue中。

 

5dSZCfzczAQc6M0F.png!thumbnail

上图中,生产者(P)发送到Exchange(X)的所有消息都会路由到图中的两个Queue,并最终被两个消费者(C1与C2)消费。

适用场景:需要将消息以广播的形式扩散出去。让多个消费者(模块)都能够接收到。消息对于每个消费者都是无差别。

direct

direct类型的Exchange路由规则也很简单,它会把消息路由到那些binding key与routing key完全匹配的Queue中。 

0ISYuwUUXgYbWg9O.png!thumbnail

以上图的配置为例,我们以routingKey=”error”发送消息到Exchange,则消息会路由到Queue1(amqp.gen-S9b…,这是由RabbitMQ自动生成的Queue名称)和Queue2(amqp.gen-Agl…)。并且在这个exchenga和queue进行bingding的时候,设置了对应的bingding key(error)。所以当direct的routing key为error时,消息将会被路由到对应的queue上。

如果我们以routingKey=”info”或routingKey=”warning”来发送消息,则消息只会路由到Queue2。如果我们以其他routingKey发送消息,则消息不会路由到这两个Queue中。

topic

与direct直接完全匹配不同。topic类型的Exchange在匹配规则上进行了扩展,支持通过通配符的形式进行匹配。同样是将消息路由到binding key与routing key相匹配的Queue中,但这里的匹配规则有些不同,它约定:

  • routing key为一个句点号“. ”分隔的字符串(我们将被句点号“. ”分隔开的每一段独立的字符串称为一个单词),如“stock.usd.nyse”、“nyse.vmw”、“quick.orange.rabbit”
  • binding key与routing key一样也是句点号“. ”分隔的字符串
  • binding key中可以存在两种特殊字符“*”与“#”,用于做模糊匹配,其中“*”用于匹配一个单词“#”用于匹配多个单词(可以是零个)

2fE7AoL4rQYzpSno.png!thumbnail

基于上图配置:routingKey=”quick.orange.rabbit”的消息会同时路由到Q1与Q2,routingKey=”lazy.orange.fox”的消息会路由到Q1与Q2,routingKey=”lazy.brown.fox”的消息会路由到Q2,routingKey=”lazy.pink.rabbit”的消息会路由到Q2(只会投递给Q2一次,虽然这个routingKey与Q2的两个bindingKey都匹配)

没有匹配到的消息,如routingKey=”orange”的消息将会被丢弃,因为它们没有匹配任何bindingKey。

消息机制

消息确认 Message acknowledgment

在实际应用中,可能会发生消费者收到Queue中的消息,但没有处理完成就宕机(或出现其他意外)的情况,这种情况下就可能会导致消息丢失。为了避免这种情况发生,我们可以要求消费者在消费完消息后发送一个回执给RabbitMQ,RabbitMQ收到消息回执(Message acknowledgment)后才将该消息从Queue中移除;如果RabbitMQ没有收到回执并检测到消费者的RabbitMQ连接断开,则RabbitMQ会将该消息发送给其他消费者(如果存在多个消费者)进行处理。这里不存在timeout概念,一个消费者处理消息时间再长也不会导致该消息被发送给其他消费者,除非它的RabbitMQ连接断开。 这里会产生另外一个问题,如果我们的开发人员在处理完业务逻辑后,忘记发送回执给RabbitMQ,这将会导致严重的bug——Queue中堆积的消息会越来越多;消费者重启后会重复消费这些消息并重复执行业务逻辑。

单消息被消费时,消费者均需要对消息做确认。队列在消息未被消费者确认前将不会讲消息重复发给其他消费者。当且仅当,消息消费者未确认,且消费者的rabbitmq连接已断开,才会将未确认的消息发送给其他消费者。这里保证了消息不重发,消息不漏发。

消息持久化 Message durability

如果我们希望即使在RabbitMQ服务重启的情况下,也不会丢失消息,我们可以将Queue与Message都设置为可持久化的(durable),这样可以保证绝大部分情况下我们的RabbitMQ消息不会丢失。但依然解决不了小概率丢失事件的发生(比如RabbitMQ服务器已经接收到生产者的消息,但还没来得及持久化该消息时RabbitMQ服务器就断电了)。

消息分配 Prefetch count

前面我们讲到如果有多个消费者同时订阅同一个Queue中的消息,Queue中的消息会被平摊给多个消费者。这时如果每个消息的处理时间不同,就有可能会导致某些消费者一直在忙,而另外一些消费者很快就处理完手头工作并一直空闲的情况。我们可以通过设置prefetchCount来限制Queue每次发送给每个消费者的消息数,比如我们设置prefetchCount=1,则Queue每次给每个消费者发送一条消息;消费者处理完这条消息后Queue会再给该消费者发送一条消息。

83cXnDimtwwcDMnU.png!thumbnail

Rabbitmq的消息机制及场景

将会按照mq的使用场景,也及mq本的消息特性来介绍消息的机制。以及实际的运用场景。详细介绍如何利用MQ来实现需求、解决问题。主要包含有以下几个场景及问题:

  • 任务队列
  • 发布/订阅
  • RPC

任务队列

任务队列的核心思想是为了避免对需要大量计算时间的任务的等待。也为了让接收任务分发任务的角色能够尽快的完成任务分配。此时我们就需要通过MQ,来作为任务的队列。来解耦任务的分发者和任务执行者。任务分发者,将任务内容放到“任务队列”中去。然后可以创建多个任务执行者,从任务队列中去接收任务内容。多个任务执行者是平等的,共同消费任务队列中的任务。

Rs7SWjkdMgwBgNJR.png!thumbnail

正常情况下任务消息会平均分配到队列的多个消费者。为了避免某个worker消费者未消费完成上一个任务,并进行确认。分发者又将任务发送过来,导致已经繁忙的消费者worker的任务(消息)堆积。此时设置prefetch=1表示预取值(woker),表示能够一下子获取到多少个任务,党任务被完成后,才能够将新任务再分发给消费者。

NaFILgTLLaEIKKqM.png!thumbnail

发布订阅

工作队列是将任务平均分配个工作进程。发布订阅的差异就在于,发布订阅是将消息发送给所有的订阅者。每个订阅者都会接收到所有的消息。并且这里的消息发布者,不会直接将消息投递到具体的队列中。而是将消息递交给Exchange,再由Exchang按照不同的类型,将我们的消息转发给具体的队列进行消费。

生产者只能向Exchange发送消息。交换是一件非常简单的事情。一方面,它接收来自生产者的消息,另一方面将它们推送到队列。Exchange必须确切知道如何处理它收到的消息。它应该附加到特定队列吗?它应该附加到许多队列吗?或者它应该被丢弃。其规则由交换类型定义 。There are a few exchange types available: direct, topic, headers and fanout. We'll focus on the last one -- the fanout.

发布/订阅模式,主要适用Exchange的fanout即类似广播的形式。

RknpdBSJVjML1U3z.png!thumbnail

临时队列

例如当我们的场景是收集日志,并且只关系新的日志不关心已处理过的旧内容时。且我们希望是订阅/发布(广播)的形式的时候。此时我们就不要再给每个队列创建具体的名称。队列可以随时创建,随时删除。每个队列后面就是一个具体的消费者。所有的消费者,都有属于自己的队列。所有队列都会接收到Exchange路由过来的消息。

此时,我们就需要创建临时队列,创建的临时队列需要能够在不使用的时候,自动删除。临时队列的名称会有exchange取一个随机值,例如:amq.gen-JzTY20BRgKO-HjmUJj0wLg.

Binding

为让exchange把对应的消息发送给queue,需要再创建queue的时候,把exchange和queue绑定在一起。

Routing Key

routing key在fanout类型的exchange会被忽略。routing key主要用于exchange的路由决策,而fanout的exchange并不需要决策,而是将消息发送给所有队列。

Direct

以上我们提到订阅发布,以广播的方式进行消息的传送。而我们需求的场景,有时是需要进行过滤的。例如我仅仅需要消息中的一部分。此时在创建exchange的时候,我们就需要给exchagne指定type为direct。并且绑定Queue加上一个routing key,以此来区分我们的队列。同时在消息发布的时候,添加上一个。

ncqdeT11FO0dhYca.png!thumbnail

而当我们使用同一个routing key绑定了多个Queue的时候,此时消息的分发模式和fanout是一样的,使用广播的形式。

lTI1ClxbVnsIDMvR.png!thumbnail

Topic路由

在topic路由模式中,message同样是通过匹配routing key和messge key的方式进行路由。和直接路由不同的是。topic模式的匹配是支持通配形式的。并且主题有一定的要求。用.点来分割连接单词。其中通过*代表1个单词的匹配。 #代表多个单词的匹配。

topic.png?version=1&modificationDate=1564122218597&api=v2

RPC

MQ本身是基于异步的消息处理,前面的示例中所有的生产者(P)将消息发送到RabbitMQ后不会知道消费者(C)处理成功或者失败(甚至连有没有消费者来处理这条消息都不知道)。 但实际的应用场景中,我们很可能需要一些同步处理,需要同步等待服务端将我的消息处理完成后再进行下一步处理。这相当于RPC(Remote Procedure Call,远程过程调用)。在RabbitMQ中也支持RPC。

P0Jk2lVtcbw1yG3H.png!thumbnail

 RabbitMQ  中实现RPC 的机制是:

  • 客户端发送请求(消息)时,在消息的属性(MessageProperties ,在AMQP 协议中定义了14中properties ,这些属性会随着消息一起发送)中设置两个值replyTo (一个Queue 名称,用于告诉服务器处理完成后将通知我的消息发送到这个Queue 中)和correlationId (此次请求的标识号,服务器处理完成后需要将此属性返还,客户端将根据这个id了解哪条请求被成功执行了或执行失败
  • 服务器端收到消息并处理
  • 服务器端处理完消息后,将生成一条应答消息到replyTo 指定的Queue ,同时带上correlationId 属性
  • 客户端之前已订阅replyTo 指定的Queue ,从中收到服务器的应答消息后,根据其中的correlationId属性分析哪条请求被执行了,根据执行结果进行后续业务处理

Rabbitmq消息机制代码示例golang

默认exchange

当我们未指定exchange 和bingding关系,直接创建Queue使用时,默认rabbitmq会将这个queue关联到默认exchange上,所有发送的消息会被均衡消费者消费掉。

发送者代码:send.go

package main
 
 
import (
        "log"
 
 
        "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://admin:admin123@172.24.185.19: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),
                })
        log.Printf(" [x] Sent %s", body)
        failOnError(err, "Failed to publish a mess
}
//------接受者-------

package main
 
import (
        "log"
        "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://admin:admin123@172.24.185.19: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")
 
 
        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
}

任务队列

任务队列的核心思想是为了避免对需要大量计算时间的任务的等待。也为了让接收任务分发任务的角色能够尽快的完成任务分配。此时我们就需要通过MQ,来作为任务的队列。来解耦任务的分发者和任务执行者。任务分发者,将任务内容放到“任务队列”中去。然后可以创建多个任务执行者,从任务队列中去接收任务内容。多个任务执行者是平等的,共同消费任务队列中的任务。

任务分发:

package main
 
import (
    "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()
 
 
    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
}

//----------任务队列的任务执行者-------
package main
 
 
import (
    "bytes"
    "log"
    "time"
 
 
    "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()
 
 
    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")
 
 
    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")
 
 
    forever := make(chan bool)
 
    go func() {
        for d := range msgs {
            log.Printf("Received a message: %s", d.Body)
            dot_count := bytes.Count(d.Body, []byte("."))
            t := time.Duration(dot_count)
            time.Sleep(t * time.Second)
            log.Printf("Done")
            d.Ack(false)  //在完成任务内容后,进行消息手动确认。
        }
    }()
 
    log.Printf(" [*] Waiting for messages. To exit press CTRL+C")
     
}

发布订阅

发布者代码:

package main
 
 
import (
    "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()
   //创建Exchange,并定义为fanout广播的类型。
    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 fanout不需要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
}

订阅者代码:

package main
 
 
import (
    "log"
 
 
    "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",   // 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 将队列与exchange绑定在一起
        "",     // 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")
 
 
    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")
}

 

topic消息处理

发布者代码:

package main
 
 
import (
    "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_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
}
接受者代码:

package main
 
 
import (
    "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_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 指定绑定的routing key和消息推送的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")
 
 
    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")
}

小结:

rabbitmq支持多种消息消费场景,其中主要涉及到Exchange,Exchange type。routing key,bingding。 在多种不同场景下会涉及到不同的消息机制例如:消息确认、消息持久化。对于不同的Exchang类型,会有针对不同的场景如:fanout广播对应任务队列,direct完全匹配-对应日志处理,topic路由匹配-对应消息的订阅发布。

转载于:https://my.oschina.net/u/4156317/blog/3079524

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值