在靠近用户的地方部署容器
本工程教育(EngEd)计划由科支持。
在全球范围内即时部署容器。Section是经济实惠、简单而强大的。
免费开始。
为什么你应该使用Celery与RabbitMQ
4月27日, 2021
Celery和RabbitMQ是在事件驱动架构中使用的一些工具。选择正确的工具组合并查看这些工具的例子,这些工具超越了"hello world",这就是本文要介绍的内容。
在今天的技术领域,随着公司开发需要在其微服务之间进行异步通信的解决方案,事件驱动架构也在增加。在系统架构中更快地摄取这些事件并对其进行处理,使系统架构具有持久性、弹性,并允许对数据进行批量处理。
概要
我们将涵盖的内容
在这篇文章中,我们将了解什么是任务队列,看到任务队列的例子(其中包括 Celery),了解消息代理。我们还将看看为什么我们需要使用 Celery 和 RabbitMQ,同时还将通过一个基本教程来展示其安装和使用。
了解 Celery 和 RabbitMQ
在讨论我们为什么要使用 Celery 和 RabbitMQ 之前,让我们了解它们是什么以及有哪些类似的工具。Celery 被归类为任务队列软件,而 RabbitMQ 被归类为消息代理。让我们更深入地了解这些是什么。
什么是任务队列软件
一个任务队列是一个由工作调度器维护的数据结构,包含要运行的工作。任务队列软件还管理必须在通常的HTTP请求-响应周期之外执行的后台工作。
它们是为异步操作而设计的,也就是说,操作是以非阻塞模式执行的,允许主操作继续处理。
为了进一步解释,假设我们有一个使用人工智能来增强图像的网络应用。随着用户数量的增加,增强图像的时间急剧增加,这导致了增强时的显著延迟。
因此,需要一个任务队列软件,因为它能有效地管理请求并确保应用程序顺利运行。
任务队列软件的例子
- Celery
- Redis队列
- 任务管理器
- Huey
- tasq
什么是消息代理
消息代理允许应用程序、系统和服务相互沟通和交换信息。现在,在后台有一个任务队列软件,网络应用需要知道正在进行的工作的状态,加上任何可能发生的错误和增强的结果。消息代理可以缓解这一过程,因为它是为了使独立的进程即 "相互交谈 "而建立的。
消息代理的例子
- IBM MQ
- Beanstalk
- 兔子MQ
- Redis
- 达尔文(Gearman)
- Solace
为什么我们应该选择 Celery 和 RabbitMQ 的组合?
在了解了什么是任务队列后,让我们看看 Celery。Celery 是一个用 Python 编写的开源任务队列软件。它非常轻巧,支持多个经纪商(RabbitMQ、Redis和Amazon SQS),还能与许多Web框架(如Django等)集成。
Celery的异步任务队列允许任务的执行,它的并发性使它在一些生产系统中很有用。例如,Instagram使用Celery将成千上万的任务扩展到数百万。
然而,任务执行需要消息中介来顺利工作。如上所述,Celery支持三种消息代理。虽然,对于Amazon SQS,不支持远程监控。
使用Redis作为消息代理有其局限性,例如。
- 它不支持自动复制。
- 它是手动的,需要额外的工作来把它变成一个消息代理。
- 作为一个内存解决方案。如果在建立队列的时候,机器的内存用完了,就有可能丢失任务。
Beanstalk也不支持复制。
RabbitMQ是更好的选择,因为它保证了消息的传递,是容错的,支持同步复制,它允许SSL建立加密连接,而且它对实时应用是极好的。
相比之下,Redis在发生崩溃时保留数据方面有问题,因为它是基于内存的,而且SSL选项是付费版本的一部分。
用 RabbitMQ 设置 Celery
我们将使用Ubuntu 18.04机器来设置celery应用程序和用于此设置的消息队列(RabbitMQ)。我们将配置该机器以与 Celery 和 Rabbitmq 一起工作。
安装和配置
需要的工具。
- virtualenv
- celery
- rabbitmq
我们将通过在终端上输入以下命令来安装和激活虚拟环境。
sudo pip install virtualenv
mkdir my_project && cd my_project
virtualenv celery_project
source celery_project/bin/activate
我们将使用pip安装celery。我们不使用sudo,因为我们正在安装celery到我们的虚拟环境。
pip install celery
然而,我们也需要在系统中安装rabbitmq,因为它在后台运行。
sudo apt-get install rabbitmq-server
sudo rabbitmq-server -detached
-detached 选项允许我们在后台运行rabbitmq-server。现在,我们可以使用默认值,但为我们的程序创建一个单独的虚拟主机总是一个不错的选择。
sudo rabbitmqctl add_user myuser mypassword
sudo rabbitmqctl add_vhost myvhost
sudo rabbitmqctl set_permissions -p myvhost myuser ".*" ".*" ".*"
第一个".*" 给予用户配置每个实体的能力,第二个 ".*"给予用户对每个实体的写入权限,第三个".*" 给予用户对每个实体的读取权限。
我们安装dotenv ,因为我们使用环境变量来保护我们应用程序中的敏感信息。环境变量是一个在程序之外定义的变量。这个值可以在任何时候被运行中的程序引用。
pip install python-dotenv
然后,我们在一个.env 文件中创建并添加我们的环境变量。
CELERY_BROKER_URL=amqp://myuser:mypassword@localhost/myvhost
CELERY_BACKEND_URL=db+sqlite:///test.db
由于sqlite在我们的后端URL中使用,它需要和sqlalchemy 一起安装。Sqlite 默认是可用的,因为它是 Python 使用的标准库的一部分。
pip install sqlalchemy
最后,我们安装flask ,因为它是我们的网络框架,用来链接客户端和服务器。requests 库也被安装,以进行API调用。
pip install flask
pip install requests
简单的用例
为了解释任务队列和消息代理是如何工作的,我们将采取一个有趣的项目。它是一个狗图生成器,所以我们叫它GenDog。它的工作原理是根据用户选择的品种和选择的图片数量,对**dog.ceo**进行API调用。
在刷新时,狗的图片在页面上得到更新。这个例子的代码在这个资源库中。
由于我们使用的是flask,我们的项目结构看起来像这样。
.
├── app.py
├── routes.py
├── static
│ └── css
│ └── main.css
├── templates
│ └── template.html
├── test.db
├── .env
└── url.txt
为了简单起见,我们选择使用一个文本文件来存储我们来自 API 调用的图片链接。RabbitMQ 创建了一个test.db 文件,用于存储有关所运行任务的所有元数据。现在,我们已经设置了我们的项目,并准备编写我们的GenDog应用程序。
让我们开始在我们的app.py 文件中编写一些代码。
#!/usr/bin/python
import os
from dotenv import load_dotenv
from celery import Celery
from flask import Flask, render_template
import requests
import json
load_dotenv()
# used to load our env variables
# used to setup celery with flask as per the official documentation
def make_celery(app):
celery = Celery(
app.import_name,
backend=app.config["CELERY_BACKEND_URL"],
broker=app.config["CELERY_BROKER_URL"],
)
celery.conf.update(app.config)
class ContextTask(celery.Task):
def __call__(self, *args, **kwargs):
with app.app_context():
return self.run(*args, **kwargs)
celery.Task = ContextTask
return celery
# We use the Flask framework to create an instance of the flask app
# We then update our broker and backend URLs with the env variables
flask_app = Flask(__name__)
flask_app.config.update(
CELERY_BROKER_URL=os.environ.get("CELERY_BROKER_URL"),
CELERY_BACKEND_URL=os.environ.get("CELERY_BACKEND_URL"),
)
# create an instance of celery using the function created earlier
celery = make_celery(flask_app)
# This fetches the links and returns an array of what's consumed
@celery.task()
def get_dog_pics(breed_type, limit):
url = "<https://dog.ceo/api/breed/>" + breed_type + "/images/random/" + limit
r = requests.get(url)
files = r.json()
for file in files["message"]:
with open("url.txt", "a") as myfile:
myfile.write(" " + file)
return files["message"]
# import routes as this is the client-side
import routes
我们使用json() ,将检索到的JSON转换为一个字典,这样我们就可以将检索到的值传递到我们的url.txt 文件中。接下来是我们的routes.py 。如其名所示,我们在此定义端点,与服务器端进行交互。
#!/usr/bin/python
from app import flask_app, get_dog_pics
from flask import render_template, request, redirect, url_for, jsonify
@flask_app.route("/", methods=["GET", "POST"])
def index():
# we define dog breeds so the user chooses from this list
dog_breeds = [
"affenpinscher",
"dalmatian",
"germanshepherd",
"kelpie",
"labrador",
"husky",
"otterhound",
"pitbull",
"pug",
"rottweiler",
]
# we store links in this list
pictures = []
open_file = open("url.txt", "r")
for images in open_file:
image = images.replace(",", " ")
image = image.replace('"', "")
pictures.extend(image.split())
# on form submission, the task is ran
if request.method == "POST":
if request.form["submit"] == "getDogPics":
breed_type = request.form.get("breed")
limit = request.form.get("limit")
get_dog_pics.delay(breed_type, limit)
return redirect(url_for("index"))
# an option for clearing all the links
elif request.form["submit"] == "clearDogPics":
f = open("url.txt", "w")
f.close()
return redirect(url_for("index"))
# Results
return render_template("template.html", breeds=dog_breeds, link=pictures)
在我们的routes.py 文件中,我们导入我们的函数来获取API并调用它,因为这触发了任务。我们还打开url.txt 文件,在我们的路由中使用结果(如果有的话)。
最后,有一个链接用于清除获取的图片。最后一行渲染了一个HTML模板,我们将在下面写上。我们将使用Bulma,因为它是响应式的,容易使用,而且它是纯CSS的。
模板.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<!-- <meta http-equiv="refresh" content="5" > -->
<title>GenDog™</title>
<link
rel="stylesheet"
href="<https://cdn.jsdelivr.net/npm/bulma@0.9.1/css/bulma.min.css>"
/>
<link
rel="stylesheet"
href="{{ url_for('static', filename='css/main.css') }}"
/>
</head>
<body>
<section class="section">
<div class="columns">
<div class="column is-one-third">
<div class="container">
<h1 class="title">GenDog™</h1>
<p class="subtitle">Generate Dog Pictures By Breed</p>
<form method="POST">
<div class="field">
<label class="label">Pick A Breed</label>
<div class="control">
<div class="select">
<select name="breed" required>
{% for breed in breeds %}
<option value="{{breed}}">{{breed}}</option>
{% endfor %}
</select>
</div>
</div>
</div>
<div class="field">
<label class="label">Limit</label>
<div class="control">
<input
class="input"
type="number"
style="width: 200px"
name="limit"
required
/>
</div>
</div>
<div class="field is-grouped">
<div class="control">
<button
class="button is-link"
value="getDogPics"
name="submit"
>
Submit
</button>
</div>
</div>
</form>
<form method="POST">
<div class="control mt-4">
<button
class="button is-link is-danger"
value="clearDogPics"
name="submit"
>
Clear Photos
</button>
</div>
</form>
</div>
</div>
<div class="column">
{% if link|length > 0 %}
<ul>
{% for links in link %}
<li style="margin: 5px">
<img src="{{links}}" />
</li>
{% endfor %}
</ul>
{% endif %}
</div>
</div>
</section>
</body>
</html>
画廊的其他样式是在我们的main.css 文件中完成的。
ul {
display: flex;
flex-wrap: wrap;
}
li {
height: 40vh;
flex-grow: 1;
}
img {
max-height: 100%;
min-width: 100%;
object-fit: cover;
vertical-align: bottom;
}
li:last-child {
flex-grow: 10;
}
@media (max-aspect-ratio: 1/1) {
li {
height: 30vh;
}
}
@media (max-height: 480px) {
li {
height: 80vh;
}
}
@media (max-aspect-ratio: 1/1) and (max-width: 480px) {
ul {
flex-direction: row;
}
li {
height: auto;
width: 100%;
}
img {
width: 100%;
max-height: 75vh;
min-width: 0;
}
}
下面的图片显示了在选择狗的品种和图片限制之前的初始屏幕。

在这里,我们看到狗的图片在屏幕上的渲染。呈现的图像是我们在前一个屏幕上选择的品种。

有了我们的代码设置和一切安排,最后两个步骤是启动celery worker和我们的flask服务器。
celery -A app worker -l info
然后,打开一个新的bash终端,激活virtualenv,并启动flask。
source celery_project/bin/activate
flask run
恭喜你,你已经做了第一个实现任务调度器和消息代理的应用。一开始,它似乎什么都没取到。刷新页面,你会看到图片。
我们用一个叫Flower的基于网络的监控工具来检查任务的进展。我们将不涉及这方面的内容,因为这更像是一个 "入门 "指南。
总结
在本教程中,我们看到了什么是任务队列和消息中介,我们看了几个例子,并讨论了使用队列/中介的最佳类型。我们还看到了rabbitmq broker和async celery,以及如何在后端架构中使用它们。
我们用它建立了一个狗生成器的网络应用程序,用来获取狗的图片。
我们还强调了为什么你应该在其他任务队列和消息代理之前使用Celery和RabbitMQ的一些原因。
构建愉快
Saiharsha Balasubramaniam提供的同行评审意见