Digg架構(gòu)史:無數(shù)個(gè)如果會帶來無數(shù)個(gè)故事
譯文【51CTO精選譯文】編者按:之前一個(gè)月,Digg宣告被Betaworks收購,很多工程師由此成為了新公司的一員,本文作者Will Larson便是其中之一。這篇博客不是要討論Digg的產(chǎn)品決策或者是未來的計(jì)劃,而是純粹討論Digg背后的技術(shù)實(shí)現(xiàn)。Digg的興起,衰落,背后的工程團(tuán)隊(duì)也經(jīng)歷了各種重組。從幾個(gè)人開始到四十多人,再縮減到十?dāng)?shù)人,團(tuán)隊(duì)的管理發(fā)生了怎樣的變化?Digg的研發(fā)團(tuán)隊(duì)是如何進(jìn)行開發(fā)、測試與交付的?Will介紹了很多好玩的故事,對其他技術(shù)團(tuán)隊(duì)而言,是寶貴的經(jīng)驗(yàn)。
以下是Will博客的全文翻譯。
一個(gè)月之前,隨著Digg v1的二度啟動,歷史似乎再一次回到原點(diǎn)。而我們對于Digg的記憶也隨著四個(gè)月前Digg技術(shù)團(tuán)隊(duì)加入SocialCode而逐漸模糊?,F(xiàn)在似乎是回顧過去的最佳時(shí)機(jī),讓我們一起來看看從2010年5月到2012年5月,Digg.com是如何在杰出的系統(tǒng)及團(tuán)隊(duì)手中一步步構(gòu)筑成形的。
本文無心涉及任何爭論、也不打算渲染過多細(xì)節(jié),我們要做的只是告訴大家自己的工作經(jīng)歷與所建立的架構(gòu)。
團(tuán)隊(duì)結(jié)構(gòu)、規(guī)模及組織方式
我們首先要介紹的是公司規(guī)模、團(tuán)隊(duì)組織方式以及一系列裁員、招聘給人員結(jié)構(gòu)帶來的影響,這算是必要的背景介紹。
出于文章核心話題的考慮,我不會討論與銷售、財(cái)務(wù)、廣告管理、設(shè)計(jì)、人力資源、業(yè)務(wù)拓展團(tuán)隊(duì)等有關(guān)的內(nèi)容;他們當(dāng)然也是公司的重要組成部分、曾為企業(yè)帶來不可磨滅的貢獻(xiàn),但這并不是本文要討論的主題。
我們的故事從一套非常傳統(tǒng)的企業(yè)布局開始,這里擁有特色鮮明的產(chǎn)品、工程技術(shù)及運(yùn)營管理機(jī)制。產(chǎn)品團(tuán)隊(duì)規(guī)模很小,只有四名成員、運(yùn)營團(tuán)隊(duì)則擁有大約八位工程師、品質(zhì)保障部門有六位工程師、而開發(fā)團(tuán)隊(duì)則包含大約二十位開發(fā)人員。
開發(fā)團(tuán)隊(duì)本身的結(jié)構(gòu)很單純,包括四大橫向部門(前端、API、平臺以及基礎(chǔ)設(shè)施)與兩大縱向部門(廣告和分析)。產(chǎn)品團(tuán)隊(duì)的運(yùn)營工作由功能性劃分、遵循縱向管理機(jī)制,協(xié)作方案則采用與各開發(fā)團(tuán)隊(duì)橫向聯(lián)合的方式。
在經(jīng)歷了數(shù)次裁員及集體離職的沖擊之后,組織結(jié)構(gòu)最終回歸到極簡化的初始狀態(tài)。
在過去的一年中,我們的運(yùn)營團(tuán)隊(duì)只有三位成員、工程技術(shù)團(tuán)隊(duì)有七位成員,而廣告團(tuán)隊(duì)則有四位成員。
代碼審查與持續(xù)部署
在處理核心開發(fā)實(shí)踐的階段,我們的工程師隊(duì)伍一度擴(kuò)張到四十人,但隨后又縮減為十四人?,F(xiàn)在我們來看看整個(gè)開發(fā)實(shí)踐流程,包括如何處理合并工作、審查并部署代碼。
現(xiàn)在具體闡述以上流程圖所表達(dá)的信息:
1. 我們使用Git作為源代碼控制工具,使用Gerrit作為代碼審查工具。每一段代碼都需要接受審查并獲得批準(zhǔn),同時(shí)必須通過所有單元測試。
2. 每當(dāng)有補(bǔ)丁集被提交至Gerrit做代碼審查,Jenkins都會自動運(yùn)行其單元測試流程,并在測試結(jié)束后將成功或者失敗結(jié)果遞交Gerrit作為審查依據(jù)。
也就是說,代碼審查人員不必在那些被自動流程檢測出問題的補(bǔ)丁身上浪費(fèi)時(shí)間。這同時(shí)意味著我們從來不會把那些無法通過單元測試的殘缺代碼合并到Git資源庫當(dāng)中。
3. 一旦補(bǔ)丁集通過了Gerrit測試并獲得代碼審查人員的批準(zhǔn),這些代碼就會被合并到Git主分支當(dāng)中,并通過Puppet以自動化形式部署到測試環(huán)境下。在順利部署到測試環(huán)境下并成功通過了集成在該環(huán)境中的測試流程后,這些新的補(bǔ)丁集就會被合并到Git中的產(chǎn)品分支里。
4. 我們以手動方式處理Jenkins工作并部署至產(chǎn)品環(huán)境,更新代碼的部署工作則由Puppet負(fù)責(zé)。
接下來我們著手進(jìn)行產(chǎn)品的持續(xù)部署,但在經(jīng)歷了幾次嚴(yán)重的部署事故之后,我們決定委派專項(xiàng)人員處理這類工作,直到確認(rèn)所有產(chǎn)品化流程都完全達(dá)到我們的預(yù)定效果。
如果遇上不得不采取持續(xù)部署的情況,我們決定進(jìn)一步改善自己的測試機(jī)制,以保證盡量不讓問題漏網(wǎng)。但這么做有點(diǎn)事倍功半,我們其實(shí)沒那么多時(shí)間做這些繁瑣的工作。
在我看來,這套審查、測試及代碼部署系統(tǒng)堪稱Digg項(xiàng)目成功的最大功臣,同時(shí)也是代碼部署工作的一大建設(shè)性突破。它的結(jié)構(gòu)非??茖W(xué),足以在保證代碼標(biāo)準(zhǔn)的同時(shí)承受技術(shù)團(tuán)隊(duì)由四十人縮減為十四人所帶來的人力匱乏。
應(yīng)急流程與實(shí)踐
我們零零散散采用過很多實(shí)踐方案,但大多數(shù)都是被現(xiàn)狀推著走,而不是提前做好了充分準(zhǔn)備。
1. 剛開始我們將單元測試的覆蓋率定得非常高,但隨著團(tuán)隊(duì)規(guī)模的萎縮,我們不得不放棄了對新測試內(nèi)容的補(bǔ)充。這顯然算不上明智的決定,但在當(dāng)時(shí)的情況下,我們不得不棄車保帥。經(jīng)過討論,我們認(rèn)為單元測試在避免關(guān)鍵性故障方面的效果并不理想,例如在生產(chǎn)負(fù)荷下與多種組件相關(guān)的系統(tǒng)問題以及前端渲染問題等。
單元測試是我們的驕傲,在之后的幾年中它一直努力工作并幫我們解決了很多大麻煩。而Selenium測試則沒那么好運(yùn),它幾乎在我們停止維護(hù)的同時(shí)就瞬間失靈了,而且隨品質(zhì)保障團(tuán)隊(duì)的離去一道徹底告別了我們。
2. 我們利用Thrift來定義并區(qū)分前端及平臺團(tuán)隊(duì)的專屬接口,這些接口的作用是為不同團(tuán)隊(duì)之間的溝通與協(xié)作提供橋梁。當(dāng)然我們也果斷舍棄了很多存在設(shè)計(jì)缺陷的接口,它們都或多或少給工作造成過負(fù)面影響。不過總體而言,利用Thrift來定義團(tuán)隊(duì)之間的類型與接口非常明智,它為我們提供了非常實(shí)用的溝通渠道。
3. 我們很少會對現(xiàn)有Thrift接口進(jìn)行改動,一般更傾向于針對需要的功能推出新接口、更新客戶端使功能奏效然后刪除舊接口。這種處理方式有點(diǎn)尷尬,但是由于我們是以獨(dú)立方式分別部署前端及后端代碼的(而且會花幾分鐘時(shí)間把全部前端或后端服務(wù)器跑一遍),因此這是最安全也最理智的方式。畢竟隨意改動會帶來不可知的后果,我們可不想讓多年的心血?dú)г谧约呼斆У膰L試中。
4. 我們利用一種新機(jī)制來啟動所有前端變更,并將其帶入用戶的子集中或者在出現(xiàn)問題時(shí)加以禁用。這種方式令我們將部署與試運(yùn)行分離開來,進(jìn)而保證了代碼部署工作的安全性與穩(wěn)定性,同時(shí)也賦予了我們準(zhǔn)時(shí)完成新功能開發(fā)的能力。
起初我們打算利用這種機(jī)制對新功能進(jìn)行A/B測試,但后來我們發(fā)現(xiàn)它最大的價(jià)值在于管理發(fā)行版以及收集來自受信任用戶群組的反饋意見。
我們還啟用了一些后端功能,希望借此解決極端情況下的性能或負(fù)載沖擊。萬事想在前頭才好,否則出現(xiàn)問題時(shí)我們只能盯著Jenkins的進(jìn)度條手足無措了。
聊完了過去的開發(fā)歷程,我們再來看看開發(fā)出的系統(tǒng)成品。
康威如是說,或者叫“架構(gòu)”
如果大家看了我們在設(shè)計(jì)v4架構(gòu)時(shí)所采用的組織結(jié)構(gòu),再看看我們開發(fā)出的架構(gòu),就會發(fā)現(xiàn)這基本上就是康威定律的現(xiàn)實(shí)版體現(xiàn)(編輯注:康威定律根據(jù)計(jì)算機(jī)科學(xué)家Melvin Conway而命名,定律的大意就是:“任何一個(gè)設(shè)計(jì)系統(tǒng)的團(tuán)隊(duì),其設(shè)計(jì)出來的架構(gòu)總會帶著團(tuán)隊(duì)本身組織架構(gòu)的條條框框”)。
我們擁有一個(gè)API團(tuán)隊(duì)和一臺API服務(wù)器、一個(gè)前端團(tuán)隊(duì)和一臺前端服務(wù)器、一個(gè)平臺團(tuán)隊(duì)和一臺后端服務(wù)器、一個(gè)廣告團(tuán)隊(duì)和一臺廣告服務(wù)器、一大堆由基礎(chǔ)設(shè)施團(tuán)隊(duì)管理著的存儲數(shù)據(jù)以及專為分析團(tuán)隊(duì)準(zhǔn)備的Hadoop集群。
也就是說,對我們的團(tuán)隊(duì)規(guī)模而言,這是一套非常標(biāo)準(zhǔn)的組織結(jié)構(gòu)。其中前端團(tuán)隊(duì)負(fù)責(zé)通過PHP/HTML/JS進(jìn)行統(tǒng)一開發(fā),狀態(tài)及存儲則由后端服務(wù)器管理,同時(shí)消息隊(duì)列則處理長時(shí)間運(yùn)行的項(xiàng)目以及非事務(wù)類流程。
最特別的方面當(dāng)數(shù)后端服務(wù)器了,這些Thrift服務(wù)器以gevent為基礎(chǔ)。但遺憾的是我們連一臺標(biāo)準(zhǔn)的高性能Python Thrift服務(wù)器也沒有,所以花了大量時(shí)間與gevent打交道,并想盡辦法讓gevent能安全地服務(wù)于客戶端池。另外我們還盡量避免在Python服務(wù)器上使用Thrift,這么做主要是為了減少同類服務(wù)器的使用量。
同時(shí)管理PHP及Python服務(wù)器的感覺令人抓狂,我的心中一直回響著“我從來沒想到會遇到這種情況”的聲音。但實(shí)踐出真知,我發(fā)現(xiàn)實(shí)際操作中并沒出現(xiàn)過太大的問題:在過去的一年時(shí)間里,我們團(tuán)隊(duì)中的幾乎每位成員都以輕松愉快的心情同時(shí)使用這兩種語言,我也沒聽到過什么抱怨之聲。
作為力求系統(tǒng)產(chǎn)品完美的設(shè)計(jì)人員,使用Tornado、Apache+modwsgi+Pylons以及Python gevent服務(wù)器的現(xiàn)狀令我心生憤懣,不過跟上面幾個(gè)問題一樣,實(shí)際操作中沒遇到過什么障礙(但這要?dú)w功于Jenkins與Puppet在系統(tǒng)部署方面的幫助,沒有它們倆相信整個(gè)流程會變得更加枯燥而痛苦)。
結(jié)束語
總而言之,我認(rèn)為我們的流程及架構(gòu)非常合理,而且也很適合我們的工作習(xí)慣。如果我們的項(xiàng)目今天才剛剛開始,那么采用的方案很可能完全不同;如果我們的團(tuán)隊(duì)沒有經(jīng)歷嚴(yán)重人員流失、如果我們沒有因?yàn)橘Y金不足而不得不同時(shí)管理多套遺留系統(tǒng)與API……無數(shù)個(gè)如果會帶來無數(shù)個(gè)故事,這些有趣的話題不妨今后慢慢聊。
在為Digg付出了無數(shù)個(gè)日夜的辛勤勞作后,我認(rèn)為圍繞數(shù)據(jù)庫與技術(shù)增值話題同樣值得寫一篇專題(包括Cassandra, MySQL, Redis, Memcache, HDFS, Hive, Hadoop, Tornado, Thrift servers, PHP, Python, Pylons, Gevent等等),但現(xiàn)在我還需要積蓄一些靈感。
又或者談?wù)勎覀兊慕M織結(jié)構(gòu)給我們的開發(fā)工作帶來了哪些影響?下一次我們再繼續(xù)討論吧。