達達-高性能服務端優化之路

達達-高性能服務端優化之路

提綱

業務場景

最初的技術選型

讀寫分離

垂直分庫

水平分庫(sharding)

總結

業務場景

達達是全國領先的最后三公里物流配送平臺。 達達的業務模式與滴滴以及Uber很相似,以眾包的方式利用社會閑散人力資源,解決O2O最后三公里即時性配送難題。 達達業務主要包含兩部分:商家發單,配送員接單配送,如下圖所示。

達達的業務規模增長極大,在1年左右的時間從零增長到每天近百萬單,給后端帶來極大的訪問壓力。壓力主要分為兩類:讀壓力、寫壓力。讀壓力來源于配送員在APP中搶單,高頻刷新查詢周圍的訂單,每天訪問量幾億次,高峰期QPS高達數千次/秒。寫壓力來源于商家發單、達達接單、取貨、完成等操作。達達業務讀的壓力遠大于寫壓力,讀請求量約是寫請求量的30倍以上。

下圖是達達過去6個月,每天的訪問量及QPS變化趨勢圖變化趨圖,可見增長極快

極速增長的業務,對技術的要求越來越高,我們必須在架構上做好充分的準備,才能迎接業務的挑戰。接下來,我們一起看看達達的后臺架構是如何演化的。

最初的技術選型

作為創業公司,最重要的一點是敏捷,快速實現產品,對外提供服務,于是我們選擇了公有云服務,保證快速實施和可擴展性,節省了自建機房等時間。在技術選型上,為快速的響應業務需求,業務系統使用python做為開發語言,數據庫使用Mysql。如下圖所示,應用層的幾大系統都訪問一個數據庫。

讀寫分離

隨著業務的發展,訪問量的極速增長,上述的方案很快不能滿足性能需求。每次請求的響應時間越來越長,比如配送員在app中刷新周圍訂單,響應時間從最初的500毫秒增加到了2秒以上。業務高峰期,系統甚至出現過宕機,一些商家和配送員甚至因此而懷疑我們的服務質量。在這生死存亡的關鍵時刻,通過監控,我們發現高期峰Mysql CPU使用率已接近80%,磁盤IO使用率接近90%,Slow query從每天1百條上升到1萬條,而且一天比一天嚴重。數據庫儼然已成為瓶頸,我們必須得快速做架構升級。

如下是數據庫一周的qps變化圖,可見數據庫壓力的增長極快。

當Web應用服務出現性能瓶頸的時候,由于服務本身無狀態(stateless),我們可以通過加機器的水平擴展方式來解決。 而數據庫顯然無法通過簡單的添加機器來實現擴展,因此我們采取了Mysql主從同步和應用服務端讀寫分離的方案。

Mysql支持主從同步,實時將主庫的數據增量復制到從庫,而且一個主庫可以連接多個從庫同步(細節參考Replication)。利用此特性,我們在應用服務端對每次請求做讀寫判斷,若是寫請求,則把這次請求內的所有DB操作發向主庫;若是讀請求,則把這次請求內的所有DB操作發向從庫,如下圖所示。

實現讀寫分離后,數據庫的壓力減少了許多,CPU使用率和IO使用率都降到了5%內,Slow Query也趨近于0。主從同步、讀寫分離給我們主要帶來如下兩個好處:

減輕了主庫(寫)壓力:達達的業務主要來源于讀操作,做讀寫分離后,讀壓力轉移到了從庫,主庫的壓力減小了數十倍。

從庫(讀)可水平擴展(加從庫機器):因系統壓力主要是讀請求,而從庫又可水平擴展,當從庫壓力太時,可直接添加從庫機器,緩解讀請求壓力

如下是優化后數據庫qps的變化圖:

讀寫分離前主庫的select qps

讀寫分離后主庫的select qps

當然,沒有一個方案是萬能的。讀寫分離,暫時解決了Mysql壓力問題,同時也帶來了新的挑戰。業務高峰期,商家發完訂單,在我的訂單列表中卻看不到當發的訂單(典型的read after write);系統內部偶爾也會出現一些查詢不到數據的異常。通過監控,我們發現,業務高峰期Mysql可能會出現主從延遲,極端情況,主從延遲高達10秒。

