達達O2O後臺架構演進實踐:從0到4000高併發請求背後的努力

一、引言

 

達達創立於2014年5月,業務覆蓋全國37個城市,擁有130萬註冊衆包配送員,日均配送百萬單,是全國領先的最後三千米物流配送平臺。 達達的業務模式與滴滴以及Uber很類似,以衆包的方式利用社會閒散人力資源,解決O2O最後三千米即時性配送難題(2016年4月,達達已經與京東到家合併)。 php

達達的業務組成簡單直接——商家下單、配送員接單和配送,也正由於理解起來簡單,使得達達的業務量在短期能實現爆發式增加。而支撐業務快速增加的背後,正是達達技術團隊持續不斷的快速技術迭代的結果,本文正好藉此機會,總結並分享了這一系列技術演進的第一手實踐資料,但願能給一樣奮鬥在互聯網創業一線的你帶來啓發。html

(本文同步發佈於:http://www.52im.net/thread-2141-1-1.html算法

二、相關文章

新手入門:零基礎理解大型分佈式架構的演進歷史、技術原理、最佳實踐數據庫

騰訊資深架構師乾貨總結:一文讀懂大型分佈式系統設計的方方面面後端

快速理解高性能HTTP服務端的負載均衡技術原理緩存

知乎技術分享:從單機到2000萬QPS併發的Redis高性能緩存實踐之路服務器

阿里技術分享:深度揭祕阿里數據庫技術方案的10年變遷史微信

阿里技術分享:阿里自研金融級數據庫OceanBase的艱辛成長之路網絡

三、技術背景

達達業務主要包含兩部分:架構

1)商家發單;

2)配送員接單配送。

達達的業務邏輯看起來很是簡單直接,以下圖所示:

 

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

下圖是達達在成長初期,天天的訪問量變化趨圖,可見增加極快:

 

下圖是達達在成長初期,高峯期請求QPS的變化趨勢圖,可見增加極快:

 

極速增加的業務,對技術的要求愈來愈高,咱們必須在架構上作好充分的準備,才能迎接業務的挑戰。接下來,咱們一塊兒看看達達的後臺架構是如何演化的。

小知識:什麼是QPS、TPS?

QPS:Queries Per Second意思是「每秒查詢率」,是一臺服務器每秒可以相應的查詢次數,是對一個特定的查詢服務器在規定時間內所處理流量多少的衡量標準。

TPS:是TransactionsPerSecond的縮寫,也就是事務數/秒。它是軟件測試結果的測量單位。一個事務是指一個客戶機向服務器發送請求而後服務器作出反應的過程。客戶機在發送請時開始計時,收到服務器響應後結束計時,以此來計算使用的時間和完成的事務個數。

四、最初的技術架構:簡單直接

做爲創業公司,最重要的一點是敏捷,快速實現產品,對外提供服務,因而咱們選擇了公有云服務,保證快速實施和可擴展性,節省了自建機房等時間。在技術選型上,爲快速的響應業務需求,業務系統使用Python作爲開發語言,數據庫使用MySQL。

以下圖所示,應用層的幾大系統都訪問一個數據庫:

 

五、中期架構優化:讀寫分離

5.1 數據庫瓶頸愈來愈嚴重

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

以下是數據庫一週的qps變化圖,可見數據庫壓力的增加極快:

 

5.2 咱們的讀寫分離方案

當Web應用服務出現性能瓶頸的時候,因爲服務自己無狀態(stateless),咱們能夠經過加機器的水平擴展方式來解決。 而數據庫顯然沒法經過簡單的添加機器來實現擴展,所以咱們採起了MySQL主從同步和應用服務端讀寫分離的方案。

MySQL支持主從同步,實時將主庫的數據增量複製到從庫,並且一個主庫能夠鏈接多個從庫同步。

利用MySQL的此特性,咱們在應用服務端對每次請求作讀寫判斷:

1)如果寫請求,則把此次請求內的全部DB操做發向主庫;

2)如果讀請求,則把此次請求內的全部DB操做發向從庫。

以下圖所示:

 

實現讀寫分離後,數據庫的壓力減小了許多,CPU使用率和IO使用率都降到了5%內,Slow Query也趨近於0。

主從同步、讀寫分離給咱們主要帶來以下兩個好處:

1)減輕了主庫(寫)壓力:達達的業務主要來源於讀操做,作讀寫分離後,讀壓力轉移到了從庫,主庫的壓力減少了數十倍;

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

以下是優化後數據庫QPS的變化圖:

 

▲ 讀寫分離前主庫的select QPS

 

▲ 讀寫分離後主庫的select QPS

5.3 新情況出現:主從延遲問題

固然,沒有一個方案是萬能的。

讀寫分離,暫時解決了MySQL壓力問題,同時也帶來了新的挑戰:

1)業務高峯期,商家發完訂單,在個人訂單列表中卻看不到當發的訂單(典型的read after write);

2)系統內部偶爾也會出現一些查詢不到數據的異常。

經過監控,咱們發現,業務高峯期MySQL可能會出現主從延遲,極端狀況,主從延遲高達10秒。

