NATS中文開發文檔:接收消息

通常,應用程序可以異步或同步接收消息。使用NAT接收消息依賴於庫的實現。
一些語言,如Go或Java,提供同步和異步api,而另一些語言可能只支持一種類型的訂閱。
在所有情況下,訂閱過程包括讓客戶端庫告訴NATS系統應用程序對某個特定主題感興趣。當應用程序完成訂閱時,它會取消訂閱,告訴服務器停止發送消息。
客戶端將爲每個匹配的訂閱接收一條消息,因此,如果連接具有使用相同或重疊主題(例如foo和>)的多個訂閱,則相同的消息將多次發送到客戶端。

同步訂閱

同步訂閱要求應用程序等待消息。這種類型的訂閱易於設置和使用,但如果需要多條消息,則需要應用程序處理循環。對於預期只有一條消息的情況,根據語言的不同,同步訂閱有時更易於管理。
例如,要訂閱主題更新並接收單個消息,您可以執行以下操作:

nc, err := nats.Connect("demo.nats.io")
if err != nil {
    log.Fatal(err)
}
defer nc.Close()

// Subscribe
sub, err := nc.SubscribeSync("updates")
if err != nil {
    log.Fatal(err)
}

// Wait for a message
msg, err := sub.NextMsg(10 * time.Second)
if err != nil {
    log.Fatal(err)
}

// Use the response
log.Printf("Reply: %s", msg.Data)

異步訂閱

異步訂閱使用某種形式的回調在消息到達時通知應用程序。這些訂閱通常更容易使用,但確實代表了庫的某種形式的內部工作和資源使用,即線程。檢查庫的文檔以瞭解與異步訂閱相關聯的任何資源使用情況。
以下示例訂閱主題更新並處理傳入消息:

nc, err := nats.Connect("demo.nats.io")
if err != nil {
    log.Fatal(err)
}
defer nc.Close()

// Use a WaitGroup to wait for a message to arrive
wg := sync.WaitGroup{}
wg.Add(1)

// Subscribe
if _, err := nc.Subscribe("updates", func(m *nats.Msg) {
    wg.Done()
}); err != nil {
    log.Fatal(err)
}

// Wait for a message to come in
wg.Wait()

取消訂閱

客戶端庫提供了一種方法來取消訂閱以前的訂閱請求。
此過程需要與服務器交互,因此對於異步訂閱,當庫處理取消訂閱時,可能會有一個很小的時間窗口顯示消息。忽略這種輕微的邊緣情況,客戶端庫將清除所有未完成的消息,並告訴服務器不再使用訂閱。

nc, err := nats.Connect("demo.nats.io")
if err != nil {
    log.Fatal(err)
}
defer nc.Close()

// Sync Subscription
sub, err := nc.SubscribeSync("updates")
if err != nil {
    log.Fatal(err)
}
if err := sub.Unsubscribe(); err != nil {
    log.Fatal(err)
}

// Async Subscription
sub, err = nc.Subscribe("updates", func(_ *nats.Msg) {})
if err != nil {
    log.Fatal(err)
}
if err := sub.Unsubscribe(); err != nil {
    log.Fatal(err)
}

在N個消息後取消訂閱

NATS提供了一種特殊的退訂形式,它配置了一個消息計數,並在許多消息發送到訂閱服務器時生效。如果只需要一條消息,則此機制非常有用。
您提供的消息計數是訂閱服務器的總消息計數。因此,如果您以1爲計數取消訂閱,服務器將在收到一條消息後停止向該訂閱發送消息。如果訂閱服務器已經收到一條或多條消息,則取消訂閱將立即生效。如果嘗試自動取消訂閱長時間運行的訂閱,則基於歷史記錄的此操作可能會令人困惑,但對於新訂閱來說是合乎邏輯的。
自動取消訂閱是基於發送給訂閱服務器的消息總數,而不僅僅是新消息。大多數客戶端庫在自動取消訂閱請求後也會跟蹤最大消息計數。在重新連接時,這使客戶機能夠用更新的總數重新發送取消訂閱。
以下示例顯示了在單個消息之後取消訂閱:

nc, err := nats.Connect("demo.nats.io")
if err != nil {
    log.Fatal(err)
}
defer nc.Close()

// Sync Subscription
sub, err := nc.SubscribeSync("updates")
if err != nil {
    log.Fatal(err)
}
if err := sub.AutoUnsubscribe(1); err != nil {
    log.Fatal(err)
}

// Async Subscription
sub, err = nc.Subscribe("updates", func(_ *nats.Msg) {})
if err != nil {
    log.Fatal(err)
}
if err := sub.AutoUnsubscribe(1); err != nil {
    log.Fatal(err)
}

回覆消息

傳入消息有一個可選的回覆字段。如果設置了該字段,則它將包含一個預期答覆的主題。
例如,下面的代碼將偵聽該請求並隨時間響應。

