Como Acelerar o Gerenciamento de Sessões de Aplicações Python/MySQL com Redis no Ubuntu 22.04

Introdução

A autenticação é o processo de verificar a identidade dos usuários durante solicitações de login. Em um processo de autenticação, os usuários fornecem suas credenciais como nomes de usuário e senhas. Em seguida, a aplicação compara essas credenciais de login com entradas armazenadas no banco de dados. A aplicação concede acesso aos usuários ao sistema se houver uma correspondência.

Armazenar credenciais de login em um banco de dados relacional como MySQL ou PostgreSQL sem um mecanismo de cache ainda é uma abordagem comum e prática, mas vem com as seguintes limitações:

  • Sobrecarga do banco de dados. A aplicação deve fazer uma viagem de ida e volta para o servidor de banco de dados para verificar as credenciais dos usuários em uma tabela do banco de dados cada vez que um usuário envia uma solicitação de login. Como o banco de dados ainda pode atender outras solicitações de leitura/gravação, todo o processo sobrecarrega o banco de dados e o torna lento.

  • Bancos de dados tradicionais baseados em disco têm problemas de escalabilidade. Quando sua aplicação recebe milhares de solicitações por segundo, os bancos de dados baseados em disco não funcionam de forma ideal.

Para superar os desafios mencionados acima, você pode usar o Redis para armazenar em cache as credenciais de login dos usuários, para que sua aplicação não precise entrar em contato com o banco de dados backend a cada solicitação de login. O Redis é um dos armazenamentos de dados ultrarrápidos mais populares, que utiliza a RAM do seu computador para armazenar dados em pares de chave-valor. Neste guia, você usará o banco de dados Redis para acelerar o gerenciamento de sessões em sua aplicação Python/MySQL no servidor Ubuntu 22.04.

Pré-requisitos

Antes de começar este tutorial, será necessário configurar o seguinte:

Passo 1 — Instalando Drivers de Banco de Dados Python para Redis e MySQL

Esta aplicação armazena permanentemente as credenciais dos usuários, como nomes e senhas, em um servidor de banco de dados MySQL. Quando um usuário faz login na aplicação, um script em Python consulta o banco de dados MySQL e compara os detalhes com os valores armazenados. Em seguida, o script Python armazena em cache as credenciais de login do usuário em um banco de dados Redis para atender a outras solicitações futuras. Para concluir essa lógica, seus scripts Python precisam de drivers de banco de dados (módulos Python) para se comunicarem com os servidores MySQL e Redis. Siga as etapas abaixo para instalar os drivers:

  1. Atualize o índice de informações do seu pacote e execute o seguinte comando para instalar python3-pip, um gerenciador de pacotes Python que permite instalar módulos adicionais que não fazem parte da biblioteca padrão do Python.
sudo apt install python3-pip
  1. Instale o driver MySQL para Python:
pip install mysql-connector-python
  1. Instale o driver Redis para Python:
pip install redis

Depois de instalar os drivers necessários para se comunicar com o MySQL e o Redis, prossiga para a próxima etapa e inicialize um banco de dados MySQL.

Passo 2 — Configurando um Exemplo de Banco de Dados MySQL

