Tarefas Assíncronas Com Flask E Redis Queue

Se uma tarefa de longa duração fizer parte do fluxo de trabalho do seu aplicativo, você deve tratá-la em segundo plano, fora do fluxo normal.

Talvez seu aplicativo da web exija que os usuários enviem uma miniatura (que provavelmente precisará ser redimensionada) e confirme seu e-mail quando se registrarem. Se seu aplicativo processou a imagem e enviou um e-mail de confirmação diretamente no manipulador de solicitação, o usuário final teria que esperar que ambos terminassem. Em vez disso, você desejará passar essas tarefas para uma fila de tarefas e deixar que um processo de trabalho separado lide com isso, para que você possa enviar imediatamente uma resposta de volta ao cliente. O usuário final pode fazer outras coisas no lado do cliente e seu aplicativo fica livre para responder às solicitações de outros usuários.

Este tutorial analisa como configurar o Redis Queue (RQ) para lidar com tarefas de longa duração em um aplicativo Flask.

Objetivos

Ao final deste tutorial, você será capaz de:

  1. Integre o Redis Queue em um aplicativo Flask e crie tarefas.
  2. Conteinerize o Flask e o Redis com o Docker.
  3. Execute tarefas de longa duração em segundo plano com um processo de trabalho separado.
  4. Configure o RQ Dashboard para monitorar filas, trabalhos e trabalhadores.
  5. Dimensione a contagem de trabalhadores com o Docker.

Fluxo de trabalho

Nosso objetivo é desenvolver um aplicativo Flask que funcione em conjunto com o Redis Queue para lidar com processos de longa execução fora do ciclo normal de solicitação/resposta.

  1. O usuário final inicia uma nova tarefa por meio de uma solicitação POST para o lado do servidor
  2. Dentro da visualização, uma tarefa é adicionada à fila e o ID da tarefa é enviado de volta ao lado do cliente
  3. Usando AJAX, o cliente continua pesquisando o servidor para verificar o status da tarefa enquanto a própria tarefa está sendo executada em segundo plano

fluxo de usuário da fila de frasco e redis

No final, o aplicativo ficará assim:

aplicativo final

Configuração do projeto

Quer acompanhar? Clone o projeto base e revise o código e a estrutura do projeto:

$ git clone https://github.com/mjhea0/flask-redis-queue --branch base --single-branch
$ cd flask-redis-queue

Como precisaremos gerenciar três processos no total (Flask, Redis, worker), usaremos o Docker para simplificar nosso fluxo de trabalho para que eles possam ser gerenciados em uma única janela de terminal.

Para testar, execute:

$ docker-compose up -d --build

Abra seu navegador para http://localhost:5004 . Você deveria ver:

frasco, fila redis, janela de encaixe

Acionar uma tarefa

Um manipulador de eventos em project/client/static/main.js é configurado para escutar um clique de botão e enviar uma solicitação POST AJAX para o servidor com o tipo de tarefa apropriado: 1, 2ou 3.

$('.btn').on('click', function() {
  $.ajax({
    url: '/tasks',
    data: { type: $(this).data('type') },
    method: 'POST'
  })
  .done((res) => {
    getStatus(res.data.task_id);
  })
  .fail((err) => {
    console.log(err);
  });
});

No lado do servidor, uma visualização já está configurada para lidar com a solicitação em project/server/main/views.py :

@main_blueprint.route("/tasks", methods=["POST"])
def run_task():
    task_type = request.form["type"]
    return jsonify(task_type), 202

Só precisamos conectar o Redis Queue.

Fila de retorno

Então, precisamos criar dois novos processos: Redis e um trabalhador. Adicione-os ao arquivo docker-compose.yml :

version: '3.8'

services:

  web:
    build: .
    image: web
    container_name: web
    ports:
      - 5004:5000
    command: python manage.py run -h 0.0.0.0
    volumes:
      - .:/usr/src/app
    environment:
      - FLASK_DEBUG=1
      - APP_SETTINGS=project.server.config.DevelopmentConfig
    depends_on:
      - redis

  worker:
    image: web
    command: python manage.py run_worker
    volumes:
      - .:/usr/src/app
    environment:
      - APP_SETTINGS=project.server.config.DevelopmentConfig
    depends_on:
      - redis

  redis:
    image: redis:6.2-alpine

Adicione a tarefa a um novo arquivo chamado tasks.py em "project/server/main":

# project/server/main/tasks.py


import time


def create_task(task_type):
    time.sleep(int(task_type) * 10)
    return True

