幫助 Medium 閱讀時間達到 2600 年的技術棧

jopen 9年前發布 | 26K 次閱讀 Medium

背景

Medium 是一個網絡。這是一個分享有價值的故事和想法的地方,在這里人們可以保持想法不斷的前行,大家已經在那里花費了 14 億分鐘(換句話說是 2600 年)的閱讀時間。

每月的獨立用戶超過了 2500 萬,每周會新增數以萬計的新帖子。但是我們心目中的 Medium 應該是以觀點量來衡量是否成功,而不是閱讀量;不會關心作者的資歷,而更關心想法是否有價值;應該能更方便的為大家推送有價值的內容。

我領導了整個工程團隊。我之前是 Google 的一名軟件工程師,從事 Google+ 和 Gmail 的開發,共同創建了 Closure 項目。在那段時間,我經歷過滑雪板比賽,飛機跳傘,還有叢林冒險。

工程團隊

我為我的這個團隊驕傲。團隊里每個人都是天才,大家都充滿求知的渴望,為了這個讓人激動的事情聚在一起。在我們看來,經歷過不同的磨練可以讓你成為更有經驗的工程師。有興趣的話可以看看我們其他的價值觀

在如何安排自己的工作上,團隊有很高的自由度,不過作為公司,我們會設立季度目標,鼓勵迭代沖刺。我們使用 GitHub 做代碼 review 和 bug跟蹤,用 Google Apps 管理郵件、文檔和電子表格。我們是 Slack 的重度用戶 (以及 slack bots),有不少團隊使用 Trello

初始的技術棧

剛開始的時候我們把服務部署到 EC2 上。主服務是用 Node.js 寫的,每次發布的時候,會合并到DynamoDB

還有一個 node 服務器用于圖片處理,調用 GraphicsMagick 來做具體的復雜的工作。另一個服務被用作 SQS 隊列處理,負責后臺任務。

我們的 email 使用 SES,靜態資源放在 S3 上,CDN 使用 CloudFront,使用 nginx 作為反向代理。另外,使用 Datadog 做監控,PagerDuty 做報警。

網站使用 TinyMCE 作為編輯器。發布之前,我們已經在使用 Closure 編譯器和部分 Closure 庫,不過模板用的是 Handlebars

當前的技術棧

Medium 這樣的站點看上去似乎很簡單,不過背后的復雜程度足以讓人驚訝。這還僅僅只是一個博客網站么?要是這樣的話,你用 Rails 幾天就能搭建一個出來。:)

閑話少說,我們從最底層說起。

產品環境

目前我們運行在 Amazon 的虛擬私有云上。我們使用 Ansible 做系統管理,可以讓我們的配置處于源碼控制下,可以通過可控的方式很輕松的進行更新。

我們有著面向服務的架構,在其上運行了大約一打的產品服務(取決于你如何去統計,還有很多小一點的服務)。是否作為獨立的服務來部署,主要取決于其功能,服務邊界上是否可能產生有依賴的變動,以及對資源的利用。

我們主要的應用服務器仍然寫在Node里,它允許我們在供應商和客戶之間交換代碼,我們用編輯器就要用到它,公布變革也是用它。Node為我們工作 得很好,但是在我們把事件循環編寫成塊的時候,性能問題就浮現了。為了緩和這一問題,我們在每臺機器上運行多個實例,并把它們路由到昂貴的端點,以此來分 隔它們。我們把它與V8運行環境相掛鉤,以深入了解哪個部件運行花費的時間較長。通常它是因為在JSON還原序列化時目標的具體化。

我們用Go編程的時候可以享受到一些輔助服務。我們發現用GO建立、打包和部署很容易。我們喜歡不需用到繁冗并調試虛擬機Java的類型安全。就我個人而言,我更喜歡在團隊環境中使用武斷的語言。它能提高一致性、減少歧義,最終讓你避免作繭自縛。

我 們現在使用CloudFlare提供靜態資源,盡管我們把5%的流量送到Fastly,同時還送5%的流量到CloudFront來保持它們的緩存熱度, 以防在緊急情況下萬一我們需要割接。最近我們也把CloudFlare用于應用流,基本上是為了DDOS保護。但是對于性能提升我們還是樂見其成的。

我們使用Nginx和HAProxy的結合作為反向代理,達到負載均衡,以實現我們需要的Venn Diagram特性。

我們仍然使用Datadog監測,使用PagerDuty報警,但是我們現在大量使用ELK(Elasticsearch,Logstash,Kibana)調試出現的問題。

數據庫

