介绍
随着Web应用程序的发展和使用的增加,用例也变得多样化。我们现在正在建设和使用网站来执行比以往更复杂的任务。其中一些任务立即转发给用户,而其他任务则需要进行进一步处理后转发。
Celery可以帮助我们分解复杂的工作,并由不同的机器来完成,以减轻一台机器上的负载或减少完成时间。
在本文中,我们将探讨Celery在Flask应用程序中安排后台任务的使用,以减轻资源密集型任务的负担并确定对最终用户的响应的优先级。
Flask
Flask 是一个基于 Python 的轻量级 Web 框架,WSGI 工具箱采用 Werkzeug,模板引擎使用 Jinja2。由于其不依赖于特殊的工具或库,并且没有数据抽象层、表单验证或是其他任何已有多种库可以胜任的功能,从而保持核心简单、易于扩展,而被定义为"微"框架。但是,Flask 可以通过扩展来添加应用功能。并且 Flask 具有自带开发用服务器和 debugger、集成单元测试和 RESTful 请求调度 (request dispatching)、支持 secure cookie 的特点。
Jinja2 是基于 Python 的模版引擎,支持 Unicode,具有集成的沙箱执行环境并支持选择自动转义。Jinja2 拥有强大的自动 HTML 转移系统,可以有效的阻止跨站脚本攻击;通过模版继承机制,对所有模版使用相似布局;通过在第一次加载时将源码转化为 Python 字节码从而加快模版执行时间。
Redis
Redis 是一个使用 ANSIC 语言编写、遵守 BSD 协议、Key-Value 的存储系统。拥有支持数据持久化、支持 string、map、list、set、sorted set 等数据结构和支持数据备份的特点。
Redis 会周期性地把更新的数据写入磁盘或把修改操作写入追加的记录文件,并且在此基础上实现主从(master-slave)同步,因此数据可以从主服务器向任意数量的从服务器上同步,从服务器可以是关联其他从服务器的主服务器。而且由于 Redis 完全实现了发布/订阅机制,使得从数据库在任何地方同步树时,可订阅一个频道并接收主服务器完整的消息发布记录。
MQ
MQ 消息队列是一种应用程序的通信方法,应用程序可通过读写出入对立的消息进行通信。MQ 是一种消费者-生产者 (Producer-Customer)模式的实现。生产者-消费者模式由生产者、消费者和缓存区三个模块构成。缓存区作为一个中介的存在,生产者将数据放入缓存区,消费者从缓存区取出数据。本系统中,Flask 作为生产者,Salesforce 作为消费者,而 MQ 则是中间的缓存区。应用生产者-消费者模式能够有效的降低两者之间的耦合,减少互相之间的依赖;由于缓存区的存在,消费者无需直接从生产者处获取数据,能够支持并发任务、减少阻塞。
RabbitMQ 则是由 erlang 开发的 AMQP(高级消息队列协议)的开源实现,作为一个消息队列管理工具与 Celery 集成后,负责处理服务器之间的通信任务。RabbitMQ 的使用过程如下:
- 客端连接到消息队列服务器并打开一个 channel。
- 客户端声明一个 exchange、一个 queue,并分别设置相关属性。
- 客户端使用 routing key 在 exchange 与 queue 之间绑定好关系。
- 客户端投递消息到 exchange,exchange 根据消息的 key 和设置好的 binding,将消息投递到队列中。
RabbitMQ 常用的 Exchange Type 有以下三种:
- Fanout:能够将所有发送到该 exchange 的消息投递到所有与它绑定的队列中。
- Direct:把消息投递到那些 binding key 与 routing key 完全匹配的队列中。
- Topic:将消息路由到 binding key 与 routing key 模式匹配的队列中。
什么是任务队列?
任务队列是一种分配小的工作单元或任务的机制,可以在不干扰大多数基于Web的应用程序的请求-响应周期的情况下执行这些任务。
任务队列有助于委派工作,否则将在等待响应时降低应用程序的速度。它们还可以用于在主机或进程与用户交互时处理资源密集型任务。
这样,与用户的交互是一致的,及时的,并且不受工作量的影响。
什么是Celery?
Celery是一个异步任务队列,它基于分布式消息传递来在计算机或线程之间分配工作负载。Celery由client、broker、worker组成。
worker负责执行放置在队列中的任务或工作并转发结果。使用Celery,您可以同时拥有本地和远程worker,这意味着可以通过Internet将工作委派给功能更强大的其他计算机。这样,减轻了主机上的负载,并且有更多资源可用于处理用户请求。
Celery的客户端负责向worker发布作业,并使用消息代理与他们进行通信。
此类消息代理的示例包括Redis和RabbitMQ。
为什么要使用Celery?
出于各种原因,我们应该选择Celery执行我们的后台任务。首先,它具有很好的可扩展性,允许按需添加更多的worker,以适应增加的负载或流量。 Celery仍在积极开发中,有简洁的文档和活跃的用户社区。
另一个优点是Celery易于集成到多个Web框架中,其中大多数都具有促进集成的库。
它还提供了webhooks与其他Web应用程序交互。
Celery还可以使用各种消息代理,这为我们提供了灵活性。建议使用RabbitMQ,但它也可以支持Redis和Beanstalk。
我们将构建一个Flask应用程序,该应用程序允许用户设置提醒,该提醒将在设定的时间传递到他们的电子邮件中。
文件结构树如下:
.
├── Pipfile # manage our environment
├── Pipfile.lock
├── README.md
├── __init__.py
├── app.py # main Flask application implementation
├── config.py # to host the configuration
├── requirements.txt # store our requirements
└── templates
└── index.html # the landing page
1 directory, 8 files
参考资料
- 本文最新版本地址
- 本文涉及的python测试开发库 谢谢点赞!
- 本文相关海量书籍下载
- python工具书籍下载-持续更新
- Windows 支持 https://stackoverflow.com/questions/37255548/how-to-run-celery-on-windows
- 本文配套视频 https://sn9.us/file/18113597-406685860 前面30分钟
- https://www.agiliq.com/blog/2015/07/getting-started-with-celery-and-redis/
- https://redis.io/topics/rediscli
- https://stackoverflow.com/questions/19354826/how-to-get-all-keys-with-their-values-in-redis
- https://pypi.org/project/redis/
实现
我们将Redis用作消息代理,我们可以在其主页上找到设置它的说明。 https://redis.io/topics/quickstart
实现
集成Celery
# Existing imports are maintained
from celery import Celery
# Flask app and flask-mail configuration truncated
# Set up celery client
client = Celery(app.name, broker=app.config['CELERY_BROKER_URL'])
client.conf.update(app.config)
# Add this decorator to our send_mail function
@client.task
def send_mail(data):
# Function remains the same
@app.route('/', methods=['GET', 'POST'])
def index():
if request.method == 'GET':
return render_template('index.html')
elif request.method == 'POST':
data = {}
data['email'] = request.form['email']
data['first_name'] = request.form['first_name']
data['last_name'] = request.form['last_name']
data['message'] = request.form['message']
duration = int(request.form['duration'])
duration_unit = request.form['duration_unit']
if duration_unit == 'minutes':
duration *= 60
elif duration_unit == 'hours':
duration *= 3600
elif duration_unit == 'days':
duration *= 86400
send_mail.apply_async(args=[data], countdown=duration)
flash(f"Email will be sent to {data['email']} in {request.form['duration']} {duration_unit}")
return redirect(url_for('index'))
执行
$ python app.py
$ celery worker -A app.client --loglevel=info
在ubuntu 18调试通过,如果大家需要代码或者技术支持,请加图片上的群。