python_rq 文档翻译-2-(workers)

727 阅读6分钟

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的生命周期

  1. 启动,加载python环境
  2. 注册.worker向系统注册自身使得系统知晓worker
  3. 开始监听.job可以从给定的任意queue弹出.如果所有queue为空且worker以burst模式运行,立即退出.否则等待直到有job.
  4. 准备执行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).序列化器至少要实现loadsdumps方法. 在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