0%

[從零開始做水平擴展] 2.問題

舊版 Worker 架構:

worker 內架構可以簡單劃成這樣

Untitled (2)

主要會分為 data threadnon-data thread 這兩個處理路徑,data thread 會開啟 10 個,用來處理設備傳輸上來的 data message, non-data thread 會開一個,用來處理設備上傳的 config、上下線資料,以及其他微服務的通知訊息等等

可以發現由於 data thread 會一路將資料從解析、存 redis、存 mongo、發 mqtt 一條龍處理到底,所以當這過程中的某個環節卡住時,data thread 就會無法接收新的資料導致整個 thread 卡住,連帶導致 message queue 裡面的資料堆積太久被丟棄,所以才會掉資料

遇到了什麼問題?

  1. 維護問題:

    舊版的 worker 是 java 寫的,且目前團隊中只有一位工程師會寫 java,所以就變成 worker 的問題只有他能解決,其他工程師都不了解 worker 實際上在做的內容

    且因為只有一位工程師在維護,所以 code 的風格就比較奔放

    所以這次重寫改使用 golang,是我們團隊大家都會的語言,這樣之後大家做維護與 code review 時比較方便

  2. 性能問題:

    這也是這次重做 worker 的主因,有客戶下了一個大訂單,購買了 2000 個設備,且每個設備有 250 個點位,如果假設設備 30 秒上傳一次資料,worker 需要處理 2000 * 250 / 30

    = 16666 message / sec

    目前的 worker 承受不住這麼多訊息一次進來,所以時常會發生 cpu or memory 爆滿、掉資料、設備上下限狀態不對等問題

    查看整個系統的瓶頸發現 worker 會卡住主要是因為在寫資料到 mongodb 那段花費的時間太久,因為 worker 寫 RawData 到 mongo 是採一天一個 collection 的方式去寫,所以設備數量多的時候,寫到晚上 mongo 資料就會很多 (幾億筆),寫的速度就會很慢

  3. 內存佔用過多:

    除了要寫資料到 mongo、redis 以外,worker 在啟動時也會將全站的所有註冊過的設備資料從 postgres 中讀取到 worker 內存中,用來 mapping 設備的 id (mac address, scadaid) 與我們平台上幫設備建立的設定,好確認這個設備是否開啟儲存頻率,如果有開的話才要存到 mongo 與發消息通知 archiver 去算 recording data,否則只要存 redis 就好

    但因為設備數量過多,導致 worker 在啟動時從 postgres 撈全部設備出來常常會把 memory 吃滿,甚至直接 OOM Killed 無法啟動

MongoDB 寫入問題:

目前 worker 一天會新增一個 collection 來存放當天的 raw data 資料

截圖_2023-07-14_上午9.25.25

之前觀察當 collection 資料多的時候寫入速度就會很慢,所以這邊有做一個簡單的壓測

模擬 800 台設備,每台設備 250 個點,每 30 秒一筆資料 = 800 * 250 = 200,000

來去寫入到 mongodb 的一個 collection

測試結果:

1
2
collection 空的時候: 寫入 200,000 筆資料花費 20 sec
持續寫入六個小時後: 寫入 200,000 筆資料花費 200 sec

Memory 佔用:

可以看到 worker 的 memory 長期為一條直線,把 memory 佔用得很滿

截圖_2023-07-14_上午9.39.06