DynamoDB仍然是我們基本的數據存儲庫,但是它仍不完美。我們遇到的常見的問題之一是在重大事件發生時和有百萬追隨用戶時的熱鍵問題。在Dynamo前面我們可以用Redis緩存器,它能通過讀取來減輕這些問題。

開發者便利性和產品穩定性二者之間的優化似乎總是存在矛盾,但是我們在努力縮小分歧。    

我們開始使用Amazon Aurora獲取最新的數據,它的查詢和過濾功能比Dynamo更靈活。

我們使用Neo4J來儲存代表媒體網絡的實體之間的關系,用兩個副本來運行一個主本。

人、郵件、標簽和合類是圖表中的節點。

邊界在實體創建的基礎上建立。當人們發生以下行為,比如跟隨、推薦和強調時,我們按圖索驥,過濾并推薦相關的郵件。



數據平臺

早起我們的數據很匱乏,所以在數據分析基礎架構上做了很多投資,為商業和產品上的決策提供幫助。最近以來,我們可以在同樣的數據處理流程上為整個產品體系提供更多的反饋,甚至可以運行類似 Explore 這樣的數據驅動的功能。

我們使用 Amazon Redshift 作為數據倉庫,它提供了可伸縮的存儲和處理系統,我們其他的工具就運行在其上。我們持續的把核心數據(例如用戶、文章)從 Dynamo 導入到 Redshift,以及把行為日志 (例如:文章閱讀、翻頁等等) 從 S3 導入到 Redshift。

我 們使用 Conduit 來對任務做調度,這是一個內部工具,可以管理計劃、數據依賴,還可以進行監控。我們的任務調度模型是基于斷言的,只有一個的所有的依賴都滿足了,這個任務 才會被執行(例如,依賴全天行為日志的每日任務)。對于產品,這方面被證明是非常重要的:數據的生產者和消費者互相解耦,簡化配置,系統狀態可預知和易調 試。

盡管對我們來說在 Redshift 上運行 SQL 查詢良好,我們還是需要將數據不斷輸入輸出 Redshift。我們越來越轉向 ETL 的 Apache Spark,這是因為它的靈活性與規模增長的能力。隨著時間的推移,Spark 可能會成為我們數據管道的首選工具。

我們使用協議緩沖(Protocol Buffers)對我們的模式(模式演化規則)來保持所有層的分布式系統同步,包括移動應用,web 服務,和數據倉庫。使用自定義選項,我們標注模式與表名和索引等配置細節,驗證約束最大長度的字符串,或者控制接受數據控制的范圍。

人們也需要保持移動和 web 應用程序同步,開發人員使得所有日志一樣,產品研究員可以以同樣的方式解釋字段。我們幫助我們的成員在數據處理模式規范上的工作,并嚴格記錄字段的消息,發布文檔生成的原型(.proto)。


圖像

我們的圖片服務器現在是用 Go 寫的,并使用了瀑布策略處理圖像。服務器使用 groupcache,它提供了 memcache 的替代方案,來減少重復的工作。支持內存中的緩存是一個持久的 S3 緩存,然后來處理圖像處理需求。這讓我們的設計師可以在不同平臺上,靈活地改變圖像的表示和優化,而不必做大的批處理縮放圖像作業。

現在主要用于調整和裁剪,早期版本的網站允許顏色清洗、模糊和其他圖像效果。處理動態 gif 一直是一個巨大的頭痛的問題,這應該又是另一篇文章了。

文本截圖

完整的文本截圖功能由一個小型的 Go 服務器驅動,使用 PhantomJS 作為渲染引擎。

幫助 Medium 閱讀時間達到 2600 年的技術棧

我一直想把渲染引擎轉換為 Pango,但在實踐中,將圖片嵌入 HTML 的方式更為靈活和方便。這項功能的使用頻率意味著我們可以很簡單地處理吞吐量。

自定義域名

我 們允許用戶對他們的 Medium 作品設置自定義域名。我們想讓單點登錄和 HTTPS 無處不在,所以讓它開始工作不是件小事。我們有一組 HAProxy 服務器專門用于管理證書和導向主應用服務器的流量。在設置域名時還需要一些手動操作,但是我們通過自定義整合 Namecheap 已經自動化了很大一部分。證書的規定和公開鏈接是由一個專用服務處理的。

Web 前端

在網頁上,我們傾向于電子化。我們有自己的單頁應用程序框架,使用閉包作為標準庫。我們使用閉包模板在客戶端和服務器渲染,我們使用閉包編譯器壓縮代碼并把它分割成模塊。編輯器是我們網頁應用最復雜的部分,Nick 寫出了 iOS 系統。

iOS 系統

我們的應用程序都是本地下載好的,很少使用網絡視圖。

