Worker是一个Python进程,通常在后台运行,只作为一个工人(纯纯牛马)存在,执行你不想在Web进程中执行的冗长或阻塞的任务.
starting workers
要开始工作,只需从项目目录的根部启动一个worker:
rq worker high default low
***
Listening for work on high, default,low
...
...
workers会从给定的queues(顺序很重要)轮询读取jobs,在所有工作都完成后等待新的任务.
每个worker一次只处理一个job.在一个worker内没有并行处理的情况发生.如果你想并发执行多个job,启动更多的worker就可以了.
生产环境中,你应该用supervisor/systemd这样的进程管理程序来启动RQ worker.
Burst Mode
默认情况下,worker会立即开始工作,当工作完成后等待.worker也可以在burst模式启动(爆发模式?突增模式?),worker会完成所有当前可用的工作,并在所有给定的队列被清空后立即退出(进程退出).
$ rq worker --burst high default low
*** Listening for work on high, default, low
Got send_newsletter('me@nvie.com') from default
Job ended normally without result
No more work, burst finished.
Registering death.
这对需要定期处理的批处理工作很有用,或者高峰期临时扩大worker规模.
Worker Arguments
除了 --burst, rq worker 接受以下参数 略
Inside the worker
worker的生命周期
- 启动,加载python环境
- 注册.worker向系统注册自身使得系统知晓worker
- 开始监听.job可以从给定的任意queue弹出.如果所有queue为空且worker以burst模式运行,立即退出.否则等待直到有job.
- 准备执行job.worker把自身状态修改为
busy来告诉系统它将开始工作,并且在StartedJobRegistry注册job 5.fork子进程.一个子进程被fork出来,在故障安全的环境(上下文)下用来做实际的工作 6.执行工作. 7.清理.worker将自身状态设置为idle并且将job和结果根据result_ttl设置过期时间.job也会从StartedJobRegistry移除,如果执行成功,job会加到FinishedJobRegistry里,如果失败会加到FailedJobRegistry里. 8.循环.从第三部开始重复.
Performance Notes
基本上,rq worker脚本是个简单的 fetch-fork-execute循环.当很多有冗长设置,或者依赖相同模块的job时,你每次执行job都需要支付这笔开销(因为每次都是先fork,理解成copy-on-write吧).这很干净,因为用这种方式的RQ不会泄露内存(理论上),但也很慢.
可以在 fork 之前就 import 必要的模块,以改进性能。RQ Worker 没有这样的设置项,但你可以在开始 worker loop 之前进行 import。
为此,你要自己实现Worker启动脚本,而不是使用rq worker.举个例子:
#!/usr/bin/env/python3
import sys
from rq import Connection, Worker
# 准备 导入你想要的包
import library_that_you_want_preloaded
# 提供要监听的队列名 和rq worker 类似
with Connection():
qs = sys.argv[1:] or ['default']
w = Worker(qs)
w.work()
Worker Names
Worker用其name向系统注册,这些name是实例化过程种随机生成的(参照监控).要重写默认行为,可以在启动worker时指定--name参数
from rq import Queue, Worker
from redis import Redis
redis = Redis()
queue = Queue('queue_name')
worker = Worker([queue], name='worker_name', connection=redis)
retrieving Worker Information
在0.10.0版更新
Worker实力在redis里存储相关运行时信息.这样查询:
from rq import Queue, Worker
from redis import Redis
redis = Redis()
# 这个连接内的所有注册的worker
workers = Worker.all(connection=redis)
# 队列里的所有worker
queue = Queue('queue_name')
workers = Worker.all(queue=queue)
worker = workers[0]
print(worker.name)
除了worker.name 还有这些属性:
略
如果为了监控目的,只想获取worker数量,Worker.count()性能好很多.
workers = Worker.count(connection=redis)
workers = Worker.count(queue=queue)
Worker with Custom Serializer
创建一个worker时,可以传自定义的序列化器(也会被隐式的传给queue).序列化器至少要实现loads和dumps方法.
在serializers.py(rq.serializers.JSONSerializer)里有例子.
默认的序列化方式是pickle
from rq import Queue, Worker
from rq.serializers import JSONSerializer
w = Worker('foo', serializer=JSONSerializer)
# 或者从queue创建
q = Queue('bar', serializer=JSONSerializer)
Worker 统计
如果想查看queue的使用情况,Worker示例存储了一些有用的信息(查看Worker类__init__里的属性)
更好的worker 进程标题
可以使用第三方包setproctitle Worker可以定义合适的标题(显示在系统工具如ps/top)
(源码里尝试import,如果没安装,覆盖一个空方法)
停止Workers
任何时候,worker接收到SIGINT(通过Ctrl+C) 或者SIGTERM(通过kill命令) 信号,worker会等待当前任务结束,然后停止循环并优雅的注销.
如果在停止的阶段又一次收到SIGINT或SIGTERM,worker会强制终止子进程(发送SIGKILL),但还是会尝试注销自身.(在redis里标记对应的值为death(rq.worker.register_death()方法))
使用配置文件
如果你想在rq worker命令时,用配置文件代替--args 命令行参数的形式配置worker,可以创建一个python文件 比如settings.py实现:
REDIS_URL = 'redis://localhost:6379/1'
# 指定redis信息
# REDIS_HOST = 'redis.example.com'
# REDIS_PORT = 6380
# REDIS_DB = 3
# REDIS_PASSWORD = 'very secret'
# 队列
QUEUES = ['high', 'default', 'low']
# sentry
# to configure RQ for it in a single step
# The 'sync+' prefix is required for raven: https://github.com/nvie/rq/issues/350#issuecomment-43592410
SENTRY_DSN = 'sync+http://public:secret@example.com/1'
# If you want custom worker name
# NAME = 'worker-1024'
上述例子展示了目前支持的全部配置项
指定配置文件使用-c
rq worker -c settings
# 多层目录时
rq worker -c folder1.folder2.filename # 如果floder是个包 也会执行里面的代码
或者你也可以在环境变量里指定参数
自定义Worker类
略
出队的Round Robin 和随机策略
默认的worker考虑了队列的优先级顺序,如果一个任务在高优先级的队列中等着(pending),它会比其它低优先级的队列里的任务更早被选中(dequeue).
在某些情况下,一个worker监听多个队列时,比如q1,q2,q3,jobs会根据RoundRobin策略轮流出队,出队顺序是:q1,q2,q3,q1,q2,q3...以此类推.rq.worker.RoundRobinWorker实现了这个策略.
另一种场景下,需要随机出队.rq.worker.RandomWorker实现.实际上,每次job 被pull,[queues]就会shuffle,所以就没有优先级了.
自定义Job和Queue类
用--job-class/--queue-class告诉worker使用自定义类
不要忘了在job入队的时候也要用自定义的类
queue = CustomQueue('default', connection=redis_conn)
queue.enqueue(some_func)
自定义DeathPenalty类
当job超时,worker会尝试使用提供的death_penalty_classkill掉它(fork出的子进程,记了pid),默认的是UnixSignalDeathPenalty.
这可以被覆盖,如果你想用其他的方式kill.
自定义异常处理
如果你想为不同的job提供不同的异常处理,或者想自定义RQ的异常处理行为,使用--exception-hanfler
看tests.test_worker.test_custom_exc_handling
(会按顺序遍历handlers,handler方法返回True就会使用)
给worker发送命令
停止worker
send_shutdown_command发送shutdown command, redis在run work()时,订阅了redis消息,并设置了handle_payload方法处理订阅的消息(内部调用rq.command.handle_command).
使用os.kill发送SIGINT
取消当前执行的job
worker.kill_horse()
停止job
send_stop_job_command
handle_stop_job_command