導讀:我們經常使用打車軟件出行,也經常思考其架構設計。本文作者在所在國家也負責開發一款打車軟件,並且開源了其中大部分代碼,可以幫助我們更好了解網絡約車軟件的架構體系。本文由高可用架構翻譯。
各位讀者好,本文將給大家分享我們如何通過內存存儲實現地圖動畫車效果。 我們公司也運營了一個類似 Uber 的軟件 Namba Taxi,我們需要在客戶端主屏幕上顯示動畫車。 這篇文章是關於功能如何完整實現的文章,主要目的不是介紹 Go 語言。
開始
這個故事始於2015年,我們的移動開發人員開發一款軟件,工作主題是為出租車司機提供打車服務。 在應用程序中,動畫汽車看起來像下面的圖中動畫那樣 [1] 。
我們的第一個挑戰是缺乏地圖跟蹤數據。我們每 15 秒獲取一次位置數據。 我們不能簡單減小上報間隔,因為當司機端程序上行數據時候,同時需要獲取當前訂單,下一個訂單,以及一些警報功能(一個SOS按鈕, 當司機按下它,其他司機就可以幫助他)。當我們減少更新間隔時,系統流量更大。 我們不確認我們是否能夠扛住如此大的刷新。
實現的第一步
我們第一次的嘗試比較簡單:
-
處理請求並保存坐標。
-
創建另一個請求並為汽車設置動畫。
顯而易見,這樣做存在一些問題,如大家在一些打車軟件所見,我們不能正確地繪制汽車路線,汽車可能跑在田野,森林,湖泊和公寓上,用這種方法后效果看起來是這樣的 [2]。
作為問題的解決方案,我們使用 OpenStreetMap Routeing Machine(OSRM)來規划線路並改進我們的算法,並使用相同的超時設置。
-
發起請求。
-
獲取坐標。
-
將保存的坐標發送到服務器。
-
通過 OSRM 構建路線。
-
返回數據到客戶端。
通過線路規划體系,現在似乎可以工作了,但我們又面臨單向道路的問題
例如,司機停留在紅點的十字路口。 但他的設備位置准確性有問題,導致數據標記在十字路口的對面。 在客戶端,我們獲取這些坐標,保存並發送到后端,OSRM 建立一個合法的路線,並返回給應用程序。因為客戶端移動得非常快,所以這種情況路線規划很可笑。
我們以一種朴素的方式解決了這個問題。 我們檢查兩點之間的最短距離,並且不建立距離小於 20 米的路線。 使用該算法經過幾天的測試后,我們決定發布我們的應用程序並希望獲取一些反饋。
盡管如此,我們的版本還存在一些問題,所以我們決定進行第二次迭代。
-
第一是車費計算器,計算是在司機端(客戶端)完成,這樣避免發送無用的請求,可以節約很多服務端資源。 另一方面,為了安全等方面考慮,我們需要在服務器端復制數據並保存它。
-
此外,我們意識到每 15 秒一次上報太少,因為用戶在屏幕打開后,15秒后才會看到車在移動。
-
此外,我們在司機端的 GPS 模塊有很多問題,這個可能跟司機的手機設備相關。
-
最后,我們想要在主屏幕上渲染動畫車。
還需要解決的問題
-
從司機收集更多的數據
-
在主屏幕上顯示動畫車
-
在服務器端存儲行車過程中計費數據
-
節約移動流量
-
每秒收集一次數據
我想談一談有關節約移動流量帶寬的問題。在我們國家,出租車收費非常便宜,我們像使用公共交通那樣使用出租車。 例如,從城市的一邊跑到另一邊可能只需要 2 歐元,這就跟在巴黎坐地鐵價格差不多。但另外一方面移動帶寬成本還也很高,如果我們每秒節約 100 字節,那么我們將給為公司節省差不多 2000 美元。
數據追蹤
-
司機位置(緯度,經度)
-
司機當前的 session 信息,在登錄時我們會給司機端提供 session id
-
訂單信息(訂單 ID 和車費)
我們決定每一次數據上報應小於 100 字節。 我們尋找傳輸協議來解決這個問題
正如你可以看到,我們審視了以下幾個協議:
-
HTTP
-
WebSockets
-
TCP
-
UDP
對我們來說理想的選擇是 UDP,因為:
-
我們只發送數據報
-
我們不需要保證送達
-
極簡主義
-
保存大量數據
-
只有 20 字節開銷
-
在我們的國家的移動網絡沒有被阻止
至於數據序列化,我們考察了:
-
JSON
-
MsgPack
-
Protobuf
我們選擇 ProtoBuf,因為它對小數據非常有效。
以看到最近的競爭對手是 PB 的三倍。(小編:可以參考 TimYang 的一條微博 [3] )
每次上報總共的數據
-
42 字節的業務數據
-
加上 20 字節的 IP 報頭
-
得到每次上報 62 字節數據
當我們獲得數據時,我們考慮如何存儲。
數據存儲
我們需要存儲這些數據:
-
標識司機的會話信息 session id
-
車牌號
-
訂單 ID 和計費信息
-
執行搜索的最后位置
-
N 次最后位置以規划路線
使用的存儲
-
使用 Percona 存儲所有數據。 我們存儲司機,訂單,計費等。
-
Redis 作為用於緩存。
-
Elasticsearch 用於地理編碼
如上所述,當有大量在線司機時候,使用這些存儲來保存數據並不方便。 所以我們需要地理索引。
我們評估了兩個地理索引:
-
KD 樹
-
R 樹。
我們對地理索引的要求:
-
搜索 N 個最近的點。
-
我們需要一個平衡樹,以在最糟糕的情況下提供最好的搜索
KD 樹
KD 樹並不適合我們的需要,因為它是不平衡的,只能搜索一個最近的點。 我們可以在 kd-tree 上實現 k-nearest 鄰居,但是沒必要重造輪子,因為 R-tree 已經解決了這個問題。
R-樹
它看起來像這樣。 我們可以執行搜索 N 個最近點,並且它是平衡樹。 我們選擇了這個。
您可以得到它的 Go 語言實現源碼 [5]。
另外,我們需要一個過期機制,因為我們需要使司機的超時機制,比如司機端 900 秒沒有響應則在服務器刪除會話。 所以我們需要 LRU 數據結構來存儲最后的位置。 同時因為我們只存儲 N 個位置。 如果我們嘗試添加數據時候,隊列存儲已滿,我們則刪除最少使用的那個條目。
下面是我們的存儲架構。
-
我們將所有數據存儲在內存中。
-
我們使用 R-tree 執行搜索最近的司機。
-
此外,我們使用兩個檢索圖,可以並按車牌號或session執行搜索
我們打車軟件最終算法
這里是后端的最終算法:
-
使用 UDP 傳輸數據
-
嘗試從存儲獲取司機
-
如果存儲不存在 - 則從 Redis 獲取司機
-
檢查並驗證數據
-
將司機保存到存儲
-
如果不存在 - 初始化 LRU
-
更新 r-tree
HTTP 接口
我們實現了這些接口:
-
返回最近的司機;
-
從存儲中刪除司機(通過車牌號或session id)
-
獲取行程信息
-
獲取司機信息
結論
最后,我想給出我們在后端系統中總結的經驗:
-
UDP + Protobuf 以節省數據
-
內存存儲
-
R 樹獲取最近的司機
-
LRU 緩存用於存儲最后的 n 個位置
-
OSRM 用於地圖匹配和定制路線
您可以在 github [5] 上查看上面整個過程的源代碼。現在功能還比較簡單,但實現了文章中描述的許多功能。
參考資源
-
GIF 動畫下載:https://cdn-images-1.medium.com/max/1600/1*nI6cNApASR1mg6F5Sjgp7Q.gif
-
https://cdn-images-1.medium.com/max/1600/1*KfGB1SARPoqOUtPtl4NNBg.gif
-
http://weibo.com/10503/24F1QpDmL
-
https://github.com/dhconnelly/rtreego
-
https://github.com/maddevsio/openfreecabs
-
本文英文原文:https://blog.maddevs.io/how-we-built-a-backend-system-for-uber-like-map-with-animated-cars-on-it-using-go-29d5dcd517a#.npo2x5788
推薦閱讀
本文由高可用架構翻譯,轉載請注明出處,技術原創及架構實踐文章,歡迎通過公眾號菜單「聯系我們」進行投稿。