架構演進實踐:從0到4000高併發請求背後的努力!
來自:即時通訊網
達達創立於2014年5月,業務覆蓋全國37個城市,擁有130萬註冊眾包配送員,日均配送百萬單,是全國領先的最後三公里物流配送平臺。達達的業務模式與滴滴以及Uber很相似,以眾包的方式利用社會閒散人力資源,解決O2O最後三公里即時性配送難題(2016年4月,達達已經與京東到家合併)。
達達的業務組成簡單直接——商家下單、配送員接單和配送,也正因為理解起來簡單,使得達達的業務量在短時間能實現爆發式增長。而支撐業務快速增長的背後,正是達達技術團隊持續不斷的快速技術迭代的結果,本文正好藉此機會,總結並分享了這一系列技術演進的第一手實踐資料,希望能給同樣奮鬥在網際網路創業一線的你帶來啟發。
技術背景
達達業務主要包含兩部分:
商家發單;
配送員接單配送;
達達的業務邏輯看起來非常簡單直接,如下圖所示:
達達的業務規模增長極大,在1年左右的時間從零增長到每天近百萬單,給後端帶來極大的訪問壓力。壓力主要分為兩類:讀壓力、寫壓力。讀壓力來源於配送員在APP中搶單,高頻重新整理查詢周圍的訂單,每天訪問量幾億次,高峰期QPS高達數千次/秒。寫壓力來源於商家發單、達達接單、取貨、完成等操作。達達業務讀的壓力遠大於寫壓力,讀請求量約是寫請求量的30倍以上。
下圖是達達在成長初期,每天的訪問量變化趨圖,可見增長極快:
下圖是達達在成長初期,高峰期請求QPS的變化趨勢圖,可見增長極快:
極速增長的業務,對技術的要求越來越高,我們必須在架構上做好充分的準備,才能迎接業務的挑戰。接下來,我們一起看看達達的後臺架構是如何演化的。
最初的技術架構:簡單直接
作為創業公司,最重要的一點是敏捷,快速實現產品,對外提供服務,於是我們選擇了公有云服務,保證快速實施和可擴充套件性,節省了自建機房等時間。在技術選型上,為快速的響應業務需求,業務系統使用Python做為開發語言,資料庫使用MySQL。
如下圖所示,應用層的幾大系統都訪問一個資料庫:
中期架構最佳化:讀寫分離
資料庫瓶頸越來越嚴重
隨著業務的發展,訪問量的極速增長,上述的方案很快不能滿足效能需求:每次請求的響應時間越來越長,比如配送員在app中重新整理周圍訂單,響應時間從最初的500毫秒增加到了2秒以上。業務高峰期,系統甚至出現過當機,一些商家和配送員甚至因此而懷疑我們的服務質量。在這生死存亡的關鍵時刻,透過監控,我們發現高期峰MySQL CPU使用率已接近80%,磁碟IO使用率接近90%,Slow Query從每天1百條上升到1萬條,而且一天比一天嚴重。資料庫儼然已成為瓶頸,我們必須得快速做架構升級。
如下是資料庫一週的qps變化圖,可見資料庫壓力的增長極快:
我們的讀寫分離方案
當Web應用服務出現效能瓶頸的時候,由於服務本身無狀態(stateless),我們可以透過加機器的水平擴充套件方式來解決。而資料庫顯然無法透過簡單的新增機器來實現擴充套件,因此我們採取了MySQL主從同步和應用服務端讀寫分離的方案。
MySQL支援主從同步,實時將主庫的資料增量複製到從庫,而且一個主庫可以連線多個從庫同步。
利用MySQL的此特性,我們在應用服務端對每次請求做讀寫判斷:
若是寫請求,則把這次請求內的所有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商家和訂單表做查詢,如下如示:
分庫後,則要重構程式碼,先透過商家名查詢商家id,再透過商家Id查詢訂單表,如下所示:
垂直分庫過程中的經驗教訓,使我們制定了SQL最佳實踐,其中一條便是程式中禁用或少用join,而應該在程式中組裝資料,讓SQL更簡單。一方面為以後進一步垂直拆分業務做準備,另一方面也避免了MySQL中join的效能較低的問題。
經過一個星期緊鑼密鼓的底層架構調整,以及業務程式碼重構,終於完成了資料庫的垂直拆分。拆分之後,每個應用程式只訪問對應的資料庫,一方面將單點資料庫拆分成了多個,分攤了主庫寫壓力;另一方面,拆分後的資料庫各自獨立,實現了業務隔離,不再互相影響。
為未來做準備,進一步升級架構:水平分庫(sharding)
透過上一節的分享,我們知道:
讀寫分離,透過從庫水平擴充套件,解決了讀壓力;
垂直分庫透過按業務拆分主庫,快取了寫壓力。
但技術團隊是否就此高枕無憂?答案是:NO。
上述架構依然存在以下隱患:
單表資料量越來越大:如訂單表,單表記錄數很快將過億,超出MySQL的極限,影響讀寫效能;
核心業務庫的寫壓力越來越大:已不能再進一次垂直拆分,MySQL 主庫不具備水平擴充套件的能力;
以前,系統壓力逼迫我們架構升級,這一次,我們需提前做好架構升級,實現資料庫的水平擴充套件(sharding)。我們的業務類似於Uber,而Uber在公司成立的5年後(2014)年才實施了水平分庫,但我們的業務發展要求我們在成立18月就要開始實施水平分庫。
水平分庫面臨的第一個問題是,按什麼邏輯進行拆分:
一種方案是按城市拆分,一個城市的所有資料在一個資料庫中;
另一種方案是按訂單ID平均拆分資料;
按城市拆分的優點是資料聚合度比較高,做聚合查詢比較簡單,實現也相對簡單,缺點是資料分佈不均勻,某些城市的資料量極大,產生熱點,而這些熱點以後可能還要被迫再次拆分。
按訂單ID拆分則正相反,優點是資料分佈均勻,不會出現一個資料庫資料極大或極小的情況,缺點是資料太分散,不利於做聚合查詢。比如,按訂單ID拆分後,一個商家的訂單可能分佈在不同的資料庫中,查詢一個商家的所有訂單,可能需要查詢多個資料庫。針對這種情況,一種解決方案是將需要聚合查詢的資料做冗餘表,冗餘的表不做拆分,同時在業務開發過程中,減少聚合查詢。
反覆權衡利弊,並參考了Uber等公司的分庫方案後,我們最後決定按訂單ID做水平分庫。
從架構上,我們將系統分為三層:
應用層:即各類業務應用系統;
資料訪問層:統一的資料訪問介面,對上層應用層遮蔽讀寫分庫、分庫、快取等技術細節;
資料層:對DB資料進行分片,並可動態的新增shard分片。
水平分庫的技術關鍵點在於資料訪問層的設計。
資料訪問層主要包含三部分:
ID生成器:生成每張表的主鍵;
資料來源路由:將每次DB操作路由到不同的shard資料來源上;
快取:採用Redis實現資料的快取,提升效能。
ID生成器是整個水平分庫的核心,它決定了如何拆分資料,以及查詢儲存-檢索資料:
ID需要跨庫全域性唯一,否則會引發業務層的衝突;
此外,ID必須是數字且升序,這主要是考慮到升序的ID能保證MySQL的效能;
同時,ID生成器必須非常穩定,因為任何故障都會影響所有的資料庫操作;
我們的ID的生成策略借鑑了Instagram的ID生成演算法。
如上圖所示,方案說明如下:
整個ID的二進位制長度為64位;
前36位使用時間戳,以保證ID是升序增加;
中間13位是分庫標識,用來標識當前這個ID對應的記錄在哪個資料庫中;
後15位為MySQL自增序列,以保證在同一秒內併發時,ID不會重複。每個shard庫都有一個自增序列表,生成自增序列時,從自增序列表中獲取當前自增序列值,並加1,做為當前ID的後15位。
寫在最後
創業是與時間賽跑的過程,前期為了快速滿足業務需求,我們採用簡單高效的方案,如使用雲服務、應用服務直接訪問單點DB。
後期隨著系統壓力增大,效能和穩定性逐漸納入考慮範圍,而DB最容易出現效能瓶頸,我們採用讀寫分離、垂直分庫、水平分庫等方案。
面對高效能和高穩定性,架構升級需要儘可能超前完成,否則,系統隨時可能出現系統響應變慢甚至當機的情況。
原文連結:https://mp.weixin.qq.com/s/IWytJhcxth4ig2qrqdQjvg
來自 “ ITPUB部落格 ” ,連結:http://blog.itpub.net/31562044/viewspace-2674667/,如需轉載,請註明出處,否則將追究法律責任。
相關文章
- 從MVC到DDD的架構演進MVC架構
- 淘寶從幾百到千萬級併發的十四次架構演進之路!架構
- Serverless 架構演進與實踐Server架構
- 高併發下的伺服器架構演變伺服器架構
- 高併發IM系統架構優化實踐架構優化
- 荔枝架構實踐與演進歷程架構
- 淘寶從百到千萬級併發情況下服務端架構的演進過程服務端架構
- Java後端中的請求最佳化:從請求合併到非同步處理的實現策略Java後端非同步
- 今日頭條架構演進之路——高壓下的架構演進專題架構
- Python後端架構演進Python後端架構
- 51信用卡 Android 架構演進實踐Android架構
- 美團配送系統架構演進實踐架構
- 【AliFlutter】Flutter Fish Redux 2.0 架構演進實踐FlutterRedux架構
- B站公網架構實踐及演進架構
- 個人筆記-服務端高併發分散式架構演進之路筆記服務端分散式架構
- 高併發架構架構
- 架構師眼中的高併發架構架構
- [分散式][高併發]高併發架構分散式架構
- 從0到10億,微信後臺架構及基礎設施設計與實踐!架構
- 技術架構分享:美團配送系統架構演進實踐架構
- 從 Llama 1 到 3.1:Llama 模型架構演進詳解模型架構
- 從 ClickHouse 到 Apache Doris,騰訊音樂內容庫資料平臺架構演進實踐Apache架構
- 智慧安全3.0實踐|SASE技術架構的演進之路架構
- 林意群:eBay HDFS架構的演進優化實踐架構優化
- 架構師眼裡的高併發架構架構
- 從零入門 Serverless | 架構的演進Server架構
- 淘寶千萬級併發分散式架構的14次演進分散式架構
- 【高併發優化實踐】10倍請求壓力來襲,你的系統會被擊垮嗎?【石杉的架構筆記】優化架構筆記
- 高併發架構的搭建(二)架構
- 大型分散式電商系統架構如何從0開始演進?分散式架構
- 服務架構學習與思考(12):從單體架構到微服務架構的演進歷程架構微服務
- 資料治理實踐:後設資料管理架構的演變架構
- 大型分散式電商系統架構是如何從0開始演進的?分散式架構
- 亮點回顧|2022 re : Invent Recap:深入核心,高併發系統架構演進之路架構
- 美團實時數倉架構演進與建設實踐架構
- MySQL資料庫架構——高可用演進MySql資料庫架構
- Airbnb的架構演進AI架構
- Serverless 架構的演進Server架構