Para este guia, você precisa de uma tabela MySQL. Em um ambiente de produção, você pode ter dezenas de tabelas que atendem a outras solicitações. Configure um banco de dados e crie a tabela executando os seguintes comandos:

  1. Acesse o servidor do banco de dados MySQL como usuário root:

    sudo mysql -u root -p
    
  2. Insira a senha do root do seu servidor MySQL quando solicitado e pressione ENTER para prosseguir. Em seguida, execute o seguinte comando para criar um banco de dados de exemplo chamado company e uma conta de usuário company_user. Substitua example-mysql-password por uma senha forte:

  1. CREATE DATABASE company;
  2. CREATE USER 'company_user'@'localhost' IDENTIFIED WITH mysql_native_password BY 'example-mysql-password';
  3. GRANT ALL PRIVILEGES ON company.* TO 'company_user'@'localhost';
  4. FLUSH PRIVILEGES;
  1. Verifique se você recebe a seguinte saída para confirmar que os comandos anteriores foram executados com sucesso:

    Saída
    Query OK, 1 row affected (0.01 sec)
  2. Mude para o novo banco de dados company:

    1. USE company;
  3. Confirme que você está conectado ao novo banco de dados verificando a seguinte saída:

    Saída
    Banco de dados alterado
  4. Crie uma tabela system_users. A coluna user_id serve como chave primária para identificar cada usuário de forma única. As colunas username e password são as credenciais de login que os usuários devem fornecer para acessar o aplicativo. As colunas first_name e last_name armazenam os nomes dos usuários:

    custom_prefix(mysql>)
    CREATE TABLE system_users (
        user_id BIGINT NOT NULL AUTO_INCREMENT PRIMARY KEY,
        username VARCHAR(50),
        first_name VARCHAR(50),
        last_name VARCHAR(50),
        password VARCHAR(50)
    ) ENGINE = InnoDB;
    
  5. Certifique-se de ter criado a nova tabela verificando a seguinte saída:

    Saída
    Query OK, 0 linhas afetadas (0.03 seg)
  6. Popule a tabela system_users com dados de exemplo. Use a função interna do MySQL MD5(...) para cifrar a senha por motivos de segurança:

    1. INSERIR EM system_users (username, first_name, last_name, password) VALORES ('john_doe', 'JOHN', 'DOE', MD5('password_1'));
    2. INSERIR EM system_users (username, first_name, last_name, password) VALORES ('mary_henry', 'MARY', 'HENRY', MD5('password_2'));
    3. INSERIR EM system_users (username, first_name, last_name, password) VALORES ('peter_jade', 'PETER', 'JADE', MD5('password_3'));
  7. Verifique a saída abaixo:

    Saída
    Consulta OK, 1 linha afetada (0.00 seg)
  8. Consulte a tabela system_users para garantir que os dados estejam no lugar:

    1. SELECIONAR
    2. user_id,
    3. first_name,
    4. last_name,
    5. senha
    6. DE system_users;
  9. Verifique a seguinte saída:

    Saída
    +---------+------------+-----------+----------------------------------+ | user_id | first_name | last_name | password | +---------+------------+-----------+----------------------------------+ | 1 | JOHN | DOE | 57210b12af5e06ad2e6e54a93b1465aa | | 2 | MARY | HENRY | 259640f97ac2b4379dd540ff4016654c | | 3 | PETER | JADE | 48ef85c894a06a4562268de8e4d934e1 | +---------+------------+-----------+----------------------------------+ 3 linhas no conjunto (0.00 seg)
  10. Sair do banco de dados MySQL:

    1. QUIT;

Agora você configurou corretamente o banco de dados MySQL para sua aplicação. No próximo passo, você construirá um módulo Python comunicando-se com seu banco de dados de exemplo.

Passo 3 — Criando um Módulo Central de Gateway MySQL para Python

Ao codificar qualquer projeto Python, você deve criar um módulo separado para cada tarefa para promover a reutilização de código. Neste passo, você configurará um módulo central que permite conectar e consultar o banco de dados MySQL a partir de um script Python. Siga as etapas abaixo:

  1. Crie um diretório project. Este diretório separa seus arquivos de código-fonte Python do restante dos arquivos do sistema:

    1. mkdir project
  2. Mude para o novo diretório project:

    1. cd project
  3. Use o editor de texto nano para abrir um novo arquivo mysql_db.py. Este arquivo abriga o módulo Python que se comunica com o banco de dados MySQL:

    nano mysql_db.py
    
  4. Insira as seguintes informações no arquivo mysql_db.py. Substitua example-mysql-password pela senha correta do MySQL para a conta company_user:

    ~/projeto/mysql_db.py
    
    import mysql.connector
    
    class MysqlDb:
    
    def db_con(self):
    
        mysql_con = mysql.connector.connect(
            host     = "localhost",
            user     = "company_user",
            password = "example-mysql-password",
            database = "company",
            port     = "3306"
        )
    
        return mysql_con
    
    def query(self, username, password):
    
        db = self.db_con()
        db_cursor = db.cursor()
    
        db_query  = "select username, password from system_users where username = %s and password = md5(%s)"
        db_cursor.execute(db_query, (username, password))
    
        result = db_cursor.fetchone()
        row_count = db_cursor.rowcount
    
        if  row_count < 1:
            return False
        else:
            return result[1]
    
  5. Salve e feche o arquivo mysql_db.py.

