如何用Go實現一款類似滴滴優步的網絡約車軟件(含源碼)


導讀:我們經常使用打車軟件出行,也經常思考其架構設計。本文作者在所在國家也負責開發一款打車軟件,並且開源了其中大部分代碼,可以幫助我們更好了解網絡約車軟件的架構體系。本文由高可用架構翻譯。

 

 

各位讀者好,本文將給大家分享我們如何通過內存存儲實現地圖動畫車效果。 我們公司也運營了一個類似 Uber 的軟件 Namba Taxi,我們需要在客戶端主屏幕上顯示動畫車。 這篇文章是關於功能如何完整實現的文章,主要目的不是介紹 Go 語言。

 

開始

 

這個故事始於2015年,我們的移動開發人員開發一款軟件,工作主題是為出租車司機提供打車服務。 在應用程序中,動畫汽車看起來像下面的圖中動畫那樣 [1] 。




我們的第一個挑戰是缺乏地圖跟蹤數據。我們每 15 秒獲取一次位置數據。 我們不能簡單減小上報間隔,因為當司機端程序上行數據時候,同時需要獲取當前訂單,下一個訂單,以及一些警報功能(一個SOS按鈕, 當司機按下它,其他司機就可以幫助他)。當我們減少更新間隔時,系統流量更大。 我們不確認我們是否能夠扛住如此大的刷新。

 

實現的第一步

 

我們第一次的嘗試比較簡單:

 

  1. 處理請求並保存坐標。

  2. 創建另一個請求並為汽車設置動畫。

 

顯而易見,這樣做存在一些問題,如大家在一些打車軟件所見,我們不能正確地繪制汽車路線,汽車可能跑在田野,森林,湖泊和公寓上,用這種方法后效果看起來是這樣的 [2]。

 


作為問題的解決方案,我們使用 OpenStreetMap Routeing Machine(OSRM)來規划線路並改進我們的算法,並使用相同的超時設置。

 

  1. 發起請求。

  2. 獲取坐標。

  3. 將保存的坐標發送到服務器。

  4. 通過 OSRM 構建路線。

  5. 返回數據到客戶端。

 

通過線路規划體系,現在似乎可以工作了,但我們又面臨單向道路的問題

 

 

例如,司機停留在紅點的十字路口。 但他的設備位置准確性有問題,導致數據標記在十字路口的對面。 在客戶端,我們獲取這些坐標,保存並發送到后端,OSRM 建立一個合法的路線,並返回給應用程序。因為客戶端移動得非常快,所以這種情況路線規划很可笑。

我們以一種朴素的方式解決了這個問題。 我們檢查兩點之間的最短距離,並且不建立距離小於 20 米的路線。 使用該算法經過幾天的測試后,我們決定發布我們的應用程序並希望獲取一些反饋。

盡管如此,我們的版本還存在一些問題,所以我們決定進行第二次迭代。

 

  1. 第一是車費計算器,計算是在司機端(客戶端)完成,這樣避免發送無用的請求,可以節約很多服務端資源。 另一方面,為了安全等方面考慮,我們需要在服務器端復制數據並保存它。

  2. 此外,我們意識到每 15 秒一次上報太少,因為用戶在屏幕打開后,15秒后才會看到車在移動。

  3. 此外,我們在司機端的 GPS 模塊有很多問題,這個可能跟司機的手機設備相關。

  4. 最后,我們想要在主屏幕上渲染動畫車。

 

還需要解決的問題

 

  1. 從司機收集更多的數據

  2. 在主屏幕上顯示動畫車

  3. 在服務器端存儲行車過程中計費數據

  4. 節約移動流量

  5. 每秒收集一次數據

 

我想談一談有關節約移動流量帶寬的問題。在我們國家,出租車收費非常便宜,我們像使用公共交通那樣使用出租車。 例如,從城市的一邊跑到另一邊可能只需要 2 歐元,這就跟在巴黎坐地鐵價格差不多。但另外一方面移動帶寬成本還也很高,如果我們每秒節約 100 字節,那么我們將給為公司節省差不多 2000 美元。

 

數據追蹤

 

  1. 司機位置(緯度,經度)

  2. 司機當前的 session 信息,在登錄時我們會給司機端提供 session id

  3. 訂單信息(訂單 ID 和車費)

 

我們決定每一次數據上報應小於 100 字節。 我們尋找傳輸協議來解決這個問題

正如你可以看到,我們審視了以下幾個協議:

 

  1. HTTP

  2. WebSockets

  3. TCP

  4. UDP

 

