你還在羨慕別人成熟的推送系統麼?
你想定製本身的推送系統麼?
你有內網推送的需求而不能使用外網推送產品的困擾麼? java
文章將向你介紹研究分佈式推送的過程與心得(本人嘔心瀝血打造......),但願看完文章的你能有所收穫。mysql
在搜索了n屢次websocket這個關鍵詞之後...我選擇用netty這個支持nio的高性能網絡框架做爲推送支持,它幫咱們屏蔽了網絡底層複雜通訊邏輯,提供簡單易用的api。(websocket的netty實現網上一搜一大把)
客戶端的websocket握手請求如:ws://127.0.0.1:9003/websocket?channelId=123456
將客戶端的惟一標識123456
與客戶端在咱們netty
中抽象出的鏈接對象channel
,維護至全局變量中git
private static Map<String,Channel> channels = new ConcurrentHashMap(1000);
推送邏輯爲:根據http
請求或者客戶端發過來的websocket
消息格式,解析內容,經過客戶端鏈接的標識channelId
找到對應客戶端的鏈接channel
對象,調用channel.writeAndFlush(new TextWebSocketFrame("須要推送的內容"))
,完成推送。github
消息體的兩個主要參數爲:web
到此,咱們的推送功能,已基本實現,只是受限於單機各項性能參數,沒有任何的拓展性。redis
咱們但願根據系統的業務職能,作初步的拆分,以便將來根據不一樣業務的負載,作更細粒度的集羣。
當咱們考慮把各個業務模塊部署多份時,咱們要面對這些問題:
1.須要對外暴露一個統一的入口來路由到咱們不一樣的集羣服務
2.對於集羣中節點的上下線要作到動態感知
咱們加入這兩個組件spring
在這個階段中,咱們須要解決一個推送業務中比較核心的問題:sql
咱們知道:
websocket
與http
瞬時無狀態的請求響應不同,客戶端在發起websocket
握手成功後,不會立馬斷開,會維持住與服務器的tcp
鏈接,用於全雙工通訊。在這種狀況下,咱們的推送請求,就不能任由portal
服務端負載均衡,路由到各個websocket
服務節點上了。
舉個栗子:
如上圖所示client1
發起websocket
握手時,由網關將握手請求路由給websocket1
節點,client1
會經過網關與websocket1
節點維持一個tcp
通道,那麼以http
請求來觸發推送時,必需要把對client1
的推送請求指定路由給websocket1
節點來處理(對於以websocket
消息觸發的推送請求也是如此,client4
發起對client1
的推送必須由接收到消息的websocket2
節點轉發給websocket1
節點處理)。
此時,咱們須要引入redis
來記錄各個websocket節點上所維護的客戶端
:數據庫
websocket
服務節點處理完客戶端的握手請求之後,將節點與客戶端的路由關係保存進redis
http
觸發推送的流程:portal
接收到推送請求時,根據須要推送的客戶端的目的地,從redis
中找到客戶端所在的服務器,轉發http
請求至客戶端所在服務器的websocket
節點,websocket
節點發起推送websocket
消息觸發推送的流程:websocket
節點接收到客戶端websocket
消息推送請求時,判斷須要推送的客戶端是否在本節點上,若是是則直接推送,若是不是則轉發給對應的其餘websocket
節點發起推送在我當前的項目中,一些地方使用到了redis
的管道特性,因此這裏redis
不支持cluster
這種分片的集羣部署方式,要想適用分片的集羣方式來提升併發只能使用codis
作代理。。要麼就單個master
。
上圖可見:服務間的調用關係很是複雜,系統間耦合很是高,在線上對端口嚴格管控的服務器下部署這樣一套系統是很是痛苦的,咱們考慮引入MQ
解耦:當有須要轉發給websocket
節點進行推送時,投遞到MQ中對應節點所訂閱的主題就行 api
簡單描述mq的選型問題:目前經常使用的分佈式高可用MQ中間件有:RabbitMQ
,kafka
,RocketMQ
RabbitMQ:須要安裝Erlang環境。。我的但願系統部署儘可能簡單,首先就排除(對java有把握一點。。)
kafka:通常用於日誌分析,大數據計算
RocketMQ: 相比於kafka,可靠,實時,易用等特性更適用與業務系統交互的場景中,註冊中心nameSer也比kafka的zk要輕便(就他了) 詳細對比傳送門
到此,功能已基本完成,只是咱們如今每部署一個服務節點都須要配置相同的註冊中心地址、redis地址、mq地址,當某個中間件地址變更時,整個服務的全部節點都須要相適配,爲避免這種繁瑣而重複的配置,咱們考慮引入配置中心:
配置中心選型:
springcloud config:須要依賴git
apollo:須要依賴mysql
nacos:服務自帶數據庫,沒有任何依賴,安裝使用簡單便捷。(就他了)
如今的完整配置如圖:
咱們的分佈式消息推送系統已經完成了O(∩_∩)O,不過這裏存在一個運行上的小問題:
如今從gateway
網關路由websocket
握手請求到各個websocket
服務節點的權重都是相同的,也就是說理論上咱們但願各個websocket
節點所維持的客戶端鏈接數量是大體相同的,可是當咱們服務部署運行一段時間後,發現各個websocket
節點的負載較高,咱們但願增長(或者服務節點重啓)websocket
服務節點的數量。可是增長(重啓)完websocket
節點之後,gateway
路由到各個websocket
節點的權重依然相同,其實咱們但願gateway
網關將websocket
握手請求能優先能路由給新部署上來的websocket
服務節點,即:
具體實如今我
完整項目傳送門中的
task
模塊
第一次寫文章。。但願你們多多支持。。。 認知有限。。若有描述錯誤的地方還請指出。。。有問題提交至項目中的issue