CODING 代碼託管架構升級之路

本文爲 CODING 創始團隊成員王振威在『CODING 技術小館:上海站』的演講實錄。linux

CODING 技術小館,是由國內專業的一站式軟件服務平臺 CODING 主辦的一系列技術沙龍。將邀請數位業內知名大牛分享技術,交流經驗。同時也將邀請覺得當地用戶進行技術分享,爲開發者們帶來一場純粹的技術沙龍。程序員

你們好,我叫王振威。我是 CODING 的初始創始團隊成員之一。CODING 從 14 年創業到如今,主要作的是代碼託管,我主要負責架構和運維方面的工做。今天給你們帶來一個技術分享是關於咱們代碼託管的整個架構是如何從最開始殘破不堪的產品,一步一步升級到如今的產品的。這是今天的主題『CODING 架構升級的演變過程』。數據庫

提及架構的話,稍微有點寫程序經驗的人來講,均可以理解架構對於整個服務的重要性。架構最核心的三個點就是:穩定性、擴展性、性能。一個好的架構主要經過這三點來看。api

會不會宕機,你的服務會不會由於自身或者第三方的緣由忽然之間中斷。可拓展性,當你的訪問量增加的時候,你的服務能不能迅速的 Copy 出不少個副本出來以適應快速增加的業務。再一個就是好比說你要作電商啊秒殺啊之類的功能的時候,能不能扛得住這種壓力。這就是評價一個架構好壞的三個基本點。緩存

咱們能夠想一想一下,一個架構比較亂是什麼樣子。就好像一個機房管理員面前全部的線亂成一團。服務器

在這裏輸入圖片描述

一個好的架構是什麼樣呢?這是Google 數據中心機房的排布,咱們能夠看到這是很是整齊的,看起來是賞心悅目的。markdown

在這裏輸入圖片描述

咱們的軟件架構和硬件架構甚至跟建築學等都是有共通之處的。一個好的架構必定是邏輯清晰,條理明確的。網絡

爲何要講代碼託管的架構呢。這要從 Coding 的最開始提及,Coding 作了一個代碼託管,作了個代碼的質量分析,一個演示平臺,還有一個在線的協做工具。代碼託管是咱們很是重要的一個業務,在整個 Coding 的架構演變升級的過程當中,包括對各類新技術的嘗試中,代碼託管一直走在最前列的,包括Docker 的嘗試等等。因此我今天和你們分享一下代碼託管架構的演變升級,我認爲這個是整個 Coding 架構系列的一個典型。架構

咱們從最開始開始講,整個升級過程分爲四大階段。負載均衡

第一階段:遠古時代

咱們三月份開始寫第一行代碼,七月份把產品上線,因此當時很是倉促,但這也是不少創業公司不可避免的一個現狀,就是你必需要快速上線,產品能夠不夠完美,但必需要快速切入市場。因此原則是先解決有沒有的問題,再解決好很差的問題。咱們當時以這樣的思路來作這個工做。咱們也參考了不少開源軟件,好比咱們的架構就是仿照 Gitlab。那時候說白了就是租了一臺服務器,而後全部的服務跑在上面,用戶全部的請求都經過這臺服務器進行交互,咱們來看一下當時的架構圖。

遠古時代:階段一

用戶從最左邊,經過咱們在 Ucloud 的 ULB 進來,到咱們本身的應用服務器,應用服務器和數據庫有些交互,和緩存等其餘組件有些交互,和 Git 倉庫有些交互。黑框框起來的部分就是咱們的應用服務器和 Git 倉庫,它們當時是部署在同一臺服務器上的,也就是說應用程序,代碼都是部署在一塊兒的。因此顯而易見,這個架構存在不少問題,若是 Ucloud 這臺服務器出了問題,咱們的網站就掛了。雖然是虛擬機,可是出問題是難保的,這就是一個很很差的架構。會不會宕機?會。能不能擴展?不能。能不能抗壓?不能。全部東西都在一塊兒。就像養寵物同樣,把全部服務都放在你的寵物身上保護好,但一旦你的寵物出現問題整個服務就都癱瘓了。