在 iOS 中,我們使用國產構架和內置構件的混合。在網絡層,我們使用 NSURLSession 提出請求,使用 Mantle 來把 JSON 解析成模型。 我們有一個建立在 NSKeyedArchiver 上的緩存層。我們有一個通用的方法把項目列入不同的列表,同一列表中的項目有共同特征。這就能讓我們快速建立不同類型內容的列表。后視圖是用 UICollectionView 自定義布局構建的。我們使用共享組件來渲染完整的文章和后預覽。

Medium 的員工盡一切努力來盡快開發和推出新的應用。我們發布更新的節奏是受限于 Appstore 的審閱周期的,但我們正盡己所能地推進這一進程,即使只有很少的更新。

測試我們使用 XCTest 和 OCMock.

Android

對于 Android,我們目前會保持 SDK 和 support 庫都是最新的。我們沒有使用綜合性的框架,而是傾向于為重復的問題建立一致性的模式。我們使用 guava 來彌補 Java 缺失的功能。不過有的情況下,我們會使用以特定問題為目標的第三方庫。我們的 API 的返回結果使用了 protocol buffer 協議,在 app 里會生成這些對象。

我們使用 mockito robolectric。 我們會為動態編寫高層級的測試用例 — 在我們剛開始添加界面或者準備重構的時候會創建一些簡單的版本。在我們不斷重現 bug 后,測試用例會越來越多,幫我們避免代碼功能回退。我們編寫底層的測試用例來驗證單個類的細節 — 在實現新功能后,通過這些用例能看到類之間是如何交互 的。

所有提交會自動推送到 play store,用于 alpha 版本,Medium 的員工可以立刻使用到。(這里包括另一個最受歡迎的 app,我們的內部的 Medium 版本— Hatch)。大多數周五,我們會把最后一個 alpha 版本發布到 beta 組, 讓大家在周末去使用。接下來在周一產品會由 beta 變成正式版本。因為最新代碼一直保持可發布狀態,所以一旦我們發現一個 bug,我們可以在正式產品上立刻修復。如果不放心某個新的功能,可以讓 beta 版測試的時間稍微長一些;如果感覺好的話,發布也可以更頻繁一些。

A|B 測試 & 功能標志

我們所有的客戶端都使用服務器提供的功能標志,它被叫做變體(variants),使用 A|B 測試來保護未完成的功能。

雜項

有很多在產品邊緣的其他東西,我在上面沒有提及:Algolia 提供我們迭代搜索相關的功能,SendGrid為出入站的郵件設計,Urban Airship 為的是通知功能,SQS 是為了處理隊列,Bloomd 是 bloom 過濾器,PubSubHubbub 和 Superfeedr 是為了 RSS,等等,等等。

編譯,測試,部署工作流

我們擁抱持續集成和交付,盡可能快地推動綠色(部署)。Jenkins 管理所有這些過程。

過去我們在使用中建立我們的系統,因此我們不會為一個新工程遷移而到 Pants

我們有一個組合單元測試HTTP 級功能測試。所有的提交必須要經過測試才可以合并。我們工作的團隊在盒子內使用 Cluster Runner 分配測試,并讓其更快。還能很好地與GitHub 集成。

我們盡可能快地部署過渡環境——目前大約 15 分鐘——然后給候選的產品使用。主要的 app 服務正常部署在一天五次左右,但是有時候可能多達 10 次。

我們做藍色/綠色部署。在生產環境中我們發送流量給 canary 實例,并在發布與部署之前發布過程監控錯誤率。Rollbacks 已經內置了 DNS 轉換。

下一步要做的

我們現在正在開始做用于作者和發布商的支付功能。這是一個全新領域的項目,我們現在基本上已經清楚如何去做了。我們認為將來需要更多的方式來產生內容,我們希望支付的手段能激勵產生更多高質量的內容和價值。

加入 Medium

我們長期對那些有消費領域經驗的任務驅動的工程師有很大興趣。我們對你了解哪門編程語言不很關心,因為我們認為好的工程師可以很快學會新的技能,但是我們期望你是求知欲高、敏銳的、堅定的和忘我投入的。也就是說,不管是iOS,Android,Node 還是 Go 的經驗,都可以來試試。

我們還在擴充我們的產品科學團隊,因此還需要構建數據流處理和大型分析系統方面有經驗的人。

另外,我還在找一些工程師 leader,可以幫我們在團隊擴充的時候管理好團隊。他們需要對組織管理很感興趣,樂于實踐,愿意為團隊奉獻。

你可以通過 Medium 聯系我們。

 本文由用戶 jopen 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!