Atualize a visualização para se conectar ao Redis, enfileirar a tarefa e responder com o id:

@main_blueprint.route("/tasks", methods=["POST"])
def run_task():
    task_type = request.form["type"]
    with Connection(redis.from_url(current_app.config["REDIS_URL"])):
        q = Queue()
        task = q.enqueue(create_task, task_type)
    response_object = {
        "status": "success",
        "data": {
            "task_id": task.get_id()
        }
    }
    return jsonify(response_object), 202

Não esqueça das importações:

import redis
from rq import Queue, Connection
from flask import render_template, Blueprint, jsonify, request, current_app

from project.server.main.tasks import create_task

Atualização BaseConfig:

class BaseConfig(object):
    """Base configuration."""

    WTF_CSRF_ENABLED = True
    REDIS_URL = "redis://redis:6379/0"
    QUEUES = ["default"]

Você notou que referenciamos o redisserviço (de docker-compose.yml ) em REDIS_URLvez de localhostou em algum outro IP? Revise os documentos do Docker Compose para obter mais informações sobre como se conectar a outros serviços por meio do nome do host.

Por fim, podemos usar um Redis Queue worker , para processar tarefas no topo da fila.

gerencie.py :

@cli.command("run_worker")
def run_worker():
    redis_url = app.config["REDIS_URL"]
    redis_connection = redis.from_url(redis_url)
    with Connection(redis_connection):
        worker = Worker(app.config["QUEUES"])
        worker.work()

Aqui, configuramos um comando CLI personalizado para acionar o trabalhador.

É importante observar que o @cli.command()decorador fornecerá acesso ao contexto do aplicativo junto com as variáveis ​​de configuração associadas de project/server/config.py quando o comando for executado.

Adicione as importações também:

import redis
from rq import Connection, Worker

Adicione as dependências ao arquivo de requisitos:

redis==4.1.1
rq==1.10.1

Construa e gire os novos contêineres:

$ docker-compose up -d --build

Para acionar uma nova tarefa, execute:

$ curl -F type=0 http://localhost:5004/tasks

Você deve ver algo como:

{
  "data": {
    "task_id": "bdad64d0-3865-430e-9cc3-ec1410ddb0fd"
  },
  "status": "success"
}

Status da tarefa

Volte para o manipulador de eventos no lado do cliente:

$('.btn').on('click', function() {
  $.ajax({
    url: '/tasks',
    data: { type: $(this).data('type') },
    method: 'POST'
  })
  .done((res) => {
    getStatus(res.data.task_id);
  })
  .fail((err) => {
    console.log(err);
  });
});

Depois que a resposta voltar da solicitação AJAX original, continuamos a chamar getStatus()com o ID da tarefa a cada segundo. Se a resposta for bem-sucedida, uma nova linha será adicionada à tabela no DOM.

function getStatus(taskID) {
  $.ajax({
    url: `/tasks/${taskID}`,
    method: 'GET',
  })
  .done((res) => {
    const html = `
    <tr>
      <td>${res.data.task_id}</td>
      <td>${res.data.task_status}</td>
      <td>${res.data.task_result}</td>
    </tr>`;
    $('#tasks').prepend(html);
    const taskStatus = res.data.task_status;
    if (taskStatus === 'finished' || taskStatus === 'failed') return false;
    setTimeout(function () {
      getStatus(res.data.task_id);
    }, 1000);
  })
  .fail((err) => {
    console.log(err);
  });
}

Atualize a visualização:

@main_blueprint.route("/tasks/<task_id>", methods=["GET"])
def get_status(task_id):
    with Connection(redis.from_url(current_app.config["REDIS_URL"])):
        q = Queue()
        task = q.fetch_job(task_id)
    if task:
        response_object = {
            "status": "success",
            "data": {
                "task_id": task.get_id(),
                "task_status": task.get_status(),
                "task_result": task.result,
            },
        }
    else:
        response_object = {"status": "error"}
    return jsonify(response_object)

Adicione uma nova tarefa à fila:

$ curl -F type=1 http://localhost:5004/tasks

Em seguida, pegue o task_idda resposta e chame o endpoint atualizado para visualizar o status:

$ curl http://localhost:5004/tasks/5819789f-ebd7-4e67-afc3-5621c28acf02

{
  "data": {
    "task_id": "5819789f-ebd7-4e67-afc3-5621c28acf02",
    "task_result": true,
    "task_status": "finished"
  },
  "status": "success"
}