後來咱們上線以後就慢慢在作一些改進,咱們發現咱們遇到最嚴重的問題,就是代碼託管和項目協做組合到一塊兒了,但不少時候業務是相對獨立的,那咱們就把他拆開吧。另一個是代碼倉庫數據量愈來愈大,單臺服務器已經很難支撐了。咱們的代碼倉庫必須具有負載均衡的策略和從新排布的這種能力。

第二階段:農耕時代

咱們的架構是這個樣子的:

農耕時代 階段二
用戶仍是經過 ULB 進來,可是咱們的代碼倉庫已經分離到單獨的機器上,咱們每個機器上都會裝備一個 RepoManager,用來專門管理這臺服務器上的全部代碼倉庫。它和咱們的主站服務是經過 RPC 這種方式來通信的。咱們的 Git 倉庫,也有了獨立的備份,不須要和主站一塊兒備份。這是第二階段作的主要改動。咱們的 Git 有多個服務器,不僅圖上所示三個。每一臺都有一個 Repomanager 管理器,再經過 RPC 作交互。大概是這樣的一個結構。

而後咱們就發現了更多問題,須要做出更多改變以承受更多的用戶量。

第三階段:手工業時代

代碼倉庫的服務分爲兩部分,一個是用戶能夠在本地使用 Git 工具來 push/pull 代碼。另一個是,能夠在網頁上直接看代碼、編輯代碼、查看提交歷史等。這兩部分操做在底層實現上來講是比較獨立的,因此咱們選擇把他們進行更完全的拆分。還有就是專門的認證服務,我和你們解釋一下,咱們每次從網站上 clone 代碼或者 push 代碼的時候,咱們必需要認證一下用戶的身份。而這個認證服務是決定了咱們服務是否穩定的一個重要組件。如今咱們就把他單獨的拆離了出來,就是說咱們有一套專門的認證服務來處理這個事情。達成咱們但願的各個環節能夠實現規模化增加。具體咱們來看一下架構圖。

手工業時代:階段三
這個圖就稍微有點不一樣了,用戶訪問咱們 Git 的服務分兩條線,一部分是黑線標註出來的,另外一部分是紅線標註出來的。紅線標註出來的其實是使用 IDE 的插件、命令行工具來訪問咱們的代碼倉庫,經過 SHH 協議、或者 Git 協議、HTTP 協議到 ULB 以後,會到咱們的 Git 的 Server,Server 會交由咱們的認證服務去作一個用戶權限的認證。這個認證服務是獨立的,好比去數據庫中校驗密碼、校驗權限,回來以後 Git Server 會在內網發一個 SSH 請求到咱們的具體的代碼倉庫的存儲機器上,最終完成代碼的交互。

另一條線,黑線表示的是咱們在網頁上操做的時候,好比查看代碼的文件數,編輯代碼等等,這條線上的請求所有都是 HTTP 請求。因此用戶到 ULB 以後,就直接代理到 Web Server,和階段二同樣,經過發送 RPC 請求,到具體的 Git 倉庫的存儲的 RepoManager 上面從而產生數據的交互。這是咱們第三階段作的主要改進。

然而隨着時間的增加咱們又發現了更多的問題。一個是咱們把代碼倉庫按分區給分離開來,但會發現代碼倉庫的活躍是不均勻的。若是一臺機器恰好這段時間的訪問量很是大,那這臺機器的壓力就很大,尤爲是計算方面的壓力,其餘的服務器又可能幾乎處於閒置狀態。存儲方面的話咱們通常會作 500G-1T 的存儲,可是 CPU 咱們通常不會配置過高,由於大多數都屬於冷數據的。這時候咱們就須要一個彈性的計算池。計算和存儲是分離的,就是咱們的存儲能夠任意搭配計算池來進行計算。另一個就是自動化監控,咱們的服務從單臺機擴展到不少臺機,還有分區。組件也愈來愈多,咱們有不少獨立的服務,好比有獨立的發郵件啊,有獨立的 markdown 編譯器啊,還有 qc 的服務,還有 CodeInsight 的服務、WebIDE 等等等。服務一多,運維的壓力就會成倍增加。這個時候咱們須要自動化監控來幫咱們解決一些問題。

第四階段:工業時代

