美团团购订单系统优化记
團購訂單系統簡介
美團團購訂單系統主要作用是支撐美團的團購業務,為上億美團用戶購買、消費提供服務保障。2015年初時,日訂單量約400萬~500萬,同年七夕訂單量達到800萬。
目標
作為線上S級服務,穩定性的提升是我們不斷的追求。尤其像七夕這類節日,高流量,高并發請求不斷挑戰著我們的系統。發現系統瓶頸,并有效地解決,使其能夠穩定高效運行,為業務增長提供可靠保障是我們的目標。
優化思路
2015年初的訂單系統,和團購其它系統如商品信息、促銷活動、商家結算等強耦合在一起,約50多個研發同時在同一個代碼庫上開發,按不同業務結點全量部署,代碼可以互相修改,有沖突在所難免。同時,對于訂單系統而言,有很多問題,架構方面不夠合理清晰,存儲方面問題不少,單點較多,數據庫連接使用不合理,連接打滿頻發等等。
針對這些問題,我們按緊迫性,由易到難,分步驟地從存儲、傳輸、架構方面對訂單系統進行了優化。
具體步驟
1. 存儲優化
訂單存儲系統之前的同事已進行了部分優化,但不夠徹底,且缺乏長遠規劃。具體表現在有分庫分表行為,但沒有解決單點問題,分庫后數據存儲不均勻。 此次優化主要從水平、垂直兩個方面進行了拆分。垂直方面,按業務進行了分庫,將非訂單相關表遷出訂單庫;水平方面,解決了單點問題后進行了均勻拆庫。
這里主要介紹一下ID分配單點問題:
系統使用一張表的自增來得到訂單號,所有的訂單生成必須先在這里insert一條數據,得到訂單號。分庫后,庫的數量變多,相應的故障次數變多,但由于單點的存在,故障影響范圍并未相應的減少,使得全年downtime上升,可用性下降。
針對ID分配單點問題,考慮到數據庫表分配性能的不足,調研了Tair、Redis、Snowflake等ID分配器,同時也考慮過將ID區間分段,多點分配。
但最后沒有使用這些方案,主要原因是ID分配對系統而言是強依賴服務,在分布式系統中,增加這樣一個服務,整體可用性必然下降。 我們還是從數據庫入手,進行改良,方案如下。
如下圖,由原來一個表分配改為100張表同時分配,業務邏輯上根據不同的表名執行一個簡單的運算得到最終的訂單號。
ID與用戶綁定:對訂單系統而言,每個用戶有一個唯一的userid,我們可以根據這個userid的末2位去對應的id_x表取訂單號,例如userid為10086的用戶去id_86表取到值為42,那訂單號就42*100+86=4286。
將訂單內容根據userid模100分表后如下圖:
通過看上面的技巧,我們發現訂單根據“userid取模”分表和根據“訂單號取模”來分表結果是一樣的,因為后兩位數一樣。到此,分庫操作就相當簡單容易了,極限情況下分成100個庫,每個庫兩個表。同一個用戶的請求一定在同一個庫完成操作,達到了完全拆分。
注:一般情況下,訂單數據分表都是按userid進行的,因為我們希望同一個用戶的數據存儲在一張表中,便于查詢。當給定一個訂單號的時候,我們無法判別這個訂單在哪個分表,所以大多數訂單系統同時維護了一個訂單號和userid的關聯關系,先根據訂單號查到userid,再根據userid確定分表進而查詢得到內容。在這里,我們通過前面的技巧發現,訂單號末二位和userid一樣,給定訂單號后,我們就直接知道了分表位置,不需要維護關聯表了。給定訂單號的情況下,單次查詢由原來2條SQL變為1條,查詢量減少50%,極大提升了系統高并發下性能。
2. 傳輸優化
當時訂單業務主要用PHP編碼,直連數據庫。隨著前端機器的增多,高流量下數據庫的連接數頻繁報警,大量連接被閑置占用,因此也發生過數次故障。另一方面,數據庫IP地址硬編碼,數據庫故障后上下線操作需要研發人員改代碼上線配合,平均故障處理時間(MTTR)達小時級。
如下圖:
在整個業務流程中,只有執行SQL的t1和t2時間需要數據庫連接,其余時間連接資源應該釋放出來供其它請求使用。現有情況是連接持有時間為t,很不合理。如果在代碼中顯式為每次操作分別建立并釋放資源,無疑增大了業務代碼的復雜度,并且建立和釋放連接的開銷變得不可忽略。最好的解決辦法是引入連接池,由連接池管理所有的數據庫連接資源。
經過調研,我們引入了DBA團隊的Atlas中間件,解決了上述問題。
有了中間件后,數據庫的連接資源不再如以前頻繁地創建、銷毀,而是和中間件保持動態穩定的數量,供業務請求復用。下圖是某個庫上線中間件后,數據庫每秒新增連接數的監控。
同時,Atlas所提供的自動讀寫分離也減輕了業務自主擇庫的復雜度。數據庫機器的上下線通過Atlas層熱切換,對業務透明。
3. 架構優化
經過前面兩步的處理,此時的訂單系統已比較穩定,但仍然有一些問題需要解決。如前面所述,50多個開發人員共享同一個代碼倉庫,開發過程互相影響,部署時需要全量發布所有機器,耗時高且成功率偏低。
在此基礎上,結合業界主流實踐,我們開始對訂單系統進行微服務化改造。服務化其實早已是很熱門的話題,最早有Amazon的服務化改造,并且收益頗豐,近年有更多公司結合自身業務所進行的一些案例。當然也有一些反思的聲音,如Martin Fowler所說,要搞微服務,你得“Tall enough”。
我們搞微服務,是否tall enough呢,或者要進行微服務化的話,有什么先決條件呢?結合業內大牛分享以及我自己的理解,我認為主要有以下三方面:
- DevOps:開發即要考慮運維。架構設計、開發過程中必須考慮好如何運維,一個大服務被拆成若干小服務,服務注冊、發現、監控等配套工具必不可少,服務治理能力得達標。
- 服務自演進:大服務被拆成小服務后,如何劃清邊界成為一個難題。拆的太細,增加系統復雜度;太粗,又達不到預期的效果。所以整個子服務的邊界也應該不斷梳理完善、細化,服務需要不斷演進。
- 團隊與架構對齊:服務的拆分應該和團隊人員配置保持一致,團隊人員如何溝通,設計出的服務架構也應一樣,這就是所謂康威定律。
公司層面,美團點評平臺主要基于Java生態,在服務治理方面已有較完善的解決方案。統一的日志收集、報警監控,服務注冊、服務發現、負載均衡等等。如果繼續使用PHP語言做服務化,困難重重且與公司技術發展方向不符,所以我們果斷地換語言,使用Java對現有的訂單系統進行升級改造。使用公司基礎設施后,業務開發人員需要考慮的,就只剩下服務的拆分與人員配置了,在這個過程中還需考慮開發后的部署運維。
結合業務實際情況,訂單核心部分主要分為三塊:下單、查詢和發券。
下單部分
由易到難,大體經過如下兩次迭代過程:
第一步:新造下單系統,分為二層結構,foundation這層主要處理數據相關,不做業務邏輯。通過這一層嚴格控制與數據庫的連接,SQL的執行。在foundation的上層,作為下單邏輯處理層,在這里我們部署了物理隔離的兩套系統,分別作為普通訂單請求和促銷訂單(節日大促等不穩定流量)請求服務。
通過從原系統www不斷切流量,完成下單服務全量走新系統,www演變為一個導流量的接入層。
第二步:在上述基礎上,分別為正常下單和促銷下單開發了front層服務,完成基本的請求接入和數據校驗,為這兩個新服務啟用新的域名URI。在這個過程中,我們推動客戶端升級開發,根據訂單發起時是否有促銷活動或優惠券,訪問不同的URI地址,從源頭上對促銷和非促流量進行了隔離。
查詢部分:
和下單部分類似,分為兩層結構,上層根據不同業務請求和重要性進行了物理隔離。
發券部分:
縱觀發券業務歷史上的一些故障原因,主要集中在兩點:
一是消息隊列本身出問題,連不上,數據不能投遞,消費者取不到消息。 二是個別臟數據問題,消費者不斷重試、失敗,造成隊列堵塞。
針對上述問題,我們設計了如圖所示架構,搭建兩組消息隊列,互相獨立。支付通知分別向L隊列和W隊列的一個10秒延時隊列投遞消息,只要有一個投遞成功即可。
- 消息到達L隊列后,迅速被發券L服務消費。發券L服務拿到消息后,先ack消息,再嘗試進行發券,不論成功或失敗,僅一次。
- 與此同時,相同的消息到達W的10秒延時隊列后,經過10秒時間,被投遞到MQ_W隊列,被發券W服務拿到。發券W服務先檢查此消息關聯的訂單是否已成功發券,若非,嘗試進行發券,并完成一系列兜底策略,如超過30分鐘自動退款等。
去掉一些細節部分,全景如下:
穩定性保障
目前,訂單系統服務化已完成,從上述模塊部署圖中可以看出,架構設計中充分考慮了隔離、降級等容災措施。具體從以下幾個方面說明:
小結
至此訂單服務化完成,架構部署比較清晰,業務日常迭代只影響相關的小服務,便于開發。 新的架構有效支撐了今年七夕等節日高流量,運行穩定。
在整個服務優化過程中,也走過一些彎路,結合一些成功的實踐,總結如下:
總結
以上是生活随笔為你收集整理的美团团购订单系统优化记的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 最新GitHub新手使用教程(Windo
- 下一篇: pbrt3在windows10环境中的编