當前位置:
首頁 > 知識 > Instagram在PyCon 2017 的演講摘要

Instagram在PyCon 2017 的演講摘要

本文經原作者授權發布。

原文:http://www.zlovezl.cn/articles/instagram-pycon-2017/

作者:piglei

圖:Instagram Loves Python

PyCon 簡介

PyCon 是全世界最大的以Python 編程語言為主題的技術大會。大會由 Python 社區組織,每年舉辦一次。在大會上,來自世界各地的 Python 用戶與核心開發者齊聚一堂,共同分享 Python 世界的新鮮事、Python 語言的應用案例、使用技巧等等內容。

Instagram 簡介

Instagram 是一款移動端的照片與視頻分享軟體,由 Kevin Systrom 和 Mike Krieger 在 2010 年創辦。Instagram 在發布後開始快速流行。於 2012 年被 Facebook 以 10 億美元的價格收購。而當時 Instagram 的員工僅有區區 13 名。

如今,Instagram 的總註冊用戶達到 30 億,月活用戶超過 7 億(作為對比,微信最新披露的月活躍用戶為 9.38 億)。而令人吃驚的是,這麼高的訪問量背後,竟完全是由以速度慢著稱的 Python Django 支撐。

在 Python 2017 上,Instagram 的工程師們帶來了一個有關 Python 在 Instagram 的主題演講,同時還分享了 Instagram 如何將整個項目運行環境升級到 Python 3 的故事。

本文為該次演講的內容摘要。

Python @Instagram為什麼選擇 Python 和 Django

Instagram 選擇 Django 的原因很簡單,Instagram 的兩位創始人(Kevin Systrom and Mike Krieger)都是產品經理出身。在他們想要創造 Instagram 時,Django 是他們所知道的最穩定和成熟的技術之一。

時至今日,即使已經擁有超過 30 億的註冊用戶。Instagram 仍然是 Python 和 Django 的重度使用者。Instagram 的工程師 Hui Ding 說到:『一直到用戶 ID 已經超過了 32bit int 的限額(約為 20 億),Django 本身仍然沒有成為我們的瓶頸所在。』

不過,除了使用 Django 的原生功能外,Instagram 還對 Django 做了很多定製化工作:

擴展 Django Models 使其支持 Sharding(一種資料庫分片技術),Instagram Engneering博客專門為這件事情寫過一篇博客,可參閱:Sharding & IDs at Instagram

手動關閉 GC(垃圾回收)來提升 Python 內存管理效率,他們同樣也寫過一篇博客來說明這件事情:Dismissing Python Garbage Collection at Instagram

在位於不同地理位置的多個數據中心部署整套系統

Python 語言的優勢所在

Instagram 的聯合創始人 Mike Krieger 說過:『我們的用戶根本不關心 Instagram 使用了哪種關係資料庫,他們當然也不關心 Instagram 是用什麼編程語言開發的。』

所以,Python 這種簡單而且實用至上的編程語言最終贏得了 Instagram 的青睞。他們認為,使用 Python 這種簡單的語言有助於塑造 Instagram 的工程師文化,那就是:

專註於定位問題、解決問題- 而不是工具本身的各種花花綠綠的特性

使用那些經過市場驗證過的成熟技術方案- 而不用被工具本身的問題所煩擾

用戶至上:專註於用戶所能看到的新特性,為用戶帶去價值

但是,即使使用 Python 語言有這麼多好處,它還是很慢,不是嗎?

不過,這對於 Instagram 不是問題,因為他們認為:『Instagram 的最大瓶頸在於開發效率,而不是代碼的執行效率』

At Instagram, our bottleneck is development velocity, not pure code execution.

所以,最終的結論是:你完全可以使用 Python 語言來實現一個超過幾十億用戶使用的產品,而根本不用擔心語言或框架本身的性能瓶頸。

如何提升運行效率

但是,即使是選用了擁有諸多好處的 Python 和 Django。在 Instagram 的用戶數迅速增長的過程中,性能問題還是出現了:伺服器數量的增長率已經慢慢的超過了用戶增長率。Instagram 是怎麼應對這個問題的呢?

