东南亚的超级APP是如何用Go打造Grab的路径规划和ETA引擎
胡泊:Grab/地圖團隊資深架構師。
入行10年,前端、后端、大數據均有涉獵。現就職于Grab,從零開始搭建了Grab路徑規劃服務,經過三年努力,在多項指標上擊敗國際地圖服務商和東南亞本地地圖服務商,成為Grab業務的主要支撐力之一。
大家好,我是來自Grab的胡泊。非常榮幸有機會跟大家分享我們是如何用Go搭建Grab路徑規劃和EAT的引擎。本次演講我分以下幾個部分:
一、Who’s Grab
二、團隊角色
三、路徑規劃和ETA引擎的構建和演進
四、Go in Grab
Who’s Grab
首先什么是Grab?去年 Gopher? China大會上,我們自我介紹我們是東南亞最大的出行平臺,但是不想做送餐的出行不是好支付,我們在收購了Uber東南亞的業務后,開始在出行、送餐和支付等各個領域發力,希望成為東南亞的Super APP,簡單說就是滴滴加美團加螞蟻金服。
? ? ?
團隊角色
什么是路徑規劃呢?就是給定起點和終點,規劃一條合理的路徑。什么是ETA呢?它是Estimated Time of Arrival的縮寫,在我們業務場景中的意思就是給定一個路線,預計一個行駛的時間。這個詞在英語里面還有一個含義就是"什么時候能做完",所以當我在公司內部交流工具里把ETA加為關鍵詞,一出現就發提醒后,全公司無論哪里出了問題,我都會被提醒,不幸成了全公司的oncall。
業務場景
派單
下面介紹一下路徑規劃和ETA在我們場景中起什么作用。大家對于出行和送餐業務場景非常熟悉,用戶下單第一步就是要找到一個合適的司機或者找到一個合適的騎手,而準確計算司機到達時間和合理的騎手路線是派單中不可缺少的環節,小而言之它會影響每一次消費者和用戶的體驗,大而言之它會影響平臺的效果。平臺之間的競爭最核心的就是用戶的體驗和整體的效率,所以這是支撐我們平臺最核心的引擎之一。
計價
計價同樣離不開準確的距離和時間計算。我們計價特點和國內不一樣,我們是一口價,當乘客決定起點和終點,就要給出固定價格。生活中,我們會遇到很多在異國或是一些地方打車時,明明是直線,在沒有Grab時,打出租車,上車后司機就開始繞行,乘客在這情況下往往沒有安全感。為了給用戶帶來安全體驗,讓他們安心,我們決定一口價。當然這也就給我們帶來壓力,乘客安心了,但是如果把錢算少了,司機肯定就不干了。
司乘體驗
下面談談司乘體驗。當我們叫到一輛車,不僅顯示司機什么時間到,而且會展示路線和路況,這樣乘客會知道司機堵在哪兒,緩解未知的焦慮。還有ERP,這是新加坡的收費站,他們收費站跟我們收費不一樣,他們在道路埋了磁感線圈,車輛經過時就會收費。這個乘客在車上很難感受到,而我們知道行使路線和狀態,所以會主動給乘客發推送,讓乘客清楚知道價格為什么發生變化。
另外我們非常重視安全,因為我們知道預計行使軌跡,還知道司機的位置、速度等一些實際狀態,這樣我們就能檢測異常。司機是否在超速,是否逆行,或者明明這段路很順暢,但是檢測到司機發生異常停留,那可能就是發生了這兩種情況: 要么司機出了車禍,要么發生一些不安全事件,我們安全組會迅速采取行動降低這種事件的發生。
綜上所述,我們路徑規劃和ETA是公司出行和送餐非常底層的技術引擎,支撐公司和用戶體驗和效率。
路徑規劃和ETA引擎的構建和演進
下面詳細介紹一下我們搭建ETA系統的歷史和心路歷程。在Grab北京研發中心剛成立時,有一天 CTO 把我們叫到一個會議室,在白板密密麻麻畫了一個線團,然后點了兩個點,說我們有東南亞最豐富軌跡的數據,我們能不能從中計算出 A 點到 B 點的時間。大家面面相覷,然后他又接著問了我們一個直擊靈魂的問題,ETA是什么?(什么時候能做完?)。領導發話了,我們就要找到解決方案。領導說的直接根據軌跡計算ETA ,其實還是探索領域,暫時沒有很好的工程落地實踐。我們還探索了一種方案: 把地圖打成一個個格子,把兩點間的時間計算轉移成格子間概率轉移的計算。但缺點是格子的大小很難定義,太大準確性無法保障,太小復雜度很難控制,最終我們還是決定把路網抽象成圖,把問題轉換成圖搜索問題。
地圖
開源地圖OSM
既然我們決定了大的技術方向是要用循路算法,那第一個問題就是:地圖在哪兒?這一幅圖是達芬奇的《救世主》,表達了我第一次見到OSM時的內心感受。相信大家都從開源軟件收益很多,有很多偉大的軟件讓我們使用,這是我第一次發現還有開源數據這樣方便實用的東西。
OSM發展歷程
簡單回顧下OSM 的發展歷程: 它在2004年最開始是眾包的形式,鼓勵地圖愛好者通過騎車和駕駛收集GPS數據,然后編輯后上傳。很快,互聯網巨頭敏銳嗅到了其中的商業價值,紛紛加入了貢獻數據的行列。最早他們用測繪車采集數據,但是這個成本非常高,效率非常低。像今天 Facebook 更多采用 AI 的手段,通過衛星圖像識別,自動化把地圖補上,這個大大降低成本,提高效率。
雖然這些巨頭在補充這個數據,但是我們面臨的問題是這些巨頭們的商業范疇主要以歐美為主,以及OSM在歐美比較活躍,在亞洲的參與者比較少。我們剛接觸這個項目,探索地圖質量,發現以北京為例就是五環以內的主要道路有,五環以外很可能是一片空白,而且里面缺少了很多的毛細血管的道路,所以我們首先要做的就是補充數據。不但要補充整個路網信息,還有補充路的屬性,比如確認這條路是不是單向路,是不是有禁止左轉,這都是為了圖的連通性完整。補充數據來源也是剛才CTO提到的: 我們有東南亞最豐富的軌跡數據,同時我們還采集街景圖像。
路網學習
下面介紹我們經過迭代非常行之有效的方法。我們最后采取的是圖形學的方法,最左邊是一個GPS軌跡點的圖片,準確說是map matching失敗的點。隨著不斷有司機經過,GPS累積,我們就能找到這一塊路的輪廓,這一塊就是疑似缺失的道路。我們把這個轉換成圖形學問題,通過一系列灰度化,找輪廓,找中心線的操作,我們就比較準確找到缺失道路的位置。我們會把這些數據給我們離線團隊進行審核編輯,幫助我們補充路網。
? ? ?
這是經過我們這些努力,使雅加達2016年基本空白到現在基本上把“毛細血管”補充非常完整。整個Grab團隊在這幾年和離線團隊配合,總共給東南亞補充了超過10萬公里的道路。這些大概相當于繞赤道兩周。而且這些數據我們都回饋給了開源社區,如果大家想到東南亞創業,也可以使用這些數據。
司機定位
有了地圖,其實還是不夠,剛才提到了,我們最核心的問題是要根據兩個點來計算一個路徑或者是一個時間,這兩個點的準確性實際也是非常重要的。這個圖有一點點夸張了,很多年前一個順風車的截圖,看到這張圖司機還在朝鮮半島,隔海相望,不太順路。
GPS誤差
剛才那個例子有些夸張,我們實際遇到的主要問題是GPS的會受到天氣或者高樓被干擾信號。如圖,判斷司機到底行駛在綠色點的路還是藍色點的路,這是我們要解決的問題。 ?
?? ??
? ? ? ?? ? ?
剛才也提到了一個詞:可能性,所以我們把這個定位問題轉化成概率問題,通過概率模型解決,我們選擇的是隱馬爾可夫模型,這里其實是經典的解碼問題。這里稍微介紹一下HMM,它的核心是有一條隱藏狀態鏈,一條觀測狀態鏈,還有狀態間概率轉換的模型。
? ? ?
我們這里觀測狀態就是GPS點,隱藏狀態就是司機到底真實的位置是什么?下面我們通過建立這個模型解決這個問題。
? ? ?
這張圖就是紅色點是GPS點,就是我們觀測狀態,我們第一步要找到的就是所有可能的隱藏狀態,我們適用一個RTree進行搜索,把GPS點投射到附近道路,這樣就有了備選的點,在四個備選點找出哪兩兩相對可能性最大。這里給大家介紹一下,發射概率就是隱藏概率和觀測概率轉移的概率。我們的場景中就是GPS點到這個路的投射點的一個垂直距離,這個大家非常好理解,如果這個距離比較近可能性就比較大。
還有轉移的概率,就是兩個隱藏狀態間的轉移概率,這里就是候選點間導航距離和直線距離的比例。這個也很好理解,因為這是一個時序的序列,如果這兩個投射點導航距離繞很大的圈這個概率就非常低。有了算法,下一步就是要解決和面對工程的問題,首先我們做了一些過濾,去掉了低速的狀況和路口的狀況,下一步最核心的問題是管理海量數據,我們有數百萬在線司機,會上傳海量數據,而且這些數據要在各個微服務之間進行傳播,這么海量的數據需要一些手段進行處理。
? ? ?
第一個方法就是剪枝,在這個距離導航過程中,如果探索需要很長時間,我們要快速丟棄掉。
第二個方法是用谷歌的方法,坐標點大家非常熟悉,就是兩個經度緯度兩個浮點數,谷歌怎么做呢?它把浮點數轉換成整數,然后不用再存每一個坐標的具體值,而是只需要增量可以了,也就是第二個坐標和第一個坐標的差,這樣就只需要存一個幾百或者幾千這樣的小整數,這樣就能很好的節省空間。
我們還用到了Golang的gob。大家對protobuf都很熟悉,而Golang提供的gob不需要額外的schema,只要復用struct結構就可以,效率也非常好。這也是我們對于Golang很深的感受。就是他在保障性能的基礎上,讓代碼寫起來很簡單舒服。?
? ? ? ? ? ? ?
我們花這么長時間定位這些點,也不只是為了定位起終點,一個個點定位準之后,再通過聚集實際就形成了路況數據,這是導航中不可缺少的因素。這張圖是雅加達日常“寧靜“的夜晚,我第一次去雅加達出差晚上12點飛機,7點我還在辦公室,領導說你怎么還不走?我想我怎么也是在北京堵過的人,但是雅加達3000萬多的人口,跟北京一樣多的車,或許還有同樣多的摩托車,再再減去大家遵守的規則,就形成了非常混亂的路況。東南亞的城市大家可能旅游過,泰國、馬尼拉各個城市都是這樣,路況對我們非常非常重要。我們面臨最核心的問題,想分享一下還是海量的數據。
? ? ?
? ? ?
路況
路況支持了多個業務產品,比如說我們要去更新這個權重,我們要把路況渲染鋪到APP界面上,其中還有一個情況給我們造成非常大的壓力,就是給機器學習提供路況特征,這個特征是這樣的,每一次計算我們要把這個結果拆成每一個路段,每一個路段獲取這個路段的路況信息,這相當于我們這個路況的量相當巨大的ETA的QPS乘以平均路段分解的數量,這個達到幾千萬的量級,非常可怕。最早我們很天真,直接上Redis,一個節點不夠就上六十個,萬萬沒想到依然無法撐住海量的讀操作。
? ? ?
我們最開始的優化就是加內存緩存層,我們實現了RingBuffer的結構,維護的值是這個路段的過去數個時間窗口不同的值,這樣我們就把讀Redis做了平滑,壓力大大緩解了,可以支撐我們的業務,但是這個帶來新的問題,每一個Client不得不在內存維護這樣數據,帶來了內存和CPU的消耗。我們繼續演進,下一步的想法就是用Spark Streaming來做增量化讓客戶端讀取數據。這也很方便定制聚集化,路況是有不同的時間段,有的需要5分鐘為窗口聚集,也的需要1分鐘,這樣可以通過訂閱不同的流來解決。這里一些詳細的信息不方便透露,在申請專利。
? ? ?
有了地圖、有了定位,有了路況,數據已經備齊,我們開始面對算法問題。這個圖大家看起來非常親切和懷念,Dijkstra,一下把我們帶回大學課堂。這是大家最熟悉最經典的一個算法,但非常遺憾,我們業務量非常大,它無法支撐住我們業務量。我們就尋找和研究新的尋路算法,最核心的思想是用預處理換一個快速處理時間,通過預處理提高查詢速度。我們最終選擇了CH,這個算法做到了預處理時間和查詢時間非常平衡。他有一個核心的思想,他希望當距離比較遠的時候,在搜索時只探索相對重要的道路。比如說搜索中關村到國貿,我們肯定不希望在圖搜索過程當中過多搜尋回龍觀小區的路,而希望直接引向四環。
? ? ?
舉一個例子詳細介紹預處理的過程。顧名思義,CH的意思就是壓縮和分層。舉一個極端的例子做一個簡單的解釋。這個圖在現實不太可能出現。我們想選尋找1到15的路徑,在傳統算法中就是一個一個節點探。CH怎么做的呢?首先分層,大家可以看到每一個節點落到了相應不同的高度,這個是層級。這個層怎么用呢?預處理之后,跑的是雙向Dijkstra,他要求找1到15,首先從1和15兩個方向照,1正向找,15反向找,都只找更高層次的節點,大家看原來4到8,5到11節點,這樣明明存在通路被打斷了,他的處理方式是增加了快捷邊,通俗地說,當出現4、5、6這樣的波谷,就會建立4到6的快捷邊,快捷邊基礎上會不斷創建新的快捷邊,建完了這些快捷邊之后,一是這個論文論證了一定會確保結果的準確性。二是看到大大提升了這個查詢的效率。原來1到15這么多路的查詢,變成雙向快速幾步可以做到,這就是CH大概的中心思想。?
? ? ? ? ? ? ?
有了CH算法,我們解決算法層的問題。下面也遇到了很多性能的問題。我們也是通過調優過程中找到一些小的點,跟大家分享一下。第一是一定重用map/slice。第二是圖的結構,經典的圖結構是臨接鏈表等,我們的經驗是flatten成一個長的數組,這樣可以減少內存的開銷。下面有一點反直覺,坐標是一個經度、一個緯度,這是WGS84坐標,實際上還有其他的坐標,比如墨卡托投影坐標。當程序員發現不同類型的坐標,第一反應就是抽象一個接口。但是當發現程序里面有數億的坐標,這個接口本身的開銷就不能忽視,所以接口的使用也需要謹慎。我們首先還是要考慮性能問題。
?? ? ? ? ? ? ?
下面分享圖權重調優。這個圖很有意思。權重并不一定是距離和時間,我們不一定找最短的路和最快的路。我們希望找到的路是什么樣的路?最快也未必是最好,最短也未必是最好的,可能司機最愿意走的路才是最合理的,我們就通過運籌學方法找到權重。這個優化的過程剛才提到了,這個指標的差值越來越小,我們也是不斷訓練,希望讓這個權重不斷合理化,讓我們能夠找到的路徑變得越來越合理。
? ? ?
可能有了以上的這些數據,能支撐大業務量的算法,對于傳統的導航公司已經基本夠用了。我們為什么還要繼續用機器學習的方法調優呢?因為我們面對兩個非常現實的問題,第一個我們在初期地圖的質量還是沒有保障,所以需要機器學習彌補這個措施。還有很重要的場景剛才提到了,叫到一個司機顯示司機到達時間,這個時間并不長,就是兩三分鐘。但是在東南亞大家很難想象,他們幾乎不會用導航軟件看路的,尤其東南亞很多都是摩托車、蹦蹦車。司機都是老鄉問路,不認識路就下車問,所以不得不把行為因素考慮進去。能想到的方法就是用機器學習方法進行調優。我們想到一些特征,比如說紅綠燈數、轉彎數還有司機畫像,不斷演進過程。
從這個圖可以看出,我們使用的XgBoost模型有成千上萬棵樹,在樹里通過不同的特征得到調整值,最后最后匯總,這聽起來就非常適合Go語言。Go語言就是性能保證情況下,寫起來非常順暢。但是我們這里也踩了小小的坑跟大家分享一下。離線訓練模型還是主要用Python,在dump它訓練出模型給線上Go使用時,發現小數定五位之后精度無法保證,出現一些隨機性,導致線上和線下不一致。可以通過數字整形化,避免跨語言出現這樣一些問題。
下面分享下線下海量數據訓練和模擬。由于Spark對Go支持不好,我們自己寫了一個簡單的分布式的系統進行任務的分發和管理,好處是復用了線上核心代碼。我們Grab分實際環境和測試環境,在測試環境復用這些環境,在這個時間跑這些任務。有了這個系統之后發現大大提高了我們自己訓練和模擬的效率。
? ? ?
同時Grab是非常注重數據驅動的公司。我們想上任何一個新模型和版本,可能經過測試,在Grab內部也有一個實驗平臺,可以非常方便幫助配置化使用,你可以定義一個時間周期、一個車型,只要配置好,就會自動幫助你組織試驗,同時又完善的數據通道,這樣可以通過數據決策,到底你的模型是否合理。
Go in Grab
下面大概給大家介紹了流程,再就是介紹Go? in? Geo。我們為什么用Go做系統?
第一是Grab系統中Go性能非常好。同時我們發現Go語言對于地圖環境非常多好用的工具。簡單介紹幾個:第一是支持核心的地理幾何計算,這個非常方便;第二是處理OSM地圖的工具,第三是很有意思是MapBox實現的開源地圖渲染引擎,非常方便自己搭建一個地圖。后面不一一介紹了,都非常方便。
? ? ?
Grab內部也是有成百上千的微服務。我們在過程中有非常方便的工具,就是GrabKit,你注冊之后自動生成非業務的相關代碼,這樣可以專注在業務層面,大大提升了開發的效率。
第二個是Gandalf,是自動測試的平臺,可以方便幫助我們進行各種API的測試。
第三是Golangcamp,新兵訓練營,任何一個人加入Grab,會在這個訓練營實踐訓練代碼。同時Grab非常推崇Gopher ,我們也在新加坡、越南支持了Gopher 新加坡、越南。還有我們Grab內部的Gopher大神寫的Go DI的書。
我們還在持續的招聘。Grab總部在新加坡,在全球各地也是有多個研發中心,其中北京是最大的一個海外的研發中心,主要負責的業務包括剛才提到的送餐,這也是我們在全力發展的新業務,第二是地圖,通過我的分享,如果大家覺得地圖也比較有意思,歡迎加入我們。
Grab崇尚奮斗,但覺得奮斗不等于996,在955名單我寫PPT的時候排在第9位,我們不在加班的互聯網公司行業內,以上是全部分享,謝謝大家。
GO 中國征稿啦!
自“Go中國 ?” 公眾號上線以來,因為扎實的干貨(害羞)、前沿的解讀(嬌羞)、滿滿的福利一直深受 Gopher 們的喜愛,為了給大家帶來更具實力的干貨以及 Go 語項目開發經驗,我們將開始對外征稿!
現在我們開始對外征稿啦!如果你有優秀的 Go 語言技術文章想要分享,熱點的行業資訊需要報道等,歡迎聯系在菜單欄回復“投稿”“合作”聯系我們的小編進行投稿。
總結
以上是生活随笔為你收集整理的东南亚的超级APP是如何用Go打造Grab的路径规划和ETA引擎的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 实验2报告 胡泊
- 下一篇: 艺赛旗RPA 第三方库系列(二):提升