我們一起來學RabbitMQ 二:RabbiMQ 的 6 種模式的基本應用

小魔童哪吒發表於2021-09-30

嗨,大家好,我是小魔童哪吒,我們們從今天開始進入開源元件的學習,一邊學習一邊總結一邊分享

文章提綱如下:

  • RabbitMQ 成員組成
  • RabbitMQ 的六種工作模式編碼

RabbitMQ 成員組成

  • 生產者 producer
  • 消費者 consumer
  • 交換機 exchange

用於接受、分配訊息

  • 訊息 message
  • 佇列 queue

用於儲存生產者的訊息

  • 通道 channel AMQP

訊息推送使用的通道

  • 連線 connections

生成者或者消費者與Rabbit 建立的TCP 連線

  • 路由鍵 routingKey

用於把生成者的資料分配到交換器上

  • 繫結鍵 BindingKey

用於把交換器的訊息繫結到佇列上

  • 連線管理器 ConnectionFactory

應用程式與 Rabbit 之間建立連線的管理器,程式程式碼中使用

RabbitMQ 的六種工作模式編碼

single 模式

  • 訊息產生者將訊息放入佇列
  • 訊息的消費者監聽訊息佇列,如果佇列中有訊息就消費掉

目錄如下:

.
├── consumer.go
├── go.mod
├── go.sum
├── main.go
└── xmtmq
    └── xmtmq.go

實際編碼如下:

每種模式的編碼思路如下:

生產者 / 消費者

  • 連線 RabbitMQ 的 server
  • 初始化連線 connection
  • 初始化通道 channel
  • 初始化交換機 exchange
  • 初始化佇列 queue
  • 使用路由key,繫結佇列 bind , key
  • 生產訊息 / 消費訊息 produce , consume

訊息xmtmq.go

package xmtmq

import (
   "github.com/streadway/amqp"
   "log"
)
// single 模式
// 定義 RabbitMQ 的資料結構
// go get github.com/streadway/amqp

type RabbitMQ struct {
   conn      *amqp.Connection // 連線
   channel   *amqp.Channel    // 通道
   QueueName string           // 佇列名
   Exchange  string           // 交換機
   Key       string           // 路由鍵
   MQUrl     string           // MQ的虛擬機器地址
}

// New 一個 RabbitMQ
func NewRabbitMQ(rbt *RabbitMQ) {
   if rbt == nil || rbt.QueueName == ""  || rbt.MQUrl == "" {
      log.Panic("please check QueueName,Exchange,MQUrl ...")
   }

   conn, err := amqp.Dial(rbt.MQUrl)
   if err != nil {
      log.Panicf("amqp.Dial error : %v", err)
   }
   rbt.conn = conn

   channel, err := rbt.conn.Channel()
   if err != nil {
      log.Panicf("rbt.conn.Channel error : %v", err)
   }
   rbt.channel = channel
}


func RabbitMQFree(rbt *RabbitMQ){
   if rbt == nil{
      log.Printf("rbt is nil,free failed")
      return
   }
   rbt.channel.Close()
   rbt.conn.Close()
}
func (rbt *RabbitMQ) Init() {
   // 申請佇列
   _, err := rbt.channel.QueueDeclare(
      rbt.QueueName, // 佇列名
      true,          // 是否持久化
      false,         // 是否自動刪除
      false,         // 是否排他
      false,         // 是否阻塞
      nil,           // 其他引數
   )
   if err != nil {
      log.Printf("rbt.channel.QueueDeclare error : %v", err)
      return
   }
}


// 生產訊息

func (rbt *RabbitMQ) Produce(data []byte) {

   // 向佇列中加入資料
   err := rbt.channel.Publish(
      rbt.Exchange,        // 交換機
      rbt.QueueName,       // 佇列名
      false,    // 若為true,根據自身exchange型別和routekey規則無法找到符合條件的佇列會把訊息返還給傳送者
      false,    // 若為true,當exchange傳送訊息到佇列後發現佇列上沒有消費者,則會把訊息返還給傳送者
      amqp.Publishing{
         ContentType: "text/plain",
         Body:        data,
      },
   )
   if err != nil {
      log.Printf("rbt.channel.Publish error : %v", err)
      return
   }
   return
}

