-
-
Notifications
You must be signed in to change notification settings - Fork 792
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
b5210e3
commit 8a99105
Showing
1 changed file
with
142 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,142 @@ | ||
Deploying | ||
========= | ||
|
||
Deploying applications using Channels requires a few more steps than a normal | ||
Django WSGI application, but it's not very many. | ||
|
||
Firstly, remember that even if you have Channels installed, it's an entirely | ||
optional part of Django. If you leave a project with the default settings | ||
(no ``CHANNEL_BACKENDS``), it'll just run and work like a normal WSGI app. | ||
|
||
When you want to enable channels in production, you need to do three things: | ||
|
||
* Set up a channel backend | ||
* Run worker servers | ||
* Run interface servers | ||
|
||
|
||
Setting up a channel backend | ||
---------------------------- | ||
|
||
The first step is to set up a channel backend. If you followed the | ||
:doc:`getting-started` guide, you will have ended up using the database | ||
backend, which is great for getting started quickly in development but totally | ||
unsuitable for production use; it will hammer your database with lots of | ||
queries as it polls for new messages. | ||
|
||
Instead, take a look at the list of :doc:`backends`, and choose one that | ||
fits your requirements (additionally, you could use a third-party pluggable | ||
backend or write your own - that page also explains the interface and rules | ||
a backend has to follow). | ||
|
||
Typically a channel backend will connect to one or more central servers that | ||
serve as the communication layer - for example, the Redis backend connects | ||
to a Redis server. All this goes into the ``CHANNEL_BACKENDS`` setting; | ||
here's an example for a remote Redis server:: | ||
|
||
CHANNEL_BACKENDS = { | ||
"default": { | ||
"BACKEND": "channels.backends.redis.RedisChannelBackend", | ||
"HOST": "redis-channel", | ||
}, | ||
} | ||
|
||
Make sure the same setting file is used across all your workers, interfaces | ||
and WSGI apps; without it, they won't be able to talk to each other and things | ||
will just fail to work. | ||
|
||
|
||
Run worker servers | ||
------------------ | ||
|
||
Because the work of running consumers is decoupled from the work of talking | ||
to HTTP, WebSocket and other client connections, you need to run a cluster | ||
of "worker servers" to do all the processing. | ||
|
||
Each server is single-threaded, so it's recommended you run around one per | ||
core on each machine; it's safe to run as many concurrent workers on the same | ||
machine as you like, as they don't open any ports (all they do is talk to | ||
the channel backend). | ||
|
||
To run a worker server, just run:: | ||
|
||
python manage.py runworker | ||
|
||
Make sure you run this inside an init system or a program like supervisord that | ||
can take care of restarting the process when it exits; the worker server has | ||
no retry-on-exit logic, though it will absorb tracebacks from inside consumers | ||
and forward them to stderr. | ||
|
||
Make sure you keep an eye on how busy your workers are; if they get overloaded, | ||
requests will take longer and longer to return as the messages queue up | ||
(until the expiry limit is reached, at which point HTTP connections will | ||
start dropping). | ||
|
||
TODO: We should probably ship some kind of latency measuring tooling. | ||
|
||
|
||
Run interface servers | ||
--------------------- | ||
|
||
The final piece of the puzzle is the "interface servers", the processes that | ||
do the work of taking incoming requests and loading them into the channels | ||
system. | ||
|
||
You can just keep running your Django code as a WSGI app if you like, behind | ||
something like uwsgi or gunicorn, and just use the WSGI interface as the app | ||
you load into the server - just set it to use | ||
``channels.interfaces.wsgi:WSGIHandler``. | ||
|
||
If you want to support WebSockets, however, you'll need to run another | ||
interface server, as the WSGI protocol has no support for WebSockets. | ||
Channels ships with an Autobahn-based WebSocket interface server | ||
that should suit your needs; however, you could also use a third-party | ||
interface server or write one yourself, as long as it follows the | ||
:doc:`message-standards`. | ||
|
||
Notably, it's possible to combine more than one protocol into the same | ||
interface server, and the one Channels ships with does just this; it can | ||
optionally serve HTTP requests as well as WebSockets, though by default | ||
it will just serve WebSockets and assume you're routing requests to the right | ||
kind of server using your load balancer or reverse proxy. | ||
|
||
To run a normal WebSocket server, just run:: | ||
|
||
python manage.py runwsserver | ||
|
||
Like ``runworker``, you should place this inside an init system or something | ||
like supervisord to ensure it is re-run if it exits unexpectedly. | ||
|
||
If you want to enable serving of normal HTTP requests as well, just run:: | ||
|
||
python manage.py runwsserver --accept-all | ||
|
||
This interface server is built on in-process asynchronous solutions | ||
(Twisted for Python 2, and asyncio for Python 3) and so should be able to | ||
handle a lot of simultaneous connections. That said, you should still plan to | ||
run a cluster of them and load-balance between them; the per-connection memory | ||
overhead is moderately high. | ||
|
||
Finally, note that it's entirely possible for interface servers to be written | ||
in a language other than Python, though this would mean they could not take | ||
advantage of the channel backend abstraction code and so they'd likely be | ||
custom-written for a single channel backend. | ||
|
||
|
||
Deploying new versions of code | ||
------------------------------ | ||
|
||
One of the benefits of decoupling the client connection handling from work | ||
processing is that it means you can run new code without dropping client | ||
connections; this is especially useful for WebSockets. | ||
|
||
Just restart your workers when you have new code (by default, if you send | ||
them SIGTERM they'll cleanly exit and finish running any in-process | ||
consumers), and any queued messages or new connections will go to the new | ||
workers. As long as the new code is session-compatible, you can even do staged | ||
rollouts to make sure workers on new code aren't experiencing high error rates. | ||
|
||
There's no need to restart the WSGI or WebSocket interface servers unless | ||
you've upgraded your version of Channels or changed any settings; | ||
none of your code is used by them, and all middleware and code that can | ||
customise requests is run on the consumers. |