python异步_Python中的异步编程
Python部落(python.freelycode.com)組織翻譯,禁止轉載,歡迎轉發。
Quora的使命就是分享和增加全世界的知識,并且為了達到這個使命,我們不斷地推出改進來讓Quora對于我們的讀者和作者來說更快。在上一篇,縮短繪制時間,我們討論了對于客戶端性能的最新優化,本文,我們將通過異步編程框架來討論服務器端性能的新發展。
為了同時優化頁面加載和用戶操作的性能,我們積極使用緩存,以確保持續快速訪問常用數據。在Quora中,對于存儲在較慢的存儲系統如MySQL中的數據,我們使用memcached作為主緩存層。例如,當我們呈現那些給答案投票者的名單時,需要從存儲層取回用戶ID到用戶名稱的映射。每個請求直接訪問MySQL的話,數據庫將很快超載,所以使用memcached存儲這個映射來替代。盡管為每一個用戶ID分配一個單獨的memcached請求會比查詢MySQL更快,但是通過一個單一的批量的多請求取回所有數據會更快。
因為網絡通常是memcached請求最昂貴的部分(在我們的環境中占總時間的80%以上),適當的批量緩存請求對于保持Quora快速是很重要的。然而,如果開發者必須手動指定所有數據如何從memcached批量檢索,它將是單調易錯的。因此,我們已經開發了一個抽象概念叫Asynq,它使開發者很容易編寫批量的緩存請求,如今它已經開源。
Priming
在我們開發Asynq之前,使用一個叫做priming的方法來給memcached發送批量請求。每次開發者編寫訪問數據的函數時,也要編寫一個單獨的priming函數來指定該函數可以訪問的所有數據。例如,假設有一個檢索給定用戶ID列表并返回用戶標識列表的函數,如下:
相應的priming函數會看起來如下:
調用get_names的代碼將負責先調用prime_get_names,它將使用多請求從memecached獲取所有必要的數據。然后,該數據將被存儲在服務器的本地緩存中,所以當真正的函數(本例是get_names)運行時,它不會調用任何網絡請求!本質上,每個prime函數表示的都是一個函數的依賴項,或者說它依賴的memecached鍵。
當我們需要調用一個緩存函數來決定從memcached中獲得什么額外數據時,priming會變得更復雜??紤]如下模板函數:
在本例中,prime_get_upvoter_names 需要知道upvoter_uids,為了那些uids調用prime_name_of_user ,但由于upvoters_of_answer 被存儲了,它也必須被啟動。因此,相應的prime函數將明顯更加復雜:
通過恰當地啟用我們所有的模型調用, 我們看到驚人的速度改進,因此,我們使用靜態分析工具來實施規則,即Quora上所有需要呈現的數據首先要被primed。然而,這些高速增長帶來了明顯的開發費用,因為實質上開發者需要編寫(并調用)所有的模版函數兩次。隨著我們代碼庫的增長,priming變得冗余、難理解且易出錯。
Asynq
為了解決priming帶來的復雜性,我們創建一個叫做Asynq的框架,它在底層采用類似的方法,但是改進了API,把緩存請求集成到模板代碼本身。Asynq中,所有需要數據訪問的函數通過調度程序運行,調度程序記錄跟蹤它們的依賴項。當一個函數需要通過調用其它函數來獲取數據時,它不再控制調度程序,而是指示需要取回的數據。然后調度程序停止執行該函數直到它解決了這個函數所有的依賴項。
Asynq中,get_names 函數早期看起來如下:
不需要額外的priming 函數——所有代碼都包含在模版函數本身。因此,開發者不僅不再需要編寫一個完全獨立的priming函數,他們也不需要記憶每次模版函數被調用時調用priming函數。之前更復雜的get_upvoter_names在Asynq中也更簡單了:
可以把Async 函數理解成創建一個依賴關系圖:在它的第一個yield中,get_upvoter_names依賴于upvoters_of_answer的完成。類似地,upvoters_of_answer可能有它自己的依賴項。Asynq調度程序分解該依賴關系圖執行async函數,直到所有目前執行的函數從memcached中獲取數據時阻塞。然后調度程序使用一個單獨的多請求從memcached取回數據,并繼續執行直到async函數完成。
假定我們有一個異步函數稱為model_call(),它有三個依賴項,每個依賴項都會讀取多個memcached鍵值。直接實現將會使用3個多請求(或者6個單一獲取),每個依賴項函數一個,而異步調度程序分解的依賴圖看起來如下所示:
我們異步編程的方法不同于Python中其它的異步庫如asyncio、Twisted、gevent和Tornado。這些庫側重于異步I/O,而Asynq卻側重于高效的批處理。例如,一個典型緩存使用memcached和asyncio的實現將分別解決緩存依賴項,因此每個memcached請求都會對memcached產生一個單獨的請求。在Asynq中,依賴項將會成批進入一個單獨的memcached多請求,這可以減少I/O阻塞的總時間。另外,Asynq允許函數被同步或異步調用(通過增加的.async屬性),而asyncio需要所有的async def函數被asyncio.get_event_loop()顯式調度。使用Asynq的批處理,我們花費很少時間阻塞在I/O,因此采用其它異步I/O庫對于我們來說不是優先選擇。
與priming相比,Asynq提供一個更通用的、簡明的、有原則的方式來支持批處理。因為邏輯僅需要被實現一次,Asynq明顯比priming花費更少的開發費用。 減少priming的重復邏輯也提升了性能,正如我們所見,服務器端的速度取勝,由于我們遷移更多代碼庫從priming到Asynq。
遷移和學習
開發出第一個版本Asynq后,通過遷移代碼庫的幾個小部分從priming到Asynq,我們著手驗證我們的設計和實現。在這樣做的過程中,我們發現并修復了各種問題:Python2.7中,生成器不能返回值,所以以上代碼片段實際上在Python2.7中是無效的。在Asynq的第一個版本中,異步函數產生的最后一個值將會被解析為函數的返回值。然而,這意味著yield關鍵字意義的超載,這使得代碼很難閱讀。作為一個替代解決方案,我們從生成器中返回值——PEP 380中有介紹——從Python 3到Python 2.7,并且在代碼庫中必要的地方,我們目前正在使用Python 2.7的一個補丁版本。(Asynq也支持Python 2.7的未打補丁版本,通過使用一個result函數,該函數拋出一個異常,該異常被解析為返回值。)
最初,使用@async()裝飾器使一個函數變為異步函數完全改變了它的接口——所有的調用者不得不使用一個特殊語法來調用異步函數。這個決策使得priming和Asynq在我們的代碼庫中更難共存,由于所有的開發者需要意識到這種差異。為了修復這個問題,我們更新@async()裝飾器,給所有的異步函數增加了一個新的.async屬性,這樣直接調用一個裝飾器函數將仍舊返回結果。
起初測試異步函數具有挑戰性。在單元測試中,我們廣泛使用Python的mock模塊,但是很難模擬異步函數,因為這樣做需要特殊處理.async的屬性并返回值。作為這個問題的解決方案,我們創建一個專用的模擬函數,asynq.mock.patch,它自動負責模擬,這使得模擬異步函數更輕松。
實現上述改進后(和其它很多改進),我們決定將我們的代碼庫完全從priming遷移到Asynq。讓這兩種抽象概念同時存在于我們的代碼庫中不利于開發速度,因為工程師需要在兩種API中根據他們正在編輯哪種模型進行上下文切換。我們利用現有的靜態分析工具自動進行遷移,因此工程師只需要去核實腳本的輸出并做一點細小的改變,而不是手動遷移代碼。
在開始大規模遷移整個代碼庫之前,不同團隊的工程師完成一些較小的“演習”,作為細化我們自動遷移工具的手段,并建立精確的范圍估計。完成幾個演練后,我們由大約30個工程師(即我們工程師團隊的50%)進行了一次有協調性的遷移,在此期間僅用了4天時間,我們遷移了Quora代碼庫的15,000多行priming代碼。
如今,我們的整個代碼庫僅使用Asynq,并且服務器端開發速度更快且更不易出錯。
開源Asynq(和朋友們)
現在可以在GitHub和PyPI上獲得Asynq。你可以閱讀源碼或者通過pip install asynq安裝Asynq。和Asynq一起,我們也將QCore(Asynq的唯一依賴項)開源,它是一個助手集,用于整個Quora代碼庫,包括一個裝飾器框架,一個枚舉實現,測試助手,和一個事件實現。Asynq和QCore同時兼容Python2.7和Python3,關于Asynq的更詳細文檔可以在GitHub倉庫查看。
未來,我們將繼續致力于使Quora對我們的用戶來說更快,使新特性對于我們的工程師來說更容易進行開發。我們目前正在招聘Platform工程師來開發像Asynq的核心框架和抽象概念,所以看看我們的職業頁面,如果你想和我們一起分享和增加全世界的知識!英文原文:https://engineering.quora.com/Asynchronous-Programming-in-Python?srid=hST?utm_source=mybridge&utm_medium=email&utm_campaign=read_more
譯者:蒲公英
總結
以上是生活随笔為你收集整理的python异步_Python中的异步编程的全部內容,希望文章能夠幫你解決所遇到的問題。
                            
                        - 上一篇: python的for语句写新的字符串_p
 - 下一篇: 又一时速350高铁即将开通 四座新建高铁