// 消費訊息
func (rbt *RabbitMQ) Consume() {

   // 消費資料
   msg, err := rbt.channel.Consume(
      rbt.QueueName,    // 佇列名
      "xmt",    // 消費者的名字
      true,     // 是否自動應答
      false,    // 是否排他
      false,    // 若為true,表示 不能將同一個Conenction中生產者傳送的訊息傳遞給這個Connection中 的消費者
      false,    // 是否阻塞
      nil,         // 其他屬性
   )

   if err != nil {
      log.Printf("rbt.channel.Consume error : %v", err)
      return
   }

   for data := range msg {
      log.Printf("received data is %v", string(data.Body))
   }

}

main.go

package main

import (
   "fmt"
   "log"
   "time"
   "xmt/xmtmq"
)

/*
RabbimtMQ single 模式 案例
應用場景:簡單訊息佇列的使用,一個生產者一個消費者
生產訊息
*/

func main() {
    // 設定日誌
   log.SetFlags(log.Llongfile | log.Ltime | log.Ldate)

   rbt := &xmtmq.RabbitMQ{
      QueueName: "xmtqueue",
      MQUrl:     "amqp://guest:guest@127.0.0.1:5672/xmtmq",
   }

   xmtmq.NewRabbitMQ(rbt)

   var index = 0

   for {
       // 生產訊息
      rbt.Produce([]byte(fmt.Sprintf("hello wolrd %d ", index)))
      log.Println("傳送成功 ", index)
      index++
      time.Sleep(1 * time.Second)
   }

}

consumer.go

package main

import (
   "log"
   "xmt/xmtmq"
)

func main() {

   log.SetFlags(log.Llongfile | log.Ltime | log.Ldate)

   rbt := &xmtmq.RabbitMQ{
      QueueName: "xmtqueue",
      MQUrl:     "amqp://guest:guest@127.0.0.1:5672/xmtmq",
   }

   xmtmq.NewRabbitMQ(rbt)
   rbt.Consume()
}

執行的時候,開啟2個終端

終端1:go run main.go

終端2:go run consumer.go

work 模式

多個消費端消費同一個佇列中的訊息,佇列採用輪詢的方式將訊息是平均傳送給消費者,此處的資源是競爭關係

當生產者生產訊息的速度大於消費者消費的速度,就要考慮用 work 工作模式,這樣能提高處理速度提高負載

work 模式與 single 模式類似, 只是work 模式比 single 模式多了一些消費者

基於single 模式,開一個終端3 : go run consumer.go

publish / subscribe 模式

publish / subscribe 釋出訂閱模式 , 相對於Work queues模式多了一個交換機,此處的資源是共享的

用於場景

  • 郵件群發
  • 群聊天
  • 廣播(廣告等)

目錄和上述編碼保持一致:

xmtmq.go

開始用到交換機 exchange ,fanout 型別

生產端先把訊息傳送到交換機,再由交換機把訊息傳送到繫結的佇列中,每個繫結的佇列都能收到由生產端傳送的訊息

package xmtmq

import (
   "github.com/streadway/amqp"
   "log"
)

// publish 模式
// 定義 RabbitMQ 的資料結構
// go get github.com/streadway/amqp

type RabbitMQ struct {
   conn      *amqp.Connection // 連線
   channel   *amqp.Channel    // 通道
   QueueName string           // 佇列名
   Exchange  string           // 交換機
   Key       string           // 路由鍵
   MQUrl     string           // MQ的虛擬機器地址
}

// New 一個 RabbitMQ
func NewRabbitMQ(rbt *RabbitMQ) {
   if rbt == nil || rbt.Exchange == "" || rbt.MQUrl == "" {
      log.Panic("please check Exchange,MQUrl ...")
   }

   conn, err := amqp.Dial(rbt.MQUrl)
   if err != nil {
      log.Panicf("amqp.Dial error : %v", err)
   }
   rbt.conn = conn

   channel, err := rbt.conn.Channel()
   if err != nil {
      log.Panicf("rbt.conn.Channel error : %v", err)
   }
   rbt.channel = channel
}

