PubSub在消息總線内部主要用于對所有線上用戶端進行實時管控的作用。每個用戶端在使用消息總線時,都“被迫”注冊到PubSub上,并“被迫”訂閱了一些Channel,以便消息總線管控台實時下發一些管控指令及時生效。
這裡有必要回顧一下之前的設計。消息總線内部的Pub/Sub的機制是通過第三方技術元件的實作(目前支援Zookeeper跟Redis),關于Pub/Sub這裡首先普及幾個概念,首先元件根據自身業務定義Channel,某個元件如果需要關注某Channel的變更就注冊對某Channel的關注(subscribe),當有元件因為業務需要向Channel發送變更(publish),凡是subscribe該Channel的所有元件都會擷取到變更。這裡因為Zookeeper跟Redis都支援資料存儲,是以這裡的publish的内容其實既可以被Push給subscribe該Channel的所有元件,也可以使得其他元件根據Channel
pull下來。
其實之前的做法的關注點在“自動化”以及“擴充性”。為了所謂的擴充性,我們利用Java注解掃描的方式來使得整個Channel的定義“自動化”,這樣就無需寫死了。并且當後續業務擴充,新增一個Channel的時候,之前Channel的定義無需作任何改變。另外為了用戶端首次擷取(目前的推送機制zookeeper以及redis都支援KV資料存儲)以及後續更新推送資料的對用戶端的一緻性,我們讓一個Channel對應資料庫的一張表,同時每個Channel都對應自己的資料自動擷取方式。
當然Pub/Sub從服務端角度來看是資料的上行(從資料庫提取資料,push到subscribe的用戶端),從用戶端角度來看是資料的下行。是以這裡我們定義了一個IDataExchange接口,用來與Pub/Sub元件進行資料交換:

然後定義了一個@Exchanger注解,它包含兩個屬性:
table:表示對應的表;
path:也即channel,對應頻道名稱;
然後涉及到變更的表都會實作為一個獨立的XXXExchanger。
為了讓每個Channel的資料源是以一緻的接口對外提供,這裡統一定義了一個擷取資料源的接口:IDataFetcher:
該接口接收一個資料序列化器,然後将擷取到的資料進行序列化并以byte[]作為統一的傳回值,因為需要将資料存儲到Pub/Sub元件裡去(它們大都支援位元組數組的API接口)。
整體的設計如下所示:
這樣的設計對最初的關注點(自動化、擴充性、用戶端首次擷取資料以及後續擷取變更資料導緻代碼處理上的一緻性)而言,确實夠了。但就性能而言,卻非常低效。因為是一張表對應一個Channel,是以其實是全表推送,既然是全表推送,那麼就無法鑒别用戶端,無法鑒别用戶端,就可能代碼無效推送(跟某個用戶端無關的關系資料,也會被推送過來),進而産生頻繁推送,無效解析等一系列惡性循環。另外全表資料,相對來說是原始資料,還需要各個用戶端做相應的解析,計算出合适的視圖,用于内部控制以及權限校驗等,并且所有的用戶端在這一步執行的邏輯幾乎是一樣的。需要解析生成的視圖如下:
對于Pub/Sub重新設計之後采用——推拉結合的模式。不再推送資料,隻推送變更通知以及變更的KEY(secret)。然後用戶端按需拉取。
優化後的設計,帶來如下一些優點:
之前Pub/Sub的設計是“首次拉取,變更全推”的做法。而且拉取的是全表資料,這對于用戶端記憶體的占用是個極大的損耗。而優化之後,将隻存儲跟目前secret相關的資料視圖。
優化之後針對用戶端使用的資料專門定制了資料結構,在服務端按照鍵值對的形式計算出某個secret對應的用戶端需要使用的視圖資料并緩存在pub/sub元件的記憶體中。這個資料視圖的資料結構如下:
這樣,用戶端在驗證通信權限的時候,将會非常快。
減少通信次數的主要手段是本地緩存(local
cache),用戶端擷取資料的方式是:如果本地有,則從本地取,如果本地沒有,則從遠端擷取擷取完之後緩存在本地記憶體裡。部分代碼如下所示:
當然通信次數的減少,還得益于特地為用戶端定制的“資料視圖”,并且是按照每個隊列的secret拆分成key/value的。管控台導緻的資料變更将過渡為變更通知事件,然後再按序更新本地緩存。而不會像原來那樣,推送資料變更,進而導緻太多無效網絡互動以及資料計算。
減少通信資料量的主要手段是隻擷取有效資料,比如當調用消息總線API的時候,每個API都要求傳入一個secret來訓示目前對應的隊列節點,是以我們隻需要從遠端擷取用戶端需要的跟目前secret相關的“資料視圖”。當然這裡我們作了一個假設:大部分場景下,一個用戶端在某個JVM程序内通常隻使用一個secret。因為API被設計為某個使用者隻需要知道自己隊列對應的secret即可使用,是以這樣的假設是合理的。當然也不排除某個應用涉及到多個隊列的操作,這種情況最多多擷取幾個secret的資料視圖。但基本的原則是:不取多餘資料,按需取用。并且,推送也從原來的資料變成了現在的變更通知,該通知雖然是廣播式的,但卻是“自認領”的機制:
拉取更新:
可以看到,隻有在推送的secret在本地有緩存時,才會去遠端拉取更新。否則,将直接丢棄該變更通知。
當然,這種完全定制化的機制,也徹底廢棄了之前關注的自動化以及擴充性的特性。這是必要的,因為我們隊消息總線的定位還是希望它具有更好的性能。
原文釋出時間為:2015-05-04
本文作者:vinoYang