工業時代 階段四
用戶經過 Web、HTTP 或者 SSH Git 協議連接到咱們的 ULB 以後,內網作轉發,網頁的訪問這邊我就沒畫,到 Web Server,仍是經過 RPC 請求到 Repomanager。不同的是紅線區域。用戶到 GitServer 以後,先認證以後會連到服務器池,下面也是一個服務器池,組成一個計算池,主要是 CPU 和內存的配備,並無什麼磁盤這種配置。下面是存儲池,存儲池經過網絡文件系統掛載到計算池上,因此如今就造成了這樣的結構。存儲由存儲的負載均衡策略來決定,可是計算池由計算的負載均衡決定。這樣壓力大時的請求並不會同時發在同一臺機器上,就能解決咱們以前說的不均勻問題。這個結構裏還有不少細節咱們接下來探討。

其中一個細節是,咱們全部的 Git 服務都用 CDN 將用戶鏈接起來。哪怕是中國最好的帶寬資源,都比不上用戶訪問的服務節點在他所在城市的骨幹節點,這時候咱們就找了 CDN 的供應商,CDN 的地域性骨幹網絡節點把咱們的請求轉發到 UCloud 源服務器上,雖然這樣成本很高,咱們付出了兩倍帶寬的價格,可是最終的使用效果仍是不錯的,不少用戶反映速度和穩定性有明顯提高。另外咱們計劃推廣 CDN 到全站,全部的服務。使用 CDN 另一個好處是能夠防 DDOS 攻擊,咱們同行包括咱們本身常常遭受這樣的困擾。DDOS 的原理是針對一個 IP 地址,肉雞不斷往這個 IP 地址發送垃圾包,從而致使帶寬被佔滿。使用 CDN 以後,咱們給用戶報 IP 地址都是全國性的,有幾百個 IP 地址,DDOS 每每都是針對單個 IP 地址來攻擊的。當咱們的節點收到攻擊的時候,供應商能夠立馬將節點替換掉,從而致使大範圍問題。因此用 CDN某種程度上來講能夠避免 DDOS 攻擊。你們都知道 Git 服務關係着公司的線上部署,對穩定性要求很是高。因此咱們仍是願意花很大的成原本作這個。看一下,CDN 大概是這樣一個結構。

工業時代的細節:CDN
用戶經過 CDN 節點,轉發到 ULB 的相關端口,和傳統的靜態分發不太同樣的是,傳統的靜態分發,CDN 節點每每都緩存一些圖片、CSS、JS 這些東西,但咱們如今全部的數據都是從咱們的原站流出去的,CDN 節點並無給咱們節省流量。全部的流量只是用 CDN 節點作了個分發,由於咱們的數據都是動態的,並且有些協議不是 HTTP 的。

第二個細節是 LB,LB 就是負載均衡,咱們如今的服務器中大量的部署了這種形式的服務。LB 把無狀態的服務接口實現了統一。什麼叫無狀態服務,就是服務不會在內存中作一些狀態的存儲,好比說緩存,無狀態服務的請求應該是和先後文無關的,下一個請求不會受前面的請求影響致使數據改變。其優勢是能夠部署不少實例,這些實例沒有任何差異。針對這些服務,咱們經過LB 把這些服務統一了起來。內部服務的相互依賴都經過 LB 完成。

而後是一個監控系統,咱們用了 Google 一個團隊開源出來的叫 Prometheus 的一個監控器。據咱們的 CTO 孫宇聰說這個監控器比較像谷歌內部的監控系統。目前咱們使用的感受仍是很不錯的。

爲何要作這個,你們都知道木桶原理,不少服務相互依賴的時候,必須全部的服務均可靠,你的服務最終纔是可靠的。LB 系統的目的在於:當某個實例出現問題的時候,自動剔除掉,就是作監控級別的自動運維。

LB 和監控系統配合的工做流程是這個樣子的。