func RabbitMQFree(rbt *RabbitMQ) {
   if rbt == nil {
      log.Printf("rbt is nil,free failed")
      return
   }

   rbt.channel.Close()
   rbt.conn.Close()
}

func (rbt *RabbitMQ) Init() {
   // 1、建立交換機
   err := rbt.channel.ExchangeDeclare(
      rbt.Exchange,        // 交換機
      amqp.ExchangeFanout, // 交換機型別
      true,                // 是否持久化
      false,               //是否自動刪除
      false,               //true表示這個exchange不可以被client用來推送訊息,僅用來進行exchange和exchange之間的繫結
      false,               // 是否阻塞
      nil,                 // 其他屬性
   )
   if err != nil {
      log.Printf("rbt.channel.ExchangeDeclare error : %v", err)
      return
   }

}

// 生產訊息 publish

func (rbt *RabbitMQ) PublishMsg(data []byte) {

   // 1、向佇列中加入資料
   err := rbt.channel.Publish(
      rbt.Exchange, // 交換機
      "",           // 佇列名
      false,        // 若為true,根據自身exchange型別和routekey規則無法找到符合條件的佇列會把訊息返還給傳送者
      false,        // 若為true,當exchange傳送訊息到佇列後發現佇列上沒有消費者,則會把訊息返還給傳送者
      amqp.Publishing{
         ContentType: "text/plain",
         Body:        data,
      },
   )
   if err != nil {
      log.Printf("rbt.channel.Publish error : %v", err)
      return
   }
   return

}

// 消費訊息
func (rbt *RabbitMQ) SubscribeMsg() {

   // 1、建立佇列
   q, err := rbt.channel.QueueDeclare(
      "", // 此處我們傳入的是空,則是隨機產生佇列的名稱
      true,
      false,
      false,
      false,
      nil,
   )
   if err != nil {
      log.Printf("rbt.channel.QueueDeclare error : %v", err)
      return
   }

   // 2、繫結佇列
   err = rbt.channel.QueueBind(
      q.Name,       // 佇列名字
      "",           // 在publish模式下,這裡key 為空
      rbt.Exchange, // 交換機名稱
      false,        // 是否阻塞
      nil,          // 其他屬性
   )
   if err != nil {
      log.Printf("rbt.channel.QueueBind error : %v", err)
      return
   }

   // 3、消費資料
   msg, err := rbt.channel.Consume(
      q.Name, // 佇列名
      "xmt",  // 消費者的名字
      true,   // 是否自動應答
      false,  // 是否排他
      false,  // 若為true,表示 不能將同一個Conenction中生產者傳送的訊息傳遞給這個Connection中 的消費者
      false,  // 是否阻塞
      nil,    // 其他屬性
   )

   if err != nil {
      log.Printf("rbt.channel.Consume error : %v", err)
      return
   }

   for data := range msg {
      log.Printf("received data is %v", string(data.Body))
   }

}

main.go

package main

import (
   "fmt"
   "log"
   "time"
   "xmt/xmtmq"
)

/*
RabbimtMQ publish 模式 案例
應用場景:郵件群發,群聊天,廣播(廣告)
生產訊息
*/

func main() {

   log.SetFlags(log.Llongfile | log.Ltime | log.Ldate)

   rbt := &xmtmq.RabbitMQ{
      Exchange:  "xmtPubEx",
      MQUrl:     "amqp://guest:guest@127.0.0.1:5672/xmtmq",
   }

   xmtmq.NewRabbitMQ(rbt)
   rbt.Init()

   var index = 0

   for {
      rbt.PublishMsg([]byte(fmt.Sprintf("hello wolrd %d ", index)))
      log.Println("傳送成功 ", index)
      index++
      time.Sleep(1 * time.Second)
   }

   xmtmq.RabbitMQFree(rbt)

}

consumer.go

package main

import (
   "log"
   "xmt/xmtmq"
)

func main() {

   log.SetFlags(log.Llongfile | log.Ltime | log.Ldate)

   rbt := &xmtmq.RabbitMQ{
      Exchange: "xmtPubEx",
      MQUrl:     "amqp://guest:guest@127.0.0.1:5672/xmtmq",
   }

   xmtmq.NewRabbitMQ(rbt)
   rbt.SubscribeMsg()
   xmtmq.RabbitMQFree(rbt)
}