nc, err := nats.Connect("demo.nats.io")
if err != nil {
    log.Fatal(err)
}
defer nc.Close()

// Subscribe
sub, err := nc.SubscribeSync("time")
if err != nil {
    log.Fatal(err)
}

// Read a message
msg, err := sub.NextMsg(10 * time.Second)
if err != nil {
    log.Fatal(err)
}

// Get the time
timeAsBytes := []byte(time.Now().String())

// Send the time as the response.
msg.Respond(timeAsBytes)

通配符訂閱

使用通配符主題訂閱沒有特殊代碼。通配符是主題名的普通部分。但是,使用傳入消息提供的主題來確定如何處理消息是一種常見的技術。
例如,您可以使用*訂閱,然後根據實際主題進行操作。

nc, err := nats.Connect("demo.nats.io")
if err != nil {
    log.Fatal(err)
}
defer nc.Close()

// Use a WaitGroup to wait for 2 messages to arrive
wg := sync.WaitGroup{}
wg.Add(2)

// Subscribe
if _, err := nc.Subscribe("time.*.east", func(m *nats.Msg) {
    log.Printf("%s: %s", m.Subject, m.Data)
    wg.Done()
}); err != nil {
    log.Fatal(err)
}

// Wait for the 2 messages to come in
wg.Wait()

或者使用類似於>:

nc, err := nats.Connect("demo.nats.io")
if err != nil {
    log.Fatal(err)
}
defer nc.Close()

// Use a WaitGroup to wait for 4 messages to arrive
wg := sync.WaitGroup{}
wg.Add(4)

// Subscribe
if _, err := nc.Subscribe("time.>", func(m *nats.Msg) {
    log.Printf("%s: %s", m.Subject, m.Data)
    wg.Done()
}); err != nil {
    log.Fatal(err)
}

// Wait for the 4 messages to come in
wg.Wait()

// Close the connection
nc.Close()

下面的示例可用於測試這兩個訂閱者。*訂閱服務器最多應接收2條消息,>訂閱服務器接收4條消息。更重要的是time.*.east訂戶將無法按時收到time.us.east.atlanta,因爲這不匹配。

nc, err := nats.Connect("demo.nats.io")
if err != nil {
    log.Fatal(err)
}
defer nc.Close()

zoneID, err := time.LoadLocation("America/New_York")
if err != nil {
    log.Fatal(err)
}
now := time.Now()
zoneDateTime := now.In(zoneID)
formatted := zoneDateTime.String()

nc.Publish("time.us.east", []byte(formatted))
nc.Publish("time.us.east.atlanta", []byte(formatted))

zoneID, err = time.LoadLocation("Europe/Warsaw")
if err != nil {
    log.Fatal(err)
}
zoneDateTime = now.In(zoneID)
formatted = zoneDateTime.String()

nc.Publish("time.eu.east", []byte(formatted))
nc.Publish("time.eu.east.warsaw", []byte(formatted))

隊列訂閱

訂閱隊列組與僅訂閱主題略有不同。應用程序只在訂閱中包含一個隊列名稱。服務器將在隊列組的所有成員之間進行負載平衡。在羣集設置中,每個成員都有相同的機會接收特定消息。
請記住,NAT中的隊列組是動態的,不需要任何服務器配置。
在這裏插入圖片描述
例如,使用主題更新訂閱隊列工作器:

nc, err := nats.Connect("demo.nats.io")
if err != nil {
    log.Fatal(err)
}
defer nc.Close()

// Use a WaitGroup to wait for 10 messages to arrive
wg := sync.WaitGroup{}
wg.Add(10)

// Create a queue subscription on "updates" with queue name "workers"
if _, err := nc.QueueSubscribe("updates", "workers", func(m *nats.Msg) {
    wg.Done()
}); err != nil {
    log.Fatal(err)
}

// Wait for messages to come in
wg.Wait()

如果將此示例與發送到更新的發佈示例一起運行,您將看到其中一個實例獲取消息,而運行的其他實例不會獲取消息。但是接收消息的實體將變化。

斷開連接前排空(drain)消息

最近在NATS客戶端庫中添加的一個特性是能夠清空連接或訂閱。關閉連接或取消訂閱通常被視爲即時請求。當您關閉或取消訂閱時,庫將停止訂閱服務器的任何掛起隊列或緩存中的消息。當您刪除訂閱或連接時,它將在關閉之前處理任何正在進行的消息和緩存/掛起的消息。
清空機制(Drain)爲使用隊列訂閱的客戶機提供了一種在不丟失任何消息的情況下關閉應用程序的方法。客戶機可以調出新的隊列成員,排出並關閉舊的隊列成員,而不會丟失發送到舊客戶機的消息。如果沒有漏失,則可能由於傳遞時間而丟失消息。
這些庫可以在連接上或訂閱服務器上,或同時在兩者上提供清空機制。
對於連接,過程本質上是:

  1. 清空所有訂閱
  2. 停止發佈新消息
  3. flush所有剩餘的已發佈消息
  4. 關閉

通常可以使用用於drain的API,也可以用於關閉:
作爲drain連接的示例:

wg := sync.WaitGroup{}
wg.Add(1)

errCh := make(chan error, 1)

// To simulate a timeout, you would set the DrainTimeout()
// to a value less than the time spent in the message callback,
// so say: nats.DrainTimeout(10*time.Millisecond).

nc, err := nats.Connect("demo.nats.io",
    nats.DrainTimeout(10*time.Second),
    nats.ErrorHandler(func(_ *nats.Conn, _ *nats.Subscription, err error) {
        errCh <- err
    }),
    nats.ClosedHandler(func(_ *nats.Conn) {
        wg.Done()
    }))
if err != nil {
    log.Fatal(err)
}

// Just to not collide using the demo server with other users.
subject := nats.NewInbox()

// Subscribe, but add some delay while processing.
if _, err := nc.Subscribe(subject, func(_ *nats.Msg) {
    time.Sleep(200 * time.Millisecond)
}); err != nil {
    log.Fatal(err)
}

// Publish a message
if err := nc.Publish(subject, []byte("hello")); err != nil {
    log.Fatal(err)
}

// Drain the connection, which will close it when done.
if err := nc.Drain(); err != nil {
    log.Fatal(err)
}

// Wait for the connection to be closed.
wg.Wait()

// Check if there was an error
select {
case e := <-errCh:
    log.Fatal(e)
default:
}

訂閱的drain機制更簡單:

  1. 退訂
  2. 處理所有緩存或飛行中的消息
  3. 清理

通常可以使用drain API也適用於取消訂閱:

    nc, err := nats.Connect("demo.nats.io")
    if err != nil {
        log.Fatal(err)
    }
    defer nc.Close()

    done := sync.WaitGroup{}
    done.Add(1)

    count := 0
    errCh := make(chan error, 1)

    msgAfterDrain := "not this one"

    // Just to not collide using the demo server with other users.
    subject := nats.NewInbox()

    // This callback will process each message slowly
    sub, err := nc.Subscribe(subject, func(m *nats.Msg) {
        if string(m.Data) == msgAfterDrain {
            errCh <- fmt.Errorf("Should not have received this message")
            return
        }
        time.Sleep(100 * time.Millisecond)
        count++
        if count == 2 {
            done.Done()
        }
    })

    // Send 2 messages
    for i := 0; i < 2; i++ {
        nc.Publish(subject, []byte("hello"))
    }

    // Call Drain on the subscription. It unsubscribes but
    // wait for all pending messages to be processed.
    if err := sub.Drain(); err != nil {
        log.Fatal(err)
    }

    // Send one more message, this message should not be received
    nc.Publish(subject, []byte(msgAfterDrain))

    // Wait for the subscription to have processed the 2 messages.
    done.Wait()

    // Now check that the 3rd message was not received
    select {
    case e := <-errCh:
        log.Fatal(e)
    case <-time.After(200 * time.Millisecond):
        // OK!
    }

由於drain可能涉及到流向服務器的消息,對於刷新和異步消息處理,drain的超時通常應高於簡單消息請求/答覆或類似消息的超時。

結構化數據

客戶端庫可以提供工具來幫助接收結構化數據,比如JSON。NATS服務器的核心通信流始終是不透明的字節數組。服務器不處理任何形式的消息有效負載。對於不提供幫助程序的庫,您始終可以在將相關字節發送到NATS客戶端之前對數據進行編碼和解碼。
例如,要接收JSON,可以執行以下操作:

nc, err := nats.Connect("demo.nats.io",
    nats.ErrorHandler(func(nc *nats.Conn, s *nats.Subscription, err error) {
        if s != nil {
        log.Printf("Async error in %q/%q: %v", s.Subject, s.Queue, err)
        } else {
        log.Printf("Async error outside subscription: %v", err)
        }
    }))
if err != nil {
    log.Fatal(err)
}
defer nc.Close()
ec, err := nats.NewEncodedConn(nc, nats.JSON_ENCODER)
if err != nil {
    log.Fatal(err)
}
defer ec.Close()

// Define the object
type stock struct {
    Symbol string
    Price  int
}

wg := sync.WaitGroup{}
wg.Add(1)

// Subscribe
// Decoding errors will be passed to the function supplied via
// nats.ErrorHandler above, and the callback supplied here will
// not be invoked.
if _, err := ec.Subscribe("updates", func(s *stock) {
    log.Printf("Stock: %s - Price: %v", s.Symbol, s.Price)
    wg.Done()
}); err != nil {
    log.Fatal(err)
}

// Wait for a message to come in
wg.Wait()
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章