Golang RabbitMQ Demo

hiword發表於2019-12-17

AMQP協議

AMQP(Advanced Message Queuing Protocol,高階訊息佇列協議)是一個程式間傳遞非同步訊息的網路協議。

RabbitMQ 就是 amqp 協議的Erlang的實現。

AMQP的模型架構的主要角色,生產者、消費者、交換器、佇列。

生產者、消費者、服務節點

  • 生產者(Producter) 訊息投遞方
  • 消費者(Consumer) 訊息接收方
  • 服務節點(Broker) 訊息的服務節點,基本上可以簡單的把一個broker看成一臺訊息伺服器

Golang RabbitMQ Demo

交換器、佇列、繫結

繫結

Rabbitmq中需要路由鍵和繫結鍵聯合使用才能使生產者成功投遞到佇列中去。

  • RoutingKey 生產者傳送給交換器繫結的Key
  • BindingKey 交換器和佇列繫結的Key

生產者將訊息投遞到交換器,通過交換器繫結的佇列,最終投遞到對應的佇列中去。

Golang RabbitMQ Demo

交換器

Rabbitmq共有4種交換器

  • fanout 把訊息投遞到所有與此交換器繫結的佇列中
  • direct 把訊息投遞到 BindingKey 和 RoutingKey 完全匹配的佇列中
  • topic 規則匹配,BindingKey中存在兩種特殊字元
    • *匹配零個或多個單詞
    • #匹配一個單詞
  • header 不依賴於RoutingKey而是通過訊息體中的headers屬性來進行匹配繫結,通過headers中的key和BindingKey完全匹配,由於效能較差一般用的比較少。

基本使用

在Golang中建立rabbitmq 生產者基本步驟是:

  1. 連線Connection
  2. 建立Channel
  3. 建立或連線一個交換器
  4. 建立或連線一個佇列
  5. 交換器繫結佇列
  6. 投遞訊息
  7. 關閉Channel
  8. 關閉Connection

連線

// connection
connection, err := amqp.Dial("amqp://guest:guest@localhost:5672/")
if err != nil {
    panic(err)
}

// channel
channel, err := connection.Channel()
if err != nil {
    panic(err)
}

建立一個交換器

if err = channel.ExchangeDeclare("e1", "direct", true, false, false, true, nil); err != nil {
    panic(err)
}

引數解析:

  • name 交換機名稱
  • kind 交換機型別
  • durable 持久化
  • autoDelete 是否自動刪除
  • internal 是否是內建交換機
  • noWait 是否等待伺服器確認
  • args 其它配置

引數說明要點:

  • autoDelete:
    自動刪除功能必須要在交換器曾經繫結過佇列或者交換器的情況下,處於不再使用的時候才會自動刪除,
    如果是剛剛建立的尚未繫結佇列或者交換器的交換器或者早已建立只是未進行佇列或者交換器繫結的交換器是不會自動刪除的。
  • internal:
    內建交換器是一種特殊的交換器,這種交換器不能直接接收生產者傳送的訊息,
    只能作為類似於佇列的方式繫結到另一個交換器,來接收這個交換器中路由的訊息,
    內建交換器同樣可以繫結佇列和路由訊息,只是其接收訊息的來源與普通交換器不同。
  • noWait
    當noWait為true時,宣告時無需等待伺服器的確認。
    該通道可能由於錯誤而關閉。 新增一個NotifyClose偵聽器應對任何異常。

建立交換器還有一個差不多的方法(ExchangeDeclarePassive),他主要是假定交換已存在,並嘗試連線到
不存在的交換將導致RabbitMQ引發異常,可用於檢測交換的存在。

建立一個佇列

if _, err := channel.QueueDeclare("q1", true, false, false, true, nil); err != nil {
    panic(err)
}

引數解析:

  • name 佇列名稱
  • durable 持久化
  • autoDelete 自動刪除
  • exclusive 排他
  • noWait 是否等待伺服器確認
  • args Table

引數說明要點:

  • exclusive 排他
    排他佇列只對首次建立它的連線可見,排他佇列是基於連線(Connection)可見的,並且該連線內的所有通道(Channel)都可以訪問這個排他佇列,在這個連線斷開之後,該佇列自動刪除,由此可見這個佇列可以說是綁到連線上的,對同一伺服器的其他連線不可見。
    同一連線中不允許建立同名的排他佇列的
    這種排他優先於持久化,即使設定了佇列持久化,在連線斷開後,該佇列也會自動刪除。
    非排他佇列不依附於連線而存在,同一伺服器上的多個連線都可以訪問這個佇列。

  • autoDelete 設定是否自動刪除。
    為true則設定佇列為自動刪除。
    自動刪除的前提是:至少有一個消費者連線到這個佇列,之後所有與這個佇列連線的消費者都斷開時,才會自動刪除。
    不能把這個引數錯誤地理解為:"當連線到此佇列的所有客戶端斷開時,這個佇列自動刪除",因為生產者客戶端建立這個佇列,或者沒有消費者客戶端與這個佇列連線時,都不會自動刪除這個佇列。

建立佇列還有一個差不多的方法(QueueDeclarePassive),他主要是假定佇列已存在,並嘗試連線到
不存在的佇列將導致RabbitMQ引發異常,可用於檢測佇列的存在。

繫結交換器和佇列

if err = channel.QueueBind("q1", "q1Key", "e1", true, nil); err != nil {
    panic(err)
}

引數解析:

  • name 佇列名稱
  • key BindingKey 根據交換機型別來設定
  • exchange 交換機名稱
  • noWait 是否等待伺服器確認
  • args Table

