過去在學Actor模型的時候,就認爲異步消息是至關的重要,在華爲的時候,也深扒了一下當時產品用的消息模型,簡單實用,支撐起了不少模塊和業務,但也有一個缺點是和其餘的框架有耦合,最近看到以太坊的事件框架,一樣簡單簡潔,理念很適合初步接觸事件框架的同窗,寫文介紹一下。小程序
以太坊的事件框架是一個單獨的基礎模塊,存在於目錄go-ethereum/event
中,它有2中獨立的事件框架實現,老點的叫TypeMux
,已經基本棄用,新的叫Feed
,當前正在普遍使用。緩存
TypeMux
和Feed
還只是簡單的事件框架,與Kafka、RocketMQ等消息系統相比,是很是的傳統和簡單,可是TypeMux
和Feed
的簡單簡潔,已經很好的支撐以太坊的上層模塊,這是當下最好的選擇。app
TypeMux
和Feed
各有優劣,最優秀的共同特色是,他們只依賴於Golang原始的包,徹底與以太坊的其餘模塊隔離開來,也就是說,你徹底能夠把這兩個事件框架用在本身的項目中。框架
TypeMux
的特色是,你把全部的訂閱塞給它就好,事件來了它自會通知你,但有可能會阻塞,通知你不是那麼及時,甚至過了一段挺長的時間。異步
Feed
的特色是,它一般不存在阻塞的狀況,會及時的把事件通知給你,但須要你爲每類事件都創建一個Feed,而後不一樣的事件去不一樣的Feed上訂閱和發送,這其實挺煩人的,若是你用錯了Feed,會致使panic。函數
接下來,介紹下這種簡單事件框架的抽象模型,而後再回歸到以太坊,介紹下TypeMux
和Feed
。post
!<--more-->ui
如上圖,輕量級的事件框架會把全部的被訂閱的事件收集起來,而後把每一個訂閱者組合成一個列表,當事件框架收到某個事件的時候,就把訂閱該事件的全部訂閱者找出來,而後把這個事件發給他們。this
它須要具備2個功能:spa
若是作成完善的消息系統,就還得考慮這些特性:可用性、吞吐量、傳輸延遲、有序消息、消息存儲、過濾、重發,這和事件框架相比就複雜上去了,咱們專一的介紹下以太坊的事件模型怎麼完成上述3個功能的。
TypeMux
是一個以太坊不太滿意的事件框架,因此以太坊就搞了Feed
出來,它解決了TypeMux
效率低下,延遲交付的問題。接下來就先看下這個TypeMux
。
TypeMux是一個同步事件框架。它的實現和上面講的事件框架的抽象結構是徹底同樣的,它維護了一個訂閱表,表裏維護了每一個事件的訂閱者列表。它的特色:
看下它2個功能的實現:
TypeMux.Subscribe()
,入參爲要訂閱的事件類型,會返回TypeMuxSubscription
給訂閱者,訂閱者可經過此控制訂閱,經過TypeMuxSubscription.Unsubscribe()
能夠取消訂閱。TypeMux.Post()
,入參爲事件類型,根據訂閱表找出該事件的訂閱者列表,遍歷列表,依次向每一個訂閱者傳遞事件,若是前一個沒有傳遞完成進入阻塞,會致使後邊的訂閱者不能及時收到事件。TypeMux
的精簡組成:
// A TypeMux dispatches events to registered receivers. Receivers can be // registered to handle events of certain type. Any operation // called after mux is stopped will return ErrMuxClosed. // // The zero value is ready to use. // // Deprecated: use Feed // 本質:哈希列表,每一個事件的訂閱者都存到對於的列表裏 type TypeMux struct { mutex sync.RWMutex // 鎖 subm map[reflect.Type][]*TypeMuxSubscription // 訂閱表:全部事件類型的全部訂閱者 stopped bool }
訂閱:
// Subscribe creates a subscription for events of the given types. The // subscription's channel is closed when it is unsubscribed // or the mux is closed. // 訂閱者只傳入訂閱的事件類型,而後TypeMux會返回給它一個訂閱對象 func (mux *TypeMux) Subscribe(types ...interface{}) *TypeMuxSubscription { sub := newsub(mux) mux.mutex.Lock() defer mux.mutex.Unlock() if mux.stopped { // set the status to closed so that calling Unsubscribe after this // call will short circuit. sub.closed = true close(sub.postC) } else { if mux.subm == nil { mux.subm = make(map[reflect.Type][]*TypeMuxSubscription) } for _, t := range types { rtyp := reflect.TypeOf(t) // 在同一次訂閱中,不要重複訂閱同一個類型的事件 oldsubs := mux.subm[rtyp] if find(oldsubs, sub) != -1 { panic(fmt.Sprintf("event: duplicate type %s in Subscribe", rtyp)) } subs := make([]*TypeMuxSubscription, len(oldsubs)+1) copy(subs, oldsubs) subs[len(oldsubs)] = sub mux.subm[rtyp] = subs } } return sub }
取消訂閱:
func (s *TypeMuxSubscription) Unsubscribe() { s.mux.del(s) s.closewait() }
發佈事件和傳遞事件:
// Post sends an event to all receivers registered for the given type. // It returns ErrMuxClosed if the mux has been stopped. // 遍歷map,找到全部訂閱的人,向它們傳遞event,同一個event對象,非拷貝,運行在調用者goroutine func (mux *TypeMux) Post(ev interface{}) error { event := &TypeMuxEvent{ Time: time.Now(), Data: ev, } rtyp := reflect.TypeOf(ev) mux.mutex.RLock() if mux.stopped { mux.mutex.RUnlock() return ErrMuxClosed } subs := mux.subm[rtyp] mux.mutex.RUnlock() for _, sub := range subs { sub.deliver(event) } return nil } func (s *TypeMuxSubscription) deliver(event *TypeMuxEvent) { // Short circuit delivery if stale event // 不發送過早(老)的消息 if s.created.After(event.Time) { return } // Otherwise deliver the event s.postMu.RLock() defer s.postMu.RUnlock() select { case s.postC <- event: case <-s.closing: } }
我上面指出了發送事件可能阻塞,阻塞在哪?關鍵就在下面這裏:建立TypeMuxSubscription
時,通道使用的是無緩存通道,讀寫是同步的,這裏註定了TypeMux是一個同步事件框架,這是以太坊改用Feed的最大緣由。
func newsub(mux *TypeMux) *TypeMuxSubscription { c := make(chan *TypeMuxEvent) // 無緩衝通道,同步讀寫 return &TypeMuxSubscription{ mux: mux, created: time.Now(), readC: c, postC: c, closing: make(chan struct{}), } }
Feed是一個流式事件框架。上文強調了TypeMux是一個同步框架,也正是由於此以太坊丟棄了它,難道Feed
就是一個異步框架?不必定是的,這取決於訂閱者是否採用有緩存的通道,採用有緩存的通道,則Feed就是異步的,採用無緩存的通道,Feed就是同步的,把同步仍是異步的選擇交給使用者。
本節強調Feed的流式特色。事件本質是一個數據,接二連三的事件就組成了一個數據流,這些數據流不停的流向它的訂閱者那裏,而且不會阻塞在任何一個訂閱者那裏。
舉幾個不是十分恰當的例子。
Feed和TypeMux相同的是,它們都是推模式,不一樣的是Feed是異步的,若是有些訂閱者阻塞了,不要緊,它會繼續向後面的訂閱者發送事件/消息。
Feed是一個一對多的事件流框架。每一個類型的事件都須要一個與之對應的Feed,訂閱者經過這個Feed進行訂閱事件,發佈者經過這個Feed發佈事件。
看下Feed是如何實現2個功能的:
Feed.Subscribe()
,入參是一個通道,一般是有緩衝的,就算是無緩存也不會形成Feed阻塞,Feed會校驗這個通道的類型和本Feed管理的事件類型是否一致,而後把通道保存下來,返回給訂閱者一個Subscription
,能夠經過它取消訂閱和讀取通道錯誤。Feed.Send()
入參是一個事件,加鎖確保本類型事件只有一個發送協程正在進行,而後校驗事件類型是否匹配,Feed會嘗試給每一個訂閱者發送事件,若是訂閱者阻塞,Feed就繼續嘗試給下一個訂閱者發送,直到給每一個訂閱者發送事件,返回發送該事件的數量。Feed定義:
// Feed implements one-to-many subscriptions where the carrier of events is a channel. // Values sent to a Feed are delivered to all subscribed channels simultaneously. // // Feeds can only be used with a single type. The type is determined by the first Send or // Subscribe operation. Subsequent calls to these methods panic if the type does not // match. // // The zero value is ready to use. // 一對多的事件訂閱管理:每一個feed對象,當別人調用send的時候,會發送給全部訂閱者 // 每種事件類型都有一個本身的feed,一個feed內訂閱的是同一種類型的事件,得用某個事件的feed才能訂閱該事件 type Feed struct { once sync.Once // ensures that init only runs once sendLock chan struct{} // sendLock has a one-element buffer and is empty when held.It protects sendCases. 這個鎖確保了只有一個協程在使用go routine removeSub chan interface{} // interrupts Send sendCases caseList // the active set of select cases used by Send,訂閱的channel列表,這些channel是活躍的 // The inbox holds newly subscribed channels until they are added to sendCases. mu sync.Mutex inbox caseList // 不活躍的在這裏 etype reflect.Type closed bool }
訂閱事件:
// Subscribe adds a channel to the feed. Future sends will be delivered on the channel // until the subscription is canceled. All channels added must have the same element type. // // The channel should have ample buffer space to avoid blocking other subscribers. // Slow subscribers are not dropped. // 訂閱者傳入接收事件的通道,feed將通道保存爲case,而後返回給訂閱者訂閱對象 func (f *Feed) Subscribe(channel interface{}) Subscription { f.once.Do(f.init) // 通道和通道類型檢查 chanval := reflect.ValueOf(channel) chantyp := chanval.Type() if chantyp.Kind() != reflect.Chan || chantyp.ChanDir()&reflect.SendDir == 0 { panic(errBadChannel) } sub := &feedSub{feed: f, channel: chanval, err: make(chan error, 1)} f.mu.Lock() defer f.mu.Unlock() if !f.typecheck(chantyp.Elem()) { panic(feedTypeError{op: "Subscribe", got: chantyp, want: reflect.ChanOf(reflect.SendDir, f.etype)}) } // 把通道保存到case // Add the select case to the inbox. // The next Send will add it to f.sendCases. cas := reflect.SelectCase{Dir: reflect.SelectSend, Chan: chanval} f.inbox = append(f.inbox, cas) return sub }
發送和傳遞事件:這個發送是比較繞一點的,要想真正掌握其中的運行,最好寫個小程序練習下。
// Send delivers to all subscribed channels simultaneously. // It returns the number of subscribers that the value was sent to. // 同時向全部的訂閱者發送事件,返回訂閱者的數量 func (f *Feed) Send(value interface{}) (nsent int) { rvalue := reflect.ValueOf(value) f.once.Do(f.init) <-f.sendLock // 獲取發送鎖 // Add new cases from the inbox after taking the send lock. // 從inbox加入到sendCases,不能訂閱的時候直接加入到sendCases,由於可能其餘協程在調用發送 f.mu.Lock() f.sendCases = append(f.sendCases, f.inbox...) f.inbox = nil // 類型檢查:若是該feed不是要發送的值的類型,釋放鎖,而且執行panic if !f.typecheck(rvalue.Type()) { f.sendLock <- struct{}{} panic(feedTypeError{op: "Send", got: rvalue.Type(), want: f.etype}) } f.mu.Unlock() // Set the sent value on all channels. // 把發送的值關聯到每一個case/channel,每個事件都有一個feed,因此這裏全是同一個事件的 for i := firstSubSendCase; i < len(f.sendCases); i++ { f.sendCases[i].Send = rvalue } // Send until all channels except removeSub have been chosen. 'cases' tracks a prefix // of sendCases. When a send succeeds, the corresponding case moves to the end of // 'cases' and it shrinks by one element. // 全部case仍然保留在sendCases,只是用過的會移動到最後面 cases := f.sendCases for { // Fast path: try sending without blocking before adding to the select set. // This should usually succeed if subscribers are fast enough and have free // buffer space. // 使用非阻塞式發送,若是不能發送就及時返回 for i := firstSubSendCase; i < len(cases); i++ { // 若是發送成功,把這個case移動到末尾,因此i這個位置就是沒處理過的,而後大小減1 if cases[i].Chan.TrySend(rvalue) { nsent++ cases = cases.deactivate(i) i-- } } // 若是這個地方成立,表明全部訂閱者都不阻塞,都發送完了 if len(cases) == firstSubSendCase { break } // Select on all the receivers, waiting for them to unblock. // 返回一個可用的,直到不阻塞。 chosen, recv, _ := reflect.Select(cases) if chosen == 0 /* <-f.removeSub */ { // 這個接收方要刪除了,刪除並縮小sendCases index := f.sendCases.find(recv.Interface()) f.sendCases = f.sendCases.delete(index) if index >= 0 && index < len(cases) { // Shrink 'cases' too because the removed case was still active. cases = f.sendCases[:len(cases)-1] } } else { // reflect已經確保數據已經發送,無需再嘗試發送 cases = cases.deactivate(chosen) nsent++ } } // 把sendCases中的send都標記爲空 // Forget about the sent value and hand off the send lock. for i := firstSubSendCase; i < len(f.sendCases); i++ { f.sendCases[i].Send = reflect.Value{} } f.sendLock <- struct{}{} return nsent }