李先懿,郭正光
(上海勢航網(wǎng)絡科技有限公司,上海 201702)
近年來,隨著電子信息技術、移動通信技術、物聯(lián)網(wǎng)技術的快速發(fā)展,車聯(lián)網(wǎng)技術也有長足的進步[1,2].與此同時,物流運輸企業(yè)和政府監(jiān)管部門都對車輛安全更加重視.這也推動了新技術在車聯(lián)網(wǎng)中的應用.在車聯(lián)網(wǎng)系統(tǒng)中,報警通知和處理是非常重要的功能模塊.按照交通部相關法規(guī)及各省交管部門的要求[3],當車輛發(fā)生超速、疲勞駕駛等危險駕駛行為時,交通部聯(lián)網(wǎng)聯(lián)控平臺的車聯(lián)網(wǎng)運營商需要及時收到通知,針對車輛異常行為進行相應處理.因此,報警通知顯得尤為重要.
當車輛發(fā)生異常報警時,終端通過socket 實時上報到車聯(lián)網(wǎng)服務器.服務器需要將報警信息實時通知到用戶.傳統(tǒng)BS 架構(gòu)的車聯(lián)網(wǎng)系統(tǒng)中,一般通過AJAX 輪詢[4]的方式拉取報警信息.這種方式實現(xiàn)簡單,但它的弊端也是顯而易見:(1)輪詢是有時間間隔的,所以報警是有延遲的.過短的輪詢間隔產(chǎn)生大量的無效請求并增大服務器壓力.(2)當用戶數(shù)量增多時,輪詢會產(chǎn)生大量的網(wǎng)絡流量,增加不必要的帶寬損耗,并導致服務器產(chǎn)生很大并發(fā)壓力.(3)當終端數(shù)據(jù)上報頻率高于AJAX 輪詢頻率時,Web 客戶端還沒來得及做一次AJAX 請求,新的報警數(shù)據(jù)已經(jīng)覆蓋了舊的報警數(shù)據(jù),會造成報警數(shù)據(jù)丟失.因此,需要采用新的技術手段解決車聯(lián)網(wǎng)系統(tǒng)中海量報警數(shù)據(jù)推送問題.
近年來,由于HTML5 技術的廣泛普及,主流瀏覽器對HTML5 標準支持更加完善,客戶端和服務器之間的實時數(shù)據(jù)交換變得更加容易.Websocket 是HTML5新標準中的通信機制[5,6],能夠?qū)崿F(xiàn)穩(wěn)定全雙工實時通信,具有簡潔高效的特點.在Websocket API 中,瀏覽器和服務器只需要完成一次握手,兩者之間就可以直接創(chuàng)建持久性的連接,并進行雙向數(shù)據(jù)傳輸,占用的網(wǎng)絡帶寬較少.它不需要安裝瀏覽器插件,所以跨瀏覽器兼容性更好.因此,Websocket 技術可作為車聯(lián)網(wǎng)系統(tǒng)中報警推送的理想方案.
針對上述AJAX 問題并考慮到Websocket 的優(yōu)點,我們設計一套基于Websocket 技術的報警推送系統(tǒng).該系統(tǒng)主要包含4 個模塊:網(wǎng)關、Redis 數(shù)據(jù)庫、Websocket推送服務、Websocket 客戶端及前端頁面.整個系統(tǒng)的架構(gòu)如圖1 所示.
圖1 Websocket 報警推送系統(tǒng)架構(gòu)
在車聯(lián)網(wǎng)系統(tǒng)中,終端通過接入網(wǎng)絡[7](包括2G/3G/4G 等無線移動通信網(wǎng)絡或WLAN 等網(wǎng)絡)按照《道路運輸車輛衛(wèi)星定位系統(tǒng)北斗兼容車載終端通訊協(xié)議技術規(guī)范》(簡稱JT/T 808)協(xié)議[8]上報定位數(shù)據(jù)、報警數(shù)據(jù)和其它附加數(shù)據(jù).網(wǎng)關模塊負責維持與眾多終端的socket 長連接,接收終端上報的數(shù)據(jù),解析數(shù)據(jù)并寫入數(shù)據(jù)庫.根據(jù)上報數(shù)據(jù)的類型不同,一些數(shù)據(jù)寫入關系型數(shù)據(jù)庫以便長期持久化.這些歷史數(shù)據(jù)將用于車輛軌跡查詢、行程分析、報警統(tǒng)計等功能.另外一些需要實時查詢和推送的數(shù)據(jù)寫入Redis 緩存數(shù)據(jù)庫.網(wǎng)關通過兩種方式寫入Redis:通過set/hset 方式寫入Redis 以便其它模塊查詢實時數(shù)據(jù);通過發(fā)布模式(publish)寫入Redis 以便實時通知其它模塊.
Redis 是一個開源的內(nèi)存數(shù)據(jù)結(jié)構(gòu)存儲系統(tǒng),它可以用作數(shù)據(jù)庫、緩存和消息中間件[9].Redis 的所有數(shù)據(jù)均位于服務器的主內(nèi)存中,因此讀寫速度非???每秒可以執(zhí)行10 萬次以上的寫入操作.Redis 的發(fā)布訂閱模式實現(xiàn)了一個簡單的消息隊列的功能.引入Redis 的目的在于:(1)降低網(wǎng)關與后續(xù)Websocket 推送服務間的耦合,從而保障網(wǎng)關的穩(wěn)定性.即使網(wǎng)絡異常導致Websocket 推送服務產(chǎn)生積壓或阻塞,也不會影響網(wǎng)關與終端的正常通信,不會影響數(shù)據(jù)寫入關系型數(shù)據(jù)庫.(2)當多個程序訂閱Redis 消息時,數(shù)據(jù)可以在系統(tǒng)內(nèi)不同模塊間實時分發(fā),實現(xiàn)數(shù)據(jù)同步.(3)Redis 本身也承擔緩存的作用,可以直接在Redis 里查詢所有車輛的最新一次位置信息、報警狀態(tài).
Websocket 推送服務主要承擔權限判斷、Websocket推送、推送統(tǒng)計等功能.它從Redis 訂閱了報警頻道(channel).當有報警信息從Redis 推送過來,它提取報警內(nèi)容以及該報警對應的車輛信息、終端信息,然后從數(shù)據(jù)庫查詢用戶與車輛的對應關系,判斷是否需要將本條報警信息推送給用戶.由此可以做到報警推送與用戶權限綁定,具有查看車輛權限的用戶才會收到報警推送.另外,可也根據(jù)用戶喜好設置,過濾特定類型的報警推送,只推送用戶關注的報警類型.
Web 前端采用Socket.io 的js 庫.Socket.io 是一個跨瀏覽器、支持Websocket 實時通訊的js 庫[10].它支持實時、雙向、基于事件的數(shù)據(jù)通信,可在不同平臺、瀏覽器、設備上工作.Socket.io 除了支持Websocket通訊協(xié)議外,還支持許多種輪詢(Polling)機制以及其它實時通信方式,并封裝成了通用的接口.Socket.io 能夠根據(jù)瀏覽器對通訊機制的支持情況自動從Websocket,Adobe Flash Socket,AJAX long pulling,AJAX multipart streaming,Forever IFrame,JSONP polling 中選擇最佳的方式來實現(xiàn)網(wǎng)絡實時應用.這樣前端開發(fā)Websocket變得更簡單.
整個車聯(lián)網(wǎng)系統(tǒng)的網(wǎng)關和后端使用Java 語言開發(fā),Web 前端基于vue.js 框架開發(fā).
車聯(lián)網(wǎng)平臺接入的終端較多,在線的終端可能數(shù)以萬計.這些終端通過TCP 長鏈接與平臺通信.因此需要一個性能強大的網(wǎng)絡連接庫.本系統(tǒng)網(wǎng)關部分采用Netty 庫處理socket 連接和數(shù)據(jù)收發(fā).Netty 是一個基于Java NIO 的客戶端/服務器網(wǎng)絡應用框架[11].它隱藏背后復雜的網(wǎng)絡操作,提供一個易于使用的API 框架.
Netty 框架的一大亮點是基于EventLoop 的高效線程模型.它是一個高性能、異步事件驅(qū)動的NIO 框架,所有IO 操作都是異步非阻塞的.高性能的Netty 可以處理數(shù)萬終端的連接和數(shù)據(jù)收發(fā).
Netty 通過bind()方法監(jiān)聽9300 端口,等待終端連接.當終端上報數(shù)據(jù)時,繼承自ChannelInboundHandler Adapter 的GatewayHandler 類的channelRead 方法接收數(shù)據(jù)并解析報警.然后通過jedis 的publish 方法發(fā)布報警到Redis 里面.核心代碼如下:
在本系統(tǒng)中,Redis 既用作了緩存數(shù)據(jù)庫,也充當了消息隊列.Redis 服務器采用了6 個Redis 節(jié)點集群的方式部署,包括3 個主節(jié)點,3 個從節(jié)點.這樣可以做到自動分配數(shù)據(jù)到不同的節(jié)點上,提高了性能.部分節(jié)點失效的情況下還能夠繼續(xù)提供服務,提高了系統(tǒng)的高可用性.
Websocket 推送服務采用開源netty-socketio 方案(https://github.com/mrniko/netty-socketio).它是Socket.io 服務器端的一個基于Netty 框架的Java 實現(xiàn).它功能非常強大,簡單易用,穩(wěn)定可靠.
Netty-socketio 推送服務的ConnectListener 監(jiān)聽了Websocket 連接事件.當用戶通過瀏覽器登錄本系統(tǒng),服務端會收到一個唯一標識(UUID).Clients 集合保存了所有已登錄用戶的標識.當終端上報一條報警數(shù)據(jù)后,程序從數(shù)據(jù)庫查詢該報警對應的車輛,再根據(jù)車輛和用戶的關系,查找到該報警需要通知的一個或多個用戶.如果用戶的個人設置里配置了接收報警推送,則從clients 集合里查找到該用戶對應的Websocket連接,將該報警通過此連接推送到web 前端.Websocket推送服務核心代碼如下:
前端引入socket.io.js 監(jiān)聽Websocket 事件,例如建立連接、斷開連接、收到數(shù)據(jù)等事件[12].當收到報警時,js 解析報警內(nèi)容,將結(jié)果展示在web 界面.核心代碼如下所示:
整個前端頁面包含了一套完整的車聯(lián)網(wǎng)平臺,包括車輛監(jiān)控、軌跡回放、報警處理、指令下發(fā)、報表查詢等功能.報警推送只是其中一個模塊.當前端收到報警推送時,通過彈框和播放聲音等方式明顯提示用戶.車輛監(jiān)控、報警處理、報表查詢等功能也會同步刷新報警數(shù)據(jù).
系統(tǒng)測試環(huán)境如下:服務器硬件配置為16 核,8 GB內(nèi)存.服務器操作系統(tǒng)為Centos 7.Java 使用Oracle JDK1.8.0_171.Web 服務器使用Tomcat 8.5.32.Redis 版本為4.0.10.Netty-socketio 版本為1.7.13.客戶端環(huán)境為Windows 10 操作系統(tǒng),瀏覽器為支持Websocket 的Google Chrome 67.0.3396.99.
我們定義報警的延遲為:從終端產(chǎn)生報警的時間到用戶瀏覽器收到報警的時間差.嚴格測試報警延遲,需要先將終端和用戶電腦校時.這里我們使用模擬終端巧妙回避校時的問題.模擬終端是一個可運行在Windows 上的程序.它模擬了車載終端的數(shù)據(jù)采集、網(wǎng)絡數(shù)據(jù)收發(fā)邏輯.將模擬終端和瀏覽器運行在同一臺Windows 電腦上,比較終端產(chǎn)生報警的時間戳和瀏覽接收到報警的時間戳,即可計算出報警推送的延遲.
使用圖2 所示模擬終端發(fā)送測試數(shù)據(jù).通過模擬終端數(shù)據(jù)發(fā)送時間和Chrome 開發(fā)者工具控制臺日志輸出時間可以計算報警延遲.每秒推送一次報警,共推送100 條報警數(shù)據(jù),從終端上傳報警數(shù)據(jù)到瀏覽器收到報警推送的時間延遲平均值為8 ms.傳統(tǒng)AJAX 輪詢間隔大約是10 到30 s.可見報警實時性大大提高.
圖2 模擬終端報警配置界面
采用開源工具apache jmeter 對netty-socketio 服務器作了壓力測試.Jmeter 的Websocket Sampler 插件可以模擬Websocket 并發(fā)連接.在每個并發(fā)連接數(shù)下,推送100 條數(shù)據(jù),取推送延遲的平均值.測試結(jié)果如圖3所示.結(jié)果表明,隨著Websocket 并發(fā)連接數(shù)增長,推送延遲并未出現(xiàn)大幅度增長,而是在7.5 ms 左右波動.這也反映出netty-socketio 的高并發(fā)、低延遲特性.
圖3 Websocket 推送延遲與并發(fā)連接數(shù)曲線
把模擬終端的數(shù)據(jù)上報時間間隔設定成1 s,連續(xù)發(fā)送1000 條報警數(shù)據(jù).瀏覽器控制臺輸出數(shù)據(jù)表明接收到1000 條報警.這說明Websocket 并未丟失任何一條數(shù)據(jù),不會出現(xiàn)AJAX 方式的報警覆蓋問題,報警推送的準確性得到保證.
通過Chrome 瀏覽器開發(fā)者工具查看網(wǎng)絡連接,當頁面加載時,服務響應的HTTP 頭信息中包含了這兩項:Connection:Upgrade 和Upgrade:Websocket,可知客戶端和服務器在握手期間從HTTP 協(xié)議升級為Websocket協(xié)議.之后Websocket 一直保持長連接.而AJAX 輪詢時,每次建立HTTP 連接,都要發(fā)送HTTP 報頭等信息.通過wireshark 抓包軟件可知,通常一次HTTP 請求和響應,需要發(fā)送大約570 B 的HTTP 頭部信息[5].這個HTTP 報頭甚至比消息本身還要大.而使用Websocket的額外開銷只有2 B,建立連接之后便可以二進制幀格式傳輸純數(shù)據(jù).我們假定一次報警的數(shù)據(jù)為50 B.通過AJAX 方式拉取1000 條報警生產(chǎn)的網(wǎng)絡流量為:(570+50)×1000=620 000 B.而通過Websocket傳輸?shù)木W(wǎng)絡流量為(2+50)×1000=52 000 B.AJAX 方式產(chǎn)生的網(wǎng)絡流量是Websocket 網(wǎng)絡流量的11 倍.當報警推送量大后,Websocket 比AJAX 輪詢節(jié)省大量的網(wǎng)絡流量.
由于主流的瀏覽器已經(jīng)支持Websocket,前端socket.io.js 也具有較好的自適應性,所以整個系統(tǒng)具有較好的瀏覽器兼容性.經(jīng)過測試,報警推送功能在最新版的Chrome,Firefox,IE11 等常用瀏覽器上均能正常工作并具有較好的性能.
本系統(tǒng)已經(jīng)在生產(chǎn)環(huán)境中使用.該平臺包含4.5 萬臺入網(wǎng)終端,其中大約2.1 萬在線終端.日均產(chǎn)生報警數(shù)據(jù)750 萬條左右.平均每秒鐘推送86 條報警.服務器的CPU、內(nèi)存和網(wǎng)絡均保持較低壓力狀態(tài).客戶端Chrome 瀏覽器的CPU 和內(nèi)存占用保持平穩(wěn),說明瀏覽器能承受如此大量的報警推送.系統(tǒng)已經(jīng)穩(wěn)定運行數(shù)月,證明這是一套可用于生產(chǎn)環(huán)境的穩(wěn)定系統(tǒng).
根據(jù)車聯(lián)網(wǎng)的特定業(yè)務場景,設計了一種基于Websocket 技術的車聯(lián)網(wǎng)報警推送系統(tǒng).對比AJAX 輪詢技術和Websocket 技術可知,使用Websocket 方案以后,大大降低報警的推送延遲,提高了報警推送的吞吐量,保證報警信息及時準確地推送到客戶.隨著HTML5 技術的普及,Websocket 將逐漸成為Web 實時通信的主流技術.
本文介紹的方案解決了報警實時性和準確性等問題.但是當數(shù)萬終端每天產(chǎn)生750 萬報警數(shù)據(jù)時,如何讓用戶方便查看并處理海量報警成了新的問題.報警聚合和報警聯(lián)動將是后續(xù)研究的方向.