在2015年初我們創建了一個微服務,它只做一件事(也確實做得很好)就是地理圍欄查詢。一年后它成了Uber高頻查詢(QPS)服務,本次要講的故事就是我們為什么創建這個服務,以及編程語言新秀Go如何幫我們快速創建和擴展該服務。
?
背景
在Uber,一個地理圍欄就是在地表人為定義的地理區域(或多邊形幾何區域)。地理圍欄在Uber被廣泛用于基于地理位置的設置。向用戶展示給定區域有哪些產品可以使用,根據特殊需要(如機場)定義區域,并在乘車高峰時在相鄰區域實施動態定價是我們產品的重要應用場景。
一個科羅拉多地理圍欄示例。
?
第一步是通過用戶手機獲取地理位置信息如經緯度,進而確定用戶所在地理圍欄。這個功能分散在多個服務或模塊中。因為我們從整體架構向微服務架構遷移,我們選擇將這個功能做成一個新的微服務。
?
?使用Go語言
Node.js曾經是我們實時市場團隊主力開發語言,所以我們在Node.js上有較多的知識儲備和經驗。但是Go在以下幾個方面更符合我們的需求:
?
1、高吞吐低延遲的需要。Uber手機應用中的每個請求都需要地理圍欄查詢,而且響應快速(99% < 100毫秒)頻繁(每秒成千上萬),
2、適用于CPU密集型。地理圍欄查詢是點聚計算的CPU密集型服務。Node.js非常適合我們其他I/O密集型應用,但由于Node天生就是解釋型動態語言,所以它不適合此類應用。
3、非中斷后臺加載。為了給查詢服務提供最新的地理圍欄數據,服務需要在后臺不斷的從多個數據源加載內存數據。因為Node.js是單線程的,所以后臺更新會對CPU造成較長時候的堵塞(例如,CPU密集的JSON解析),從而影響到查詢響應時長。但Go不存在這些問題,因為goroutines 可以使用多核,后臺任務和前臺查詢可以并行。
是否使用地理信息索引:這是一個問題
?
通過經緯度指定一個地理位置后,如果從我們成千上萬的地理圍欄中確定它屬于哪一個?簡單粗暴的做法是:使用點聚檢查方式,如光線投射算法,從所有地理圍欄數據中查找。但這種式太慢。所以,我們如何縮小查詢范圍以提高效率?
?
我們沒有使用R-tree做地理圍欄索引和比較復雜的S2,通過觀察我們發現Uber的業務模式是以城市為中心的;業務規則和地理圍欄通常用一個城市來定義,所以我們選擇了一個簡單的路由方式。我們把地理圍欄整理為兩層結構,第一層是城市地理圍欄(定義城市邊界),第二層是每個城市內的地理圍欄。
?
?對于每個查詢,我們首先對所有城市地理圍欄做線性掃描查找所在城市,然后對該城市的地理圍欄數據做線性掃描。這個解析方案的運算復雜度是O(N),? 通過這個簡單的技術我們將N從10,000s減少到100s。
?
架構
我們希望這個服務是無狀態的,這樣每個請求可以發送到任意實例,而且得到結果是一致的。這意味著每個實例都擁有全量數據,而不是只存儲部分數據。我們生成了一個統一的拉取計劃,這樣不同服務實際的地理圍欄數據可以保持同步。因面這個服務的架構也就變得簡單。后臺任務定時從不同的數據存儲拉取地理圍欄數據。這些數據是在內存中存儲,以提高查詢速度,當服務需要重啟時會序列化到本地文件。
我們的地理圍欄數據查詢架構
處理Go內存模型
在我們的架構中需要對內存中的地理索引數據并發讀寫。當后臺拉取任務寫索引時,可能前臺查詢引擎同步讀取索引。有Node.js經驗的人熟悉了單線程模式,Go的內存模型對他們是一個挑戰。這對我們曾產生對負面影響。我們試圖使用sync/atomic 包的原語StorePointer/LoadPointer 來管理內存邊界問題,但這導致代碼很脆弱且難以維護。
?
?最后,我們采取了折中的方式,使用讀寫鎖來異步處理對地理索引的訪問。為了減少鎖的爭奪,新的索引在以原子的方式合并到主索引之前先建立索引片段。與 StorePointer/LoadPointer的方式相比,這些稍微增加一些延遲,但我們有理由相信代碼的簡潔和可維護性比這一點小小的延遲更有價值。
?
我們的經驗
回顧以往,我們很慶幸當初使用Go語言,并使用這種新的語言開發我們的服務。亮點如下:
?
1、開發效率高。C++,Java和Node.js的開發者只需要很短的時間就可以掌握Go,代碼易于維護。(靜態語言更加清晰,沒有莫名其妙的意外)。
2、在吞吐量和延遲方面性能很好。我們主數據中心,有針對非中國區的獨立服務,在2015年度高峰期間40臺服務器在170k QPS的負載情況下CPU只使用了35%。95%的響應時間小于5毫秒,99%的響應時間小于50毫秒。
3、超級穩定,這個服務自上線以來,99.99%的時間正常運轉。當機時間主要是由初學者的編程錯誤和第三方庫的文件描述符泄露導致。我們至今尚未遇到Go的運行時錯誤。
接下來?
過去Uber主要使用Node.js和Python,很多Uber新的服務開始選擇使用Go來創建。Go是Uber未來的趨勢,所以如果你對Go很有激情,無論是專家還是初學者,都歡迎你來應聘,我們正在招聘Go開發者,噢對了,傳送門請點這里!
?
圖片來源:“金門地鼠”,作者:Conor Myhrvold,攝于三藩市的金門公園。標題解釋:地鼠(Go gopher)是Go項目的吉祥物,是Go的標識。
?