Teste também no navegador:

frasco, fila redis, janela de encaixe

Painel

O RQ Dashboard é um sistema de monitoramento leve e baseado na Web para Redis Queue.

Para configurar, primeiro adicione um novo diretório ao diretório "project" chamado "dashboard". Em seguida, adicione um novo Dockerfile a esse diretório recém-criado:

FROM python:3.10-alpine

RUN pip install rq-dashboard

# https://github.com/rq/rq/issues/1469
RUN pip uninstall -y click
RUN pip install click==7.1.2

EXPOSE 9181

CMD ["rq-dashboard"]

Basta adicionar o serviço ao arquivo docker-compose.yml assim:

version: '3.8'

services:

  web:
    build: .
    image: web
    container_name: web
    ports:
      - 5004:5000
    command: python manage.py run -h 0.0.0.0
    volumes:
      - .:/usr/src/app
    environment:
      - FLASK_DEBUG=1
      - APP_SETTINGS=project.server.config.DevelopmentConfig
    depends_on:
      - redis

  worker:
    image: web
    command: python manage.py run_worker
    volumes:
      - .:/usr/src/app
    environment:
      - APP_SETTINGS=project.server.config.DevelopmentConfig
    depends_on:
      - redis

  redis:
    image: redis:6.2-alpine

  dashboard:
    build: ./project/dashboard
    image: dashboard
    container_name: dashboard
    ports:
      - 9181:9181
    command: rq-dashboard -H redis
    depends_on:
      - redis

Construa a imagem e gire o contêiner:

$ docker-compose up -d --build

Navegue até http://localhost:9181 para visualizar o painel:

painel rq

Dê início a alguns trabalhos para testar completamente o painel:

painel rq

Tente adicionar mais alguns trabalhadores para ver como isso afeta as coisas:

$ docker-compose up -d --build --scale worker=3

Conclusão

Este foi um guia básico sobre como configurar o Redis Queue para executar tarefas de longa duração em um aplicativo Flask. Você deve deixar a fila lidar com quaisquer processos que possam bloquear ou retardar o código voltado para o usuário.

Pegue o código do repositório .

Fonte:  https://testdrive.io

#flask #redis 

What is GEEK

Buddha Community

Tarefas Assíncronas Com Flask E Redis Queue

Tarefas Assíncronas Com Flask E Redis Queue

Se uma tarefa de longa duração fizer parte do fluxo de trabalho do seu aplicativo, você deve tratá-la em segundo plano, fora do fluxo normal.

Talvez seu aplicativo da web exija que os usuários enviem uma miniatura (que provavelmente precisará ser redimensionada) e confirme seu e-mail quando se registrarem. Se seu aplicativo processou a imagem e enviou um e-mail de confirmação diretamente no manipulador de solicitação, o usuário final teria que esperar que ambos terminassem. Em vez disso, você desejará passar essas tarefas para uma fila de tarefas e deixar que um processo de trabalho separado lide com isso, para que você possa enviar imediatamente uma resposta de volta ao cliente. O usuário final pode fazer outras coisas no lado do cliente e seu aplicativo fica livre para responder às solicitações de outros usuários.

Este tutorial analisa como configurar o Redis Queue (RQ) para lidar com tarefas de longa duração em um aplicativo Flask.

Objetivos

Ao final deste tutorial, você será capaz de:

  1. Integre o Redis Queue em um aplicativo Flask e crie tarefas.
  2. Conteinerize o Flask e o Redis com o Docker.
  3. Execute tarefas de longa duração em segundo plano com um processo de trabalho separado.
  4. Configure o RQ Dashboard para monitorar filas, trabalhos e trabalhadores.
  5. Dimensione a contagem de trabalhadores com o Docker.

Fluxo de trabalho

Nosso objetivo é desenvolver um aplicativo Flask que funcione em conjunto com o Redis Queue para lidar com processos de longa execução fora do ciclo normal de solicitação/resposta.

  1. O usuário final inicia uma nova tarefa por meio de uma solicitação POST para o lado do servidor
  2. Dentro da visualização, uma tarefa é adicionada à fila e o ID da tarefa é enviado de volta ao lado do cliente
  3. Usando AJAX, o cliente continua pesquisando o servidor para verificar o status da tarefa enquanto a própria tarefa está sendo executada em segundo plano

fluxo de usuário da fila de frasco e redis

No final, o aplicativo ficará assim:

aplicativo final

Configuração do projeto

Quer acompanhar? Clone o projeto base e revise o código e a estrutura do projeto:

