公司接了個智慧水務(wù)的項目,老板讓我這個“老Java”牽頭。一開始覺得就是CRUD,真干起來才發(fā)現(xiàn)全是門道。今天不聊虛的,分享一下我們用SpringBoot落地這個項目的核心設(shè)計和幾個讓我掉光頭發(fā)的技術(shù)難點。
剛接手這個項目時,我以為就是個普通的管理后臺,無非是抄抄水表、收收費。等看到需求文檔才懵了:要實時監(jiān)測水壓、流量、水質(zhì)(PH值、余氯),要預(yù)測用水量,還要能自動生成巡檢工單。得,這玩意兒是個物聯(lián)網(wǎng)加大數(shù)據(jù)的活。
一、技術(shù)選型:怎么穩(wěn)怎么來
后臺框架沒得說,SpringBoot 2.7 + MyBatis-Plus,這組合我們團(tuán)隊熟,能快速出活。數(shù)據(jù)庫用了MySQL 8.0存業(yè)務(wù)數(shù)據(jù),時序數(shù)據(jù)(就是那些每秒都在上報的壓力、流量值)是個大問題,最后選了TDengine。這玩意兒專門為物聯(lián)網(wǎng)時序數(shù)據(jù)設(shè)計,壓縮率和查詢速度比直接用MySQL分表強(qiáng)太多了。
難點來了,幾萬個傳感器每10秒上報一次數(shù)據(jù),并發(fā)寫入怎么扛?我們沒直接用TDengine的HTTP接口,而是自己用Netty搭了一個TCP接入服務(wù),做協(xié)議解析和數(shù)據(jù)緩沖,再批量寫入TDengine。這一步,光是處理TCP的粘包拆包就調(diào)試了兩天。
二、核心模塊設(shè)計(畫個丑圖)

- 設(shè)備管理:每個傳感器一個唯一編碼,關(guān)聯(lián)到具體的管網(wǎng)節(jié)點(如“XX小區(qū)3號樓進(jìn)水口”)。這里用了樹形結(jié)構(gòu),方便追溯整個供水路徑。
- 實時告警:這是核心。我們<typo id="typo-608" data-origin="沒用" ignoretag="true">沒用</typo>簡單的 if(value > threshold),而是引入了規(guī)則引擎Drools。比如規(guī)則可以寫成:“某個區(qū)域,連續(xù)3個時間點水壓下降超過10%,且該時段無計劃停水,則觸發(fā)爆管疑似告警”。Drools規(guī)則可以動態(tài)加載,運維小姐姐自己就能在后臺配,不用我們改代碼發(fā)布。
- 工單系統(tǒng):告警自動生成工單,通過WebSocket實時推送到運維人員的PC和APP端。這里結(jié)合了Redis的GEO功能,能根據(jù)工單地點就近派單給巡檢人員。
- 數(shù)據(jù)分析:用SpringBoot集成EasyExcel做報表導(dǎo)出是基礎(chǔ)。高級一點的是,我們基于TDengine的窗口函數(shù),做了每日用水量趨勢預(yù)測(一個簡單的線性回歸,用Java寫的,沒上Python),效果還行,能輔助水廠調(diào)度。
三、讓我印象最深的“坑”
最大的坑不是技術(shù),是數(shù)據(jù)一致性。一個“關(guān)閥止水”的指令下發(fā)到設(shè)備,設(shè)備執(zhí)行了,但這個狀態(tài)回傳可能延遲或丟失。業(yè)務(wù)上要求這個狀態(tài)必須絕對準(zhǔn)確。我們的解決方案是:
- 指令下發(fā)后,在Redis生成一個帶超時時間的“指令狀態(tài)跟蹤鍵”。
- 啟動一個后臺線程輪詢(當(dāng)然,用分布式調(diào)度xxl-job),超時未收到確認(rèn),則觸發(fā)重發(fā)或轉(zhuǎn)為人工干預(yù)。
- 最終,所有設(shè)備事件和指令,我們都用Canal訂閱MySQL的binlog,同步到Elasticsearch,做全鏈路日志追蹤,方便撕鍋(劃掉)排查問題。
四、小結(jié)
這個項目做下來,最大的感觸是:物聯(lián)網(wǎng)項目,后端開發(fā)的重點不再是復(fù)雜的業(yè)務(wù)邏輯,而是如何高效、穩(wěn)定地處理海量的時序數(shù)據(jù),并保證端到端的數(shù)據(jù)一致性。 SpringBoot讓我們快速搭建了業(yè)務(wù)骨架,但真正發(fā)力的,是像TDengine、Netty、Drools這些專項工具。代碼就不貼了,有興趣的可以留言討論具體模塊。