Code:美團代碼托管平臺的演進與實踐(美團寫代碼)
美團代碼托管平臺經過長期的打磨,完成了分布式架構的改造落地,托管數以萬計的倉庫,日均Git相關請求達到千萬級別。本文主要介紹了美團代碼托管平臺在迭代演進過程中面臨的挑戰(zhàn)及解決思路,希望對大家有所幫助或啟發(fā)。
1. 引言
Code是美團自研的代碼托管平臺,其中包括了代碼版本管理、分支管理及代碼評審等功能,協(xié)同眾多研發(fā)流程工具平臺,支撐內部所有工程師的日常研發(fā)工作。經過近3年的建設,目前Code托管了數以萬計的倉庫,日常處理千萬級的Git相關請求,穩(wěn)定支撐著美團研發(fā)流程規(guī)范的持續(xù)落地。本文主要介紹美團在建設代碼托管平臺過程中面臨的一些挑戰(zhàn)和實踐經驗。
2. 美團代碼托管平臺建設之路
2.1 代碼托管平臺的發(fā)展史
回顧美團代碼托管平臺的發(fā)展史,整個歷程可以劃分為三個階段:單機部署、多機部署以及自研分布式代碼托管平臺。
第一階段:單機部署
美團最初的代碼托管平臺,和絕大多數Web系統(tǒng)一樣,單機部署即可運行,所有用戶的請求均通過Web應用進行響應。由于Git使用基于文件組織形式的存儲模式,無論是通過頁面訪問還是執(zhí)行Git命令操作,最終都會表現為磁盤的文件讀寫,高IO磁盤尤為重要。整體架構如下圖1所示:
圖1 單機部署
第二階段:多機部署
在訪問規(guī)模不大的情況下,第一階段這種單機架構可以滿足日常的開發(fā)需求。但隨著研發(fā)團隊業(yè)務需求的不斷增長,測試自動化流程的逐步完善,擴展性瓶頸也愈發(fā)明顯,主要表現為以下2個方面:
- 存儲:由于公司資源限制和地域分配不均等因素,代碼托管平臺部署機器已配置最大容量的可用SSD磁盤,使用率仍高達80%,可用空間嚴重不足。
- 負載:隨著研發(fā)人員的不斷增多,在訪問高峰期,CPU和IO負載高達95%以上,頁面出現嚴重的卡頓,僅能通過限流保障系統(tǒng)的持續(xù)服務。
因而,單機部署無法再承載高峰期的訪問量,系統(tǒng)擴容刻不容緩。于是,我們開始設計了一套能夠通過多機負載同一倉庫IO的讀寫分離架構方案,以解決較為嚴重的IO負載問題。在讀寫分離架構中,最重要的是要保證用戶視角的數據一致性(用戶隨時可以讀取提交的最新代碼),這里采取了以下措施:
- 寫操作僅發(fā)生在主節(jié)點。
- 采用懶漢同步模式,在讀取數據時觸發(fā)從節(jié)點同步數據,若失敗,則路由到主節(jié)點。
- 采用獨主兜底模式,遇遇到突發(fā)情況時可以迅速禁用從節(jié)點,保障數據安全。
圖2 多機部署
如圖2所示,我們將倉庫訪問形式按照應用層協(xié)議區(qū)分為HTTP和SSH,分別由對應的解析代理模塊進行讀寫分發(fā)操作后再下發(fā)到主從節(jié)點(此處采用了Round-Bobin的算法分發(fā)讀請求),使得讀吞吐量整體擴大了2倍。對于從節(jié)點,我們部署了Agent,在用戶發(fā)起讀請求時會觸發(fā)同步倉庫數據的Fetch操作,以保證數據的一致性。
第三階段:自研分布式代碼托管平臺
在第二階段,雖然通過多機負載IO的讀寫分離架構短暫性地解決了擴展性瓶頸問題,但倉庫數據仍在持續(xù)不斷地指數增長。同時,除擴展性問題之外,可用性瓶頸也凸顯出來,主要表現在以下2個方面:
- 運維:無論是日常迭代更新版本還是熱修復緊急Bug,都需要停服才能部署系統(tǒng),停服期間用戶無法使用代碼托管平臺。
- 備份:系統(tǒng)采用冷備份的方式多副本存儲Git數據,無法保證核心數據的實時恢復,異常情況下存在數據丟失風險。
因此,搭建具備高可用性和水平擴展性的分布式架構迫在眉睫。我們調研了業(yè)界主流代碼托管平臺的分布式方案,并結合公司內部的業(yè)務特性,最終選擇了基于應用層分片的分布式架構,該架構滿足了以下2個特性: - 高可用:采用三副本多活模式,規(guī)避代碼丟失風險,且系統(tǒng)版本更新無需停服,單機斷電、宕機均可正常提供服務。
- 水平擴展:可通過擴容分片集群的方式進行存儲和負載擴展,實現廣義下的“無限”容量。
綜上所述,Code基于GitLab生態(tài)開源組件二次開發(fā),并采用了應用層分片多活模式的分布式架構方案,簡介如下:
- 底層存儲服務基于GitLab生態(tài)開源組件二次開發(fā),有良好的生態(tài)和豐富的功能支持。
- 各服務間均通過gRPC進行交互通信,主要考慮點是Git大多數為二進制數據通信,gRPC基于HTTP 2.0,有良好的傳輸性能和流式支持。
- 通過路由模塊實現邏輯層與存儲層有效隔離,邏輯層對物理分片無感知,存儲層如同一個整體提供服務。
- 采用了多活復制模式的數據保障架構,提高讀寫吞吐量,滿足日均千萬級的請求量需求。
- 針對于應用層分片的劣勢,在架構設計時也做了相應的針對性優(yōu)化,具體如下: 熱點庫:提供了自動化的分片遷移能力,在發(fā)現倉庫出現熱點時,可進行分片遷移達到分片均衡??绶制瑪祿换ィ和ㄟ^業(yè)務層的Git事務包裝,我們使用共享Object的模式并確保相互關聯(lián)的倉庫均落在同一分片上,既避免了跨分片通信的問題,也減少了磁盤空間占用和訪問時延。
圖3 Code系統(tǒng)架構圖
3. 美團代碼托管平臺架構演進的落地和挑戰(zhàn)
代碼托管平臺在架構演進過程中,最終完成了以下兩個目標:
- 高可用:縮短停機時間,提高可用性,系統(tǒng)穩(wěn)定可靠。
- 高擴展:針對計算和存儲資源,可以實現水平擴展。
接下來,針對于每個目標,本文分別從技術挑戰(zhàn)、方案選型、設計及解決方案等方面詳細介紹我們的實踐經驗。
3.1 擴展性目標
3.1.1 技術挑戰(zhàn)
在進行水平擴展改造時,主要面臨了以下兩類挑戰(zhàn):
- 規(guī)模性:在研發(fā)流程自動化等背景下,美團代碼托管平臺需要具備千萬級吞吐、低延遲及高可用的系統(tǒng)性能,以提高研發(fā)效率。
- 兼容性:技術改造涉及的場景比較多,主要有兩方面的考量:(1)用戶低感知,新老系統(tǒng)保證現有通信方式及平臺使用方式不變;(2)兼顧過渡時期底層存儲介質多樣性、運維體系兼容等問題,并保障上下游系統(tǒng)的正常運行。
3.1.2 方案選型
經過對主流代碼托管平臺(GitHub、GitLab、Bitbucket等)的調研分析,我們發(fā)現各大平臺主要采用了以下兩種架構解決擴展性問題。
通過上述對比可以發(fā)現,如果直接接入共享存儲,暫時無法滿足代碼托管平臺的穩(wěn)定性和性能要求(若Git機制進行并行優(yōu)化,且使用更高讀寫性能的分布式存儲系統(tǒng),或許是一個不錯的選擇)。在共享存儲優(yōu)化改造成本較高的前提下,我們最終采用了應用層分片的分布式架構,它既滿足擴展性的要求,也更加成熟和穩(wěn)定,并表現出不錯的性能。
3.1.3 方案設計
我們通過代理模塊實現了請求分發(fā),通過路由模塊實現了倉庫分片,通過應用模塊的無狀態(tài)改造實現了彈性伸縮,從而達成了水平擴展的架構目標。下面將對這些模塊進行詳細的介紹。
代理模塊
- SSH Proxy:提供Git-SSH操作代理,提供Git-SSH請求代理,通過路由模塊獲取路由信息,到目標機器執(zhí)行SSH操作。SSH Proxy組件基于go-crypto庫開發(fā),實現了公鑰識別用戶,流量控制,長連接超時處理,SSH轉gRPC等功能。后續(xù)計劃引入signature校驗,以應對不同的使用場景。
- HTTP Proxy:提供Git-HTTP/Web請求代理,通過路由模塊存儲的倉庫分片映射關系,決策倉庫路由節(jié)點。HTTP Proxy基于Go-Gin開發(fā),實現了請求甄別,流量控制,多層代理等功能。最初HTTP Proxy還被作為灰度遷移的核心組件,通過流量統(tǒng)一收口,支持請求分發(fā)到新老Code系統(tǒng),以確保請求和數據的平滑遷移。
圖5 代理模塊
路由模塊
- Shard:記錄倉庫與其所在分片之間的映射關系,是應用層分片架構的“中樞系統(tǒng)”。Shard服務除維護映射關系外,還是容災模塊必不可少的“決策者”,通過獲取各個節(jié)點當前訪問倉庫的最新版本,從而決定讀寫路由。由于Golang出色的高并發(fā)表現,目前路由相關接口的平均響應時間在15ms以內。該模塊的主要特性如下: 建立倉庫和分片的映射關系,為了避免由于倉庫路徑更新造成文件夾拷貝/移動等行為帶來一定的復雜性,這里采用了倉庫ID作為唯一標識。利用Go Routine獲取節(jié)點的數據同步狀態(tài),并通過超時機制保障用戶非無限時等待。使用Key-Value Cache存儲倉庫和分片的映射,以降低映射關系的請求時延。
圖6 路由模塊
應用模塊
應用模塊主要包括以下兩點功能:
- 提供Git相關的業(yè)務邏輯接口處理代碼內容信息、代碼評審等復雜性業(yè)務請求。
- 監(jiān)聽代碼和分支變更消息,發(fā)送事件通知打通第三方業(yè)務系統(tǒng)和收集度量信息。
整體模塊架構如下圖7所示:
圖7 應用模塊
3.1.4 解決思路
規(guī)模性解決思路
規(guī)?;闹饕繕耸牵壕邆?/span>支撐千萬級請求的系統(tǒng)能力,并支持計算、存儲等資源的水平擴展能力,其中路由均衡是必不可少的一環(huán)。
a. 路由均衡
Code系統(tǒng)對數據源可靠性要求較高,而對性能要求相對比較低,因而我們采用了嚴格仲裁的路由模式,具體的邏輯配置如下:
- 使用版本號判定哪個節(jié)點提供的代碼內容最新:版本號越大,代表數據越新,當版本最大時即為最新的數據。當W R > N時總能讀到最新的數據(N:總節(jié)點個數,W:判斷寫入成功需要的響應節(jié)點數,R:讀取數據時至少要成功讀取的個數),當W越小時,寫入的可用性就越高,R越小,讀取的可用性就越高。我們選擇了N=3,R=W=2的常規(guī)推薦配置,根據概率推算可達到99.999%的可用性水平。
- 采用讀修復模式:當讀取數據時,若發(fā)現節(jié)點數據不一致,此時觸發(fā)數據同步邏輯,以修復落后節(jié)點的數據。
該功能內置于路由模塊的Shard服務,架構如下圖8所示:
圖8 路由邏輯示意圖
兼容性解決思路
兼容性目標總結為一句話就是:業(yè)務使用無感知。因此,我們主要從以下三個方面考慮兼容性。
a. 與各系統(tǒng)交互方式及現有基礎設施兼容
Code系統(tǒng)的眾多下游系統(tǒng)(多套前端UI、業(yè)務研發(fā)流程工具平臺等)依賴系統(tǒng)提供的開放API、Hook機制等擴展功能,為了減少系統(tǒng)升級對業(yè)務方造成影響,需要保證系統(tǒng)交互方式兼容。同時還要確保系統(tǒng)運維監(jiān)控體系正常運行,維持可監(jiān)測狀態(tài),我們主要做了以下四件事情:
- 兼容核心功能:使用頻度高的功能平移到新系統(tǒng),而使用中低頻的功能,與業(yè)務溝通使用場景,再評估是否兼容。
- 重新設計部分功能:提供更為合理的WebHook配置能力及嶄新的代碼評審功能。
- 邊緣功能運營下線:推進廢棄和歷史遺留功能的下線,并提供合理的替代方案。
- 打通運維體系:保持現有監(jiān)控埋點及運維接口接入方式,使系統(tǒng)處于可維護、可監(jiān)測的狀態(tài)。
b. 非分布式版本無縫切換到分布式版本
Code系統(tǒng)倉庫眾多,需要有低成本的用戶自主切換方式保障數據逐步遷移,我們主要做了以下三件事情:
- 可視化自動切換:通過頁面一鍵遷移按鈕,低成本實現從非分布式版本切換到分布式版本(遷移進度可感知,執(zhí)行過程中倉庫可讀不可寫,確保數據完整)。
- Proxy屏蔽底層存儲介質多樣性:通過Proxy保持單一的調用方式不變,可兼顧獲取非分布式版本和分布式版本的存儲數據。
- 特殊數據共享存儲:用戶和SSH Public Key等數據與倉庫數據沒有強制關聯(lián)關系,可實現數據共享。
c. 歷史數據平滑遷移
Code系統(tǒng)存在眾多的歷史代碼數據和業(yè)務數據,如何有效、完整地將歷史數據平滑遷移到新的分布式系統(tǒng),變得尤為重要。為了達成業(yè)務使用無感知的目標,主要做了以下兩件事情:
- 優(yōu)先遷移“輕量”倉庫:先遷移使用功能單一的倉庫,根據用戶反饋逐步完善遷移能力。
- 業(yè)務維度批次遷移:按照業(yè)務線劃分遷移批次,同類使用模式的倉庫同期遷移,以規(guī)避反饋問題多樣性。
3.2 可用性目標
3.2.1 技術挑戰(zhàn)
在進行可用性改造時,我們主要面臨數據安全性層面的挑戰(zhàn)。代碼作為公司的重要資產之一,需達到兩方面的要求:
- 代碼單點丟失可數據恢復。
- 用戶視角可以讀到正確的代碼數據。
3.2.2 方案選型
目前,業(yè)界主要有以下三種主流的數據復制模式。
圖9 數據保障方案對比
業(yè)界大多數分布式版本控制系統(tǒng)采用的是單主復制模式保障數據安全,隨著美團內部研發(fā)流程的逐步完善,對于創(chuàng)建注釋Tag、分支管理等需求逐步增加,讀寫比從最初的10:1縮短到現在的5:1,因此需要較高的寫入性能。
我們權衡了高吞吐量和數據強一致性的雙重目標,在單主復制架構的基礎上,采用以倉庫維度單主復制為核心,節(jié)點多活為特性的復制模式(下文簡稱為多活模式),從而保證了數據安全和系統(tǒng)可用性。
3.2.3 方案設計
我們主要通過存儲模塊中,對Git的讀、寫及初始化三類不同的請求分別采取相對應的數據處理機制,并結合多活復制模式,達成了高可用性的目標。
存儲模塊
Git Server:主要存儲和管理Git倉庫數據,提供Git相關的gRPC接口。該服務基于GitLab生態(tài)開源組件二次開發(fā),主要在數據同步機制、容災模塊、部分底層命令上做了適配性優(yōu)化,共涉及以下4個邏輯模塊:
- Replication Manager:數據復制核心模塊,根據不同的請求(讀、寫或初始化)執(zhí)行不同的復制邏輯,從而保障數據一致性。
- Code Core:Git Server的核心服務模塊,主要提供了Git的gRPC API供上游模塊使用。
- Git Core:實現擴展性和高可用性的重要組件,這里通過源碼的方式將GitLab生態(tài)開源組件引入到項目中,作為第三方Git API供項目使用。
- Git Command Factory:Git命令的中樞控制器,提供控制Git進程數量、傳遞參數上下文,隔離執(zhí)行環(huán)境及格式化輸出數據等功能。
各個邏輯模塊間關聯(lián)如下圖10所示:
圖10 存儲模塊
Git Cluster:又稱為分片,它由三個Git Server節(jié)點組成。三個節(jié)點間通過各自的Replication Manager模塊獲取到集群中其余節(jié)點的IP等信息,使用gRPC協(xié)議進行數據復制備份,可以保證用戶視角的數據一致性,邏輯架構如下圖11所示:
圖11 Git Cluster
3.2.4 解決思路
數據安全性解決思路
Code系統(tǒng)要解決的問題中,數據安全問題尤為重要,是保證研發(fā)流程安全可靠的關鍵。在考慮數據安全性解決思路之前,先要明確數據一致性判別準則,Code采用以下準則評判兩個倉庫數據一致。
數據一致評判準則:若倉庫所在兩個節(jié)點存儲的refs數據完全一致,則稱為這兩個節(jié)點上的倉庫數據一致。
目前系統(tǒng)數據安全機制主要有以下幾個特點:
a.多活復制
目前Code系統(tǒng)每個分片包含3個節(jié)點,即代碼數據保證三副本,即使出現1~2臺節(jié)點故障導致數據不可恢復的情況,也可通過其他節(jié)點進行數據恢復。我們采用了多活復制模式,即任何一個滿足必要條件(當前訪問倉庫在該節(jié)點的數據均重演至最新版本)的機器節(jié)點均可以進行讀寫操作,與單主模式相比提高了寫操作的吞吐量,節(jié)省了主備切換的成本,使部署、節(jié)點替換及異?;謴透雍唵巍6嗷顝椭颇J郊s束有以下兩點:
- “單寫”機制:在同一時刻,同一個倉庫的寫操作須在同一節(jié)點進行。
- 數據安全鎖機制:若某倉庫底層Git的操作出現異常錯誤,則在數據未恢復前,其后對該倉庫的所有操作均會在該節(jié)點進行,會產生局部熱點。
多活復制主要由數據存儲和數據壓縮兩個部分組成。
01 數據存儲
- Git主要由objects和refs兩類數據組成。objects數據為不可變數據,創(chuàng)建后為只讀模式,以文件的形式存儲于本地磁盤中;refs數據為可變數據,可以進行更新。兩類數據分別采用不同數據源進行存儲。
- 用戶在訪問倉庫時,如果某個objects沒有在任何一個分支的關聯(lián)鏈中,那么判定為不可達,對于不可達的objects,無需維護其一致性。不可達object的示例如下:
圖12 不可達object示例圖
02 數據壓縮
在Code系統(tǒng)中,需要記錄refs的變更日志以進行數據回放,保證系統(tǒng)的數據一致性。由于每個倉庫的refs數據變換是比較頻繁的,會產生大量的日志,從而造成存儲壓力。因而我們采用了日志壓縮技術,減少不必要的數據開銷,壓縮方式如下圖13所示:
圖13 數據壓縮
例如上圖中的main分支,其初始狀態(tài)為main -> a,第4個log為main -> e,第5個log為main -> f,則這3個log可以壓縮為一個log,即main -> f并將其應用于初始狀態(tài),與壓縮前回放觸發(fā)的結果是一致的,main都將指向值為f的commit。
03 相關優(yōu)化
在實踐過程中,我們發(fā)現采用純Git命令執(zhí)行數據復制操作無法有效控制資源分配,因而從通信方式、并發(fā)形式及復制粒度等方面做了優(yōu)化,從而提高了整體的數據復制效率。
b. 跨機房備份
Code系統(tǒng)每組分片的3個節(jié)點至少來自于兩個不同的機房(目前按照規(guī)范化部署,均改造為3機房),若其中一個機房發(fā)生故障,仍可提供服務。我們對該架構做了針對性的容災演練,通過演練驗證了節(jié)點掉線對系統(tǒng)的影響較小,結合靈活的節(jié)點替換能力,可在30分鐘內上線新的節(jié)點,達到容災平衡狀態(tài)。
圖14 跨機房備份
c. 數據熱備
Code系統(tǒng)提供數據熱備機制,通過數據復制的方式,任何寫入的新數據會立即同步到其余副本節(jié)點,基本“0”延遲,保證了用戶視角的強一致性。數據同步機制是熱備的關鍵,我們主要通過以下步驟實現。
01 寫操作階段
- 通過引入倉庫粒度的寫鎖,保證同一個倉庫同時只能在一個節(jié)點執(zhí)行寫入操作,然后通過Git Internal Hook機制觸發(fā)object數據的同步,并持久化記錄refs數據。
- 副本節(jié)點通過讀取持久化的refs數據,重演操作,從而保持了refs數據與寫入節(jié)點一致。
圖15 寫操作步驟
02 讀操作階段
- 如果當前倉庫持有寫鎖,則直接路由至持有寫鎖的節(jié)點讀取數據。
- 如果未持有寫鎖,則用各個節(jié)點的版本和數據源存儲的版本數據進行對比,將版本大于等于數據源存儲的最新版本的所有節(jié)點作為候選路由節(jié)點,并采用負載均衡算法進行路由;如果沒有符合條件的節(jié)點則需進行同步補償,待補償成功后再進行路由選擇 。
圖16 讀操作步驟
03 相關優(yōu)化
在最初實現中,我們采用了無狀態(tài)同步,發(fā)現存在同步任務被多次執(zhí)行的情況,后續(xù)通過任務前置檢查等方式避免了不必要的數據同步任務,最終減少了50%的同步任務。
d. 數據巡檢
數據巡檢是保證系統(tǒng)平穩(wěn)運行,數據安全可靠必不可少的一個環(huán)節(jié),它可以及早地發(fā)現系統(tǒng)中潛在的隱患。巡檢服務作為Code系統(tǒng)的核心服務,在降低數據風險,提高系統(tǒng)服務的穩(wěn)定性方面起到了關鍵作用。對于巡檢服務,我們主要從以下幾個方面進行考慮:
- 透明性:盡可能地避免對用戶的正常請求產生影響,減少不必要的干擾,對于系統(tǒng)訪問可以做到平穩(wěn)可控。
- 可靠性:作為數據安全的重要服務,它自身也要做到彈性伸縮,多點容災,具有高可用的特性。
- 可維護性:對于數據巡檢發(fā)現的問題,能夠通過有效手段進行處理。同時要提高巡檢服務的效率,隨著系統(tǒng)架構的迭代出新、模塊升級,巡檢服務要隨之更新,從而做到有效的保障。
綜合以上幾點,我們采用了無狀態(tài)的服務架構,提供定點巡檢、全量巡檢、定時巡檢等模式保障數據安全。其中巡檢的數據主要分為以下兩類:
- refs數據:根據數據一致性評判準則,refs數據是Git核心數據,因而它的檢驗是必不可少的。
- 版本數據:Code系統(tǒng)是基于版本進行讀寫路由的,因而當版本過大時,可能會產生大量的數據同步,為了避免突增同步請求對系統(tǒng)造成一定的IO抖動,監(jiān)控版本差距是尤為必要的。
巡檢服務的整體架構如下圖17所示:
圖17 巡檢模塊
4. 總結
本文系統(tǒng)性地介紹了美團在Code系統(tǒng)演進過程中面臨的擴展性和可用性兩大瓶頸,并分別針對上述兩類瓶頸和對應的挑戰(zhàn),詳細闡述了解決方案和落地的實踐經驗。
基于上述的架構改造實踐,目前美團代碼托管平臺實現了倉庫容量水平擴展、負載自主均衡等特性,穩(wěn)定支撐著研發(fā)流程規(guī)范的落地。我們未來會在支撐研發(fā)效率,保障研發(fā)安全方面繼續(xù)進行探索和演進,爭取積累更多寶貴的實踐經驗,后續(xù)再跟大家分享。
5. 未來展望
- 自動化運維:目前系統(tǒng)的運維機制自動化程度低,我們希望未來可以自動檢出系統(tǒng)異常并進行恢復,其中包括數據修復,自動擴容及熱點遷移等功能。
- 提供代碼領域最佳實踐:依托研發(fā)工具平臺,持續(xù)推動美團研發(fā)流程規(guī)范的迭代更新,沉淀最佳實踐并提供有力的工具支撐。
- 代碼安全:與信息安全團隊緊密合作,提供更為完備的安全控制策略,包括代碼掃描、漏洞自動修復、危險行為預警等功能。
6. 本文作者及團隊簡介
潘陶、費翔、丹丹、毛強等,來自基礎研發(fā)平臺-研發(fā)質量與效率團隊。
美團研發(fā)質量與效率團隊,負責公司研發(fā)效能領域平臺和工具的建設(包括研發(fā)需求管理工具、CI/CD流水線、分布式代碼托管平臺、多語言構建工具、發(fā)布平臺、測試環(huán)境管理平臺、全鏈路壓測平臺等),致力于不斷推進優(yōu)秀的研發(fā)理念和工程實踐,建設一流的工程基礎設施。
| 本文系美團技術團隊出品,著作權歸屬美團。歡迎出于分享和交流等非商業(yè)目的轉載或使用本文內容,敬請注明“內容轉載自美團技術團隊”。本文未經許可,不得進行商業(yè)性轉載或者使用。任何商用行為,請發(fā)送郵件至tech@meituan.com申請授權。