$ git clone https://github.com/mjhea0/flask-redis-queue --branch base --single-branch
$ cd flask-redis-queue

Como precisaremos gerenciar três processos no total (Flask, Redis, worker), usaremos o Docker para simplificar nosso fluxo de trabalho para que eles possam ser gerenciados em uma única janela de terminal.

Para testar, execute:

$ docker-compose up -d --build

Abra seu navegador para http://localhost:5004 . Você deveria ver:

frasco, fila redis, janela de encaixe

Acionar uma tarefa

Um manipulador de eventos em project/client/static/main.js é configurado para escutar um clique de botão e enviar uma solicitação POST AJAX para o servidor com o tipo de tarefa apropriado: 1, 2ou 3.

$('.btn').on('click', function() {
  $.ajax({
    url: '/tasks',
    data: { type: $(this).data('type') },
    method: 'POST'
  })
  .done((res) => {
    getStatus(res.data.task_id);
  })
  .fail((err) => {
    console.log(err);
  });
});

No lado do servidor, uma visualização já está configurada para lidar com a solicitação em project/server/main/views.py :

@main_blueprint.route("/tasks", methods=["POST"])
def run_task():
    task_type = request.form["type"]
    return jsonify(task_type), 202

Só precisamos conectar o Redis Queue.

Fila de retorno

Então, precisamos criar dois novos processos: Redis e um trabalhador. Adicione-os ao arquivo docker-compose.yml :

version: '3.8'

services:

  web:
    build: .
    image: web
    container_name: web
    ports:
      - 5004:5000
    command: python manage.py run -h 0.0.0.0
    volumes:
      - .:/usr/src/app
    environment:
      - FLASK_DEBUG=1
      - APP_SETTINGS=project.server.config.DevelopmentConfig
    depends_on:
      - redis

  worker:
    image: web
    command: python manage.py run_worker
    volumes:
      - .:/usr/src/app
    environment:
      - APP_SETTINGS=project.server.config.DevelopmentConfig
    depends_on:
      - redis

  redis:
    image: redis:6.2-alpine

Adicione a tarefa a um novo arquivo chamado tasks.py em "project/server/main":

# project/server/main/tasks.py


import time


def create_task(task_type):
    time.sleep(int(task_type) * 10)
    return True

Atualize a visualização para se conectar ao Redis, enfileirar a tarefa e responder com o id:

@main_blueprint.route("/tasks", methods=["POST"])
def run_task():
    task_type = request.form["type"]
    with Connection(redis.from_url(current_app.config["REDIS_URL"])):
        q = Queue()
        task = q.enqueue(create_task, task_type)
    response_object = {
        "status": "success",
        "data": {
            "task_id": task.get_id()
        }
    }
    return jsonify(response_object), 202

Não esqueça das importações:

import redis
from rq import Queue, Connection
from flask import render_template, Blueprint, jsonify, request, current_app

from project.server.main.tasks import create_task

Atualização BaseConfig:

class BaseConfig(object):
    """Base configuration."""

    WTF_CSRF_ENABLED = True
    REDIS_URL = "redis://redis:6379/0"
    QUEUES = ["default"]

Você notou que referenciamos o redisserviço (de docker-compose.yml ) em REDIS_URLvez de localhostou em algum outro IP? Revise os documentos do Docker Compose para obter mais informações sobre como se conectar a outros serviços por meio do nome do host.

Por fim, podemos usar um Redis Queue worker , para processar tarefas no topo da fila.

gerencie.py :

@cli.command("run_worker")
def run_worker():
    redis_url = app.config["REDIS_URL"]
    redis_connection = redis.from_url(redis_url)
    with Connection(redis_connection):
        worker = Worker(app.config["QUEUES"])
        worker.work()

Aqui, configuramos um comando CLI personalizado para acionar o trabalhador.

É importante observar que o @cli.command()decorador fornecerá acesso ao contexto do aplicativo junto com as variáveis ​​de configuração associadas de project/server/config.py quando o comando for executado.

Adicione as importações também:

import redis
from rq import Connection, Worker

Adicione as dependências ao arquivo de requisitos:

redis==4.1.1
rq==1.10.1

Construa e gire os novos contêineres:

$ docker-compose up -d --build

Para acionar uma nova tarefa, execute:

$ curl -F type=0 http://localhost:5004/tasks

Você deve ver algo como:

{
  "data": {
    "task_id": "bdad64d0-3865-430e-9cc3-ec1410ddb0fd"
  },
  "status": "success"
}