他們使用了這些手段來緩解性能問題:

開發工具來幫助調優:Instagram 開發了很多涵蓋各個層面的工具,來幫助他們進行性能調優以及找到性能瓶頸。

使用 C/C 來重寫部分組件:把那些穩定而且對性能最敏感的組件,使用 C 或 C 來重寫,比如訪問 memcache 的 library。

使用Cython:Cython 也是他們用來提升 Python 效率的法寶之一。

除了上面這些手段,他們還在探索非同步 IO 以及新的 Python Runtime 所能帶來的性能可能性。

升級到 Python 3

在相當長的一段時間,Instagram 都跑在 Python 2.7 Django 1.3 的組合之上。在這個已經落後社區很多年的環境上,他們的工程師們還打了非常非常多的小 patch。難道他們要被永遠卡在這個版本上嗎?

所以,在經過一系列的討論後,他們最終做出一個重大的決定:升級到 Python 3!!

事實上,Instagram 目前已經完成了將運行環境遷移到 Python 3 的工作 - 他們的整套服務已經在 Python 3 上跑了好幾個月了。那麼他們是怎麼做到的呢?接下來便是由 Instagram 工程師 Lisa guo 帶來的 Instagram 如何遷移到 Python 3 的故事。

Instagram 升級到 Python 3 的故事為什麼要升級到 Python 3

對於 Instagram 來說,下面這些因素是推動他們將運行環境遷移到 Python 3 的主要原因:

1. 新特性:類型註解 Type Annotations

看看下面這段代碼:

圖中函數的 參數究竟是什麼類型呢?int?tuple?或是 list? 等等,函數文檔裡面說它是 str 類型。

但隨著時間推移,萬一這個參數的類型發生變化了呢?如果某位粗心的工程師修改代碼的同時忘了更新文檔,那就會給函數的使用者帶來很大麻煩,最終還不如沒有注釋呢。

2. 性能

Instagram 的整個 Django Stack 都跑在 uwsgi 之上,全部使用了同步的網路 IO。這意味著同一個 uwsgi 進程在同一時間只能接收並處理一個請求。這讓如何調優每台機器上應該運行的 uwsgi 進程數成了一個麻煩事:

為了更好利用 CPU,使用更多的進程數?但那樣會消耗大量的內存。而過少的進程數量又會導致 CPU 不能被充分利用。

為此,他們決定跳過 Python 2 中哪些蹩腳的非同步 IO 實現(可憐的 gevent、tornado、twisted 眾),直接升級到 Python 3,去探索標準庫中的 asyncio 模塊所能帶來的可能性。

3. 社區

因為 Python 社區已經停止了對 Python 2 的支持。如果把整個運行環境升級到 Python 3,Instagram 的工程師們就能和 Python 社區走的更近,可以更好的把他們的工作回饋給社區。

確定遷移方案

在 Instagram,進行 Python 3 的遷移需要必須滿足兩個前提條件:

不停機,不能有任何的服務因此不可用

不能影響產品新特性的開發

但是,在 Instagram 的開發環境中,要滿足上面這兩點來完成遷移到 Python 3.6 這種龐大的工程是非常困難的。

基於主分支的開發流程

即便使用了以多分支功能著稱的 git,Instagram 所有的開發工作都是主要在 master 分支上進行的,Instagram 所奉行的開發哲學是:『不管是多大的新特性或代碼重構,都應該拆解成較小的 Commit 來進行。』

那些被合并進 master 分支的代碼,都將在一個小時內被發布到線上環境。而這樣的發布過程每天將會發生上百次。在這麼頻繁的發布頻率下,如何在滿足之前的那兩個前提下來完成遷移變得尤其困難。

被棄用的遷移方案

創建一個新分支

很多人在處理這類問題時,第一個蹦進腦子的想法就是:『讓我們創建一個分支,當我們開發完後,再把分支合并進來』

但在 Instagram 這麼高的迭代頻率上,使用一個獨立分支並不是好主意:

