Skip to content

Commit 45cbf9d

Browse files
committed
Initial Commit
0 parents  commit 45cbf9d

22 files changed

+2023
-0
lines changed

django-queue-manager/LICENSE

Lines changed: 674 additions & 0 deletions
Large diffs are not rendered by default.

django-queue-manager/MANIFEST.in

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
include LICENSE
2+
include README.rst

django-queue-manager/README.md

Lines changed: 347 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,347 @@
1+
Django Queue Manager
2+
====================
3+
4+
**A simple async tasks queue via a django app and SocketServer, zero
5+
configs.**
6+
7+
`Why? <#why>`__
8+
`Overview <#overview>`__
9+
`Install <#install>`__
10+
`Settings <#settings>`__
11+
`Run the Tasks Queue Server <#run-the-tasks-queue-server>`__
12+
`Persistency <#persistency>`__
13+
`Failed Tasks <#failed-tasks>`__
14+
`Run the Tasks Queue on Another Server <#run-the-tasks-queue-on-another-server>`__
15+
16+
Why?
17+
----
18+
19+
Although Celery is pretty much the standard for a django tasks queue
20+
solution, it can be complex to install and config.
21+
22+
The common case for a web application queue is to send emails: you don't
23+
want the django thread to wait until the SMTP, or email provider API,
24+
finishes. But to send emails from a site without a lot of traffic, or to
25+
run other similar simple tasks, you don't need Celery.
26+
27+
This queue app is a simple, up and running queueing solution. The more
28+
complex distributed queues can wait until the website has a lot of
29+
traffic, and the scalability is really required.
30+
31+
In addition, the Django-queue-manager provides a simple and stunning easy-to-use interface in the admin backend page
32+
33+
Overview
34+
--------
35+
36+
In a nutshell, a python SocketServer runs in the background, and listens
37+
to a tcp socket. SocketServer gets the request to run a task from it's
38+
socket, puts the task on a Queue. A Worker thread picks tasks from this
39+
Queue, and runs the tasks one by one.
40+
41+
The SocketServer istance can be one or multiple, depending on your app requirements.
42+
43+
You send a task request to the default SocketServer with:
44+
45+
::
46+
47+
from mysite.django-queue-manager.API import push_task_to_queue
48+
...
49+
push_task_to_queue(a_callable, *args, **kwargs)
50+
51+
Sending email might look like:
52+
53+
::
54+
55+
push_task_to_queue(send_mail,subject="foo",message="baz",recipient_list=[user.email])
56+
57+
If you have more of one SocketServer istance, you can specify the parameter dqmqueue, in order to send the task to another queue, like below:
58+
59+
::
60+
specific_queue = DQMQueue.objects.get(description='foo_queue')
61+
push_task_to_queue(send_mail,subject="foo",message="baz",recipient_list=[user.email], dqmqueue=specific_queue)
62+
63+
64+
Components
65+
~~~~~~~~~~
66+
67+
1. Python SocketServer that listens to a tcp socket.
68+
2. A Worker thread.
69+
3. A python Queue
70+
71+
Workflow
72+
~~~~~~~~
73+
74+
The workflow that runs an async task:
75+
76+
1. When ``SocketServer`` starts, it initializes the ``Worker`` thread.
77+
2. ``SocketServer`` listens to requests.
78+
3. When ``SocketServer`` receives a request - a callables with args and
79+
kwargs - it puts the request on a python ``Queue``.
80+
4. The ``Worker`` thread picks a task from the ``Queue``.
81+
5. The ``Worker`` thread runs the task.
82+
83+
Can this queue scale to production?
84+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
85+
86+
Depends on the traffic: SocketServer is simple, but solid, and as the
87+
site gets more traffic, it's possible to move the django-queue-manager server to
88+
another machine, separate database, use multiple istance of SocketServer, etc...
89+
At some point, probably, it's better to pick Celery. Until then, django-queue-manager is a simple, solid, and
90+
no-hustle solution.
91+
92+
Install
93+
-------
94+
95+
1. Install the django-queue-manager with the following pip command ``pip3 install django-queue-manager``.
96+
97+
2. Add ``django-queue-manager`` in the ``INSTALLED_APPS`` list.
98+
99+
3. Migrate:
100+
101+
::
102+
103+
$ manange.py migrate
104+
105+
4. The django-queue-manager app has an API module, with a ``push_task_to_queue``
106+
function. Use this function to send callables with args and kwargs to the queue,
107+
you can specify a specific queue with the parameter dqmqueue or use the default one if none it's specified, for the async run.
108+
109+
Settings
110+
--------
111+
112+
To change the default django-queue-manager settings, you can modify the backend default queue present in the django admin pages.
113+
114+
In a glance, the queue, has the following parameters:
115+
116+
**description** The description of the queue.
117+
118+
**queue\_host** The host to run the SocketServer. The default is
119+
'localhost'. (It can be also a remote host)
120+
121+
**queue\_port**
122+
The port that SocketServer listens to. The default is
123+
8002.
124+
125+
**max\_retries** The number of times the Worker thread will try to run a
126+
task before skipping it. The default is 3.
127+
128+
129+
So, in a nutshell, for using multiple queues, simply add a new queue
130+
in the admin page and pass the istance of a valid ``DQMQueue`` object in the function like below:
131+
132+
::
133+
134+
from mysite.django-queue-manager.API import push_task_to_queue
135+
...
136+
specific_queue = DQMQueue.objects.get(description='foo_queue')
137+
push_task_to_queue(send_mail,subject="foo",message="baz",recipient_list=[user.email], dqmqueue=specific_queue)
138+
139+
140+
Run the Tasks Queue Server
141+
--------------------------
142+
143+
Start the Server
144+
~~~~~~~~~~~~~~~~
145+
146+
From shell or a process control system, run the following script with python >= 3
147+
(if you use a VirtualEnv, specify the environment path in supervisor conf.d file):
148+
149+
::
150+
151+
import os
152+
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "YOUR-APP-NAME.settings")
153+
import django
154+
django.setup()
155+
import time
156+
from django_queue_manager import worker_manager
157+
from django_queue_manager.models import DQMQueue
158+
from django_queue_manager.server_manager import TaskSocketServerThread
159+
worker_manager.start()
160+
server_thread = TaskSocketServerThread('localhost', DQMQueue.objects.first().queue_port)
161+
time.sleep(5)
162+
socket_server = server_thread.socket_server()
163+
socket_server.serve_forever()
164+
165+
166+
*Note: You have to change the variable "YOUR-APP-NAME.settings" with the
167+
name of your app, like that: "email_sender.settings")*
168+
169+
170+
The Shell interface
171+
~~~~~~~~~~~~~~~~~~~
172+
173+
Django-queue-manager, provides a simple script called ``shell.py``
174+
that it's useful in order to see how the queue, worker and server it's going on,
175+
the base syntax it's really simple
176+
177+
::
178+
179+
$ python <package-install-dir>/shell.py queue-host queue-port command
180+
181+
Stop the Server
182+
~~~~~~~~~~~~~~~
183+
184+
185+
To stop the worker thread gracefully:
186+
187+
::
188+
189+
$ python django-queue-manager/shell.py localhost 8002 stop
190+
Sent: ping
191+
Received: (False, 'Worker Off')
192+
193+
This will send a stop event to the Worker thread. Check that the Worker
194+
thread stopped:
195+
196+
::
197+
198+
$ python django-queue-manager/shell.py localhost 8002 ping
199+
Sent: ping
200+
Received: (False, 'Worker Off')
201+
202+
Now you can safely stop SocketServer:
203+
204+
::
205+
206+
$ ps ax | grep django-queue-manager
207+
12345 pts/1 S 7:20 <process name>
208+
$ sudo kill 12345
209+
210+
Ping the Server
211+
~~~~~~~~~~~~~~~
212+
213+
From shell:
214+
215+
::
216+
217+
$ python django-queue-manager/shell.py localhost 8002 ping
218+
Sent: ping
219+
Received: (True, "I'm OK")
220+
221+
Tasks that are waiting on the Queue
222+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
223+
224+
From shell:
225+
226+
::
227+
228+
$ python django-queue-manager/shell.py localhost 8002 waiting
229+
Sent: waiting
230+
Received: (True, 115)
231+
232+
115 tasks are waiting on the queue
233+
234+
Count total tasks handled to the Queue
235+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
236+
237+
From shell:
238+
239+
::
240+
241+
$ python django-queue-manager/shell.py localhost 8002 handled
242+
Sent: handled
243+
Received: (True, 862)
244+
245+
Total of 862 tasks were handled to the Queue from the moment the thread
246+
started
247+
248+
*Note: If you use the tasks server commands a lot, add shell aliases for
249+
these commands*
250+
251+
Use shell.py script for another Queue
252+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
253+
254+
From shell, it's very easy to use the above command with another Queue,
255+
in a simple way, change the hostname and host port values:
256+
257+
::
258+
259+
$ python django-queue-manager/shell.py localhost 8003 ping
260+
Sent: ping
261+
Received: (True, "I'm OK")
262+
263+
::
264+
265+
$ python django-queue-manager/shell.py 10.50.3.100 8007 ping
266+
Sent: ping
267+
Received: (True, "I'm OK")
268+
269+
270+
Persistency
271+
-----------
272+
273+
Tasks saved in the database
274+
~~~~~~~~~~~~~~~~~~~~~~~~~~~
275+
276+
**QueuedTasks** The model saves every tasks pushed to the queue and not yet processed.
277+
The task is pickled as a ``django-queue-manager.task_manager.Task`` object, which is a
278+
simple class with a ``callable``,\ ``args``, ``dqmqueue`` and ``kwargs`` attributes,
279+
and one method: ``run()``. After a successful execution, the QueuedTasks will be deleted
280+
and moved into the ``SuccessTask`` queue.
281+
282+
*Note: If you use the requeue task function in the django admin dropdown action, the
283+
selected tasks will be requeued like NEW TASKS (with a new ``task_id``) in the ``QueuedTasks`` table.*
284+
285+
**SuccessTasks** The Worker thread saves to this model the successfully executed job
286+
with all informations like above:
287+
288+
``task_function_name``: The complete function name like "module.function_name"
289+
``task_args``: The variable list arguments in plain text
290+
``task_kwargs``: The dictionary arguments in plain text
291+
``task_id``: The task id carried from the initial QueuedTask istance
292+
``success_on``: The success datetime
293+
``pickled_task``: The complete pickled task
294+
``dqmqueue``: The reference of the dqmqueue queue istance
295+
296+
**FailedTasks** After the Worker tries to run a task several times
297+
according to ``max_retries``(specified in the dqmqueue used), and the task still fails, the Worker saves it to this model with all informations like above:
298+
299+
``task_function_name``: The complete function name like "module.function_name"
300+
``task_args``: The variable list arguments in plain text
301+
``task_kwargs``: The dictionary arguments in plain text
302+
``task_id``: The task id carried from the initial QueuedTask istance
303+
``failed_on``: The last failed run datetime
304+
``exception``: The exception message, only the exception from the last run is saved.
305+
``pickled_task``: The complete pickled task
306+
``dqmqueue``: The reference of the dqmqueue queue istance
307+
308+
*Note: If you use the requeue task function in the django admin dropdown action, the
309+
selected tasks will be requeued like NEW TASKS (with a new ``task_id``) in the ``QueuedTasks`` table.*
310+
311+
Purge Tasks
312+
~~~~~~~~~~~
313+
314+
According to your project needs, you can purge tasks using the django admin
315+
interface or manually with a query execution.
316+
317+
In a similar way, delete the failed/success tasks. You can run a cron script, or
318+
other script, to purge the tasks.
319+
320+
Connections
321+
~~~~~~~~~~~
322+
323+
If most of the tasks require a specific connection, such as SMTP or a
324+
database, you can subclass (...or edit directly) the Worker class and add a ping or other check
325+
for this connection **before** the tasks runs. If the connection is
326+
not avaialable, just try to re-connect.
327+
328+
Otherwise the Worker will just run and fail a lot of tasks.
329+
330+
Run the Tasks Queue on Another Server
331+
-------------------------------------
332+
333+
The same ``django-queue-manager`` app can run from another server, and provide a
334+
seprate server queue for the async tasks.
335+
336+
Here is a simple way to do it:
337+
338+
1. The queue server should be similar to the main django server, just
339+
without a webserver.
340+
2. Deploy your django code to these two remotes: the main with the
341+
web-server, and the queue server
342+
3. Open firewalls ports between the main django server, and the queue
343+
server, and between the main django database and the queue server host
344+
4. On the django main server, change the host and port details directly from the admin site.
345+
346+
That's it!
347+
For any support/issue request, contact the author: [email protected]
25.5 KB
Binary file not shown.

0 commit comments

Comments
 (0)