淺談 Gunicorn 各個 worker type 適合的情境

Genchi Lu
Genchi Lu
Jan 29, 2018 · 10 min read

Gunicorn 在 Python 2.7 有幾種 worker type,分別是 sync、gthread、eventlet、gevent 和 tornado。

根據底層運作的原理可以將 worker 分成三種類型:

  1. sync 底層實作是每個請求都由一個 process 處理。
  2. gthread 則是每個請求都由一個 thread 處理。
  3. eventlet、gevent、tarnado 底層則是利用非同步 IO 讓一個 process 在等待 IO 回應時繼續處理下個請求。

在接下來的文章我會以下面的 sample code 做範例,簡單描述各種類型的 worker 在 CPU bound 和 IO bound 的程式在效能上的表現。

用 process 處理請求

當 gunicorn worker type 使用 sync 時,web 啟動時會預先開好對應數量的 process 處理請求,理論上 concurrency 的上限等同於 worker 數量。如下gunicorn 啟動時開了一個 pid 為 569 的 process 來處理請求,理論上每次只能處理一個請求:

$> gunicorn -w 1 -k sync HelloWorld.wsgi:application -b 192.168.55.100:80

用 siege 分別對 IO bound task 和 CPU bound task 發出 2 個請求可以明顯看到第二個 request 被第一個 request 阻塞,如下:

$> siege -c 2 -r 1 http://192.168.55.100/ioTask -v

這種類型的好處是錯誤隔離高,一個 process 掛掉只會影響該 process 當下服務的請求,而不會影響其他請求。

壞處則為 process 資源開銷較大,開太多 worker 時對記憶體或 CPU 的影響很大,因此 concurrency 理論上限極低。

用 thread 處理請求

當 gunicorn worker type 用 gthread 時,可額外加參數 --thread 指定每個 process 能開的 thread 數量,此時 concurrency 的上限為 worker 數量乘以給個 worker 能開的 thread 數量。如下 gunicorn 啟動時開了一個 pid 為 595 的 process 來處理請求, thread 數量為 2,理論上每次只能處理二個請求:

$> gunicorn -w 1 -k gthread --thread=2 HelloWorld.wsgi:application -b 192.168.55.100:80

用 siege 分別對 IO bound task 和 CPU bound task 發出 4 個請求可以明顯看到第三個請求以後才會被阻塞:

$> siege -c 4 -r 1 http://192.168.55.100/ioTask -v

這種類型的 worker 好處是 concurrency 理論上限會比 process 高,壞處依然是 thread 數量,OS 中 thread 數量是有限的,過多的 thread 依然會造成系統負擔。

用非同步 IO 處理每個請求

當 gunicorn worker type 用 eventlet、gevent、tarnado 等類型時,每個請求都由同一個 process 處理,而當遇到 IO 時該 process 不會等 IO 回應,會繼續處理下個請求直到該 IO 完成,理論上 concurrency 無上限。以 gevent 為例,gunicorn 啟動時開了一個 pid 為 733 的 process 來處理請求:

$> gunicorn -w 1 -k gevent HelloWorld.wsgi:application -b 192.168.55.100:80

用 siege 對 IO bound task 發出 10 個請求可以明顯看到沒有任何請求被阻塞:

$> siege -c 10 -r 1 http://192.168.55.100/ioTask -v

但當面臨 CPU bound 請求時,則會退化成用 process 處理請求一樣,concurrency 上限為 worker 數量。如下用 siege 對 CPU bound task 發出 10 個請求,可以看到第二個請求以後就被阻塞:

$> siege -c 10 -r 1 http://192.168.55.100/cpuTask -v

因此使用非同步類型的 worker 好處和壞處非常明顯,對 IO bound task 的高效能,但在 CPU bound task 會不如 thread。

結論

當談到效能時,必須考慮到使用情境。 gunicorn + 非同步 IO 效能就一定比較好的說法並不一定成立。

從上面的數據三種類型的 worker 都有其相對適合的場景:

當需要穩定的系統時, 用 process 處理請求可以保證一個請求的異常導致程式 crash 不會影響到其他請求。

當 web 服務內大部分都是 cpu 運算時,用 thread 可以提供不錯的效能。

當 web 服務內大部分都是 io 時,用非同步 io 可以達到極高的 concurrency 數量。

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch

Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore

Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store