那如何監控主從同步狀態?在從庫機器上,執行show slave status,查看Seconds_Behind_Master值,表明主從同步從庫落後主庫的時間,單位爲秒,若同從同步無延遲,這個值爲0。MySQL主從延遲一個重要的緣由之一是主從複製是單線程串行執行。

那如何爲避免或解決主從延遲?咱們作了以下一些優化:

1)優化MySQL參數,好比增大innodb_buffer_pool_size,讓更多操做在MySQL內存中完成,減小磁盤操做;

2)使用高性能CPU主機;

3)數據庫使用物理主機,避免使用虛擬雲主機,提高IO性能;

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

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

5.4 主庫的寫操做變的愈來愈慢

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

下圖是數據庫主庫的壓力:

 

▲ 可見磁盤IO使用率已經很是高,高峯期IO響應時間最大達到636毫秒,IO使用率最高達到100%

同時,業務愈來愈複雜,多個應用系統使用同一個數據庫,其中一個很小的非核心功能出現Slow query,經常影響主庫上的其它核心業務功能。

咱們有一個應用系統在MySQL中記錄日誌,日誌量很是大,近1億行記錄,而這張表的ID是UUID,某一天高峯期,整個系統忽然變慢,進而引起了宕機。監控發現,這張表insert極慢,拖慢了整個MySQL Master,進而拖跨了整個系統。(固然在MySQL中記日誌不是一種好的設計,所以咱們開發了大數據日誌系統。另外一方面,UUID作主鍵是個糟糕的選擇,在下文的水平分庫中,針對ID的生成,有更深刻的講述)。

5.5 進一步對主庫進行拆分,優化主庫寫操做慢的問題

這時,主庫成爲了性能瓶頸,咱們意識到,必需得再一次作架構升級,將主庫作拆分:

1)一方面以提高性能;

2)另外一方面減小系統間的相互影響,以提高系統穩定性。

這一次,咱們將系統按業務進行了垂直拆分。

以下圖所示,將最初龐大的數據庫按業務拆分紅不一樣的業務數據庫,每一個系統僅訪問對應業務的數據庫,避免或減小跨庫訪問:

 

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

 

將來是美好的,道路是曲折的。

垂直分庫過程,也遇到很多挑戰,最大的挑戰是:不能跨庫join,同時須要對現有代碼重構。單庫時,能夠簡單的使用join關聯表查詢;拆庫後,拆分後的數據庫在不一樣的實例上,就不能跨庫使用join了。

好比在CRM系統中,須要經過商家名查詢某個商家的全部訂單,在垂直分庫前,能夠join商家和訂單表作查詢,以下如示:

 

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

 

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

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

六、爲將來作準備,進一步升級架構:水平分庫(sharding)

經過上一節的分享,咱們知道:

1)讀寫分離,經過從庫水平擴展,解決了讀壓力;

2)垂直分庫經過按業務拆分主庫,緩存了寫壓力。

但技術團隊是否就此高枕無憂?答案是:NO。

上述架構依然存在如下隱患:

1)單表數據量愈來愈大:如訂單表,單表記錄數很快將過億,超出MySQL的極限,影響讀寫性能;

2)核心業務庫的寫壓力愈來愈大:已不能再進一次垂直拆分,MySQL 主庫不具有水平擴展的能力。

之前,系統壓力逼迫咱們架構升級,這一次,咱們需提早作好架構升級,實現數據庫的水平擴展(sharding)。咱們的業務相似於Uber,而Uber在公司成立的5年後(2014)年才實施了水平分庫,但咱們的業務發展要求咱們在成立18月就要開始實施水平分庫。

本次架構升級的邏輯架構圖以下圖所示:

 

水平分庫面臨的第一個問題是,按什麼邏輯進行拆分:

1)一種方案是按城市拆分,一個城市的全部數據在一個數據庫中;

2)另外一種方案是按訂單ID平均拆分數據。

按城市拆分的優勢是數據聚合度比較高,作聚合查詢比較簡單,實現也相對簡單,缺點是數據分佈不均勻,某些城市的數據量極大,產生熱點,而這些熱點之後可能還要被迫再次拆分。

按訂單ID拆分則正相反,優勢是數據分佈均勻,不會出現一個數據庫數據極大或極小的狀況,缺點是數據太分散,不利於作聚合查詢。好比,按訂單ID拆分後,一個商家的訂單可能分佈在不一樣的數據庫中,查詢一個商家的全部訂單,可能須要查詢多個數據庫。針對這種狀況,一種解決方案是將須要聚合查詢的數據作冗餘表,冗餘的表不作拆分,同時在業務開發過程當中,減小聚合查詢。

反覆權衡利弊,並參考了Uber等公司的分庫方案後,咱們最後決定按訂單ID作水平分庫。

從架構上,咱們將系統分爲三層:

1)應用層:即各種業務應用系統;

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

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

水平分庫的技術關鍵點在於數據訪問層的設計。

數據訪問層主要包含三部分:

1)ID生成器:生成每張表的主鍵;

2)數據源路由:將每次DB操做路由到不一樣的shard數據源上;