Status da tarefa

Volte para o manipulador de eventos no lado do cliente:

$('.btn').on('click', function() {
  $.ajax({
    url: '/tasks',
    data: { type: $(this).data('type') },
    method: 'POST'
  })
  .done((res) => {
    getStatus(res.data.task_id);
  })
  .fail((err) => {
    console.log(err);
  });
});

Depois que a resposta voltar da solicitação AJAX original, continuamos a chamar getStatus()com o ID da tarefa a cada segundo. Se a resposta for bem-sucedida, uma nova linha será adicionada à tabela no DOM.

function getStatus(taskID) {
  $.ajax({
    url: `/tasks/${taskID}`,
    method: 'GET',
  })
  .done((res) => {
    const html = `
    <tr>
      <td>${res.data.task_id}</td>
      <td>${res.data.task_status}</td>
      <td>${res.data.task_result}</td>
    </tr>`;
    $('#tasks').prepend(html);
    const taskStatus = res.data.task_status;
    if (taskStatus === 'finished' || taskStatus === 'failed') return false;
    setTimeout(function () {
      getStatus(res.data.task_id);
    }, 1000);
  })
  .fail((err) => {
    console.log(err);
  });
}

Atualize a visualização:

@main_blueprint.route("/tasks/<task_id>", methods=["GET"])
def get_status(task_id):
    with Connection(redis.from_url(current_app.config["REDIS_URL"])):
        q = Queue()
        task = q.fetch_job(task_id)
    if task:
        response_object = {
            "status": "success",
            "data": {
                "task_id": task.get_id(),
                "task_status": task.get_status(),
                "task_result": task.result,
            },
        }
    else:
        response_object = {"status": "error"}
    return jsonify(response_object)

Adicione uma nova tarefa à fila:

$ curl -F type=1 http://localhost:5004/tasks

Em seguida, pegue o task_idda resposta e chame o endpoint atualizado para visualizar o status:

$ curl http://localhost:5004/tasks/5819789f-ebd7-4e67-afc3-5621c28acf02

{
  "data": {
    "task_id": "5819789f-ebd7-4e67-afc3-5621c28acf02",
    "task_result": true,
    "task_status": "finished"
  },
  "status": "success"
}

Teste também no navegador:

frasco, fila redis, janela de encaixe

Painel

O RQ Dashboard é um sistema de monitoramento leve e baseado na Web para Redis Queue.

Para configurar, primeiro adicione um novo diretório ao diretório "project" chamado "dashboard". Em seguida, adicione um novo Dockerfile a esse diretório recém-criado:

FROM python:3.10-alpine

RUN pip install rq-dashboard

# https://github.com/rq/rq/issues/1469
RUN pip uninstall -y click
RUN pip install click==7.1.2

EXPOSE 9181

CMD ["rq-dashboard"]

Basta adicionar o serviço ao arquivo docker-compose.yml assim:

version: '3.8'

services:

  web:
    build: .
    image: web
    container_name: web
    ports:
      - 5004:5000
    command: python manage.py run -h 0.0.0.0
    volumes:
      - .:/usr/src/app
    environment:
      - FLASK_DEBUG=1
      - APP_SETTINGS=project.server.config.DevelopmentConfig
    depends_on:
      - redis

  worker:
    image: web
    command: python manage.py run_worker
    volumes:
      - .:/usr/src/app
    environment:
      - APP_SETTINGS=project.server.config.DevelopmentConfig
    depends_on:
      - redis

  redis:
    image: redis:6.2-alpine

  dashboard:
    build: ./project/dashboard
    image: dashboard
    container_name: dashboard
    ports:
      - 9181:9181
    command: rq-dashboard -H redis
    depends_on:
      - redis

Construa a imagem e gire o contêiner:

$ docker-compose up -d --build

Navegue até http://localhost:9181 para visualizar o painel:

painel rq

Dê início a alguns trabalhos para testar completamente o painel:

painel rq

Tente adicionar mais alguns trabalhadores para ver como isso afeta as coisas:

$ docker-compose up -d --build --scale worker=3

Conclusão

Este foi um guia básico sobre como configurar o Redis Queue para executar tarefas de longa duração em um aplicativo Flask. Você deve deixar a fila lidar com quaisquer processos que possam bloquear ou retardar o código voltado para o usuário.

Pegue o código do repositório .

Fonte:  https://testdrive.io

#flask #redis 

Loma  Baumbach