執行的操作和上述保持一致

終端1:go run main.go

終端2:go run consumer.go

終端3:go run consumer.go

效果和上述single 模式和 work模式的明顯區別是:釋出訂閱模式的案例,生產者生產的訊息,對應的消費者消費其生產的內容

routing 模式

訊息生產者將訊息傳送給交換機按照路由判斷,路由是字串 當前產生的訊息攜帶路由字元(物件的方法),交換機根據路由的key,只能匹配上路由key對應的訊息佇列,對應的消費者才能消費訊息

應用場景:從系統的程式碼邏輯中獲取對應的功能字串,將訊息任務扔到對應的佇列中業務場景,例如處理錯誤,處理特定訊息等

生產者處理流程:

宣告佇列並宣告交換機 -> 建立連線 -> 建立通道 -> 通道宣告交換機 -> 通道宣告佇列 -> 通過通道使佇列繫結到交換機並指定該佇列的routingkey(萬用字元) -> 制定訊息 -> 傳送訊息並指定routingkey(萬用字元)

消費者處理流程:

宣告佇列並宣告交換機 -> 建立連線 -> 建立通道 -> 通道宣告交換機 -> 通道宣告佇列 -> 通過通道使佇列繫結到交換機並指定routingkey(萬用字元) -> 重寫訊息消費方法 -> 執行訊息方法

目錄結構如下:

.
├── consumer2.go
├── consumer.go
├── go.mod
├── go.sum
├── main.go
└── xmtmq
    └── xmtmq.go

xmtmq.go

  • 用到交換機 為 direct 型別

  • 用到路由鍵

package xmtmq

import (
   "github.com/streadway/amqp"
   "log"
)

// routing 模式
// 定義 RabbitMQ 的資料結構
// go get github.com/streadway/amqp

type RabbitMQ struct {
   conn      *amqp.Connection // 連線
   channel   *amqp.Channel    // 通道
   QueueName string           // 佇列名
   Exchange  string           // 交換機
   Key       string           // 路由鍵
   MQUrl     string           // MQ的虛擬機器地址
}

// New 一個 RabbitMQ
func NewRabbitMQ(rbt *RabbitMQ) {
   if rbt == nil || rbt.Exchange == "" || rbt.QueueName == "" || rbt.Key == "" || rbt.MQUrl == "" {
      log.Panic("please check Exchange,,QueueName,Key,MQUrl ...")
   }

   conn, err := amqp.Dial(rbt.MQUrl)
   if err != nil {
      log.Panicf("amqp.Dial error : %v", err)
   }
   rbt.conn = conn

   channel, err := rbt.conn.Channel()
   if err != nil {
      log.Panicf("rbt.conn.Channel error : %v", err)
   }
   rbt.channel = channel
}

func RabbitMQFree(rbt *RabbitMQ) {
   if rbt == nil {
      log.Printf("rbt is nil,free failed")
      return
   }

   rbt.channel.Close()
   rbt.conn.Close()
}

func (rbt *RabbitMQ) Init() {
   // 1、建立交換機
   err := rbt.channel.ExchangeDeclare(
      rbt.Exchange, // 交換機
      amqp.ExchangeDirect,     // 交換機型別
      true,         // 是否持久化
      false,        //是否自動刪除
      false,        //true表示這個exchange不可以被client用來推送訊息,僅用來進行exchange和exchange之間的繫結
      false,        // 是否阻塞
      nil,          // 其他屬性
   )
   if err != nil {
      log.Printf("rbt.channel.ExchangeDeclare error : %v", err)
      return
   }

   // 2、建立佇列
   _, err = rbt.channel.QueueDeclare(
      rbt.QueueName, // 此處我們傳入的是空,則是隨機產生佇列的名稱
      true,
      false,
      false,
      false,
      nil,
   )
   if err != nil {
      log.Printf("rbt.channel.QueueDeclare error : %v", err)
      return
   }

   // 3、繫結佇列
   err = rbt.channel.QueueBind(
      rbt.QueueName, // 佇列名字
      rbt.Key,       // routing,這裡key 需要填
      rbt.Exchange,  // 交換機名稱
      false,         // 是否阻塞
      nil,           // 其他屬性
   )
   if err != nil {
      log.Printf("rbt.channel.QueueBind error : %v", err)
      return
   }

}

