快的打車架構實踐

快的打車從2013年年末到2014年下半年,系統訪問量迅速膨脹,不少複雜的問題要在短期內解決,且不能影響線上業務,這是比較大的挑戰,本文將會闡述快的打車架構演變過程遇到的一些有表明性的問題和解決方案。前端

LBS的瓶頸和方案

先看看基本的系統模型,如圖1所示。數據庫

圖片描述
圖1 系統模型示意圖編程

  1. 司機每隔幾秒鐘上報一次經緯度,存儲在MongoDB裏;
  2. 乘客發單時,經過MongoDB圈選出附近司機;
  3. 將訂單經過長鏈接服務推送給司機;
  4. 司機接單,開始服務。

MongoDB集羣是一主多從的複製集方式,讀寫都很密集(4w+/s寫、1w+/s讀)時出現如下問題:後端

  1. 從服務器CPU負載急劇上升;
  2. 查詢性能急劇下降(大量查詢耗時超過800毫秒);
  3. 查詢吞吐量大幅下降;
  4. 主從複製出現較大的延遲。

緣由是當時的MongoDB版本(2.6.4)是庫級別的鎖每次寫都會鎖庫,還有每一次LBS查詢會分解成許多單獨的子查詢,增大整個查詢的鎖等待機率。咱們最後將全國分爲4個大區,部署多個獨立的MongoDB集羣,每一個大區的用戶存儲在對應的MongoDB集羣裏。安全

長鏈接服務穩定性

咱們的長鏈接服務經過Socket接收客戶端心跳、推送消息給乘客和司機。打車大戰期間,長鏈接服務很是不穩定。服務器

先說說硬件問題,現象是CPU的第一個核常用率100%,其餘的核卻很是空閒,系統吞吐量上不去,對業務的影響很大。通過較長時間排查,最終發現這是由於服務器用了單隊列網卡,I/O中斷都被分配到了一個CPU核上,大量數據包到來時,單個CPU核沒法所有處理,致使LVS不斷丟包鏈接中斷。最後解決這個問題其實很簡單,換成多隊列網卡就行。網絡

再看軟件問題,長鏈接服務當時用Mina實現,Mina自己存在一些問題:內存使用控制粒度不夠細、垃圾回收難以有效控制、空閒鏈接檢查效率不高、大量鏈接時週期性CPU使用率飆高。快的打車的長鏈接服務特色是:大量的廣播、消息推送具備不一樣的優先級、細粒度的資源監控。最後咱們用AIO重寫了這個長鏈接服務框架,完全解決了這個問題。主要有如下特性:架構

  1. 針對快的場景定製開發;
  2. 資源(主要是ByteBuffer)池化,減小GC形成的影響;
  3. 廣播時,一份ByteBuffer複用到多個通道,減小內存拷貝;
  4. 使用TimeWheel檢測空閒鏈接,消除空閒鏈接檢測形成的CPU尖峯;
  5. 支持按優先級發送數據。

其實Netty已經實現了資源池化和TimeWheel方式檢測空閒鏈接,但沒法作到消息優先級區分和細粒度監控,這也算是快的自身的定製特性吧,通用的通訊框架確實很差知足。選用AIO方式僅僅是由於AIO的編程模型比較簡單而已,其實底層的性能並無多大差異。併發

系統分佈式改造

快的打車最初只有兩個系統,一個提供HTTP服務的Web系統,一個提供TCP長鏈接服務的推送系統,全部業務運行在這個Web系統裏,代碼量很是龐大,代碼下載和編譯都須要花較長時間。框架

業務代碼都混在一塊兒,頻繁的平常變動致使並行開發的分支很是多,測試和代碼合併以及發佈驗證的效率很是低下,經常一發布就通宵。這種狀況下,系統的伸縮性和擴展性很是差,關鍵業務和非關鍵業務混在一塊兒,互相影響。

所以咱們Web系統作了拆分,將整個系統從上往下分爲3個大的層次:業務層、服務層以及數據層。

咱們在拆分的同時,也仔細梳理了系統之間的依賴。對於強依賴場景,用Dubbo實現了RPC和服務治理。對於弱依賴場景,經過RocketMQ實現。Dubbo是阿里開源的框架,在阿里內部和國內大型互聯網公司有普遍的應用,咱們對Dubbo源碼比較瞭解。RocketMQ也是阿里開源的,在內部獲得了很是普遍的應用,也有不少外部用戶,可簡單將RocketMQ理解爲Java版的Kafka,咱們一樣也對RocketMQ源碼很是瞭解,快的打車全部的消息都是經過RocketMQ實現的,這兩個中間件在線上運行得很是穩定。

