20

I am working on a web service implemented on top of nginx+gunicorn+django. The clients are smartphone applications. The application needs to make some long running calls to external APIs (Facebook, Amazon S3...), so the server simply queues the job to a job server (using Celery over Redis).

Whenever possible, once the server has queued the job, it returns right away, and the HTTP connection is closed. This works fine and allows the server to sustain very high load.

client                   server                 job server
  .                        |                        |
  .                        |                        |
  |------HTTP request----->|                        |
  |                        |--------queue job------>|
  |<--------close----------|                        |
  .                        |                        |
  .                        |                        |

But in some cases, the client needs to get the result as soon as the job is finished. Unfortunately, there's no way the server can contact the client once the HTTP connection is closed. One solution would be to rely on the client application polling the server every few seconds until the job is completed. I would like to avoid this solution, if possible, mostly because it would hinder the reactiveness of the service, and also because it would load the server with many unnecessary poll requests.

In short, I would like to keep the HTTP connection up and running, doing nothing (except perhaps sending a whitespace every once in a while to keep the TCP connection alive, just like Amazon S3 does), until the job is done, and the server returns the result.

client                   server                 job server
  .                        |                        |
  .                        |                        |
  |------HTTP request----->|                        |
  |                        |--------queue job------>|
  |<------keep-alive-------|                        |
  |         [...]          |                        |
  |<------keep-alive-------|                        |
  |                        |<--------result---------|
  |<----result + close-----|                        |
  .                        |                        |
  .                        |                        |

How can I implement long running HTTP connections in an efficient way, assuming the server is under very high load (it is not the case yet, but the goal to be able to sustain the highest possible load, with hundreds or thousands of requests per second)?

Offloading the actual jobs to other servers should ensure a low CPU usage on the server, but how can I avoid processes piling up and using all the server's RAM, or incoming requests being dropped because of too many open connections?

This is probably mostly a matter of configuring nginx and gunicorn properly. I have read a bit about async workers based on greenlets in gunicorn: the documentation says that async workers are used by "Applications making long blocking calls (Ie, external web services)", this sounds perfect. It also says "In general, an application should be able to make use of these worker classes with no changes". This sounds great. Any feedback on this?

Thanks for your advices.

4

1 に答える 1

34

私は自分の質問に答えています。おそらく誰かがより良い解決策を持っています。

gunicornのドキュメントをもう少し読んで、eventletgeventについてもう少し読むと、gunicorn が私の質問に完全に答えていると思います。Gunicorn には、ワーカーのプールを管理するマスター プロセスがあります。各ワーカーは、同期 (シングル スレッド、一度に 1 つの要求を処理する) または非同期 (各ワーカーが実際には複数の要求をほぼ同時に処理する) のいずれかです。

同期ワーカーは理解とデバッグが非常に簡単で、ワーカーが失敗した場合でも失われるリクエストは 1 つだけです。しかし、長時間の外部 API 呼び出しでワーカーが動かなくなった場合、ワーカーは基本的にスリープ状態になります。そのため、負荷が高い場合、結果を待っている間にすべてのワーカーがスリープ状態になる可能性があり、リクエストが破棄されることになります。

そのため、解決策は、デフォルトのワーカー タイプを同期から非同期に変更することです (eventlet または gevent を選択します。ここで比較します)。現在、各ワーカーは複数のグリーン スレッドを実行しており、それぞれが非常に軽量です。1 つのスレッドが何らかの I/O を待機する必要があるときはいつでも、別の緑色のスレッドが実行を再開します。これは、協調的マルチタスキングと呼ばれます。これは非常に高速で、非常に軽量です (I/O を待機している場合、単一のワーカーが数千の同時要求を処理できます)。まさに私が必要とするもの。

既存のコードをどのように変更すればよいのか疑問に思っていましたが、明らかに標準の python モジュールは、起動時に gunicorn によって (実際には eventlet または gevent によって)モンキー パッチが適用されているため、既存のすべてのコードは変更なしで実行でき、他のスレッドでも適切に動作します。

たとえば、gunicorn のパラメーターを使用する同時クライアントの最大数、パラメーターworker_connectionsを使用する保留中の接続の最大数など、gunicorn で調整できるパラメーターが多数あります。backlog

これは素晴らしいです。すぐにテストを開始します。

于 2012-08-09T16:16:43.783 に答える