// 生產訊息 publish

func (rbt *RabbitMQ) ProduceRouting(data []byte) {

   // 1、向佇列中加入資料
   err := rbt.channel.Publish(
      rbt.Exchange, // 交換機
      rbt.Key,      // key
      false,        // 若為true,根據自身exchange型別和routekey規則無法找到符合條件的佇列會把訊息返還給傳送者
      false,        // 若為true,當exchange傳送訊息到佇列後發現佇列上沒有消費者,則會把訊息返還給傳送者
      amqp.Publishing{
         ContentType: "text/plain",
         Body:        data,
      },
   )
   if err != nil {
      log.Printf("rbt.channel.Publish error : %v", err)
      return
   }

   return
}

// 消費訊息
func (rbt *RabbitMQ) ConsumeRoutingMsg() {

   // 4、消費資料
   msg, err := rbt.channel.Consume(
      rbt.QueueName, // 佇列名
      "",     // 消費者的名字
      true,   // 是否自動應答
      false,  // 是否排他
      false,  // 若為true,表示 不能將同一個Conenction中生產者傳送的訊息傳遞給這個Connection中 的消費者
      false,  // 是否阻塞
      nil,    // 其他屬性
   )

   if err != nil {
      log.Printf("rbt.channel.Consume error : %v", err)
      return
   }


   for data := range msg {
      log.Printf("received data is %v", string(data.Body))
   }

}

main.go

package main

import (
   "fmt"
   "log"
   "time"
   "xmt/xmtmq"
)

/*
RabbimtMQ routing 模式 案例
應用場景:從系統的程式碼邏輯中獲取對應的功能字串,將訊息任務扔到對應的佇列中業務場景,例如處理錯誤,處理特定訊息等
生產訊息
*/

func main() {

   log.SetFlags(log.Llongfile | log.Ltime | log.Ldate)

   rbt1 := &xmtmq.RabbitMQ{
      Exchange: "xmtPubEx2",
      Key: "xmt1",
      QueueName: "Routingqueuexmt1",
      MQUrl:     "amqp://guest:guest@127.0.0.1:5672/xmtmq",
   }

   xmtmq.NewRabbitMQ(rbt1)
   rbt1.Init()


   rbt2 := &xmtmq.RabbitMQ{
      Exchange: "xmtPubEx2",
      Key: "xmt2",
      QueueName: "Routingqueuexmt2",
      MQUrl:     "amqp://guest:guest@127.0.0.1:5672/xmtmq",
   }

   xmtmq.NewRabbitMQ(rbt2)
   rbt2.Init()


   var index = 0

   for {
      rbt1.ProduceRouting([]byte(fmt.Sprintf("hello wolrd xmt1  %d ", index)))
      log.Println("傳送成功xmt1  ", index)

      rbt2.ProduceRouting([]byte(fmt.Sprintf("hello wolrd xmt2  %d ", index)))
      log.Println("傳送成功xmt2  ", index)


      index++
      time.Sleep(1 * time.Second)
   }


   xmtmq.RabbitMQFree(rbt1)
   xmtmq.RabbitMQFree(rbt2)

}

consumer.go

package main

import (
   "log"
   "xmt/xmtmq"
)

func main() {

   log.SetFlags(log.Llongfile | log.Ltime | log.Ldate)

   rbt := &xmtmq.RabbitMQ{
      Exchange: "xmtPubEx2",
      Key: "xmt1",
      QueueName: "Routingqueuexmt1",
      MQUrl:     "amqp://guest:guest@127.0.0.1:5672/xmtmq",
   }

   xmtmq.NewRabbitMQ(rbt)
   rbt.ConsumeRoutingMsg()
   xmtmq.RabbitMQFree(rbt)
}

consumer2.go

package main