工業時代的細節: LB
這張圖展現了咱們內部系統的服務是如何相互依賴的。這裏有幾個角色,一個是 LB,一個是監控系統,一個是運維人員,就是左上角這個 ops。ops 操做線上的服務的時候,我麼是直接操做這個 LB 配置。舉個例子,咱們有個 markdown 的編譯器,以服務的形式存在,給咱們服務中的其餘服務提供 markdown 編譯的底層的支持。假若有一天你發現 markdown 編譯器的承載量已經不夠了,必須新加一個實例。這時候上線以後,每每要作一大堆配置才能生效,可是咱們操做 LB 讓這個實例掛載在 LB上,LB 但是實現動態 reload ,因此能夠實現快速上下線。LB 的工做是這樣的,咱們的 LB 是用配置文件來描述的,ops 操做完 LB,Confd 會生成最終的配置文件。把這個配置文件發送到 Etcd 集羣,Etcd 就是一個配置中心,有不少配置項在裏面。發到 Etcd 以後,會有另一個程序就是 Confd,他會一直監控在這個 ETCD 的狀態,當 LB 狀態發生變化的時候,它就把這個變化過狀態的配置拿下來,生成最終的 LB,生成最終配置文件,reload 後服務就上線了。

還有一個是監控系統的角色,咱們的監控器是用 Prometheus 搭建的,監控系統有一個配置項,是用來配置服務監控的數據的接口,咱們每個服務都會起一個 HTTP 端口,提供基礎的「關鍵指標」。「關鍵指標」能顯示你的服務是否健康,壓力有多少。仍是舉 markdown 編譯器的例子,他的「關鍵指標」是每秒的處理量,一個 markdown 文本編譯完的時間,就是一些本身健康狀態的指標。每一個服務必須統計好本身的關鍵指標,再把這些信息以 HTTP Metric 的形式暴露出來,咱們的監控系統每隔一段時間去抓取一下這個數據,若是抓取不到或者抓取的關鍵指標出現了異常,根據配置的警報策略和自動處理策略開始行動,好比他認定某個實例 down 的時候,他就去通知 Etcd 某個實例 down 了, Confd 偵測到後 ,LB 就把這個實例下線。另外,當某個實例出現問題的時候,監控會經過 WebHook 的形式,去通知咱們的通知中心,把這個實例有問題的信息發給咱們的運維人員,好比發短信,發 App push、發郵件等讓運維人員進行下一步處理。這是工業時代一個幾乎半自動化的架構。無人值守的時候這套流程也基本能夠正常運轉。咱們從系統上能夠容許 Ucloud 內網出現波動,由於不管是任何狀況,只要是「關鍵指標」有變化,咱們的報警和自動處理策略就會生效。

工業時代,就是自動化生產的時代,另外要講的一點,就是容器化。其實咱們在第三階段就開始嘗試容器化了。到目前咱們 95% 以上的服務都是用 Docker 來提供的。我來介紹一下咱們目前是如何使用 Docker 的。一個是咱們本身搭建了 Docker Rigestry,目前發了第二版,叫作 distribution ,考慮過遷移到新版但竟然不兼容,遷移工具也不夠好用,初版又沒有明顯的問題,因此咱們一直沿用到如今。

此外,咱們線上的代碼,都是編譯在Docker 中,運行在 Docker 中,爲何要這麼作呢?編譯在 Docker 中有一個明顯的好處,好比咱們在本地開發的時候,咱們是用 JDK8,那咱們線上不可能用 JDK7 去編譯這個版本,若是更嚴格咱們可能要求小的版本號也一致。想保證版本使用嚴謹,用容器是一個很是方便的選擇。若是在服務器上 linux 操做系統上裝這個 JDK,可能過兩天就要升級一下,很是麻煩,可是若是在 Docker 中使用,很容易指定版本。另外咱們在 Docker 中編譯程序,在 Docker 中運行程序。

而後是 Docker Daemon 的管理,每一臺服務器上都裝一臺 Docker 的守護進程,它來管理上面的 Container,咱們寫了一套工具,原理就是給 Docker 發請求,告訴他應該起哪一個 Container,應該停哪一個 Container。

這個圖大概展現了一下咱們是如何用 Docker 的。

