tendermint 中用到了訂閱發(fā)布模式,這種模式大家都不會陌生,比如你打開你的微信訂閱號,你訂閱的作者發(fā)布的文章,會廣播給每個訂閱者。在這個場景里,微信公眾號就是一個Pulisher,而你就是一個Subscriber,你收到的文章就是一個Message。
今天不過多討論 tendermint 的業(yè)務流程,主要和大家聊一下如何使用 go 語言寫一個訂閱發(fā)布模式。下面這個圖是 tendermint 中發(fā)布訂閱模式的草圖:
發(fā)布訂閱模式
接下來我再介紹幾個重要的數(shù)據(jù)結構:
type state struct {
// query string -> client -> subscription
subscriptions map[string]map[string]*Subscription
// query string -> queryPlusRefCount
queries map[string]*queryPlusRefCount
}
state 是用來維護所有 subscriber,當需要 publish 時,遍歷這個對象中的兩個 map,找到需要通知的 Subscription,然后通知。我們接著看其他數(shù)據(jù)結構:
type Server struct {
cmn.BaseService
?
cmds chan cmd
cmdsCap int
// check if we have subscription before
// subscribing or unsubscribing
mtx sync.RWMutex
subscriptions map[string]map[string]struct{} // subscriber -> query (string) -> empty struct
}
這個結構中我們主要看 cmds 這個 channel,當我們有新的訂閱時,我們向這個 channel 中發(fā)送一個數(shù)據(jù)。subscriptions 這個結構也維護了所有的訂閱者的信息,有新的訂閱時,首先檢查一下之前是否已經(jīng)訂閱過,如果有,就返回對應錯誤信息。
type cmd struct {
op operation
?
// subscribe, unsubscribe
query Query
subscription *Subscription
clientID string
?
// publish
msg interface{}
tags map[string]string
}
上面這個是 cmds 的結構,在 channel 中發(fā)送這個類型的數(shù)據(jù), query:
type Query interface {
Matches(tags map[string]string) bool
String() string
}
這個里面有兩個接口,一個是返回 string,另外一個就是判斷是否相同。這里她的作用就是類似于 tag,我們訂閱時,需要指定我們關注的類型,可以理解為 tag,發(fā)布時也是如此,發(fā)布對應 tag 的消息。其中 Matches 中,傳入一個 map,根據(jù)其算法實現(xiàn)匹配,這里我們不深追這個算法,我們可以理解為,訂閱時,我們生成了一個 query 的對象,發(fā)布時要說明發(fā)布的消息的類型,或者可以說是具有某種 tag 的消息,然后對比 query 對象,匹配上就發(fā)送消息。
到這里我們訂閱模式的各種結構基本上都聊了一下,還有 EventBus 部分沒有細說,這部分主要就是和業(yè)務相關的了,所以我們這里留在后面的文章寫,還有 tendermint 是如何使用上面的發(fā)布訂閱模式也在下一篇文章中和大家分享。
最后啰嗦幾句,我一直都是非常推薦大家平時多閱讀源碼的,很多東西雖然我們通過理論可以學習到,但是動手實現(xiàn)就是另一回事了,尤其是很多時候我們自己動手實現(xiàn)的東西都是為了理解這個內(nèi)容而實現(xiàn)的,很多是不能夠在項目中應用的,這時候我們能夠閱讀著名項目的源碼,就事半功倍了。
后續(xù)會寫更過關于源碼的分享,希望大家持續(xù)關注,你的在看是我碼字的動力(所有文章均是個人一個字一個字敲出來的,如有失誤之處,敬請大家諒解,更多內(nèi)容可以關注公眾號:Go語言之美)。