Instagram 的 Codebase 每天都在頻繁更新,在開發 Python 3 分支的過程中,讓新分支與現有 master 分支保持同步開銷極大,同時極易出錯

最終將 Python 3 分支這個改動非常多的分支合并回 Master 擁有非常高的風險

只有少數幾個工程師在 Python 3 分支上專職負責升級工作,其他想幫助遷移工作的工程師無法參與進來

挨個替換介面

還有一個方案就是,挨個替換 Instagram 的 API 介面。但是 Instagram 的不同介面共享著很多通用模塊。這個方案要實施起來也非常困難。

微服務

還有一個方案就是將 Instagram 改造成微服務架構。通過將那些通用模塊重寫成 Python 3 版本的微服務來一步步完成遷移工作。

但是這個方案需要重新組織海量的代碼。同時,當發生在進程內的函數調用變成 RPC 後 ,整個站點的延遲會變大。此外,更多的微服務也會引入更高的部署複雜度。

所以,既然 Instagram 的開發哲學是:小步前進,快速迭代。他們最終決定的方案是:一步一步來,最終讓 master 分支上的代碼同時兼容 Python 2 和 Python 3 。

開始遷移工作

既然要讓整個 codebase 同時兼容 Python 2 和 Python 3,那麼首先要符合這點的就是那些被大量使用的第三方 package。針對第三方 package,Instagram 做到了下面幾點:

拒絕引入所有不兼容 Python 3 的新 package

去掉所有不再使用的 package

替換那些不兼容 Python 3 的 package

在代碼的遷移過程中,他們使用了工具modernize來幫助他們。

使用 modernize 時,有一個小技巧:每次修復多個文件的一個兼容問題,而不是一下修復一個文件中的多個兼容問題。這樣可以讓 Code Review 過程簡單很多,因為 Reviewer 每次只需要關注一個問題。

使用單元測試來幫助遷移

對於 Python 這種靈活性極強的動態語言來說,除了真正去執行代碼外,幾乎沒有其他比較好的檢查代碼錯誤的手段。

前面提到,Instagram 所有被合并到 master 的代碼提交會在一個小時內上線到線上環境,但這不是沒有前提條件的。在上線前,所有的提交都需要通過成千上萬個單元測試。

於是,他們開始加入 Python 3 來執行所有的單元測試。一開始,只有極少數的單元測試能夠在 Python 3 環境下通過,但隨著 Instagram 的工程師們不斷的修復那些失敗的單元測試,最終所有的單元測試都可以在 Python 3 環境下成功執行。

單元測試的局限性

但是,單元測試也是有局限性的:

Instagram 的單元測試沒有做到 100% 的代碼覆蓋率

很多第三方模塊都使用了 mock 技術,而 mock 的行為與真實的線上服務可能會有所不同

所以,當所有的單元測試都被修復後,他們開始在線上正式使用 Python 3 來運行服務。

這個過程並不是一蹴而就的。首先,所有的 Instagram 工程師開始訪問到這些使用 Python 3 來執行的新服務,然後是 Facebook 的所有僱員,隨後是 0.1%、20% 的用戶,最終 Python 3 覆蓋到了所有的 Instagram 用戶。

圖:循序漸進的發布流程

遷移過程的技術問題

Instagram 在遷移到 Python 3 時碰到很多問題,下面是最典型的幾個:

Unicode 相關的字元串問題

Python 3 相比 Python 2 最大的改動之一,就是在語言內部對 unicode 的處理。

在 Python 2 中,文本類型(也就是 unicode)和二進位類型(也就是 str)的邊界非常模糊。很多函數的參數既可以是文本,也可以是二進位。但是在 Python 3 中,文本類型和二進位類型的字元串被完全的區分開了。

於是,下面這段在 Python 2 下可以正常運行的代碼在 Python 3 下就會報錯:

解決辦法其實很簡單,只要加上判斷:如果 value 是文本類型,就將其轉換為二進位。如下所示:

但是,在整個代碼庫中,像上面這樣的情況非常多。作為開發人員,如果需要在調用每個函數時都要想想:這裡到底是應該編碼成二進位,或者是解碼成文本呢?將會是非常大的負擔。