Loma Baumbach

1596679140

Redis Transactions & Long-Running Lua Scripts

Redis offers two mechanisms for handling transactions – MULTI/EXEC based transactions and Lua scripts evaluation. Redis Lua scripting is the recommended approach and is fairly popular in usage.

Our Redis™ customers who have Lua scripts deployed often report this error – “BUSY Redis is busy running a script. You can only call SCRIPT KILL or SHUTDOWN NOSAVE”. In this post, we will explain the Redis transactional property of scripts, what this error is about, and why we must be extra careful about it on Sentinel-managed systems that can failover.

Redis Lua Scripts Diagram - ScaleGrid Blog

Transactional Nature of Redis Lua Scripts

Redis “transactions” aren’t really transactions as understood conventionally – in case of errors, there is no rollback of writes made by the script.

Atomicity” of Redis scripts is guaranteed in the following manner:

  • Once a script begins executing, all other commands/scripts are blocked until the script completes. So, other clients either see the changes made by the script or they don’t. This is because they can only execute either before the script or after the script.
  • However, Redis doesn’t do rollbacks, so on an error within a script, any changes already made by the script will be retained and future commands/scripts will see those partial changes.
  • Since all other clients are blocked while the script executes, it is critical that the script is well-behaved and finishes in time.

The ‘lua-time-limit’ Value

It is highly recommended that the script complete within a time limit. Redis enforces this in a weak manner with the ‘lua-time-limit’ value. This is the maximum allowed time (in ms) that the script is allowed to run. The default value is 5 seconds. This is a really long time for CPU-bound activity (scripts have limited access and can’t run commands that access the disk).

However, the script is not killed when it executes beyond this time. Redis starts accepting client commands again, but responds to them with a BUSY error.

If you must kill the script at this point, there are two options available:

  • SCRIPT KILL command can be used to stop a script that hasn’t yet done any writes.
  • If the script has already performed writes to the server and must still be killed, use the SHUTDOWN NOSAVE to shutdown the server completely.

It is usually better to just wait for the script to complete its operation. The complete information on methods to kill the script execution and related behavior are available in the documentation.

#cloud #database #developer #high availability #howto #redis #scalegrid #lua-time-limit #redis diagram #redis master #redis scripts #redis sentinel #redis servers #redis transactions #sentinel-managed #server failures

How to Asynchronous Tasks with Flask and Redis Queue

If a long-running task is part of your application's workflow you should handle it in the background, outside the normal flow.

Perhaps your web application requires users to submit a thumbnail (which will probably need to be re-sized) and confirm their email when they register. If your application processed the image and sent a confirmation email directly in the request handler, then the end user would have to wait for them both to finish. Instead, you'll want to pass these tasks off to a task queue and let a separate worker process deal with it, so you can immediately send a response back to the client. The end user can do other things on the client-side and your application is free to respond to requests from other users.

This tutorial looks at how to configure Redis Queue (RQ) to handle long-running tasks in a Flask app.

Celery is a viable solution as well. Check out Asynchronous Tasks with Flask and Celery for more.

Objectives

By the end of this tutorial, you will be able to:

  1. Integrate Redis Queue into a Flask app and create tasks.
  2. Containerize Flask and Redis with Docker.
  3. Run long-running tasks in the background with a separate worker process.
  4. Set up RQ Dashboard to monitor queues, jobs, and workers.
  5. Scale the worker count with Docker.

Workflow

Our goal is to develop a Flask application that works in conjunction with Redis Queue to handle long-running processes outside the normal request/response cycle.

  1. The end user kicks off a new task via a POST request to the server-side
  2. Within the view, a task is added to the queue and the task id is sent back to the client-side
  3. Using AJAX, the client continues to poll the server to check the status of the task while the task itself is running in the background

flask and redis queue user flow

In the end, the app will look like this:

final app

Project Setup

Want to follow along? Clone down the base project, and then review the code and project structure:

$ git clone https://github.com/mjhea0/flask-redis-queue --branch base --single-branch
$ cd flask-redis-queue

Since we'll need to manage three processes in total (Flask, Redis, worker), we'll use Docker to simplify our workflow so they can be managed from a single terminal window.

To test, run:

$ docker-compose up -d --build

Open your browser to http://localhost:5004. You should see:

flask, redis queue, docker

Trigger a Task

An event handler in project/client/static/main.js is set up that listens for a button click and sends an AJAX POST request to the server with the appropriate task type: 1, 2, or 3.