繫結交換器

if err = channel.ExchangeBind("dest", "q1Key", "src", false, nil); err != nil {
    panic(err)
}

引數解析:

  • destination 目的交換器
  • key RoutingKey 路由鍵
  • source 源交換器
  • noWait 是否等待伺服器確認
  • args Table 其它引數

生產者傳送訊息至交換器source中,交換器source根據路由鍵找到與其匹配的另一個交換器destination,井把訊息轉發到destination中,進而儲存在.destination繫結的佇列queue中,某種程度上來說destination交換器可以看作一個佇列。如圖:

Golang RabbitMQ Demo

投遞訊息

if err = channel.Publish("e1", "q1Key", true, false, amqp.Publishing{
    Timestamp:   time.Now(),
    ContentType: "text/plain",
    Body:        []byte("Hello Golang and AMQP(Rabbitmq)!"),
}); err != nil {
    panic(err)
}

引數解析:

  • exchange 交換器名稱
  • key RouterKey
  • mandatory 是否為無法路由的訊息進行返回處理
  • immediate 是否對路由到無消費者佇列的訊息進行返回處理 RabbitMQ 3.0 廢棄
  • msg 訊息體

引數說明要點:

  • mandatory
    訊息釋出的時候設定訊息的 mandatory 屬性用於設定訊息在傳送到交換器之後無法路由到佇列的情況對訊息的處理方式,
    設定為 true 表示將訊息返回到生產者,否則直接丟棄訊息。

  • immediate
    引數告訴伺服器至少將該訊息路由到一個佇列中,否則將訊息返回給生產者。imrnediate引數告訴伺服器,如果該訊息關聯的佇列上有消費者,則立刻投遞:如果所有匹配的佇列上都沒有消費者,則直接將訊息返還給生產者,不用將訊息存入佇列而等待消費者了。

RabbitMQ 3.0版本開始去掉了對imrnediate引數的支援

消費資訊

Rabbitmq消費方式共有2種,分別是推模式和拉模式

推模式是通過持續訂閱的方式來消費資訊,
Consume將通道(Channel)直為接收模式,直到取消佇列的訂閱為止。在接收模式期間,RabbitMQ會不斷地推送訊息給消費者。
推送訊息的個數還是會受到channel.Qos的限制

deliveries, err := channel.Consume("q1", "any", false, false, false, true, nil)
if err != nil {
    panic(err)
}

如果ack設定為false則表示需要手動進行ack消費

v, ok := <-deliveries
if ok {
    // 手動ack確認
    // 注意: 這裡只要呼叫了ack就是手動確認模式,
    // multiple 表示的是在此channel中先前所有未確認的deliveries都將被確認
    // 並不是表示設定為false就不進行當前ack確認
    if err := v.Ack(true); err != nil {
        fmt.Println(err.Error())
    }
} else {
    fmt.Println("Channel close")
}

引數解析:

  • queue 佇列名稱
  • consumer 訊息者名稱
  • autoAck 是否確認消費
  • exclusive 排他
  • noLocal
  • noWait bool
  • args Table

引數說明要點:

  • noLocal
    設定為true則表示不能將同一個Connection中生產者傳送的訊息傳送給這個Connection中的消費者

拉模式:
相對來說比較簡單,是由消費者主動拉取資訊來消費,同樣也需要進行ack確認消費

channel.Get(queue string, autoAck bool)

簡單示例Demo

下面是一個簡單示例,只是為了通訊測試,單條資料收發

func Connection() (*amqp.Connection) {
    conn, err := amqp.Dial("amqp://guest:guest@localhost:5672/")
    if err != nil {
        panic(err)
    }
    return conn
}

func Sample() {
    var wg sync.WaitGroup
    wg.Add(1)
    go SampleConsumption(&wg)

    // 建立連線
    connection := Connection()
    defer connection.Close()

    // 開啟 channel
    channel, err := connection.Channel()
    if err != nil {
        panic(err)
    }

    defer channel.Close()

    if err = channel.ExchangeDeclare("e1", "direct", true, false, false, true, nil); err != nil {
        panic(err)
    }

    if _, err := channel.QueueDeclare("q1", true, false, false, true, nil); err != nil {
        panic(err)
    }

    if err = channel.QueueBind("q1", "q1Key", "e1", true, nil); err != nil {
        panic(err)
    }

    // mandatory true 未找到佇列返回給消費者
    returnChan := make(chan amqp.Return,0)
    channel.NotifyReturn(returnChan)

    // Publish
    if err = channel.Publish("e1", "q1Key", true, false, amqp.Publishing{
        Timestamp:   time.Now(),
        ContentType: "text/plain",
        Body:        []byte("Hello Golang and AMQP(Rabbitmq)!"),
    }); err != nil {
        panic(err)
    }

    //for v := range returnChan{
    //  fmt.Printf("Return %#v\n",v)
    //}

    wg.Wait()
}

func SampleConsumption(wg *sync.WaitGroup) {
    connection := Connection()
    defer connection.Close()

    channel, err := connection.Channel()
    if err != nil {
        panic(err)
    }

    defer channel.Close()

    deliveries, err := channel.Consume("q1", "any", false, false, false, true, nil)
    if err != nil {
        panic(err)
    }

    // 這裡只取一條,因為product只發一條
    v, ok := <-deliveries
    if ok {
        if err := v.Ack(true); err != nil {
            fmt.Println(err.Error())
        }
    } else {
        fmt.Println("Channel close")
    }
    wg.Done()
}

文章來源:https://blog.crcms.cn/2019/09/29/go-ioc/

本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章