於是 Instagram 封裝了一些名為 、 、 的幫助函數,開發人員只需對那些不確定類型的字元串,使用這些幫助函數先做一次轉換就好。

不同 Python 版本的 pickle 差異

Instagram 的代碼中大量使用了 pickle。比如用它序列化某個對象,然後將其存儲在 memcache 中。如下面的代碼所示:

問題在於,Python 2 與 Python 3 的 pickle 模塊是有差別的。

如果上文的第一行代碼,剛好是由 Python 3 運行的服務進行序列化後存入 memcache。而反序列化的過程卻是由 Python 2 進行,那代碼運行時就會出現下面的錯誤:

這是由於在 Python 3 中, 的值為 ,而 Python 2 中的的 pickle 最高支持的版本號卻是 。那麼如何解決這個問題呢?

Instagram 最終選擇讓 Python 2 和 Python 3 使用完全不同的 namespace 來訪問 memcache。通過將二者的數據讀寫完全隔開來解決這個問題。

迭代器

在 Python 3 中,很多內置函數被修改成了只返成迭代器 Iterator:

迭代器有諸多好處,最大的好處就是,使用迭代器不需要一次性分配大量內存,所以它的內存效率比較高。

但是迭代器有一個天然的特點,當你對某個迭代器做了一次迭代,訪問完它的內容後,就沒法再次訪問那些內容了。迭代器中的所有內容都只能被訪問一次。

在 Instagram 的 Python 3 遷移過程中,就因為迭代器的這個特性被坑了一次,看看下面這段代碼:

這段代碼的用處是挨個編譯 Cython 源文件。當他們把運行環境切換到 Python 3 後,一個奇怪的問題出現了:CYTHON_SOURCES 中的第一個文件永遠都被跳過了編譯。為什麼呢?

這都是迭代器的鍋。在 Python 3 中, 函數不再返回整個 list,而是返回一個迭代器。

於是,當第二行代碼生成 builds 這個迭代器後,第三行代碼的 while 循環迭代了 builds,剛好取出了第一個元素。于是之後的 pending 對象便裡面永遠少了那第一個元素。

這個問題解決起來也挺簡單的,你只要手動的吧 builds 轉換成 list 就可以了:

但是這類 bug 非常難定位到。如果用戶的 feeds 裡面永遠少了那最新的第一條,用戶很少會注意到。

字典的順序

看看下面這段代碼:

它會輸出什麼結果呢?

在不同的 Python 版本下,這個 json dumps 的結果是完全不一樣的。甚至在 3.5.1 中,它會完全隨機的返回兩個不同的結果。Instagram 有一段判斷配置文件是否發生變動的模塊,就是因為這個原因出了問題。

這個問題的解決辦法是,在調用 傳入 參數:

遷移到 Python 3.6 後的性能提升

當 Instagram 解決了這些奇奇怪怪的版本差異問題後,還有一個巨大的謎題困擾著他們:性能問題

在 Instagram,他們使用兩個主要指標來衡量他們的服務性能:

每次請求產生的 CPU 指令數(越低越好)

每秒能夠處理的請求數(越高越好)

所以,當所有的遷移工作完成後,他們非常驚喜的發現:第一個性能指標,每次請求產生的 CPU 指令數居然足足下降了 12% !!!

但是,按理說第二個指標 - 每秒請求數也應該獲得接近 12% 的提升。不過最後的變化卻是 0%。究竟是出了什麼問題呢?

他們最終定位到,是由於不同 Python 版本下的內存優化配置不同,導致 CPU 指令數下降帶來的性能提升被抵消了。那為什麼不同 Python 版本下的內存優化配置會不一樣呢?

這是他們用來檢查 uwsgi 配置的代碼:

注意到那段 了嗎?在 Python 3 中,這個條件判斷總是不會被滿足。問題就在於 unicode。在將代碼中的 換成 (也就是將文本類型換成二進位,這種判斷在 Python 2 中完全不區分的)後,問題解決了。

所以,最終因為加上了一個小小的字母 ,程序的整體性能提升了 12%。