藉着分佈式改造的機會,咱們對系統全局也作了梳理,創建研發流程、代碼規範、SQL規範,梳理鏈路上的單點和性能瓶頸,創建服務降級機制。

無線開放平臺

當時客戶端與服務端通訊面臨如下問題。

  1. 每新增一個業務請求,Web工程就要改動發佈。
  2. 請求和響應格式沒有規範,致使服務端很難對請求作統一處理,並且與第三方集成的方式很是多,維護成本高。
  3. 來多少請求就處理多少,根本不考慮後端服務的承受能力,而某些時候須要對後端作保護。
  4. 業務邏輯比較分散,有的在Web應用裏,有的在Dubbo服務裏。提供新功能時,工程師關注的點比較多,增長了系統風險。
  5. 業務頻繁變化和快速發展,文檔沒法跟上,最後沒人能說清到底有哪些協議,協議裏的字段含義。

針對這些問題,咱們設計了快的無線開放平臺KOP,如下是一些大的設計原則。

  1. 接入權限控制 
    爲接入的客戶端分配標示和密鑰,密鑰由客戶端保管,用來對請求作數字簽名。服務端對客戶端請求作簽名校驗,校驗經過纔會執行請求。

  2. 流量分配和降級 
    一樣的API,不一樣接入端的訪問限制能夠不同。可按城市、客戶端平臺類型作ABTest。極端狀況下,優先保證核心客戶端的流量,同時也會優先保證核心API的服務能力,例如登陸、下單、接單、支付這些核心的API。被訪問被限制時,返回一個限流錯誤碼,客戶端根據不一樣場景酌情處理。

  3. 流量分析 
    從客戶端、API、IP、用戶多個維度,實時分析當前請求是否惡意請求,惡意的IP和用戶會被凍結一段時間或永久封禁。

  4. 實時發佈 
    上線或下線API不須要對KOP進行發佈,實時生效。固然,爲了安全,會有API的審覈機制。

  5. 實時監控 
    能統計每一個客戶端對每一個API每分鐘的調用總量、成功量、失敗量、平均耗時,能以分鐘爲單位查看指定時間段內的數據曲線,而且能對比歷史數據。當響應時間或失敗數量超過閾值時,系統會自動發送報警短信。

實時計算與監控

咱們基於Storm和HBase設計了本身的實時監控平臺,分鐘級別實時展示系統運行情況和業務數據(架構如圖2所示),包含如下幾個主要部分。

圖片描述
圖2 監控系統架構圖

  1. 核心計算模型 
    求和、求平均、分組。

  2. 基於Storm的實時計算 
    Storm的邏輯並不複雜,只有兩個Bolt,一個將一條日誌解析成KV對,另一個基於KV和設定的規則進行計算。每隔一分鐘將數據寫入RocketMQ。

  3. 基於HBase的數據存儲 
    只有插入沒有更新,避免了HBase行鎖競爭。rowkey是有序的,由於要根據維度和時間段查詢,這樣會造成HBase Region熱點,致使寫入比較集中,可是沒有性能問題,由於每一個維度每隔1分鐘定時插入,平均每秒的插入不多。即便前端應用的日誌量忽然增長不少,HBase的插入頻度仍然是穩定的。

  4. 基於RocketMQ的數據緩衝 
    收集的日誌和Storm計算的結果都先放入MetaQ集羣,不管Storm集羣仍是存儲節點,發生故障時系統仍然是穩定的,不會將故障放大;即便有忽然的流量高峯,由於有消息隊列作緩衝,Storm和HBase仍然能以穩定的TPS處理。這些都極大的保證了系統的穩定性。RocketMQ集羣自身的健壯性足夠強,都是物理機。SSD存儲盤、高配內存和CPU、Broker所有是M/S結構。能夠存儲足夠多的緩衝數據。

某個系統的實時業務指標(關鍵數據被隱藏),見圖3。

圖片描述
圖3 某個業務系統大盤截圖

數據層改造

