|
| 1 | +# Celery 的简单使用 |
| 2 | + |
| 3 | +标签(空格分隔): python celery |
| 4 | + |
| 5 | +-------------------------------------------------------------------------------- |
| 6 | + |
| 7 | +** Celery 是一个简单、灵活并且可靠的处理大量消息的分发系统。并且是自带电池的,本身提供了维护和操作这个系统的工具。** |
| 8 | + |
| 9 | +Celery 专注于实时处理的任务队列,并且支持任务调度。 优点: |
| 10 | + |
| 11 | +1. 简单 |
| 12 | +2. 高可用 |
| 13 | +3. 快速 |
| 14 | +4. 灵活 |
| 15 | + |
| 16 | +## Celery 架构 |
| 17 | + |
| 18 | +- Celery Beat: 任务调度器 |
| 19 | +- Celery Worker: 消费者 |
| 20 | +- Broker: 消息中间件,常用的是 RabbitMQ 和 Redis |
| 21 | +- Producer:任务生产者 |
| 22 | +- Result Backend:用于结果保存。 |
| 23 | + |
| 24 | +## Celery 序列化 |
| 25 | + |
| 26 | +## 一个简单的简单例子 |
| 27 | + |
| 28 | +项目目录为 |
| 29 | + |
| 30 | +```bash |
| 31 | +celeries/proj/ |
| 32 | +├── celeryconfig.py |
| 33 | +├── celery.py |
| 34 | +├── __init__.py |
| 35 | +└── tasks.py |
| 36 | +``` |
| 37 | + |
| 38 | +-------------------------------------------------------------------------------- |
| 39 | + |
| 40 | +主程序 celery.py |
| 41 | + |
| 42 | +```python |
| 43 | +from __future__ import absolute_import |
| 44 | +from celery import Celery |
| 45 | + |
| 46 | +app = Celery('proj', include=['proj.tasks'], |
| 47 | +app.config_from_object('proj.celeryconfig') |
| 48 | + |
| 49 | + |
| 50 | +if __name__ == "main": |
| 51 | + app.start() |
| 52 | +``` |
| 53 | + |
| 54 | +任务函数 tasks.py |
| 55 | + |
| 56 | +```python |
| 57 | +# coding=utf-8 |
| 58 | +from __future__ import absolute_import |
| 59 | + |
| 60 | +from .celery import app |
| 61 | + |
| 62 | + |
| 63 | +@app.task |
| 64 | +def add(x, y): |
| 65 | + return x + y |
| 66 | + |
| 67 | + |
| 68 | +@app.task |
| 69 | +def mul(x, y): |
| 70 | + return x * y |
| 71 | +``` |
| 72 | + |
| 73 | +接下来是 配置文件 celeryconfig.py |
| 74 | + |
| 75 | +```python |
| 76 | +# coding=utf-8 |
| 77 | +BROKER_URL = 'amqp://localhost' # RabbitMQ 作为消息代理 |
| 78 | +CELERY_RESULT_BACKEND = 'redis://localhost:6379/0' # Redis 作为结果存储 |
| 79 | +CELERY_TASK_SERIALIZER = 'msgpack' |
| 80 | +# 任务序列化和反序列化格式为 msgpack, 别忘了安装 msgpack-python |
| 81 | +CELERY_RESULT_SERIALIZER = 'json' # 结果存储序列化格式为 json |
| 82 | +CELERY_ACCEPT_CONTENT = ['msgpack', 'json'] # 任务接受格式类型 |
| 83 | +``` |
| 84 | + |
| 85 | +因为没有任务调度,所以直接启动消费者就行了。在启动之前,要先去安装 RabbitMQ 和 Redis, 并启动。 |
| 86 | + |
| 87 | +现在启动我们的消费者函数, 命令行直接启动: |
| 88 | + |
| 89 | +``` |
| 90 | +> cd celeries |
| 91 | +> celery -A celeries worker -l info |
| 92 | +``` |
| 93 | + |
| 94 | +看到下面的提示信息,表示成功启动 |
| 95 | + |
| 96 | +```python |
| 97 | +-------------- celery@mouse-pc v4.0.2 (latentcall) |
| 98 | +---- **** ----- |
| 99 | +--- * *** * -- Linux-4.9.15-1-MANJARO-x86_64-with-glibc2.2.5 2017-03-22 21:53:05 |
| 100 | +-- * - **** --- |
| 101 | +- ** ---------- [config] |
| 102 | +- ** ---------- .> app: celeries:0x7f9737da7a58 |
| 103 | +- ** ---------- .> transport: amqp://guest:**@localhost:5672// |
| 104 | +- ** ---------- .> results: redis://localhost/ |
| 105 | +- *** --- * --- .> concurrency: 2 (prefork) |
| 106 | +-- ******* ---- .> task events: OFF (enable -E to monitor tasks in this worker) |
| 107 | +--- ***** ----- |
| 108 | + -------------- [queues] |
| 109 | + .> celery exchange=celery(direct) key=celery |
| 110 | + |
| 111 | + |
| 112 | +[tasks] |
| 113 | + . celeries.tasks.add |
| 114 | + . celeries.tasks.mul |
| 115 | + . celeries.tasks.xsum |
| 116 | + |
| 117 | +[2017-03-22 21:53:06,011: INFO/MainProcess] Connected to amqp://guest:**@127.0.0.1:5672// |
| 118 | +[2017-03-22 21:53:06,034: INFO/MainProcess] mingle: searching for neighbors |
| 119 | +[2017-03-22 21:53:07,088: INFO/MainProcess] mingle: all alone |
| 120 | +[2017-03-22 21:53:07,115: INFO/MainProcess] celery@mouse-pc ready. |
| 121 | +``` |
| 122 | + |
| 123 | +打开 IPython 测试一下我们的几个函数。 |
| 124 | + |
| 125 | +```python |
| 126 | +~ ▶︎︎ ipython |
| 127 | +Python 3.6.0 |Anaconda 4.3.1 (64-bit)| (default, Dec 23 2016, 12:22:00) |
| 128 | +Type "copyright", "credits" or "license" for more information. |
| 129 | + |
| 130 | + |
| 131 | +In [1]: from celeries.tasks import add, mul, xsum |
| 132 | + |
| 133 | +In [2]: add.delay(1, 9) |
| 134 | +Out[2]: <AsyncResult: 38022eec-2d3d-4ee0-8c7e-367ef92b5f1f> |
| 135 | +In [3]: r = mul.delay(2, 4) |
| 136 | + |
| 137 | +In [4]: r.status |
| 138 | +Out[4]: 'SUCCESS' |
| 139 | + |
| 140 | +In [5]: r.result |
| 141 | +Out[5]: 8 |
| 142 | + |
| 143 | +In [6]: r.successful |
| 144 | +Out[6]: <bound method AsyncResult.successful of <AsyncResult: 17af4e48-736d-44c9-a8be-a50a35bbc435>> |
| 145 | + |
| 146 | +In [7]: r.backend |
| 147 | +Out[7]: <celery.backends.redis.RedisBackend at 0x7f5aebbbcba8> # 结果存储在 redis 里 |
| 148 | +``` |
| 149 | + |
| 150 | +delay() 是 apply_async() 的快捷方式。你也直接调用 apply_async() : |
| 151 | + |
| 152 | +```python |
| 153 | +In [24]: r = mul.apply_async((2, 4)) |
| 154 | + |
| 155 | +In [25]: r.result |
| 156 | +Out[25]: 8 |
| 157 | +``` |
| 158 | + |
| 159 | +delay() & apply_async 返回的都是 AsyncResult 实例,可用于查看任务的执行状态,但首先你要配置好 result backend. 此时,在worker终端上可以看到,任务信息和结果 |
| 160 | + |
| 161 | +```bash |
| 162 | +[2017-03-22 22:05:13,689: INFO/MainProcess] Received task: celeries.tasks.add[38022eec-2d3d-4ee0-8c7e-367ef92b5f1f] |
| 163 | +[2017-03-22 22:05:14,765: INFO/PoolWorker-2] Task celeries.tasks.add[38022eec-2d3d-4ee0-8c7e-367ef92b5f1f] succeeded in 0.007736653999018017s: 10 |
| 164 | +[2017-03-22 22:08:36,378: INFO/MainProcess] Received task: celeries.tasks.mul[17af4e48-736d-44c9-a8be-a50a35bbc435] |
| 165 | +[2017-03-22 22:08:37,010: INFO/PoolWorker-2] Task celeries.tasks.mul[17af4e48-736d-44c9-a8be-a50a35bbc435] succeeded in 0.011531784999533556s: 8 |
| 166 | +``` |
| 167 | + |
| 168 | +仔细看,每个任务都有一个 task_id。我们可以通过 task_id 获得任务的结果。 |
| 169 | + |
| 170 | +取 add 任务的 id: |
| 171 | + |
| 172 | +```bash |
| 173 | +task_id = '38022eec-2d3d-4ee0-8c7e-367ef92b5f1f' |
| 174 | +In [8]: task_id = '38022eec-2d3d-4ee0-8c7e-367ef92b5f1f' |
| 175 | + |
| 176 | +In [9]: add.AsyncResult(task_id).get() |
| 177 | +Out[9]: 10 |
| 178 | +``` |
| 179 | + |
| 180 | +**关联任务** |
| 181 | + |
| 182 | +``` |
| 183 | +In [2]: m = mul.apply_async((2, 2), link=mul.s(3)) |
| 184 | +``` |
| 185 | + |
| 186 | +在 Worker 终端里会看到两个值,关联之前和之后的。 |
| 187 | + |
| 188 | +``` |
| 189 | +[2017-03-23 13:27:13,045: INFO/MainProcess] Received task: proj.tasks.mul[40492357-44bb-41e4-979f-6eb197107a5b] |
| 190 | +[2017-03-23 13:27:13,731: INFO/PoolWorker-2] Task proj.tasks.mul[40492357-44bb-41e4-979f-6eb197107a5b] succeeded in 0.0023383530005958164s: 4 |
| 191 | +[2017-03-23 13:27:13,732: INFO/MainProcess] Received task: proj.tasks.mul[b01be1b8-f957-48b2-9d72-8187af6ac161] |
| 192 | +[2017-03-23 13:27:13,734: INFO/PoolWorker-2] Task proj.tasks.mul[b01be1b8-f957-48b2-9d72-8187af6ac161] succeeded in 0.0006868359996587969s: 12 |
| 193 | +``` |
| 194 | + |
| 195 | +## 指定队列 |
| 196 | + |
| 197 | +在 celeries 目录下新建一个目录 projb, 代码使用 proj 中的。 |
| 198 | + |
| 199 | +```bash |
| 200 | +celeries/projb |
| 201 | +├── celeryconfig.py |
| 202 | +├── celery.py |
| 203 | +├── __init__.py |
| 204 | +└── tasks.py |
| 205 | +``` |
| 206 | + |
| 207 | +在 celeryconfig.py 添加些配置: |
| 208 | + |
| 209 | +``` |
| 210 | +# coding=utf-8 |
| 211 | +from kombu import Queue |
| 212 | + |
| 213 | +BROKER_URL = 'amqp://localhost' # RabbitMQ 作为消息代理 |
| 214 | +CELERY_RESULT_BACKEND = 'redis://localhost:6379/0' # Redis 作为结果存储 |
| 215 | +CELERY_TASK_SERIALIZER = 'msgpack' |
| 216 | +# 任务序列化和反序列化格式为 msgpack, 别忘了安装 msgpack-python |
| 217 | +CELERY_RESULT_SERIALIZER = 'json' # 结果存储序列化格式为 json |
| 218 | +CELERY_ACCEPT_CONTENT = ['msgpack', 'json'] # 任务接受格式类型 |
| 219 | + |
| 220 | +CELERY_QUEUES = { |
| 221 | + Queue('foo', routing_key='task.#'), # 路由键以 task. 开头的消息进入此队列 |
| 222 | + Queue('feed_task', routing_key='*.feed'), # 路由键以 .feed 结尾的消息进入此队列 |
| 223 | +} |
| 224 | +CELERY_DEFAULT_QUEUE = 'foo' # 默认队列 |
| 225 | + |
| 226 | +CELERY_DEFAULT_EXCHANGE = 'tasks' # 默认交换机 |
| 227 | + |
| 228 | +CELERY_DEFAULT_EXCHANGE_TYPE = 'topic' # 默认交换机类型 topic |
| 229 | + |
| 230 | +CELERY_DEFAULT_ROUTING_KEY = 'task.foooooooo' # 默认交换机路由键, task. 后的值不影响 |
| 231 | + |
| 232 | +CELERY_ROUTES = { |
| 233 | + 'projb.tasks.mul': { |
| 234 | + 'queue': 'feed_task', # 消息全都进入 feed_task 队列 |
| 235 | + 'routing_key': 'mul.feed', |
| 236 | + }, |
| 237 | +} |
| 238 | +``` |
| 239 | + |
| 240 | +然后,我们以指定队列的方式启动: |
| 241 | + |
| 242 | +``` |
| 243 | +> celery -A projb worker -Q foo,feed_task -l info |
| 244 | +``` |
| 245 | + |
| 246 | +tasks.py 中的 mul 函数只会通过队列 feed_task 被执行。add 函数通过默认队列 foo 执行。 |
| 247 | + |
| 248 | +```python |
| 249 | +In [84]: from projb.tasks import mul, add |
| 250 | + |
| 251 | +In [85]: r = add.delay(3, 3) |
| 252 | + |
| 253 | +In [86]: r.result |
| 254 | +Out[86]: 6 |
| 255 | + |
| 256 | +In [87]: res = mul.delay(3, 3) |
| 257 | + |
| 258 | +In [88]: res.result |
| 259 | +Out[88]: 9 |
| 260 | +``` |
| 261 | + |
| 262 | +不过,我们可以使用 apply_async() 函数来指定队列。 |
| 263 | + |
| 264 | +```python |
| 265 | +In [90]: r = add.apply_async((3, 3), queue='feed_task', routing_key='mul.feed') |
| 266 | + |
| 267 | +In [91]: r.result |
| 268 | +Out[91]: 6 |
| 269 | + |
| 270 | +In [92]: res = mul.apply_async((3, 3), queue='foo', routing_key='task.foooooo') |
| 271 | + |
| 272 | +In [93]: res.result |
| 273 | +Out[93]: 9 |
| 274 | +``` |
| 275 | + |
| 276 | +## 任务调度 |
| 277 | + |
| 278 | +依法炮制,基于 projb 的代码,创建目录 projc,在 proc/celeryconfig.py 中添加如下配置。 |
| 279 | + |
| 280 | +``` |
| 281 | +CELERYBEAT_SCHEDULE = { |
| 282 | + 'mul-every-30-seconds': { |
| 283 | + 'task': 'projc.tasks.mul', |
| 284 | + 'schedule': 30.0, |
| 285 | + 'args': (2, 2), |
| 286 | + } |
| 287 | +} |
| 288 | +``` |
| 289 | + |
| 290 | +执行 |
| 291 | + |
| 292 | +``` |
| 293 | +> celery -B -A projc worker -l info |
| 294 | +``` |
| 295 | + |
| 296 | +就可以在终端看到每 30s 执行一次任务。 |
| 297 | + |
| 298 | +``` |
| 299 | +[2017-03-23 12:23:13,920: INFO/Beat] Scheduler: Sending due task mul-every-30-seconds (projc.tasks.mul) |
| 300 | +[2017-03-23 12:23:13,923: INFO/MainProcess] Received task: projc.tasks.mul[9c414257-d627-4c36-a9d8-9daed7e295c0] |
| 301 | +[2017-03-23 12:23:15,177: INFO/PoolWorker-3] Task projc.tasks.mul[9c414257-d627-4c36-a9d8-9daed7e295c0] succeeded in 0.0010301589991286164s: 4 |
| 302 | +``` |
| 303 | + |
| 304 | +## 任务绑定、日志记录和错误重试 |
| 305 | + |
| 306 | +任务绑定、记录日志和重试是 Celery 3 个常用的高级功能。接下来,修改 proj 的 tasks.py 文件。添加一个 div 函数。 |
| 307 | + |
| 308 | +``` |
| 309 | +@app.task(bind=True) |
| 310 | +def div(self, x, y): |
| 311 | + logger.info( |
| 312 | + ''' |
| 313 | + Executing task : {0.id} |
| 314 | + task.args : {0.args!r} |
| 315 | + task.kwargs : {0.kwargs!r} |
| 316 | + '''.format(self.request) |
| 317 | + ) |
| 318 | + try: |
| 319 | + res = x / y |
| 320 | + except ZeroDivisionError as e: |
| 321 | + raise self.retry(exc=e, countdown=3, max_retries=3) |
| 322 | + else: |
| 323 | + return res |
| 324 | +``` |
| 325 | + |
| 326 | +在 Ipython 调用: |
| 327 | + |
| 328 | +``` |
| 329 | +In [3]: d = div.delay(2, 1) |
| 330 | +``` |
| 331 | + |
| 332 | +在 worker 中可以看到 |
| 333 | + |
| 334 | +``` |
| 335 | +[2017-03-23 14:57:17,361: INFO/PoolWorker-2] proj.tasks.div[68ef1584-16ac-4236-9858-b00842891bbc]: |
| 336 | + Executing task : 68ef1584-16ac-4236-9858-b00842891bbc |
| 337 | + task.args : [2, 1] |
| 338 | + task.kwargs : {} |
| 339 | + |
| 340 | +[2017-03-23 14:57:17,369: INFO/PoolWorker-2] Task proj.tasks.div[68ef1584-16ac-4236-9858-b00842891bbc] succeeded in 0.007741746998362942s: 2.0 |
| 341 | +``` |
| 342 | + |
| 343 | +换成可以引起异常的参数: |
| 344 | + |
| 345 | +``` |
| 346 | +In [4]: d = div.delay(2, 0) |
| 347 | +``` |
| 348 | + |
| 349 | +可以看到,在 worker 中每 3s 重试一次,总共重复三次(执行了 4 次),然后抛出异常! |
0 commit comments