結論

在今年二月份,Instagram 的後端代碼的運行環境完全切換到了 Python 3 下:

?

圖:Instagram 版本遷移時間線

當所有的代碼都都遷移到 Python 3 運行環境後:

節約了 12% 的整體 CPU 使用率(Django/uwsgi)

節約了 30% 的內存使用(celery)

同時,在整個遷移期間,Instagram 的月活用戶經歷了從 4 億到 6 億 的巨大增長。產品也發布了評論過濾、直播等非常多新功能。

那麼,那幾個最開始驅動他們遷移到 Python 3 的目的呢?

類型註解:Instagram 的整個 codebase 里已經有 2% 的代碼添加上了類型註解,同時他們還開發了一些工具來輔助開發者添加類型提示

asyncio:他們在單個介面中利用 asynio 平行的去做多件事情,最終降低了 20-30% 的請求延遲。

社區:他們與 Intel 的工程師聯合,幫助他們更好的對 CPU 利用率進行調優。同時還開發了很多新的工具,幫助他們進行性能調優

Instagram 帶給我們的啟示

Instagram 的演講視頻時間不長,但是內容很豐富,在編寫此文前,我完全沒有想到最終的文章會這麼長。

那麼,Instagram 的視頻可以給我們哪些啟示呢?

Python Django 的組合完全可以負載用戶數以 10 億記的服務,如果你正準備開始一個項目,放心使用 Python 吧!

完善的單元測試對於複雜項目是非常有必要的。如果沒有那『成千上萬的單元測試』。很難想像 Instagram 的遷移項目可以成功進行下去。

開發者和同事也是你的產品用戶,利用好他們。用他們為你的新特性發布前多一道測試。

完全基於主分支的開發流程,可以給你更快的迭代速度。前提是擁有完善的單元測試和持續部署流程。

Python 3 是大勢所趨,如果你正準備開始一個新項目,無需遲疑,擁抱 Python 3 吧!

好了,就到這兒吧。Happy Hacking!

題圖:pexels,CC0 授權。

點擊展開全文

喜歡這篇文章嗎?立刻分享出去讓更多人知道吧!

本站內容充實豐富,博大精深,小編精選每日熱門資訊,隨時更新,點擊「搶先收到最新資訊」瀏覽吧!


請您繼續閱讀更多來自 編程派 的精彩文章:

今晚8點半直播,一個小時入門負載均衡
Flask 源碼解析:session
Flask 源碼解析:響應
如何從開發者成長為雲端架構師

TAG:編程派 |

您可能感興趣

GDC 2018 State of Unreal 演講精彩內容回顧
「2018 Unreal Open Day」Epic Games演講主題曝光
Datatrak International首席執行官將在2019 DIA中國年會發表主題演講
芒格2019年Daily Journal演講
Ian GoodFellow ICRL 2019 演講PPT
Morketing Brand Summit 2019品牌高峰會首批演講嘉賓陣容公布
30秒電梯演講。Sally』s Quote of the Day
2018 UOD舉行Epic Games創始人Tim Sweeney進行主題演講
「2018 Unreal Open Day 虛幻引擎技術開放日」Epic Games演講主題曝光
Oculus Connect 5上Oculus CTO卡馬克發表重要演講
嘉賓風采|M.D.Anderson腫瘤中心Zhimin(James)Lu教授應邀參加 ICC &CMT並做主題演講
Supercell 2018GDC演講:手游設計就像是叢林探險
何為 Public Speaking 公眾演講?
支撐20億人的機器學習:Jeff Dean、賈揚清等ScaledML大會演講
「全球CEO峰會」重磅演講者:Jean-Marc Chery
學習世界模型,通向AI的下一步:Yann LeCun在IJCAI 2018上的演講
RSA Conference 2018亞太及日本大會公布主題演講嘉賓陣容
谷歌CVPR最全總結:45篇論文,Ian Goodfellow GAN演講PPT下載
Meta光學專家發表TED演講,公開diss Magic Leap
「全球CEO峰會」重磅演講者:Tyson Tuttle