$('.btn').on('click', function() {
  $.ajax({
    url: '/tasks',
    data: { type: $(this).data('type') },
    method: 'POST'
  })
  .done((res) => {
    getStatus(res.data.task_id);
  })
  .fail((err) => {
    console.log(err);
  });
});

On the server-side, a view is already configured to handle the request in project/server/main/views.py:

@main_blueprint.route("/tasks", methods=["POST"])
def run_task():
    task_type = request.form["type"]
    return jsonify(task_type), 202

We just need to wire up Redis Queue.

Redis Queue

So, we need to spin up two new processes: Redis and a worker. Add them to the docker-compose.yml file:

version: '3.8'

services:

  web:
    build: .
    image: web
    container_name: web
    ports:
      - 5004:5000
    command: python manage.py run -h 0.0.0.0
    volumes:
      - .:/usr/src/app
    environment:
      - FLASK_DEBUG=1
      - APP_SETTINGS=project.server.config.DevelopmentConfig
    depends_on:
      - redis

  worker:
    image: web
    command: python manage.py run_worker
    volumes:
      - .:/usr/src/app
    environment:
      - APP_SETTINGS=project.server.config.DevelopmentConfig
    depends_on:
      - redis

  redis:
    image: redis:6.2-alpine

Add the task to a new file called tasks.py in "project/server/main":

# project/server/main/tasks.py


import time


def create_task(task_type):
    time.sleep(int(task_type) * 10)
    return True

Update the view to connect to Redis, enqueue the task, and respond with the id:

@main_blueprint.route("/tasks", methods=["POST"])
def run_task():
    task_type = request.form["type"]
    with Connection(redis.from_url(current_app.config["REDIS_URL"])):
        q = Queue()
        task = q.enqueue(create_task, task_type)
    response_object = {
        "status": "success",
        "data": {
            "task_id": task.get_id()
        }
    }
    return jsonify(response_object), 202

Don't forget the imports:

import redis
from rq import Queue, Connection
from flask import render_template, Blueprint, jsonify, request, current_app

from project.server.main.tasks import create_task

Update BaseConfig:

class BaseConfig(object):
    """Base configuration."""

    WTF_CSRF_ENABLED = True
    REDIS_URL = "redis://redis:6379/0"
    QUEUES = ["default"]

Did you notice that we referenced the redis service (from docker-compose.yml) in the REDIS_URL rather than localhost or some other IP? Review the Docker Compose docs for more info on connecting to other services via the hostname.

Finally, we can use a Redis Queue worker, to process tasks at the top of the queue.

manage.py:

@cli.command("run_worker")
def run_worker():
    redis_url = app.config["REDIS_URL"]
    redis_connection = redis.from_url(redis_url)
    with Connection(redis_connection):
        worker = Worker(app.config["QUEUES"])
        worker.work()

Here, we set up a custom CLI command to fire the worker.

It's important to note that the @cli.command() decorator will provide access to the application context along with the associated config variables from project/server/config.py when the command is executed.

Add the imports as well:

import redis
from rq import Connection, Worker

Add the dependencies to the requirements file:

redis==4.1.1
rq==1.10.1

Build and spin up the new containers:

$ docker-compose up -d --build

To trigger a new task, run:

$ curl -F type=0 http://localhost:5004/tasks

You should see something like:

{
  "data": {
    "task_id": "bdad64d0-3865-430e-9cc3-ec1410ddb0fd"
  },
  "status": "success"
}

Task Status

Turn back to the event handler on the client-side:

$('.btn').on('click', function() {
  $.ajax({
    url: '/tasks',
    data: { type: $(this).data('type') },
    method: 'POST'
  })
  .done((res) => {
    getStatus(res.data.task_id);
  })
  .fail((err) => {
    console.log(err);
  });
});

Once the response comes back from the original AJAX request, we then continue to call getStatus() with the task id every second. If the response is successful, a new row is added to the table on the DOM.

function getStatus(taskID) {
  $.ajax({
    url: `/tasks/${taskID}`,
    method: 'GET',
  })
  .done((res) => {
    const html = `
    <tr>
      <td>${res.data.task_id}</td>
      <td>${res.data.task_status}</td>
      <td>${res.data.task_result}</td>
    </tr>`;
    $('#tasks').prepend(html);
    const taskStatus = res.data.task_status;
    if (taskStatus === 'finished' || taskStatus === 'failed') return false;
    setTimeout(function () {
      getStatus(res.data.task_id);
    }, 1000);
  })
  .fail((err) => {
    console.log(err);
  });
}

