任務與消息隊列
來自專欄做後端開發,除了寫代碼,你還需要知道這些
非同步任務
任務(Task)是Web後端開發中一個常見的場景。
舉個例子,有一個用戶註冊扇貝單詞的賬號時,發送了一個請求,帶有用戶名/密碼/郵箱/手機號等信息。在這個請求到達服務端時,我們要做的事情可能有:
- 在User表裡創建一條新記錄
- 給該用戶發送驗證郵件
- 給該用戶發送註冊簡訊
- 給該用戶初始化各種學習數據
- 返回註冊成功的響應
步驟很多,如果都在請求的生命周期內去做的話,會導致響應時間很高,進而減弱服務端處理請求的並發量。
其實我們分析一下會發現,這個請求的響應內容只是事件1的結果:創建成功或者創建失敗。而標號2/3/4的事件非常緩慢,而且和響應結果並不相關。這幾件事可以不用阻塞在請求的生命周期里,而是非同步的去做。只要我們知道,最終這些事情一定會被執行就可以了。
這樣一來,步驟就變成了:創建User記錄;啟動一個Task非同步地去做2/3/4;不用管Task的處理,直接返回註冊成功響應。大大的縮短了響應時間。
總結一下,就是說在一個請求到達服務端時,Web程序需要處理一些事情,然後把響應返回給客戶端。但有時因為這些事情太重,而且我們並不在意這些事情的結果(只在意它們在有限時間內一定會被執行),那我們可以非同步地執行他們,這些事情被叫做一種任務(Task)。
那這種Task應該怎麼樣實現呢?
有同學會想到:我們可以把這些很重的事情包裝成一個函數,在請求來了之後另起一個線程或進程去執行這個函數。可是,這樣做只是看起來解決了響應時間的問題。想像一下,如果某天扇貝做了一些推廣活動,註冊人數突然暴增,瞬間機器上就要新建上千個進程(線程)做這些耗時的重操作。而且可能在大量Task還未結束時,註冊的請求還在源源不斷的過來,這時我們的Web伺服器可能已經掛掉了。
在實際生產環境中,我們會使用消息隊列中間件來控制Task的執行。
消息隊列
分析一下上面的場景和實現方式導致我們會掛掉的原因,本質上還是:在大量請求(幾乎)同時過來的時候,我們去瞬時地啟動Task,想要把他們(幾乎)同時的完成。這就不得不依賴機器本身的性能。
而分析上面Task的需求:給用戶發註冊郵件和簡訊,然後給他初始化學習數據。這些事情根本沒有瞬時完成的必要,即使要花費幾十秒甚至兩三分鐘也是完全可以接受的。那麼,我們設置一個Task的最大並行數,然後讓這些Task排隊去執行,問題就得到解決了。
消息隊列就提供了這樣的機制。
這個模型可以被簡化為上圖。這時生產者是接收HTTP請求的web server程序,它接收到一個註冊的請求時,發送一個消息到消息隊列;消費者是專門用來執行Task的程序(往往是單獨用來執行Task的伺服器),他從消息隊列取得消息,然後根據消息內容啟動進程執行Task。以此為例,講一下消息隊列的幾種常見職能。
錯峰控流
上面讓Task可以排隊的方案,就是消息隊列的一個重要職能:錯峰控流。可能我們的web server能接收瞬時上萬的請求,但是處理Task的速度是很難提升的,比如資料庫和網路I/O等耗時的處理,又比如假設上例中簡訊系統限制了每秒只能發幾百條,那處理完幾百條請求我們就必須等待了。所以,利用消息隊列這種中間的通信系統,等到下游有能力處理消息的時候再去處理,是一種合理的解決方案。
解耦
消息隊列解決的本質問題,其實是邏輯的解耦:web server只用做完自己最核心的業務,然後發出去一條消息通知別人。以上面的場景為例,處理請求的伺服器只需要往User表裡寫入一條新記錄,然後帶著這個記錄的信息(user_id/郵箱/手機號/註冊時間等)發出一條消息到消息隊列,直接響應就結束了。不用關心後面不影響主流程的動作,也不會出現因簡訊通知失敗而導致整個註冊介面失敗這種可怕的後果。
廣播
假設扇貝的系統架構是這樣的(實際上並不是這樣):處理註冊請求的web server是一個web項目,而郵件系統和簡訊系統都是單獨的rpc項目,另外存放學習數據的又是一個單獨的web項目。
如果沒有廣播的功能,寫註冊介面的時候,我們需要另外再寫一個函數,這個函數里去分別調用郵件系統/簡訊系統/用戶學習數據系統提供的介面。最後我們運行一個帶著這個函數的消費者程序,它在收消息後運行這個函數。
有了廣播的功能後,這四個系統才真正做到解耦:註冊系統只管發送消息到消息隊列,這條消息是被廣播的,另外三個系統都可以接收到消息,而各自去決定收到這種消息時應該做什麼。此時,另三個系統都是消費者。
此時,註冊系統不在意其他系統需要註冊成功的消息做什麼。這個時候扇貝閱讀這個業務突然決定:在新用戶註冊成功後就給他送幾本書(實際上並不是這樣),那它只需要從消息隊列里接收被廣播的註冊成功消息,然後自己去做。而註冊系統不會有任何改動。
消息隊列有很多個,他們的異同可以參考:
Kafka,Mq,Redis作為消息隊列使用時的差異?Python Web應用中常用的一個消息隊列是RabbitMQ,通常搭配celery來做非同步任務。除了非同步任務之外,celery還能實現定時任務(某一時刻或者時間周期)和倒計時任務。使用方式直接參考celery的文檔即可。
推薦閱讀:
※計算化學,有python基礎還有必要學習matlab么?
※數據規整化:清理、轉換、合併、重塑
※PY交易(二)使用Pygame寫一個小遊戲——Pie
※flask中操作SQLAlchemy資料庫出現問題?
※Python Shell 中敲擊方向鍵顯示「^[[C^[[D」,原因是什麼?如何修復?