背景
在京東到家商家中心系統(tǒng)中,商家提出在 Web 端實現(xiàn)自動打印的需求,不需要人工盯守點擊打印,直接打印小票,以節(jié)約人工成本。
解決思路
關于問題的思考邏輯:
第一種:想到的是可以用ajax來輪詢服務端獲取最新訂單,也就是pull。
第二種:我們是否可以用類似推送的設計來實現(xiàn),也就是push。
兩種思路我們評估其優(yōu)缺點:
ajax方式實現(xiàn)簡單,只需要定時從服務端pull數(shù)據(jù)即可,但也增加了很多次無效的輪詢, 無形中增加服務端無效查詢。
push方式實現(xiàn)稍復雜,需要服務端與PC端保持連接,這就需要建立長連接,最終通過長連接的方式來實現(xiàn)push效果。
經(jīng)過討論,我們選擇了第二種,訂單中心生產(chǎn)出的新訂單,通過MQ的方式推送給web端,最終獲得一個比較好的用戶體驗。
方案介紹
關于長連接方案的選擇,我們參考了不少帖子,最終選擇使用websocket協(xié)議來實現(xiàn)長連接,類似場景如IM,服務端即時推送等都使用了這個協(xié)議。
接下來我們比較一下websocket的框架,比較主流的有netty、tomcat、socketIO 三個框架。
基于支持websocket的容器,開發(fā)簡單,例如tomcat,但在高并發(fā)的支持不是很好,連接的時候容易連接斷開,還有就是依賴容器。
netty-socketIO是在netty4基礎之上做了一層封裝,效率如同netty一樣,是一個全平臺方案,友好的API,京東的logbook也是用了socketIO來傳遞日志,也是我們的一個備選方案。
netty是業(yè)內主流的NIO框架,netty對javaNIO做了封裝,讓開發(fā)者更多關注業(yè)務,降低開發(fā)成本,很多著名的RPC框架都采用了netty作為傳輸層,友好的API,功能強大,內置了很多編解碼協(xié)議,實現(xiàn)websocket協(xié)議也是十分方便。
那我們橫向比較一下這些框架。
所以在選型方面我們還是定位在socketIO 與 netty 上面,在兼顧擴展性與靈活性的同時,我們也考慮到netty可以提供http的功能,最終我們選擇了使用netty,當然socketIO封裝了很多功能,也是十分強大,相比較來說netty更適合我們,比較輕量。
netty的特性
netty具有異步非阻塞的特性,傳統(tǒng)IO是面向流的,NIO是面向緩沖區(qū)的,這也是它的非阻塞原因所在。
netty的線程模型如圖所示:
這種模型就是我們常說的Reactor模型,boss線程其實是一個獨立的NIO線程池,用于接收client請求,默認線程池大小為1,worker線程池用于處理具體的讀寫操作,默認線程池大小為2*cpu個數(shù)。
在上述模型中要特別注意ExecutionHandler,ExecutionHandler是運行在worker線程中的,所以耗時的操作最好在線程池中運行, 比如IO或者計算,不然會影響整個netty的吞吐。
了解了這些,我們根據(jù)自己的業(yè)務設計出流程如下圖所示:
步驟(1) web端請求服務端進行注冊,注冊成功保持長連接。
步驟(2)服務端發(fā)送MQ。
步驟(3)netty將收到的消息推送給web端。
步驟(4)web端調用打印控件進行打印,打印控件需提前安裝好(打印控件是pc上安裝的一個驅動程序,用過JS方式來調用)。
如果調用JS成功,控件將把打印信息放入打印隊列,如果不成功,重復步驟(4)
當然現(xiàn)在的結構只是單機版,不滿足生產(chǎn)條件,那將來的結構可能會演變成如下圖所示:
我們會在服務端與netty之間建立路由層,路由層的主要職責:
第一:收集集群存活信息。
第二:記錄落點,就是落在哪一臺機器上面。
第三:接收消息與分發(fā)消息。
有了這三種能力,我們就可以輕松的指定信息分發(fā)策略。這里我們希望使用http協(xié)議來路由,所以就需要netty有http短連接接收的能力 ,所以netty整體上需要長短連接兩種能力。
講了這么多,還是來點干貨,下面是部分代碼。
netty啟動類,我們通過spring來啟動netty,因為netty啟動會阻塞主線程,所以需要在子線程中來啟動netty,下面是啟動參數(shù)。
接著來寫我們的ChannelInitializer,HttpServerCodec為編×××,WSServerProtocolHandler為websocket協(xié)議握手,其中我們更關注業(yè)務層面自定義的兩個hander,httpRequestHandler,authorizeHandler。
httpRequestHandler的作用是處理url是否合法,接收參數(shù),httpRequestHandler此方法中也可以根據(jù)URI來過濾,自定義自己的短連接請求。
authorizeHandler的作用是校驗數(shù)據(jù)是否正確,如果正確會將channel保存到map中,通過map建立起業(yè)務ID與通道之間的關系。
校驗的過程我們在authorizeHandler中的channelRead展開,如果未通過,直接關閉當前channel,如果通過校驗,則通過ctx.fireChannelRead(msg);方法將信息傳入下一個handler去處理。
在項目里主要是以傳遞參數(shù)來進行數(shù)據(jù)校驗的,也就是通過URL傳參來實現(xiàn)。在httpRequestHandler中我們將URL參數(shù)set到channel的attr中,并傳遞給了下一個handler,也就是authorizeHandler,所以在authorize方法中我們可以利用get()方法得到參數(shù)值,u是經(jīng)過加密的數(shù)據(jù),我們需要在這里進行解密,解密失敗,可認為校驗失敗。
當然如果有跨應用的服務,也可以通過Cookie的方式來進行加密串的讀寫,通過request.getHeader 是可以獲取Cookie中的信息,這就看具體業(yè)務了,示例代碼如下:
這個map 可以理解為servlet中的session,當有信息需要傳送給某個客戶端時,我們調用map.get(key)方式的到當前該客戶端的channel,調用writeAndFlush方法將信息發(fā)送出去,下面舉例通過接收MQ消息后的處理邏輯。
接下來有人可能想到,那如果通道關閉了怎么辦?map中的channel是不是就失效了呢?那其實我們還需要有一個類似心跳的機制去維護channel,間接的去維護這個map,如果是通道正常關閉,可以通過channelInactive方法來監(jiān)聽,如果是長時間空閑:在項目中我們使用了增加的IdleStateHandler來處理,通過覆蓋userEventTriggered方法來監(jiān)聽空閑channel,當某個channel到達我們設置的超時時間時,netty會回調此方法。
至此,核心部分已經(jīng)處理完成,剩下的就是通過保存的channel來發(fā)送信息給客戶端了。
最后在web端,我們采用了 reconnecting-websocket,它是一個小型的 JavaScript 庫,封裝了 WebSocket API, 提供了在連接斷開時自動重連的機制,很能夠幫助我們完成斷開重連的操作。
遇到的問題
經(jīng)過測試,在ws的uri后面不能傳遞參數(shù),不然在netty實現(xiàn)websocket協(xié)議握手的時候會出現(xiàn)斷開連接的情況,針對這種情況在websocketHandler之前做了一層httpHander過濾,將傳遞參數(shù)放入channel的attr中,然后重寫request的uri,并傳入下一個管道中,基本上解決這個問題。
在讀寫空閑的時候盡量以發(fā)心跳包的方式維護連接,但在客戶端由于網(wǎng)絡不穩(wěn)定或者是服務端重啟,連接會斷開,瞬間有可能接收不到訂單消息,為此在客戶端需要實現(xiàn)斷開重連機制,此問題我們采用?reconnecting-websocket的js框架,此框架擴展了原生websocket的實現(xiàn),做了斷開重連機制,有效的防止斷開后不能及時連接。
在測試過程中由于控件與小票機的問題,可能會出現(xiàn)打印異?;蛘咝∑睓C沒紙的情況,Lodop控件其實是將打印信息放入電腦的打印隊列,如果沒紙了,小票機會報警,再次放入小票紙,打印機會自動打印隊列中的數(shù)據(jù)。
出現(xiàn)調用控件異常偶爾發(fā)生,現(xiàn)在處理辦法是在js中進行了的try catch 如果失敗 進行重試,重試次數(shù)自定義,超過重試次數(shù)暫不做處理,此處還不太嚴謹,需要在進行優(yōu)化。
總結
通過上面的實踐,我們基本已經(jīng)實現(xiàn)了web端的自動打印,經(jīng)過長時間的內部測試,服務端與客戶端通信穩(wěn)定,我們將灰度商家做用戶體驗。
在特定的場景下,選擇適當?shù)募夹g會提高我們的效率,否則會適得其反。選擇長連接,大家可以把握三個大原則:
服務端是否需要主動推送數(shù)據(jù)到客戶端以實現(xiàn)控制的效果。
對于實時性的要求是否苛刻。
對于客戶端是否需要關注其在線狀態(tài)的實時變化。
覺得不錯請點贊支持,歡迎留言或進我的個人群855801563領取【架構資料專題目合集90期】、【BATJTMD大廠JAVA面試真題1000+】,本群專用于學習交流技術、分享面試機會,拒絕廣告,我也會在群內不定期答題、探討。
創(chuàng)新互聯(lián)www.cdcxhl.cn,專業(yè)提供香港、美國云服務器,動態(tài)BGP最優(yōu)骨干路由自動選擇,持續(xù)穩(wěn)定高效的網(wǎng)絡助力業(yè)務部署。公司持有工信部辦法的idc、isp許可證, 機房獨有T級流量清洗系統(tǒng)配攻擊溯源,準確進行流量調度,確保服務器高可用性。佳節(jié)活動現(xiàn)已開啟,新人活動云服務器買多久送多久。