为什么你应该用RabbitMQ使用Celery?

1,036 阅读9分钟

在靠近用户的地方部署容器

本工程教育(EngEd)计划由科支持。

在全球范围内即时部署容器。Section是经济实惠、简单而强大的。

免费开始

为什么你应该使用Celery与RabbitMQ

4月27日, 2021

Celery和RabbitMQ是在事件驱动架构中使用的一些工具。选择正确的工具组合并查看这些工具的例子,这些工具超越了"hello world",这就是本文要介绍的内容。

在今天的技术领域,随着公司开发需要在其微服务之间进行异步通信的解决方案,事件驱动架构也在增加。在系统架构中更快地摄取这些事件并对其进行处理,使系统架构具有持久性、弹性,并允许对数据进行批量处理。

概要

  1. 我们将涵盖的内容
  2. 了解 Celery 和 RabbitMQ
  3. 为什么我们应该选择 Celery 和 RabbitMQ 组合?
  4. 用 RabbitMQ 设置 Celery
  5. 总结

我们将涵盖的内容

在这篇文章中,我们将了解什么是任务队列,看到任务队列的例子(其中包括 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&#8482;</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&#8482;</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;
  }
}

下面的图片显示了在选择狗的品种和图片限制之前的初始屏幕。

Output Screen 1

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

Output Screen 2

有了我们的代码设置和一切安排,最后两个步骤是启动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提供的同行评审意见