工業時代的細節:容器化
運維在操做的時候,有兩個接口,有一個 UI Dashboard,咱們能夠在 Dashboard 上去控制某一個實例,也就是某一個容器,還能夠經過命令行的形式來處理,最終造成 Docker 運行的指令,咱們的程序由此進行管理和運行,好比咱們發一個請求說要構建 markdown 編譯器,代碼被檢出後,在 Docker 中構建,構建完了以後再把 Runtime 進行打包。打包了以後就把這個 Image 推到自建的 Docker Registry,這個時候咱們的而其餘服務器均可以從這裏把 Image pull 下來,在推送完以後,他就通知某一臺服務器,好比咱們指定了某一臺 markdown 編譯器是運行在某一個服務器上,他就通知這個服務器,從上面拉下來 Image,而後去把他啓動起來,咱們通常狀況下不會直接操縱這些服務器,都是直接在 UI Dashboard 完成了運維的操做。

下一階段:信息時代

第四階段我認爲是一個自動化的階段,下一步就是信息時代,也就是數據驅動的時代,自動化,規模化的生產纔是咱們的目標。最初農耕時代,可能全部都是程序員或者運維上去執行,搞完了以後進入手工業時代,有一些腳本和程序能夠協助咱們,後來又進入了工業時代,工業時代就有一些自動化的流程作一些自動化的運維處理,最終的目標是進入信息化時代,信息化時代就是整個咱們的服務集羣是一個雲,這個雲是彈性的,只須要告訴他咱們須要什麼就好了,後面的事情他本身會解決。固然這個目標離實現仍是有必定距離。

來展望一下:要作到這些,一個是自動化監控,咱們如今有了,可是這個監控仍是有些問題,好比說每一個組件的關鍵指標都不同,每個都須要單獨去配置,咱們但願把一些關鍵指標統一化,更好的量化,這樣咱們統一寫一些監控的報警規則或者自動化處理規則就能夠了。日誌數據分析決策,這是什麼呢,咱們不少組件天天在產生上百萬行上千萬行的日誌,這些日誌靠運維去看是不可能的,咱們但願能對日誌進行一些分析,作一些自動化的決策,好比說某一個組件,當他數據出來,咱們可能就認爲他有問題了,經過 LB 把他下線,再把相關的問題發郵件給相關的人員去看,作自動化的目的是能夠解放運維。

架構全球化,多機房異地部署,CODING 是確定會走向國際的,這是咱們已經在規劃的一件事情。尤爲是碼市,會面向全球去接項目的,遲早有一天咱們要向全球部署服務,因此咱們的服務必須兼容異地化、高延時的跨機房部署。最後一個主要是爲了節省成本,當咱們服務愈來愈多的時候,有些服務這幾天要求計算資源高,有些服務哪幾天要求計算資源高,會存在浪費,因此咱們但願實現整個系統能夠自動的擴容,造成運維的閉環,在運維人員不多幹涉的狀況下,自動幫咱們節省成本又不失穩定性,我相信在一個超大型的公司,幾百萬臺服務器,確定都是自動化處理的,但願有一天,咱們也能實現這樣的願景。

這是咱們但願最終實現的模型。

最底層咱們仍是會選擇雲服務商,好比說 Ucloud,AWS,包括咱們在香港也部署了一些服務。像最底層的服務,物理機房、CDN,這些咱們都選擇找供應商,這些供應商均可以水平大規模擴展的。上面一層也是咱們不用考慮的,這層主要是 VM,就是說這些服務商把下面這些硬件資源抽象化成上面的這些虛擬的計算機虛擬的網絡,把虛擬的網絡以 api 的形式提供給咱們,咱們去編寫一些程序,這個程序可能會在某個適當的時機幫咱們啓動服務器,幫咱們自動增長帶寬,自動增長 CDN 節點,這就是Resource API。在這一層上面,是咱們的 Docker Container 層,全部服務都運行在這層。再上面一層就是咱們本身的服務了,例如一個 markdown 編譯器,一個 Web 網站,一個 Git 服務,還有咱們的 LB,監控、緩存、消息隊列等等。咱們的運維只經過 Job Manganer 告訴整個集羣:我須要起一個實例,大概計算量是多少,那這個雲就會自動幫咱們調 Resource API、幫咱們開虛擬機,配置網絡,監控等等把事情所有搞定。最終就是但願實現的架構運維的閉環。

信息時代:下一階段

今天個人分享就到這裏,謝謝你們。

相關文章
相關標籤/搜索