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