import (
   "log"
   "xmt/xmtmq"
)

func main() {

   log.SetFlags(log.Llongfile | log.Ltime | log.Ldate)

   rbt := &xmtmq.RabbitMQ{
      Exchange: "xmtPubEx2",
      Key:      "xmt2",
      QueueName: "Routingqueuexmt2",
      MQUrl:    "amqp://guest:guest@127.0.0.1:5672/xmtmq",
   }

   xmtmq.NewRabbitMQ(rbt)
   rbt.ConsumeRoutingMsg()
   xmtmq.RabbitMQFree(rbt)
}

topic 模式

話題模式,一個訊息被多個消費者獲取,訊息的目標 queue 可用 BindingKey 的萬用字元

Topics 模式實際上是路由模式的一種

他倆的最大的區別是 : Topics 模式傳送訊息和消費訊息的時候是通過萬用字元去進行匹配的

  • *號代表可以同通配一個單詞
  • #號代表可以通配零個或多個單詞

編碼的案例與上述 routing 模式保持一直,只是 exchange 為 topic型別

如下是上述幾種模式涉及到的交換機佇列

rpc 模式

RPC 遠端過程呼叫,客戶端遠端呼叫服務端的方法 ,使用 MQ 可以實現 RPC 的非同步呼叫

目錄結構為:

.
├── consumer.go
├── go.mod
├── go.sum
├── main.go
└── xmtmq
    └── xmtmq.go
  • 客戶端即是生產者也是消費者,向 RPC 請求佇列傳送 RPC 呼叫訊息,同時監聽RPC響應佇列
  • 服務端監聽RPC請求佇列的訊息,收到訊息後執行服務端的方法,得到方法返回的結果
  • 服務端將RPC方法 的結果傳送到RPC響應佇列。
  • 客戶端監聽RPC響應佇列,接收到RPC呼叫結果

xmtmq.go

package xmtmq

import (
   "github.com/streadway/amqp"
   "log"
   "math/rand"
)

// rpc 模式
// 定義 RabbitMQ 的資料結構
// go get github.com/streadway/amqp

type RabbitMQ struct {
   conn      *amqp.Connection // 連線
   channel   *amqp.Channel    // 通道
   QueueName string           // 佇列名
   Exchange  string           // 交換機
   Key       string           // 路由鍵
   MQUrl     string           // MQ的虛擬機器地址
}

// New 一個 RabbitMQ
func NewRabbitMQ(rbt *RabbitMQ) {
   if rbt == nil || rbt.QueueName == "" || rbt.MQUrl == "" {
      log.Panic("please check QueueName,Exchange,MQUrl ...")
   }

   conn, err := amqp.Dial(rbt.MQUrl)
   if err != nil {
      log.Panicf("amqp.Dial error : %v", err)
   }
   rbt.conn = conn

   channel, err := rbt.conn.Channel()
   if err != nil {
      log.Panicf("rbt.conn.Channel error : %v", err)
   }
   rbt.channel = channel
}

func RabbitMQFree(rbt *RabbitMQ) {
   if rbt == nil {
      log.Printf("rbt is nil,free failed")
      return
   }
   rbt.channel.Close()
   rbt.conn.Close()
}

// 生產訊息

func (rbt *RabbitMQ) Produce(data []byte) {

   // 申請佇列
   q, err := rbt.channel.QueueDeclare(
      rbt.QueueName, // 佇列名
      true,          // 是否持久化
      false,         // 是否自動刪除
      false,         // 是否排他
      false,         // 是否阻塞
      nil,           // 其他引數
   )
   if err != nil {
      log.Printf("rbt.channel.QueueDeclare error : %v", err)
      return
   }

   err = rbt.channel.Qos(1, 0, false)
   if err != nil {
      log.Printf("rbt.channel.Qos error : %v", err)
      return
   }

   d, err := rbt.channel.Consume(
      q.Name,
      "",
      false,
      false,
      false,
      false,
      nil)
   if err != nil {
      log.Printf("rbt.channel.Consume error : %v", err)
      return
   }

   for msg := range d {
      log.Println("received msg is  ", string(msg.Body))
      err := rbt.channel.Publish(
         "",
         msg.ReplyTo,
         false,
         false,
         amqp.Publishing{
            ContentType:   "test/plain",
            CorrelationId: msg.CorrelationId,
            Body:          data,
         })
      if err != nil {
         log.Printf("rbt.channel.Publish error : %v", err)
         return
      }
      msg.Ack(false)
      log.Println("svr response ok ")
   }

   return
}
func randomString(l int) string {
   bytes := make([]byte, l)
   for i := 0; i < l; i++ {
      bytes[i] = byte(rand.Intn(l))
   }
   return string(bytes)
}