Update the view:

@main_blueprint.route("/tasks/<task_id>", methods=["GET"])
def get_status(task_id):
    with Connection(redis.from_url(current_app.config["REDIS_URL"])):
        q = Queue()
        task = q.fetch_job(task_id)
    if task:
        response_object = {
            "status": "success",
            "data": {
                "task_id": task.get_id(),
                "task_status": task.get_status(),
                "task_result": task.result,
            },
        }
    else:
        response_object = {"status": "error"}
    return jsonify(response_object)

Add a new task to the queue:

$ curl -F type=1 http://localhost:5004/tasks

Then, grab the task_id from the response and call the updated endpoint to view the status:

$ curl http://localhost:5004/tasks/5819789f-ebd7-4e67-afc3-5621c28acf02

{
  "data": {
    "task_id": "5819789f-ebd7-4e67-afc3-5621c28acf02",
    "task_result": true,
    "task_status": "finished"
  },
  "status": "success"
}

Test it out in the browser as well:

flask, redis queue, docker

Dashboard

RQ Dashboard is a lightweight, web-based monitoring system for Redis Queue.

To set up, first add a new directory to the "project" directory called "dashboard". Then, add a new Dockerfile to that newly created directory:

FROM python:3.10-alpine

RUN pip install rq-dashboard

# https://github.com/rq/rq/issues/1469
RUN pip uninstall -y click
RUN pip install click==7.1.2

EXPOSE 9181

CMD ["rq-dashboard"]

Simply add the service to the docker-compose.yml file like so:

version: '3.8'

services:

  web:
    build: .
    image: web
    container_name: web
    ports:
      - 5004:5000
    command: python manage.py run -h 0.0.0.0
    volumes:
      - .:/usr/src/app
    environment:
      - FLASK_DEBUG=1
      - APP_SETTINGS=project.server.config.DevelopmentConfig
    depends_on:
      - redis

  worker:
    image: web
    command: python manage.py run_worker
    volumes:
      - .:/usr/src/app
    environment:
      - APP_SETTINGS=project.server.config.DevelopmentConfig
    depends_on:
      - redis

  redis:
    image: redis:6.2-alpine

  dashboard:
    build: ./project/dashboard
    image: dashboard
    container_name: dashboard
    ports:
      - 9181:9181
    command: rq-dashboard -H redis
    depends_on:
      - redis

Build the image and spin up the container:

$ docker-compose up -d --build

Navigate to http://localhost:9181 to view the dashboard:

rq dashboard

Kick off a few jobs to fully test the dashboard:

rq dashboard

Try adding a few more workers to see how that affects things:

$ docker-compose up -d --build --scale worker=3

Conclusion

This has been a basic guide on how to configure Redis Queue to run long-running tasks in a Flask app. You should let the queue handle any processes that could block or slow down the user-facing code.

Looking for some challenges?

  1. Deploy this application across a number of DigitalOcean droplets using Kubernetes or Docker Swarm.
  2. Write unit tests for the new endpoints. (Mock out the Redis instance with fakeredis)
  3. Instead of polling the server, try using Flask-SocketIO to open up a websocket connection.

Grab the code from the repo.

Original article source at: https://testdriven.io/

#flask #redis #queue 

james allen

1611609121

Quick Pay Portal | www.quickpayportal.com | www.quickpayportal.com

Pay Medical Bills your bills @https://sites.google.com/view/www-quickpayportal-com/

It is really very easy to pay your bills at [priviabillpay.com](https://sites.google.com/view/www-quickpayportal-com/ "priviabillpay.com"). First of all, patients will have to go to the official Privia Medical Community Online portal . Patients can use the quick pay code of priviabillpay.com to make a one-time payment. On the first page of your statement, the QuickPay code is found. Using Priviabillpay to follow a few steps to get paid.

First of all, you must visit the official portal at [www.priviabillpay.com](https://sites.google.com/view/www-quickpayportal-com/ "www.priviabillpay.com")

In the box, fill out the QuickPay Code and tap Make a Payment.

You will be redirected to a page showing all your current rates.

Now select the fees you want to pay and click the check box that you want to accept quickly.

Finally, click on the payment option button.

Your payment details will be asked on the screen.

Fill out the field required and submit your payment.

Our Official Website : https://sites.google.com/view/www-quickpayportal-com/

#www.priviabillpay.com #www-quickpayportal-com #quickpayportal.com #www.quickpayportal.com. #quickpayportal.com.