請求響應模式
無論是發布訂閱模式還是queue模式,nats都不能保證訊息一定發送到訂閱者,除非訂閱者發送一個響應給發行者。
所以訂閱者發送一個回執給發行者,就是請求響應模式。
這種模式有什麼用?
nats要求訂閱者一定要先完成訂閱,發布訊息後,訂閱者才能收到訊息,類似離線訊息的模式nats不支援。就算先完成訂閱,後發送訊息,訊息發送方也不知道是否有訂閱者收到了訊息,請求響應模式就是應對這種情況。
基本流程
A發送訊息,B收到訊息,發送回執給A。這就是request reply的基本流程。
基本實現原理
- A啟用request模式發送訊息(訊息中包含了回執資訊,replya主題),同步等待回執(有逾時時間)。
- B收到訊息,在訊息中取出回執資訊=replay主題,對replay主題,
主動
發送普通訊息(訊息內容可自訂,比如server A上的service1收到msgid=xxxx的訊息。)。
- A在逾時內收到訊息,確認結束。
- A在逾時內未收到訊息,逾時結束。
注意
- 因為A發送的訊息中封裝了回執測相關資訊,訂閱者B收到訊息後,也要主動發送回執,所以請求響應模式,對雙方都有影響。
- A發送訊息後,等待B的回執,需要給A設定逾時時間,逾時後,不在等待回執,直接結束,效果和不需要回執的訊息發送一樣,不在關心是否有訂閱者收到訊息。
兩種模式
request reply有兩種模式:
1條訊息,N個訂閱者,訊息發送方,僅會收到一條回執記錄
(因為訊息發送方收到回執訊息後,就自動斷開了對回執訊息的訂閱。),即使N個訂閱都都收到了訊息。注意:pub/sub和queue模式的不同
1條訊息,N個訂閱者,訊息發送方,可以自己設定一個數量限制N,接受到N個回執訊息後,斷開對回執訊息的訂閱。
Server
package mainimport ( "github.com/nats-io/go-nats" "log" "flag")const ( //url = "nats://192.168.3.125:4222" url = nats.DefaultURL)var ( nc *nats.Conn encodeConn *nats.EncodedConn err error)func init() { if nc, err = nats.Connect(url); checkErr(err) { // if encodeConn, err = nats.NewEncodedConn(nc, nats.JSON_ENCODER); checkErr(err) { } }}func main() { var ( servername = flag.String("servername", "Y", "name for server") queueGroup = flag.String("group", "", "group name for Subscribe") subj = flag.String("subj", "yasenagat", "subject name") ) flag.Parse() mode := "queue" if *queueGroup == "" { mode = "pub/sub" } log.Printf("Server[%v] Subscribe Subject[%v] in [%v]Mode", *servername, *subj, mode) startService(*subj, *servername+" worker1", *queueGroup) startService(*subj, *servername+" worker2", *queueGroup) startService(*subj, *servername+" worker3", *queueGroup) nc.Flush() select {}}//receive messagefunc startService(subj, name, queue string) { go async(nc, subj, name, queue)}func async(nc *nats.Conn, subj, name, queue string) { replyMsg := name + " Received a msg" if queue == "" { nc.Subscribe(subj, func(msg *nats.Msg) { nc.Publish(msg.Reply, []byte(replyMsg)) log.Println(name, "Received a message From Async : ", string(msg.Data)) }) } else { nc.QueueSubscribe(subj, queue, func(msg *nats.Msg) { nc.Publish(msg.Reply, []byte(replyMsg)) log.Println(name, "Received a message From Async : ", string(msg.Data)) }) }}func checkErr(err error) bool { if err != nil { log.Println(err) return false } return true}
Client
package mainimport ( "github.com/nats-io/go-nats" "log" "github.com/pborman/uuid" "flag" "time")const ( //url = "nats://192.168.3.125:4222" url = nats.DefaultURL)var ( nc *nats.Conn encodeConn *nats.EncodedConn err error)func init() { if nc, err = nats.Connect(url); checkErr(err, func() { }) { // if encodeConn, err = nats.NewEncodedConn(nc, nats.JSON_ENCODER); checkErr(err, func() { }) { } }}func main() { var ( subj = flag.String("subj", "yasenagat", "subject name") ) flag.Parse() log.Println(*subj) startClient(*subj) time.Sleep(time.Second)}//send message to serverfunc startClient(subj string) { for i := 0; i < 3; i++ { id := uuid.New() log.Println(id) if msg, err := nc.Request(subj, []byte(id+" hello"), time.Second); checkErr(err, func() { // handle err }) { log.Println(string(msg.Data)) } }}func checkErr(err error, errFun func()) bool { if err != nil { log.Println(err) errFun() return false } return true}
pub/sub模式啟動
$ ./main2018/08/18 18:54:10 Server[Y] Subscribe Subject[yasenagat] in [pub/sub]Mode2018/08/18 18:54:26 Y worker2 Received a message From Async : b035d7c2-e7e9-4337-bb8a-a23ec85fc31a hello2018/08/18 18:54:26 Y worker1 Received a message From Async : b035d7c2-e7e9-4337-bb8a-a23ec85fc31a hello2018/08/18 18:54:26 Y worker3 Received a message From Async : b035d7c2-e7e9-4337-bb8a-a23ec85fc31a hello2018/08/18 18:54:26 Y worker2 Received a message From Async : 2d8dfe75-8fee-4b4c-8599-1824638dfa8c hello2018/08/18 18:54:26 Y worker1 Received a message From Async : 2d8dfe75-8fee-4b4c-8599-1824638dfa8c hello2018/08/18 18:54:26 Y worker3 Received a message From Async : 2d8dfe75-8fee-4b4c-8599-1824638dfa8c hello2018/08/18 18:54:26 Y worker2 Received a message From Async : fe9f773a-129b-4919-9bc4-c8a4571fef6e hello2018/08/18 18:54:26 Y worker1 Received a message From Async : fe9f773a-129b-4919-9bc4-c8a4571fef6e hello2018/08/18 18:54:26 Y worker3 Received a message From Async : fe9f773a-129b-4919-9bc4-c8a4571fef6e hello
發送訊息
$ ./main2018/08/18 18:54:26 yasenagat2018/08/18 18:54:26 b035d7c2-e7e9-4337-bb8a-a23ec85fc31a2018/08/18 18:54:26 Y worker3 Received a msg2018/08/18 18:54:26 2d8dfe75-8fee-4b4c-8599-1824638dfa8c2018/08/18 18:54:26 Y worker2 Received a msg2018/08/18 18:54:26 fe9f773a-129b-4919-9bc4-c8a4571fef6e2018/08/18 18:54:26 Y worker2 Received a msg
queue模式啟動
$ ./main -group=test2018/08/18 19:14:31 Server[Y] Subscribe Subject[yasenagat] in [queue]Mode2018/08/18 19:14:33 Y worker2 Received a message From Async : 4ecf2728-b3a7-4181-893a-aefde3bc8d2e hello Y worker2 Received a msg2018/08/18 19:14:33 Y worker3 Received a message From Async : 4e7f1363-9a47-4705-b87a-4aaeb80164f0 hello Y worker3 Received a msg2018/08/18 19:14:33 Y worker2 Received a message From Async : 38b1f74b-8a3b-46ba-a10e-62e50efbc127 hello Y worker2 Received a msg
發送訊息
$ ./main2018/08/18 19:14:33 yasenagat2018/08/18 19:14:33 4ecf2728-b3a7-4181-893a-aefde3bc8d2e2018/08/18 19:14:33 Y worker2 Received a msg2018/08/18 19:14:33 4e7f1363-9a47-4705-b87a-4aaeb80164f02018/08/18 19:14:33 Y worker3 Received a msg2018/08/18 19:14:33 38b1f74b-8a3b-46ba-a10e-62e50efbc1272018/08/18 19:14:33 Y worker2 Received a msg
queue模式下,發送3條訊息,3個訂閱者有相同的queue,每條訊息只有一個訂閱者收到。
pub/sub模式下,發送3條訊息,3個訂閱者都收到3條訊息,一共9條。
總結:
回執主要解決:訂閱者是否收到訊息的問題、有多少個訂閱者收到訊息的問題。(不是具體業務是否執行完成的回執!)
基於事件的架構模式可以構建於訊息機制之上,依賴訊息機制。非同步呼叫的其中一種實現方式,就是基於事件模式。非同步呼叫又是分布式系統中常見的任務處理方式。
業務模式
- 業務A發送eventA給事件中心,
等待回執
- 事件中心告知A收到了訊息,開始對外發送廣播
- 訂閱者B訂閱了eventA主題
- 事件中心對eventA主題發送廣播,
等待回執
- B收到訊息,告知事件中心,收到eventA,開始執行任務taskA
- B非同步執行完taskA,通知事件中心taskAComplete,
等待回執
- 事件中心發送回執給B,對外發送廣播,taskAComplete
- ........
如果逾時,未能收到回執,需要回執資訊的確認方可以主動調用相關介面,查詢任務執行狀態,根據任務狀態做後續的處理。