在本文中,我將講述我是如何從使用python轉換到使用Erlang的。如果你不是一個使用python的開發者(很可能對基於python的網頁服務具有深入的瞭解),或者你不需要將一些事物大規模化,那麼你將不會覺得本文很實用。如果你不用開發一個基礎的框架,或者你只需要開發一些小的部落格、小的資源管理平臺、簡單的導航指南類網頁,那麼本文也將對你沒有什麼幫助。還有,如果你正想著選擇一門語言入門,那麼請不要讓我的觀點影響到你的決定。我所要講述的,是我在使用python過程遇到的問題,然後Erlang又是如何幫我解決這些問題的。
下面我將從一個簡短的發展史開始,然後以我自己的推論結尾。如果你不同意我的觀點,那麼十分歡迎一起來討論!這是我分享我的體驗的初衷。
我開始寫程式碼的時候,使用的是MEL(maya嵌入式語言)。然後我找到一份工作,收到我人生中第一份薪水。很快因為越來越多的緊急開發因素,我開始使用python並且閱讀了K&R,學習到了如何使用C語言來開發python語言的各種功能。隨著一年一年過去,我對網頁開發越來越感興趣。因為個人興趣,我從動畫行業辭職出來,然後被德黑蘭一家非常有名的科技公司僱傭。
最近我開發了Appido.IR,這是一個用python寫的視訊和音樂流服務。
下面我將開始講述我所遇到的問題。
每個人都喜歡Django。但是我討厭它!可能是因為我幫助馬西莫開發了Web2py,也可能是簡單的因為Web2py讓我無法選擇其他的框架。但是我最終還是在一個無聊的專案中嘗試過使用Django。
那麼,Django甚至是Web2py到底有什麼問題呢?沒有!直到你開始通過一些模組引擎和資料庫管理系統來使用Bottle和Falcon,然後你就會發現,這些企業級的框架執行起來很慢。對一個簡單的RESTful 風格的API 服務,你不得不因此浪費部分你cpu的效能。對於複雜的API服務,你必須越獄,然後將一個全新的架構合併到你所謂的全棧架構中。這裡舉個例子:
在Appido.ir 流技術中,我們通過FFMPEG和其他n多開源工具才把Dash協議加入其中。Appido有它自己的OAuth2 服務端,它有認證系統、工作流引擎、排程器、監視器、日誌系統、除錯系統、報表系統以及審計系統,只要你能想到的都有。當然,這是經過反覆除錯修改,痛苦煎熬使用Web2py和Django好幾個星期才完成的,顯然這個系統不可能在一個簡單的框架下工作。建立一些資料夾然後試著使用MVC模式或者是在大框架中擁有一個非常讚的資料庫管理介面也將無法讓你將整個系統組合起來。然後最終一些用不上的功能也會讓你無語。遲早的事。雖然確實全棧的python框架有一些優點,但是仍然存在一些問題。所以我開發了我自己的基於Falcon的框架。
因此你開始使用Bottle框架、Falcon框架或Flask框架…並且你會發現自己需要安裝任務佇列和排程模組(例如, Celery、RQ )。為什麼需要呢?因為,在web2.0中超過500ms的每一次請求都必須是有狀態的(stateful)!這是一個不成文的規定。你需要給使用者提供一些長時間等待請求的狀態。而不能讓客戶在計算過程中一直等待。你需要把傳送郵件、轉換圖片一類的沉重負擔放置在Celery框架中(或者自己定製開發的多程式佇列)。又有什麼問題呢?我們來看看:
假設你有一個流媒體服務。客戶要上傳20Gb的4K raw視訊檔案,你把檔案轉換成10個不同的解析度並給他(她)發郵件說結果已準備好。
你使用有40個工作者(workers)的celery框架。視訊開始轉換,伺服器變得超載,越來越慢。這時你會找到一個巧妙的解決辦法!安裝另一個帶有流媒體程式碼和工具的伺服器,使用Celery作為一個工作者。好了,問題得以解決!不!沒有那麼快!到了半夜,你會發現其中有5個或6個伺服器的CPU利用率為0,而第6個卻達到100%。為什麼呢?事實證明Redis和Celery一起使用時有時序的問題,目的是防止工作者挑揀工作。(有一個很顯然的解決方案:歡迎來到扇出模式和能見度超時的世界),安裝 RabbitMQ可以解決問題(還會產生另外一些新的奇怪的問題)。(如果你開發了這樣一個系統,它不存在任何所提及的問題,那麼恭喜你!你是世界上最幸運的傢伙!)。你找到了蜂蜜卻沒有被蜜蜂叮咬。
服務執行得很棒,所以現在需要做的是提高WEB服務的RPS(每秒請求數量),要麼通過增加WSGI的工人數量,要麼通過使用Tornado/Gevent,最後藉助於Varnish的快取,使用HAProxy或Nginx實現負載均衡。哈哈,簡直就是完美的解決方案!同時你會注意到使用SQLAlchemy會降低你的查詢速度(除非SQLAlchemy的極其複雜實現),而使用原始的SQL語句可以解決這個問題。讓我看如下使用Postgres資料庫的案例,這個案列有數萬行記錄:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
def pure_python(): max_per_task = db.DBSession.query( Version.task_id, func.max(Version.version_number).label( 'max'))\ .join(Task)\ .filter(Task.project_id == proj_id)\ .group_by(Version.task_id)\ .subquery() return Version.query\ .join(max_per_task, tuple_(max_per_task.c.task_id, max_per_task.c.max) == tuple_(Version.task_id, Version.version_number))\ .all() |
1 2 3 4 5 6 7 8 9 10 11 12 13 |
def simple_sql(): sql = """ select max("Versions".id) as id from "Versions" join "Tasks" on "Versions".task_id = "Tasks".id join "Projects" on "Tasks".project_id = "Projects".id where "Projects".id = %s group by "Versions".task_id, "Versions".take_name """ % proj_id conn = db.DBSession.connection() result = conn.execute(sql) return result.fetchall() |
結果
1 2 |
pure_python: 3.284 sec simple_sql: 0.228 sec |
這真人讓印象深刻!希望你能明白其中的道理!
真的是全部解決了嗎?好吧,你只是使用Python寫了一些Erlang特性的程式碼,因為Erlang並不會出現這些分散式/可擴充性問題。可擴充性問題經常是Python需要解決的。對於擴充套件性,你需要分散式,需要完善的架構用於面向服務的內部呼叫,你需要故障切換(failover)、容錯性(fault tolerant),我不得不接受所有這些都是Python亟待解決的,但這麼多技術的使用往往會付出代價。你需要規避Python的問題。你需要正確的原生SQL語句,需要建立自定義的索引(亦或物化檢視)。Python的全域性直譯器鎖使用起來並不複雜,對於快速啟動,共享狀態通常是有用的,但這樣有時候會以災難式的結果集退出。除此之外,對於實際生活中的每個計算,你都需要為Python寫擴充套件(原生C API/Swig/Cython亦或pypy)。
Erlang 沒有比 C 快,但是它的分散式模式能讓它易於編寫程式來利用資料中心裡的每一個核心。(如果你知道NIF, 那麼你就會覺得有價值)。在 Erlang 中連線資料庫也需要 SQL 知識。(這與Python一致)
如果你需要建立一個雲服務,或者為系統擴充套件上千的使用者,就需要使用 USE RIGHT TOOLS 。Python 是偉大的快速測試和模擬工具。它非常適合你取得合同!你使用 python 過一夜,就可以實現複雜的東西。它很好地適應了許多上百人使用者的大工程。但是當它遇到需要巨大的系統伸縮時,我希望我停止在商業上的自我煩惱,因為我正試圖用錯誤的工具去解決一個錯誤的問題。
簡而言之,使用 Python 來贏得合同,使用 erlang 來完成工作