Uber是如何基於Go語言構建高QPS服務的?

2021-09-17 08:38:19 字數 2244 閱讀 2624

在2023年初,我們構建了乙個只做一件事(也的確做的非常好)的微服務——查詢地理圍欄(geofence lookup)。一年後,這項服務已經成為uber數百個正在執行的服務中每秒查詢次數(qps)最高的服務。接下來,本文將談論我們構建這項服務的原因以及我們是如何使用go語言快速構建和擴充套件這項服務的。\

\ 在uber,乙個地理圍欄就表示地球表面上人為劃分的乙個地理區域。此外,我們進一步在基於地理的配置中使用地理圍欄的概念。地理圍欄的概念在很多地方發揮了很重要的作用——向使用者展示在某個位置可使用的產品時,定義機場等特殊用途區域時以及在很多人同時呼叫實現動態定價時。\

\ colorado的乙個地理圍欄樣例\

在提取使用者手機的經緯度座標等基於地理位置的配置資訊時,需要做的第一件事情就是確定該位置落在哪個地理圍欄中。而該功能已經在多個服務或模組中被實現。但是,當我們從一體化架構轉移到基於微服務的架構時,我們選擇了將這項功能集中在乙個新的單獨的微服務中進行實現。\

\ 當我們評估所要使用語言的時候,node.js正是廣大的服務設計團隊普遍採用的程式語言。而我們也在node.js的使用方面有著豐富的經驗。然而,go語言卻由於以下原因滿足了我們的需求:\

cpu密集型負載。查詢地理圍欄需要使用計算密集型的point-in-polygon(pip)演算法。儘管node.js可以很好的用於i/o密集型的服務,解釋執行以及動態型別定義等的特性使得它並不適合於我們的使用場景。 \

非中斷式的後台載入。為了保證查詢操作是基於最新的地理圍欄資訊而進行的,服務必須要能夠根據多個資料來源的資訊在後台實時重新整理記憶體中的地理圍欄資料。因為node.js是單執行緒的,後台重新整理很可能會占用一定的cpu時間(如cpu密集型的json的編譯工作),最終導致部分查詢的響應時間過長。但是,對於go語言而言,這完全不是問題。goroutine可以執行在多個cpu核上,並且可以在響應前端查詢的同時後台並行進行重新整理資料的工作。

\ 當給定了乙個經緯度座標的位置資訊時,如何從上萬個地理圍欄中找到該位置所在的那乙個呢?最直接而暴力的解決方法為:瀏覽所有的地理圍欄,然後採用光線投射(ray casting)等演算法進行pip檢查。但是,這種方法實在是太慢了。那麼,我們怎麼才能有效的縮小搜尋空間呢?\

由於uber的商業模型是以城市為中心的,我們並沒有採用r-tree或s2等結構來索引地理圍欄,而是採用了乙個相對要簡單很多的演算法;商業規則以及相關的地理圍欄都和城市相關。這使得我們可以採用層次式的方式來組織地理圍欄——第一層是定義城市邊界的圍欄,而第二層是城市內的城市圍欄。\

對於每一次查詢,我們首先線性掃瞄所有的邊界城市圍欄,找到目的城市。然後,我們採用另外一種線性掃瞄的方法來找到城市內的目標城市圍欄。儘管新演算法的複雜度仍然是o(n)

,它卻把n從萬降到了百,大大減少了演算法執行的複雜性。\

\ 根據設計需求,我們希望這項服務是無狀態的。因此,每一次請求都可以傳送給任意的服務例項,並獲得相同的結果。這意味著每乙個服務例項都必須掌握全域性資訊,而非區域性資訊。我們採用了一種確定性的輪詢排程策略,從而保證了不同服務例項的地理圍欄資料都是同步的。這樣,該服務的架構也非常簡單。後台任務周期性地輪詢來自不同資料來源的資料。而這些資料就儲存在主存中以服務不同的查詢。同時,資料被序列儲存在本地檔案系統中,以實現系統重啟時的快速引導。\

\ 查詢地理圍欄服務的架構\

\ 我們的服務架構需要對記憶體中的地理索引資訊進行並行讀寫訪問。在特殊情況下,後台輪詢任務修改索引,而前台查詢引擎同時從索引中讀取資訊。相比於利用單執行緒的node.js進行服務編寫的情況而言,go儲存模型必然會遇到挑戰。儘管go語言可以利用goroutine和channel自然的實現並行讀寫,其帶來的效能影響不可忽視。我們試圖利用sync/atomic包中的storepointerloadpointer原語來自己管理記憶體屏障。但是,這導致了**難以理解和維護。\

最終,我們選擇了一種折衷的方式——利用讀寫鎖來保證對地理索引的同步訪問。為了減少鎖的競爭,新的索引片段在被自動交換到主索引之前都是處於隱藏狀態的。相比於storepointerloadpointer方法,這種鎖會輕微增加查詢延遲。但是,我們維護**庫的工作卻變得簡單很多,非常值得。\

\ 回首過往,我們非常開心選擇了go語言來編寫我們的服務。其帶來的好處包括:\

\ 儘管uber曾經主要採用node.js和python,go語言正在成為uber設計師構建新服務的選擇。\\\

感謝郭蕾對本文的審校。

\

Uber是如何基於Go語言構建高QPS服務的?

在2015年初,我們構建了乙個只做一件事 也的確做的非常好 的微服務 查詢地理圍欄 geofence lookup 一年後,這項服務已經成為uber數百個正在執行的服務中每秒查詢次數 qps 最高的服務。接下來,本文將談論我們構建這項服務的原因以及我們是如何使用go語言快速構建和擴充套件這項服務的。...

Uber是如何基於Go語言構建高QPS服務的?

在2015年初,我們構建了乙個只做一件事 也的確做的非常好 的微服務 查詢地理圍欄 geofence lookup 一年後,這項服務已經成為uber數百個正在執行的服務中每秒查詢次數 qps 最高的服務。接下來,本文將談論我們構建這項服務的原因以及我們是如何使用go語言快速構建和擴充套件這項服務的。...

Go語言的構建方法總結

趁著近期要換工作的空閒時間,看了一下go語言,與c 相比,go語言的確在不少地方輕便了不少,例如 增加了內建的字串型別 多個返回值 支援協程 簡單的構建方法等等。使得在生產效率方面有了不少的提高。今天這裡對go語言的構建方法做個簡單的總結。在c c 的工程中,極少使用單個命令來編譯 一般是通過一些工...