// 消費訊息
func (rbt *RabbitMQ) Consume() {

   // 申請佇列
   q, err := rbt.channel.QueueDeclare(
      "",    // 佇列名
      true,  // 是否持久化
      false, // 是否自動刪除
      false, // 是否排他
      false, // 是否阻塞
      nil,   // 其他引數
   )
   if err != nil {
      log.Printf("rbt.channel.QueueDeclare error : %v", err)
      return
   }

   // 消費資料
   msg, err := rbt.channel.Consume(
      q.Name, // 佇列名
      "xmt",  // 消費者的名字
      true,   // 是否自動應答
      false,  // 是否排他
      false,  // 若為true,表示 不能將同一個Conenction中生產者傳送的訊息傳遞給這個Connection中 的消費者
      false,  // 是否阻塞
      nil,    // 其他屬性
   )
   if err != nil {
      log.Printf("rbt.channel.Consume error : %v", err)
      return
   }
   id := randomString(32)
   err = rbt.channel.Publish(
      "",
      rbt.QueueName,
      false,
      false,
      amqp.Publishing{
         ContentType:   "test/plain",
         CorrelationId: id,
         ReplyTo:       q.Name,
         Body:          []byte("321"),
      })
   if err != nil {
      log.Printf("rbt.channel.Publish error : %v", err)
      return
   }

   for data := range msg {
      log.Printf("received data is %v", string(data.Body))
   }
}

main.go

package main

import (
   "fmt"
   "log"
   "xmt/xmtmq"
)

/*
RabbimtMQ rpc 模式 案例
應用場景:簡單訊息佇列的使用,一個生產者一個消費者
生產訊息
*/

func main() {

   log.SetFlags(log.Llongfile | log.Ltime | log.Ldate)

   rbt := &xmtmq.RabbitMQ{
      QueueName: "xmtqueue",
      MQUrl:     "amqp://guest:guest@127.0.0.1:5672/xmtmq",
   }

   xmtmq.NewRabbitMQ(rbt)

   rbt.Produce([]byte(fmt.Sprintf("hello wolrd")))

}

consumer.go

package main

import (
   "log"
   "math/rand"
   "time"
   "xmt/xmtmq"
)

func main() {

   log.SetFlags(log.Llongfile | log.Ltime | log.Ldate)
   rand.Seed(time.Now().UTC().UnixNano())
   rbt := &xmtmq.RabbitMQ{
      QueueName: "xmtqueue",
      MQUrl:     "amqp://guest:guest@127.0.0.1:5672/xmtmq",
   }

   xmtmq.NewRabbitMQ(rbt)
   rbt.Consume()
}

我們們先執行消費者,多執行幾個,可以看到我們們的佇列中已經有資料了,我們們執行的是2個消費者,因此此處是 2

再執行生產者,就能看到生產者將消費者傳送的訊息消費掉,並且通過 CorrelationId 找到對應消費者監聽的佇列,將資料傳送到佇列中

消費者監聽的佇列有資料了,消費者就取出來進行消費

總結

RabbitMQ 的六種工作模式:

  • single 模式
  • work 模式
  • publish / subscribe 模式
  • routing 模式
  • topic 模式
  • rpc 模式

參考資料:

RabbitMQ Tutorials

歡迎點贊,關注,收藏

朋友們,你的支援和鼓勵,是我堅持分享,提高質量的動力

好了,本次就到這裡

技術是開放的,我們的心態,更應是開放的。擁抱變化,向陽而生,努力向前行。

我是小魔童哪吒,歡迎點贊關注收藏,下次見~

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

相關文章