3)緩存: 採用Redis實現數據的緩存,提高性能。

ID生成器是整個水平分庫的核心,它決定了如何拆分數據,以及查詢存儲-檢索數據:

1)ID須要跨庫全局惟一,不然會引起業務層的衝突;

2)此外,ID必須是數字且升序,這主要是考慮到升序的ID能保證MySQL的性能;

3)同時,ID生成器必須很是穩定,由於任何故障都會影響全部的數據庫操做。

咱們的ID的生成策略借鑑了Instagram的ID生成算法。

咱們具體的ID生成算法方案以下:

 

如上圖所示,方案說明以下:

1)整個ID的二進制長度爲64位;

2)前36位使用時間戳,以保證ID是升序增長;

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

4)後15位爲MySQL自增序列,以保證在同一秒內併發時,ID不會重複。每一個shard庫都有一個自增序列表,生成自增序列時,從自增序列表中獲取當前自增序列值,並加1,作爲當前ID的後15位。

七、寫在最後

創業是與時間賽跑的過程,前期爲了快速知足業務需求,咱們採用簡單高效的方案,如使用雲服務、應用服務直接訪問單點DB。

後期隨着系統壓力增大,性能和穩定性逐漸歸入考慮範圍,而DB最容易出現性能瓶頸,咱們採用讀寫分離、垂直分庫、水平分庫等方案。

面對高性能和高穩定性,架構升級須要儘量超前完成,不然,系統隨時可能出現系統響應變慢甚至宕機的狀況。

附錄:架構設計相關文章彙總

[1] 有關IM架構設計的文章:

淺談IM系統的架構設計

簡述移動端IM開發的那些坑:架構設計、通訊協議和客戶端

一套海量在線用戶的移動端IM架構設計實踐分享(含詳細圖文)

一套原創分佈式即時通信(IM)系統理論架構方案

從零到卓越:京東客服即時通信系統的技術架構演進歷程

蘑菇街即時通信/IM服務器開發之架構選擇

騰訊QQ1.4億在線用戶的技術挑戰和架構演進之路PPT

微信後臺基於時間序的海量數據冷熱分級架構設計實踐

微信技術總監談架構:微信之道——大道至簡(演講全文)

如何解讀《微信技術總監談架構:微信之道——大道至簡》

快速裂變:見證微信強大後臺架構從0到1的演進歷程(一)

17年的實踐:騰訊海量產品的技術方法論

移動端IM中大規模羣消息的推送如何保證效率、實時性?

現代IM系統中聊天消息的同步和存儲方案探討

IM開發基礎知識補課(二):如何設計大量圖片文件的服務端存儲架構?

IM開發基礎知識補課(三):快速理解服務端數據庫讀寫分離原理及實踐建議

IM開發基礎知識補課(四):正確理解HTTP短鏈接中的Cookie、Session和Token

WhatsApp技術實踐分享:32人工程團隊創造的技術神話

微信朋友圈千億訪問量背後的技術挑戰和實踐總結

王者榮耀2億用戶量的背後:產品定位、技術架構、網絡方案等

IM系統的MQ消息中間件選型:Kafka仍是RabbitMQ?

騰訊資深架構師乾貨總結:一文讀懂大型分佈式系統設計的方方面面

以微博類應用場景爲例,總結海量社交系統的架構設計步驟

快速理解高性能HTTP服務端的負載均衡技術原理

子彈短信光鮮的背後:網易雲信首席架構師分享億級IM平臺的技術實踐

知乎技術分享:從單機到2000萬QPS併發的Redis高性能緩存實踐之路

IM開發基礎知識補課(五):通俗易懂,正確理解並用好MQ消息隊列

微信技術分享:微信的海量IM聊天消息序列號生成實踐(算法原理篇)

微信技術分享:微信的海量IM聊天消息序列號生成實踐(容災方案篇)

新手入門:零基礎理解大型分佈式架構的演進歷史、技術原理、最佳實踐

一套高可用、易伸縮、高併發的IM羣聊架構方案設計實踐

阿里技術分享:深度揭祕阿里數據庫技術方案的10年變遷史

阿里技術分享:阿里自研金融級數據庫OceanBase的艱辛成長之路

>> 更多同類文章 ……

[2] 更多其它架構設計相關文章:

騰訊資深架構師乾貨總結:一文讀懂大型分佈式系統設計的方方面面

快速理解高性能HTTP服務端的負載均衡技術原理

子彈短信光鮮的背後:網易雲信首席架構師分享億級IM平臺的技術實踐

知乎技術分享:從單機到2000萬QPS併發的Redis高性能緩存實踐之路

新手入門:零基礎理解大型分佈式架構的演進歷史、技術原理、最佳實踐

阿里技術分享:深度揭祕阿里數據庫技術方案的10年變遷史

阿里技術分享:阿里自研金融級數據庫OceanBase的艱辛成長之路

達達O2O後臺架構演進實踐:從0到4000高併發請求背後的努力

>> 更多同類文章 ……

(本文同步發佈於:http://www.52im.net/thread-2141-1-1.html

相關文章
相關標籤/搜索