那如何監控主從同步狀態?在從庫機器上,執行show slave status,查看Seconds_Behind_Master值,代表主從同步從庫落后主庫的時間,單位為秒,若主從同步無延遲,這個值為0。Mysql主從延遲一個重要的原因之一是主從復制是單線程串行執行。

那如何為避免或解決主從延遲?我們做了如下一些優化:

優化Mysql參數,比如增大innodb_buffer_pool_size,讓更多操作在Mysql內存中完成,減少磁盤操作。

使用高性能CPU主機

數據庫使用物理主機,避免使用虛擬云主機,提升IO性能

使用SSD磁盤,提升IO性能。SSD的隨機IO性能約是SATA硬盤的10倍。

業務代碼優化,將實時性要求高的某些操作,使用主庫做讀操作

垂直分庫

讀寫分離很好的解決讀壓力問題,每次讀壓力增加,可以通過加從庫的方式水平擴展。但是寫操作的壓力隨著業務爆發式的增長沒有很有效的緩解辦法,比如商家發單起來越慢,嚴重影響了商家的使用體驗。我們監控發現,數據庫寫操作越來越慢,一次普通的insert操作,甚至可能會執行1秒以上。

下圖是數據庫主庫的壓力, 可見磁盤IO使用率已經非常高,高峰期IO響應時間最大達到636毫秒,IO使用率最高達到100%。

同時,業務越來越復雜,多個應用系統使用同一個數據庫,其中一個很小的非核心功能出現Slow query,常常影響主庫上的其它核心業務功能。我們有一個應用系統在MySql中記錄日志,日志量非常大,近1億行記錄,而這張表的ID是UUID,某一天高峰期,整個系統突然變慢,進而引發了宕機。監控發現,這張表insert極慢,拖慢了整個MySql Master,進而拖跨了整個系統。(當然在mysql中記日志不是一種好的設計,因此我們開發了大數據日志系統,敬請關注本博客后續文章。另一方面,UUID做主鍵是個糟糕的選擇,在下文的水平分庫中,針對ID的生成,有更深入的講述)。

這時,主庫成為了性能瓶頸,我們意識到,必需得再一次做架構升級,將主庫做拆分,一方面以提升性能,另一方面減少系統間的相互影響,以提升系統穩定性。這一次,我們將系統按業務進行了垂直拆分。如下圖所示,將最初龐大的數據庫按業務拆分成不同的業務數據庫,每個系統僅訪問對應業務的數據庫,避免或減少跨庫訪問。

下圖是垂直拆分后,數據庫主庫的壓力,可見磁盤IO使用率已降低了許多,高峰期IO響應時間在2.33毫秒內,IO使用率最高只到22.8%。

未來是美好的,道路是曲折的。垂直分庫過程,我們也遇到不少挑戰,最大的挑戰是:不能跨庫join,同時需要對現有代碼重構。單庫時,可以簡單的使用join關聯表查詢;拆庫后,拆分后的數據庫在不同的實例上,就不能跨庫使用join了。比如在CRM系統中,需要通過商家名查詢某個商家的所有訂單,在垂直分庫前,可以join商家和訂單表做查詢,如下如示:

select * from tb_order where supplier_id in (select id from supplier where name=‘上海海底撈’);

分庫后,則要重構代碼,先通過商家名查詢商家id,再通過商家Id查詢訂單表,如下所示:

supplier_ids = select id from supplier where name=‘上海海底撈’

select * from tb_order where supplier_id in (supplier_ids )

垂直分庫過程中的經驗教訓,使我們制定了SQL最佳實踐,其中一條便是程序中禁用或少用join,而應該在程序中組裝數據,讓SQL更簡單。一方面為以后進一步垂直拆分業務做準備,另一方面也避免了Mysql中join的性能較低的問題。

經過一個星期緊鑼密鼓的底層架構調整,以及業務代碼重構,終于完成了數據庫的垂直拆分。拆分之后,每個應用程序只訪問對應的數據庫,一方面將單點數據庫拆分成了多個,分攤了主庫寫壓力;另一方面,拆分后的數據庫各自獨立,實現了業務隔離,不再互相影響。

水平分庫(sharding)

讀寫分離,通過從庫水平擴展,解決了讀壓力;垂直分庫通過按業務拆分主庫,緩存了寫壓力,但系統依然存在以下隱患:

單表數據量越來越大。如訂單表,單表記錄數很快將過億,超出MySql的極限,影響讀寫性能。

核心業務庫的寫壓力越來越大,已不能再進一次垂直拆分,Mysql 主庫不具備水平擴展的能力

以前,系統壓力逼迫我們架構升級,這一次,我們需提前做好架構升級,實現數據庫的水平擴展(sharding)。業務類似于我們的Uber在公司成立的5年后(2014)年才實施了水平分庫(mezzanine-migration),但我們的業務發展要求我們在成立18月就要開始實施水平分庫。邏輯架構圖如下圖所示:

水平分庫面臨的第一個問題是,按什么邏輯進行拆分。一種方案是按城市拆分,一個城市的所有數據在一個數據庫中;另一種方案是按訂單ID平均拆分數據。按城市拆分的優點是數據聚合度比較高,做聚合查詢比較簡單,實現也相對簡單,缺點是數據分布不均勻,某些城市的數據量極大,產生熱點,而這些熱點以后可能還要被迫再次拆分。按訂單ID拆分則正相反,優點是數據分布均勻,不會出現一個數據庫數據極大或極小的情況,缺點是數據太分散,不利于做聚合查詢。比如,按訂單ID拆分后,一個商家的訂單可能分布在不同的數據庫中,查詢一個商家的所有訂單,可能需要查詢多個數據庫。針對這種情況,一種解決方案是將需要聚合查詢的數據做冗余表,冗余的表不做拆分,同時在業務開發過程中,減少聚合查詢。

反復權衡利弊,并參考了Uber等公司的分庫方案后,我們最后決定按訂單ID做水平分庫。從架構上,我們將系統分為三層:

應用層:即各類業務應用系統

數據訪問層:統一的數據訪問接口,對上層應用層屏蔽讀寫分庫、分庫、緩存等技術細節。

數據層:對DB數據進行分片,并可動態的添加shard分片。

水平分庫的技術關鍵點在于數據訪問層的設計,數據訪問層主要包含三部分:

ID生成器:生成每張表的主鍵

數據源路由:將每次DB操作路由到不同的shard數據源上

緩存: 采用Redis實現數據的緩存,提升性能(以后會有詳細文章)

ID生成器是整個水平分庫的核心,它決定了如何拆分數據,以及查詢存儲-檢索數據。ID需要跨庫全局唯一,否則會引發業務層的沖突。此外,ID必須是數字且升序,這主要是考慮到升序的ID能保證Mysql的性能(若是UUID等隨機字符串,在高并發和大數據量情況下,性能極差。對比性能測試數據可供參考uuid-vs-int-insert-performance)。同時,ID生成器必須非常穩定,因為任何故障都會影響所有的數據庫操作。

我們的ID的生成策略借鑒了Instagram的ID生成算法(sharding-ids-at-instagram)。具體方案如下:

整個ID的二進制長度為64位

前36位使用時間戳,以保證ID是升序增加

中間13位是分庫標識,用來標識當前這個ID對應的記錄在哪個數據庫中

后15位為自增序列,以保證在同一秒內并發時,ID不會重復。每個shard庫都有一個自增序列表,生成自增序列時,從自增序列表中獲取當前自增序列值,并加1,做為當前ID的后15位

水平分庫是一個極具挑戰的項目,我們也還在夜以繼日的開發中,欲知詳情,敬請繼續關注本博客。

總結

創業是與時間賽跑的過程,前期為了快速滿足業務需求,我們采用簡單高效的方案,如使用云服務、應用服務直接訪問單點DB;后期隨著系統壓力增大,性能和穩定性逐漸納入考慮范圍,而DB最容易出現性能瓶頸,我們采用讀寫分離、垂直分庫、水平分庫等方案。面對高性能和高穩定性,架構升級需要盡可能超前完成,否則,系統隨時可能出現系統響應變慢甚至宕機的情況。當然,架構升級的過程都是痛并快樂著,每次通宵達旦實施系統架構升級,一邊享受公司準備的美味佳肴夜宵,一邊感受解決技術難題,釋放公司生產力的成就感,不亦樂乎。

更多技術分享,敬請關注達達博客、公眾號、微信群。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,460評論 6 538
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,067評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,467評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,468評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,184評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,582評論 1 325
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,616評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,794評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,343評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,096評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,291評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,863評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,513評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,941評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,190評論 1 291
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,026評論 3 396
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,253評論 2 375

推薦閱讀更多精彩內容