隨着業務發展,單數據庫單表已經沒法知足性能要求,特別是發券和訂單,咱們選擇在客戶端分庫分表,本身作了一個通用框架解決分庫分表的問題。可是還有如下問題:

  1. 數據同步 
    快的原來的數據庫分爲前臺庫和後臺庫,前臺庫給應用系統使用,後臺庫只供後臺使用。無論前臺應用有多少庫,後臺庫只有一個,那麼前臺的多個庫多個表如何對應到後臺的單庫單表?MySQL的複製沒法解決這個問題。

  2. 離線計算抽取 
    還有大數據的場景,大數據同事常常要dump數據作離線計算,都是經過Sqoop到後臺庫抽數據,有的複雜SQL常常會使數據庫變得不穩定。並且,不一樣業務場景下的Sqoop會形成數據重複抽取,給數據庫添加了更多的負擔。

咱們最終實現了一個數據同步平臺,見圖4。

圖片描述
圖4 數據同步平臺架構圖

  1. 數據抽取用開源的canal實現,MySQL binlog改成Row模式,將canal抽取的binlog解析爲MQ消息,打包傳輸給MQ;
  2. 一份數據,多種消費場景,以前是每種場景都抽取一份數據;
  3. 各個消費端不須要關心MySQL,只須要關心MQ的Topic;
  4. 支持全局有序,局部有序,併發亂序;
  5. 能夠指定時間點回放數據;
  6. 數據鏈路監控、報警;
  7. 經過管理平臺自動部署節點。

分庫分表解決了前臺應用的性能問題,數據同步解決了多庫多表歸一的問題,可是隨着時間推移,後臺單庫的問題愈來愈嚴重,迫切須要一種方案解決海量數據存儲的問題,同時又要讓現有的上層應用不會有太大改動。所以咱們基於HBase和數據同步設計了實時數據中心,如圖5所示。

圖片描述
圖5 實時數據中心架構圖

  1. 將前臺MySQL多庫多表經過同步平臺,都同步到了HBase;
  2. 爲減小後臺應用層的改動,設計了一個SQL解析模塊,將SQL查詢轉換爲HBase查詢;
  3. 支持二級索引。 
    說說二級索引,HBase並不支持二級索引,對它而言只有一個索引,那就是Rowkey。若是要按其它字段查詢,那就要爲這些字段創建與Rowkey的映射關係,這就是所謂的二級索引。HBase二級索引能夠經過Coprocessor在數據插入以前執行一段代碼,這段代碼運行在HBase服務端(Region Server),可讓這段代碼負責插入二級索引。實時數據中心的二級索引是在客戶端負責插入的,並無使用Coprocessor,主要緣由是Coprocessor不容易實現索引的批量插入,而批量插入,實踐證實,是提高HBase插入性能很是有效的手段。二級索引的應用其實還有些條件,以下:

  4. 排序 
    在HBase中,只有一種排序,就是按Rowkey排序,所以,在創建索引的時候,實際上就定死了未來查詢結果的排序。某個索引字段的reverse屬性爲true,則按這個字段倒序排序,不然正序排序。

  5. 打散 
    單調變化的Rowkey讀寫壓力很難均勻分佈到多個Region上,而打散將會使讀寫均勻分佈到多個Region,所以提高了讀寫性能。但打散也有侷限性,主要的是,通過打散的字段將沒法支持範圍查詢。並且,hash和reverse這兩個屬性是互斥的,且hash優先級高,就是說一旦設置了hash=true,則會忽略reverse這個屬性。
  6. 串聯 
    另外須要特別強調的是,索引配置也影響到多表歸一,做爲「串聯」的字段,必須創建惟一索引,若是串聯字段上沒有創建惟一索引,將沒法完成多表歸一。

咱們還實現了一套將SQL語句轉換成HBase API的引擎,能夠經過SQL語句直接操做HBase。這裏須要指出的是HSQL引擎和Hive是不一樣的,Hive主要用於將SQL語句轉換成Hadoop的Map/Reduce任務,固然也能夠轉換成HBase的查詢。但Hive沒法利用二級索引(HBase原本就不存在二級索引這個概念),Hive主要面向的是大批量、低頻度、高延遲、順序讀的訪問場景,而HSQL能夠有效利用二級索引,它面向的是小批量、高頻度、低延遲、隨機讀的訪問場景。

做者簡介:

王小雪滴滴出行架構師,原快的打車架構師。從無到有組建了快的打車基礎服務團隊,主持研發、引進了衆多基礎框架和服務,推動快的架構升級,從穩定性、可用性、性能、安全、監控多方面體系化的建設了快的高可用架構。對高併發分佈式系統架構、實時數據處理、網絡通訊和Java中間件有比較深厚的經驗積累。
相關文章
相關標籤/搜索