對我們來說理想的選擇是 UDP,因為:

 

  1. 我們只發送數據報

  2. 我們不需要保證送達

  3. 極簡主義

  4. 保存大量數據

  5. 只有 20 字節開銷

  6. 在我們的國家的移動網絡沒有被阻止

 

至於數據序列化,我們考察了:

 

  1. JSON

  2. MsgPack

  3. Protobuf

 

我們選擇 ProtoBuf,因為它對小數據非常有效。

 

 

以看到最近的競爭對手是 PB 的三倍。(小編:可以參考 TimYang 的一條微博 [3] )

 

每次上報總共的數據

 

  1. 42 字節的業務數據

  2. 加上 20 字節的 IP 報頭

  3. 得到每次上報 62 字節數據

 

當我們獲得數據時,我們考慮如何存儲。

 

數據存儲

 

我們需要存儲這些數據:

 

  1. 標識司機的會話信息 session id

  2. 車牌號

  3. 訂單 ID 和計費信息

  4. 執行搜索的最后位置

  5. N 次最后位置以規划路線

 

使用的存儲

 

  1. 使用 Percona 存儲所有數據。 我們存儲司機,訂單,計費等。

  2. Redis 作為用於緩存。

  3. Elasticsearch 用於地理編碼

 

如上所述,當有大量在線司機時候,使用這些存儲來保存數據並不方便。 所以我們需要地理索引。

我們評估了兩個地理索引:

 

  1. KD 樹

  2. R 樹。

 

我們對地理索引的要求:

 

  1. 搜索 N 個最近的點。

  2. 我們需要一個平衡樹,以在最糟糕的情況下提供最好的搜索

 

KD 樹

KD 樹並不適合我們的需要,因為它是不平衡的,只能搜索一個最近的點。 我們可以在 kd-tree 上實現 k-nearest 鄰居,但是沒必要重造輪子,因為 R-tree 已經解決了這個問題。

 

R-樹

 

 

它看起來像這樣。 我們可以執行搜索 N 個最近點,並且它是平衡樹。 我們選擇了這個。

您可以得到它的 Go 語言實現源碼 [5]。


另外,我們需要一個過期機制,因為我們需要使司機的超時機制,比如司機端 900 秒沒有響應則在服務器刪除會話。 所以我們需要 LRU 數據結構來存儲最后的位置。 同時因為我們只存儲 N 個位置。 如果我們嘗試添加數據時候,隊列存儲已滿,我們則刪除最少使用的那個條目。 

 

下面是我們的存儲架構。

 

  1. 我們將所有數據存儲在內存中。

  2. 我們使用 R-tree 執行搜索最近的司機

  3. 此外,我們使用兩個檢索圖,可以並按車牌號或session執行搜索

 

我們打車軟件最終算法

 

這里是后端的最終算法:

 

  1. 使用 UDP 傳輸數據

  2. 嘗試從存儲獲取司機

  3. 如果存儲不存在 - 則從 Redis 獲取司機

  4. 檢查並驗證數據

  5. 將司機保存到存儲

  6. 如果不存在 - 初始化 LRU

  7. 更新 r-tree

     

HTTP 接口

 

我們實現了這些接口:

 

  1. 返回最近的司機;

  2. 從存儲中刪除司機(通過車牌號或session id)

  3. 獲取行程信息

  4. 獲取司機信息

 

結論

 

最后,我想給出我們在后端系統中總結的經驗:

 

  1. UDP + Protobuf 以節省數據

  2. 內存存儲

  3. R 樹獲取最近的司機

  4. LRU 緩存用於存儲最后的 n 個位置

  5. OSRM 用於地圖匹配和定制路線

     

     

 

您可以在 github [5] 上查看上面整個過程的源代碼。現在功能還比較簡單,但實現了文章中描述的許多功能。

 

參考資源

 

  1. GIF 動畫下載:https://cdn-images-1.medium.com/max/1600/1*nI6cNApASR1mg6F5Sjgp7Q.gif

  2. https://cdn-images-1.medium.com/max/1600/1*KfGB1SARPoqOUtPtl4NNBg.gif

  3. http://weibo.com/10503/24F1QpDmL

  4. https://github.com/dhconnelly/rtreego

  5. https://github.com/maddevsio/openfreecabs

  6. 本文英文原文:https://blog.maddevs.io/how-we-built-a-backend-system-for-uber-like-map-with-animated-cars-on-it-using-go-29d5dcd517a#.npo2x5788

 

推薦閱讀

 

 

本文由高可用架構翻譯,轉載請注明出處,技術原創及架構實踐文章,歡迎通過公眾號菜單「聯系我們」進行投稿。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM