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()
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章