O arquivo de módulo mysql_db.py possui uma classe (MysqlDb:) com dois métodos:
db_con(self):, conecta-se ao banco de dados de exemplo company que você criou anteriormente e retorna uma conexão MySQL reutilizável usando a declaração return mysql_con.
query(self, username, password):, um método que aceita um username e password e consulta a tabela system_users para verificar se há correspondência. A declaração condicional if row_count < 1: ... else: return result[1] retorna o valor booleano False se um usuário não existir na tabela ou a senha do usuário (result[1]) se o aplicativo encontrar uma correspondência.

Com o módulo MySQL pronto, siga para o próximo passo para configurar um módulo Redis semelhante que se comunique com o armazenamento de chave-valor do Redis.

Passo 4 — Criando um Módulo Redis Central para Python

Neste passo, você codificará um módulo que se conecta ao servidor Redis. Execute os seguintes passos:

  1. Abra um novo arquivo redis_db.py:

    nano redis_db.py
    
  2. Insira as seguintes informações no arquivo redis_db.py. Substitua example-redis-password pela senha correta do servidor Redis:

    ~/projeto/redis_db.py
    import redis
    class RedisDb:
        def db_con(self):
            r_host = 'localhost'
            r_port = 6379
            r_pass = 'example-redis-password'
            redis_con = redis.Redis(host = r_host, port = r_port, password = r_pass)
            return redis_con
    
  3. Salve e feche o arquivo redis_db.py.

  • O arquivo acima possui uma classe (RedisDb:).

  • Dentro desta classe, o método db_con(self): utiliza as credenciais fornecidas para se conectar ao servidor Redis e retorna uma conexão reutilizável usando a instrução return redis_con.

Após configurar a classe Redis, crie o arquivo principal para o seu projeto no próximo passo.

Passo 5 — Criando o Ponto de Entrada da Aplicação

Toda aplicação Python deve ter um ponto de entrada ou o arquivo principal que é executado quando a aplicação é iniciada. Neste arquivo, você criará um código que mostra a hora atual do servidor para usuários autenticados. Este arquivo utiliza os módulos personalizados MySQL e Redis que você criou para autenticar os usuários. Siga os passos abaixo para criar o arquivo:

  1. Abrir um novo arquivo index.py:

    nano index.py
    
  2. Insira as seguintes informações no arquivo index.py:

    ~/projeto/index.py
    from encodings import utf_8
    import base64
    from hashlib import md5
    import json
    import datetime
    import http.server
    from http import HTTPStatus
    import socketserver
    import mysql_db
    import redis_db
    
    class HttpHandler(http.server.SimpleHTTPRequestHandler):
        def do_GET(self):
            self.send_response(HTTPStatus.OK)
            self.send_header('Content-type', 'application/json')
            self.end_headers()
            authHeader = self.headers.get('Authorization').split(' ');
            auth_user, auth_password = base64.b64decode(authHeader[1]).decode('utf8').split(':')
            mysql_server = mysql_db.MysqlDb()
            redis_server = redis_db.RedisDb()
            redis_client =  redis_server.db_con()
            now = datetime.datetime.now()
            current_time = now.strftime("%Y-%m-%d %H:%M:%S")
            resp = {}
            if redis_client.exists(auth_user):
                if md5(auth_password.encode('utf8')).hexdigest() != redis_client.get(auth_user).decode('utf8'):
                    resp = {"error": "Nome de usuário/senha inválidos."}
                else:
                    resp = {"tempo": current_time, "autorizado por": "servidor Redis"}
            else:
                mysql_resp  = mysql_server.query(auth_user, auth_password)
                if mysql_resp == False:
                     resp =  {"error": "Nome de usuário/senha inválidos."}
                else:
                    resp = {"tempo": current_time, "autorizado por": "servidor MySQL"}
                    redis_client.set(auth_user, mysql_resp)
            self.wfile.write(bytes(json.dumps(resp, indent = 2) + "\r\n", "utf8"))
    httpd = socketserver.TCPServer(('', 8080), HttpHandler)
    print("Servidor web está rodando na porta 8080...")
    
    try:
        httpd.serve_forever()
    except KeyboardInterrupt:
        httpd.server_close()
        print("Servidor web parou de rodar.")
    
  3. Salve e feche o arquivo index.py.

  • No arquivo index.py, a seção import... adiciona os seguintes módulos ao seu projeto:

    • utf_8, base64, md5 e json, módulos de codificação e formatação de texto.

    • http.server, HTTPStatus e socketserver, módulos de servidor web.

    • datetime, módulo de data/hora.

    • mysql_db e redis_db, módulos personalizados que você criou anteriormente para acessar os servidores MySQL e Redis.

  • O HttpHandler(http.server.SimpleHTTPRequestHandler): é uma classe de manipulador para o servidor HTTP. Sob a classe, o método do_GET(self): serve as solicitações HTTP GET e exibe a data/hora do sistema para usuários autenticados.

  • No logic if ... : else: ..., o script Python executa o if redis_client.exists(auth_user): para verificar se as credenciais do usuário existem no servidor Redis. Se os detalhes do usuário existirem e a senha armazenada no Redis não corresponder à senha enviada pelo usuário, o aplicativo retorna o erro {"error": "Nome de usuário/senha inválido."}.

