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 as solicitações de login. Em um processo de autenticação, os usuários enviam 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:

  • Sobrecarregando o banco de dados. A aplicação deve fazer uma ida e volta para o servidor de banco de dados para verificar as credenciais dos usuários em uma tabela de banco de dados cada vez que um usuário envia uma solicitação de login. Como o banco de dados ainda pode atender a 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, bancos de dados baseados em disco não têm um desempenho ideal.

Para superar os desafios 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 contatar o banco de dados backend durante cada solicitação de login. O Redis é um dos mais populares bancos de dados ultrarrápidos que utiliza a RAM do seu computador para armazenar dados em pares 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, você precisará 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 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 completar essa lógica, seus scripts Python requerem drivers de banco de dados (módulos Python) para se comunicar 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.

Etapa 2 — Configurando um Banco de Dados MySQL de Exemplo

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 chamada 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. Assegure-se de receber a seguinte saída para confirmar que os comandos anteriores foram executados com sucesso:

    Saída
    Query OK, 1 linha afetada (0.01 seg)
  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 uma PRIMARY KEY para identificar cada usuário de forma única. As colunas username e password são as credenciais de login que os usuários devem enviar 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. Assegure-se de ter criado a nova tabela verificando a seguinte saída:

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

    1. INSERT INTO system_users (username, first_name, last_name, password) VALUES ('john_doe', 'JOHN', 'DOE', MD5('senha_1'));
    2. INSERT INTO system_users (username, first_name, last_name, password) VALUES ('mary_henry', 'MARY', 'HENRY', MD5('senha_2'));
    3. INSERT INTO system_users (username, first_name, last_name, password) VALUES ('peter_jade', 'PETER', 'JADE', MD5('senha_3'));
  7. Verifique a saída abaixo:

    Saída
    Query 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. password
    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. Desconectar do banco de dados MySQL:

    1. QUIT;

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

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

Ao codificar qualquer projeto em Python, você deve criar um módulo separado para cada tarefa para promover a reutilização de código. Nesta etapa, você configurará um módulo central que permite conectar e consultar o banco de dados MySQL a partir de um script Python. Siga os passos 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 projeto:

    1. cd projeto
  3. Use o editor de texto nano para abrir um novo arquivo mysql_db.py. Este arquivo hospeda 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 do 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 encontrar se há uma 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 a aplicação encontrar uma correspondência.

Com o módulo MySQL pronto, siga para a próxima etapa para configurar um módulo Redis similar que se comunique com o armazenamento de chave-valor do Redis.

Etapa 4 — Criando um Módulo Central do Redis para Python

Nesta etapa, você irá 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 dessa 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 na próxima etapa.

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

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

  1. Abra 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 = {"hora": 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 = {"hora": 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á executando na porta 8080...")
    
    try:
        httpd.serve_forever()
    except KeyboardInterrupt:
        httpd.server_close()
        print("Servidor web parou de executar.")
    
  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 de texto e formatação.

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

    • datetime, módulo de tempo/data.

    • 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 manipuladora para o servidor HTTP. Sob a classe, o método do_GET(self): atende às solicitações HTTP GET e exibe a data/hora do sistema para usuários autenticados.

  • No lógica if ... : else: ..., o script Python executa a instrução lógica 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, a aplicação retorna o erro {"error": "Nome de usuário/senha inválidos."}.

Se os detalhes do usuário não existirem no servidor Redis, o aplicativo 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, o aplicativo retorna o erro {"error": "Nome de usuário/senha inválidos."}. Caso contrário, o aplicativo 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 coincidem com os detalhes do Redis/MySQL, o aplicativo exibe a data/hora atual do sistema usando a declaração {"time": current_time, ...}. A entrada authorized by na saída permite ver o servidor de banco de dados que autentica os usuários no aplicativo.

    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álido(s)."}
          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álido(s)."}
          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 ver se o mecanismo de cache do Redis funciona. Execute os comandos abaixo para testar o aplicativo:

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

    python3 index.py
    
  2. Assegure-se de que o servidor web personalizado do aplicativo esteja em execução:

    Saída
    Servidor web está em execução na porta 8080...
  3. Estabeleça outra conexão SSH para 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. Acrescente [1-4] ao final da 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 atende apenas ao primeiro pedido de autenticação. Em seguida, o banco de dados Redis atende aos três pedidos seguintes.

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

Sua lógica de aplicativo está agora funcionando conforme 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 Redis.

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