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