Se os detalhes do usuário não existirem no servidor Redis, a aplicação consulta o servidor de banco de dados MySQL usando a instrução mysql_resp = mysql_server.query(auth_user, auth_password). Caso a senha fornecida pelo usuário não corresponda ao valor armazenado no banco de dados, a aplicação retorna o erro {"error": "Nome de usuário/senha inválido."}. Caso contrário, a aplicação armazena em cache os detalhes do usuário no servidor Redis usando a instrução redis_client.set(auth_user, mysql_resp).

  • Em todos os casos em que as credenciais do usuário correspondem aos detalhes do Redis/MySQL, a aplicação exibe a data/hora atual do sistema usando a declaração {"time": current_time, ...}. A entrada authorized by na saída permite visualizar o servidor de banco de dados que autentica os usuários na aplicação.

    if redis_client.exists(auth_user):
          if md5(auth_password.encode('utf8')).hexdigest() != redis_client.get(auth_user).decode('utf8'):
              resp = {"error": "Nome de usuário/senha inválidos."}
          else:
              resp = {"time": current_time, "autorizado por": "Servidor Redis"}
      else:
          mysql_resp  = mysql_server.query(auth_user, auth_password)
          if mysql_resp == False:
               resp =  {"error": "Nome de usuário/senha inválidos."}
          else:
              resp = {"time": current_time, "autorizado por": "Servidor MySQL"}
              redis_client.set(auth_user, mysql_resp)   
    

Você agora configurou o arquivo principal do aplicativo. No próximo passo, você testará o aplicativo.

Passo 6 — Testando o Aplicativo

Neste passo, você executará seu aplicativo para verificar se o mecanismo de cache do Redis está funcionando. Execute os comandos abaixo para testar o aplicativo:

  1. Use o seguinte comando python3 para executar o aplicativo:

    python3 index.py
    
  2. Garanta que o servidor web personalizado do aplicativo esteja em execução:

    O servidor web está em execução na porta 8080...
  3. Estabeleça outra conexão SSH com o seu servidor em uma nova janela de terminal e execute os seguintes comandos curl para enviar quatro solicitações GET usando as credenciais de john_doe. Adicione [1-4] ao final do URL http://localhost:8080/ para enviar as quatro solicitações em um único comando:

    curl -X GET -u john_doe:password_1  http://localhost:8080/[1-4]
    
  4. Verifique as seguintes saídas. O servidor MySQL serve apenas o primeiro pedido de autenticação. Em seguida, o banco de dados Redis atende aos três pedidos seguintes.

    Saída
    [1/4] { "time": "2023-11-07 10:04:38", "autorizado por": "servidor MySQL" } [4/4] { "time": "2023-11-07 10:04:38", "autorizado por": "servidor Redis" }

A lógica da sua aplicação agora está funcionando conforme o esperado.

Conclusão

Neste guia, você construiu uma aplicação Python que utiliza o servidor Redis para armazenar em cache as credenciais de login dos usuários. O Redis é um servidor de banco de dados altamente disponível e escalável que pode realizar milhares de transações por segundo. Com o mecanismo de cache do Redis em sua aplicação, você pode reduzir significativamente o tráfego em seu servidor de banco de dados backend. Para saber mais sobre aplicações Redis, consulte nossos tutoriais do Redis.

Source:
https://www.digitalocean.com/community/tutorials/how-to-speed-up-python-mysql-application-session-handling-with-redis-on-ubuntu-22-04