Aeron 是如何實現的?—— Ipc Subscription

{"type":"doc","content":[{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"接上文","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Aeron 是什麼?","attrs":{}},{"type":"link","attrs":{"href":"https://xie.infoq.cn/article/27422063a2cadcc054187135e","title":"","type":null},"content":[{"type":"text","text":"https://xie.infoq.cn/article/27422063a2cadcc054187135e","attrs":{}}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Aeron 中這麼多空閒策略選哪個?","attrs":{}},{"type":"link","attrs":{"href":"https://xie.infoq.cn/article/41d0885f46594e90cbdba4b2b","title":"","type":null},"content":[{"type":"text","text":"https://xie.infoq.cn/article/41d0885f46594e90cbdba4b2b","attrs":{}}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Aeron 是如何實現的?—— Conductor ","attrs":{}},{"type":"link","attrs":{"href":"https://xie.infoq.cn/article/b4953b06323cd26e3a1397874","title":"","type":null},"content":[{"type":"text","text":"https://xie.infoq.cn/article/b4953b06323cd26e3a1397874","attrs":{}}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Aeron 是如何實現的?—— Ipc Publication ","attrs":{}},{"type":"link","attrs":{"href":"https://xie.infoq.cn/article/9c3c085daef8eb12d533d2f66","title":"","type":null},"content":[{"type":"text","text":"https://xie.infoq.cn/article/9c3c085daef8eb12d533d2f66","attrs":{}}]}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"0. 簡介","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"最近我們用 Aeron 實現了 Mesh agent 與 sdk 之間的共享內存通信,但是在使用過程中越來越感覺到 Aeron 框架太重了,其中很大部分功能完全用不到,有些想要自定義的邏輯很難在現有框架中實現。所以我們計劃深入到 Aeron 源碼中,看看它是如何實現的,最終嘗試實現一個輕量的 Mesh 共享內存通信類庫。上文分析了 Ipc Publication 的邏輯,本文繼續分析 Ipc Subscription 的邏輯。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"1. Driver Conductor - onAddIpcSubscription","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在讀取數據之前,需要先向 Driver Conductor 發送ADD_SUBSCRIPTION命令,在 Driver 中構建發送接收的關係。至於 Conductor 交互的邏輯不再贅述,直接看 Driver Conductor 處理ADD_SUBSCRIPTION命令的邏輯。處理邏輯的入口在io.aeron.driver.ClientCommandAdapter,這裏只關心 Ipc 的情況,找到DriverConductor 中的處理邏輯:","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/84/84cde4c65ce462a498275974bf7edcb0.webp","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"構建完IpcSubscriptionLink之後,就返回ON_SUBSCRIPTION_READY。然後再匹配當前的ipcPublications,如果有 match 的,那麼將訂閱關係添加到對應的IpcPublication中:","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"1.1 UnsafeBufferPosition","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在發送接收關係中,最核心的數據結構是Position,記錄消費的位置,本質上就是 cnc.dat 中的一個 Counter。","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/c2/c299e0c40037aac53033fa09d3370613.webp","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"type id 是 SUBSCRIBER_POSITION_TYPE_ID(4),label 是 \"sub-pos: ${registrationId} ${sessionId} ${streamId} ${channel} @${joinPosition}\"。這個信息可以通過io.aeron.samples.AeronStat工具查看。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"1.2 關係映射","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在SubscriptionLink中,positionBySubscribableMap維護着訂閱的多個 Publication 的消費位置。此處之所以是多個的原因是,同一個 stream 可以有多個 session。","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/bc/bc591eddcaef627247ab1555ad65a56e.webp","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在IpcPublication中,subscriberPositions維護着多個 subscriber 的消費位置。(tether 的邏輯暫時忽略,異常情況處理下一篇再分析)","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/9f/9fbb118229a3a7ba207a54528c43f0d0.webp","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"heading","attrs":{"align":null,"level":3},"content":[{"type":"text","text":"1.3 ON_AVAILABLE_IMAGE","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Driver 這邊的關係信息維護好之後,向 Client 廣播可用ON_AVAILABLE_IMAGE信息。","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/c4/c4f6d0dc34696a834f1e5f64dc0b1450.webp","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"其中包含 Publication 生成的 logbuffer 信息,Subscription 也是直接讀該共享內存。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"2. Client Conductor - addSubscription","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Client Conductor 的處理邏輯也很清晰,主要的處理邏輯就是響應ON_SUBSCRIPTION_READY和ON_AVAILABLE_IMAGE這兩個消息。","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/31/31af377dea65f24e40105b50af34a6ba.webp","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"收到ON_SUBSCRIPTION_READY之後,Client Conductor 構建相應的io.aeron.Subscription封裝類,然後添加到resourceByRegIdMap這個映射關係中。此時調用 poll 拉取消息是不行的,因爲還沒有 image,也就是對應 Publication 的信息。這個信息是通過ON_AVAILABLE_IMAGE消息通知的,處理的方法是onAvailableImage:","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/8b/8b4e6ed5bceb396f95c46426db9f6e07.webp","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"構建的io.aeron.Image包裝類主要包含 logbuffer 和 position counter 這兩個信息。接着調Subscription的addImage方法,將其添加到images數組中。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"3. Subscription.poll & controlledPoll","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"此時就可以調用 poll 方法拉取消息了。","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/ac/ac238e7155cb08fc23f23feb0a84ca3a.webp","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"最終調用的是 image 的 poll 方法:","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/6c/6c585663accd43c05fdcad41df28f8da.webp","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"根據上次讀的位置(首次是 joinPosition)計算對應的 term 讀取。","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/fc/fc48b7dc5ec6f7cc0598660016eb00ab.webp","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"邏輯很清楚,讀數據,最後更新位置。有個細節,此處沒有檢查數據覆蓋的情況,這說明在 Driver 中嚴格控制了 Publication 的最大位置。讀取數據還有個controlledPoll方法,主幹邏輯與poll一樣,區別主要在於更新位置信息的邏輯。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":2},"content":[{"type":"text","text":"4. Driver 對 publisherLimit 的維護","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Driver 維護 position 的邏輯在DriverConductor的trackStreamPositions方法中。對於 ipc 場景調用的是IpcPublication的updatePublisherLimit方法:","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/33/33799e06a1ab3380e18549a5a272baba.webp","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"核心邏輯就是更新publisherLimit。publisherLimit和consumerPosition的起始位置都是 Publication 的初始寫入位置。這說明對於一個沒有 subscriber 的 Publication 是不能寫入的,當有ADD_SUBSCRIPTION後,就進入第一個 if 邏輯:","attrs":{}}]},{"type":"numberedlist","attrs":{"start":null,"normalizeStart":1},"content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":1,"align":null,"origin":null},"content":[{"type":"text","text":"遍歷所有 subscriberPositions,找出最大位置 maxSubscriberPosition 和最小位置 minSubscriberPosition","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":2,"align":null,"origin":null},"content":[{"type":"text","text":"consumerPosition 設定爲最大讀取位置","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":3,"align":null,"origin":null},"content":[{"type":"text","text":"proposedLimit 也就是提議給 Publication 的寫入限制,初始值爲最小讀取位置加上 termWindowLength(termWindowLength 默認爲 termLength 的一半,且自定義不能大於該值)","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":4,"align":null,"origin":null},"content":[{"type":"text","text":"通過 tripLimit 和 tripGain 限制更新的步長,默認爲 termWindowLength 的 1/8","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":5,"align":null,"origin":null},"content":[{"type":"text","text":"cleanBufferTo 方法清理之前的共享內存","attrs":{}}]}]}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"從這裏的邏輯看,ipc 場景用兩個 termBuffer 就可以,三個豈不是浪費內存?再精簡一下,一個 RingBuffer 看起來也可以。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":"br"}},{"type":"horizontalrule","attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/a1/a1952246cc74835b57e19e801c0d98ce.jpeg","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/1c/1ca71f6ad90d8fba744e3f9ecd9134f5.jpeg","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/9d/9d3abba7b4cfe3e32b78cdb1b198b22d.jpeg","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章