Come accelerare la gestione delle sessioni delle applicazioni Python/MySQL con Redis su Ubuntu 22.04

Introduzione

L’autenticazione è il processo di verifica dell’identità degli utenti durante le richieste di accesso. In un processo di autenticazione, gli utenti inviano le proprie credenziali come nome utente e password. Successivamente, l’applicazione confronta quelle credenziali di accesso con le voci memorizzate nel database. Se c’è una corrispondenza, l’applicazione concede agli utenti l’accesso al sistema.

Memorizzare le credenziali di accesso in un database relazionale come MySQL o PostgreSQL senza un meccanismo di caching è ancora un approccio comune e pratico, ma presenta i seguenti limiti:

  • Sovraccarico del database. L’applicazione deve fare un viaggio di andata e ritorno al server del database per verificare le credenziali degli utenti da una tabella del database ogni volta che un utente invia una richiesta di accesso. Poiché il database potrebbe ancora gestire altre richieste di lettura/scrittura, l’intero processo sovraccarica il database e lo rende lento.

  • I database tradizionali basati su disco hanno problemi di scalabilità. Quando la tua applicazione riceve migliaia di richieste al secondo, i database basati su disco non funzionano in modo ottimale.

Per superare le sfide di cui sopra, puoi utilizzare Redis per memorizzare nella cache le credenziali di accesso degli utenti in modo che la tua applicazione non debba contattare il database di backend durante ogni richiesta di accesso. Redis è uno dei database più popolari e ultra veloci che utilizza la RAM del tuo computer per archiviare i dati in coppie chiave-valore. In questa guida, utilizzerai il database Redis per velocizzare la gestione delle sessioni nella tua applicazione Python/MySQL su un server Ubuntu 22.04.

Prerequisiti

Prima di iniziare questo tutorial, dovrai configurare quanto segue:

Passaggio 1: installazione dei driver di database Python per Redis e MySQL

Questa applicazione memorizza permanentemente le credenziali degli utenti, come nomi e password, in un server di database MySQL. Quando un utente accede all’applicazione, uno script Python interroga il database MySQL e confronta i dettagli con i valori memorizzati. Successivamente, lo script Python memorizza nella cache le credenziali di accesso dell’utente in un database Redis per servire altre richieste future. Per completare questa logica, gli script Python richiedono i driver del database (moduli Python) per comunicare con i server MySQL e Redis. Seguire i passaggi seguenti per installare i driver:

  1. Aggiornare l’indice delle informazioni dei pacchetti e eseguire il seguente comando per installare python3-pip, un gestore di pacchetti Python che consente di installare moduli aggiuntivi non inclusi nella libreria standard di Python.
sudo apt install python3-pip
  1. Installare il driver MySQL per Python:
pip install mysql-connector-python
  1. Installare il driver Redis per Python:
pip install redis

Dopo aver installato i driver necessari per comunicare con MySQL e Redis, procedere al passaggio successivo e inizializzare un database MySQL.

Passaggio 2 – Configurazione di un database MySQL di esempio

Per questa guida, è necessaria una tabella MySQL. In un ambiente di produzione, è possibile avere decine di tabelle che servono altre richieste. Configurare un database e creare la tabella eseguendo i seguenti comandi:

  1. Accedi al server del database MySQL come utente root:

    sudo mysql -u root -p
    
  2. Inserisci la password di root del tuo server MySQL quando richiesto e premi INVIO per procedere. Quindi, esegui il seguente comando per creare un database di esempio chiamato company e un account chiamato company_user. Sostituisci example-mysql-password con una password sicura:

  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. Assicurati di ricevere il seguente output per confermare che i comandi precedenti siano stati eseguiti correttamente:

    Output
    Query OK, 1 riga affetta (0.01 sec)
  2. Passa al nuovo database company:

    1. USE company;
  3. Conferma di essere connesso al nuovo database verificando il seguente output:

    Output
    Database cambiato
  4. Crea una tabella system_users. La colonna user_id serve come PRIMARY KEY per identificare univocamente ogni utente. Le colonne username e password sono le credenziali di accesso che gli utenti devono inviare per effettuare il login nell’applicazione. Le colonne first_name e last_name memorizzano i nomi degli utenti:

    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. Assicurati di aver creato la nuova tabella verificando l’output seguente:

    Output
    Query OK, 0 righe affettate (0.03 sec)
  6. Popola la tabella system_users con dati di esempio. Utilizza la funzione MD5(...) integrata in MySQL per criptare la password per motivi di sicurezza:

    1. INSERT INTO system_users (username, first_name, last_name, password) VALUES ('john_doe', 'JOHN', 'DOE', MD5('password_1'));
    2. INSERT INTO system_users (username, first_name, last_name, password) VALUES ('mary_henry', 'MARY', 'HENRY', MD5('password_2'));
    3. INSERT INTO system_users (username, first_name, last_name, password) VALUES ('peter_jade', 'PETER', 'JADE', MD5('password_3'));
  7. Verificare l’output di seguito:

    Output
    Query OK, 1 riga modificata (0.00 sec)
  8. Effettuare una query sulla tabella system_users per assicurarsi che i dati siano stati inseriti:

    1. SELECT
    2. user_id,
    3. first_name,
    4. last_name,
    5. password
    6. FROM system_users;
  9. Verifica l’output seguente:

    Output
    +---------+------------+-----------+----------------------------------+ | user_id | first_name | last_name | password | +---------+------------+-----------+----------------------------------+ | 1 | JOHN | DOE | 57210b12af5e06ad2e6e54a93b1465aa | | 2 | MARY | HENRY | 259640f97ac2b4379dd540ff4016654c | | 3 | PETER | JADE | 48ef85c894a06a4562268de8e4d934e1 | +---------+------------+-----------+----------------------------------+ 3 righe in set (0.00 sec)
  10. Esci dal database MySQL:

    1. QUIT;

Ora hai configurato correttamente il database MySQL per la tua applicazione. Nel prossimo passaggio, creerai un modulo Python che comunica con il tuo database di esempio.

Passaggio 3 – Creazione di un modulo centrale per il gateway MySQL per Python

Quando si sviluppa un progetto Python, è consigliabile creare un modulo separato per ogni compito al fine di promuovere la riusabilità del codice. In questo passaggio, configurerai un modulo centrale che ti permette di connetterti e interrogare il database MySQL da uno script Python. Segui i passaggi seguenti:

  1. Crea una directory project. Questa directory separa i file sorgente Python dal resto dei file di sistema:

    1. mkdir project
  2. Passa alla nuova directory project:

    1. cd project
  3. Usa l’editor di testo nano per aprire un nuovo file mysql_db.py. Questo file ospita il modulo Python che comunica con il database MySQL:

    nano mysql_db.py
    
  4. Inserisci le seguenti informazioni nel file mysql_db.py. Sostituisci example-mysql-password con la password corretta di MySQL per l’account company_user:

    ~/project/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. Salva e chiudi il file mysql_db.py.

Il file del modulo mysql_db.py ha una classe (MysqlDb:) con due metodi:
db_con(self):, si connette al database di esempio company creato in precedenza e restituisce una connessione a MySQL riutilizzabile utilizzando l’istruzione return mysql_con.
query(self, username, password):, un metodo che accetta un username e una password e interroga la tabella system_users per verificare se c’è una corrispondenza. L’istruzione condizionale if row_count < 1: ... else: return result[1] restituisce il valore booleano False se un utente non esiste nella tabella o la password dell’utente (result[1]) se l’applicazione trova una corrispondenza.

Con il modulo MySQL pronto, segui il passaggio successivo per configurare un modulo Redis simile che comunica con il database Redis chiave-valore.

Passaggio 4 – Creazione di un modulo Redis centrale per Python

In questo passaggio, scriverai un modulo che si connette al server Redis. Esegui i seguenti passaggi:

  1. Apri un nuovo file redis_db.py:

    nano redis_db.py
    
  2. Inserisci le seguenti informazioni nel file redis_db.py. Sostituisci example-redis-password con la password corretta per il server Redis:

    ~/project/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. Salva e chiudi il file redis_db.py.

  • Il file sopra riportato ha una classe (RedisDb:).

  • Sotto questa classe, il metodo db_con(self): utilizza le credenziali fornite per connettersi al server Redis e restituisce una connessione riutilizzabile utilizzando l’istruzione return redis_con.

Dopo aver configurato la classe Redis, crea il file principale per il tuo progetto nel passaggio successivo.

Passaggio 5: Creazione del punto di ingresso dell’applicazione

Ogni applicazione Python deve avere un punto di ingresso o il file principale che viene eseguito quando l’applicazione viene avviata. In questo file, creerai un codice che mostra l’ora corrente del server per gli utenti autenticati. Questo file utilizza i moduli personalizzati MySQL e Redis che hai creato per autenticare gli utenti. Segui i passaggi di seguito per creare il file:

  1. Apri un nuovo file index.py:

    nano index.py
    
  2. Inserisci le seguenti informazioni nel file index.py:

    ~/progetto/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 utente/password non validi."}
                else:
                    resp = {"time": current_time, "autorizzato da": "server Redis"}
            else:
                mysql_resp  = mysql_server.query(auth_user, auth_password)
                if mysql_resp == False:
                     resp =  {"error": "Nome utente/password non validi."}
                else:
                    resp = {"time": current_time, "autorizzato da": "server 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("Il server web è in esecuzione sulla porta 8080...")
    
    try:
        httpd.serve_forever()
    except KeyboardInterrupt:
        httpd.server_close()
        print("Il server web si è interrotto.")
    
  3. Salva e chiudi il file index.py.

  • Nel file index.py, la sezione import... aggiunge i seguenti moduli al tuo progetto:

    • utf_8, base64, md5 e json, moduli per la codifica e formattazione del testo.

    • http.server, HTTPStatus e socketserver, moduli per il server web.

    • datetime, modulo per la gestione di data e ora.

    • mysql_db e redis_db, moduli personalizzati che hai creato in precedenza per accedere ai server MySQL e Redis.

  • L’HttpHandler(http.server.SimpleHTTPRequestHandler): è una classe gestore per il server HTTP. All’interno della classe, il metodo do_GET(self): gestisce le richieste HTTP GET e mostra la data/ora di sistema per gli utenti autenticati.

  • Nella logica if ... : else: ..., lo script Python esegue la dichiarazione logica if redis_client.exists(auth_user): per verificare se le credenziali dell’utente esistono nel server Redis. Se i dettagli dell’utente esistono e la password memorizzata in Redis non corrisponde alla password inviata dall’utente, l’applicazione restituisce l’errore {"error": "Nome utente/password non validi."}.

Se i dettagli dell’utente non esistono nel server Redis, l’applicazione interroga il server del database MySQL utilizzando l’istruzione mysql_resp = mysql_server.query(auth_user, auth_password). Nel caso in cui la password fornita dall’utente non corrisponda al valore memorizzato nel database, l’applicazione restituisce l’errore {"error": "Nome utente/password non validi."}. In caso contrario, l’applicazione memorizza nella cache i dettagli dell’utente nel server Redis utilizzando l’istruzione redis_client.set(auth_user, mysql_resp).

  • In tutti i casi in cui le credenziali dell’utente corrispondono ai dettagli di Redis/MySQL, l’applicazione visualizza la data/ora corrente del sistema utilizzando l’istruzione {"time": current_time, ...}. L’ingresso authorized by nell’output consente di vedere il server di database che autentica gli utenti nell’applicazione.
      if redis_client.exists(auth_user):
          if md5(auth_password.encode('utf8')).hexdigest() != redis_client.get(auth_user).decode('utf8'):
              resp = {"error": "Nome utente/password non validi."}
          else:
              resp = {"time": current_time, "authorized by": "Server Redis"}
      else:
          mysql_resp  = mysql_server.query(auth_user, auth_password)
          if mysql_resp == False:
               resp =  {"error": "Nome utente/password non validi."}
          else:
              resp = {"time": current_time, "authorized by": "Server MySQL"}
              redis_client.set(auth_user, mysql_resp)   
    

Hai ora configurato il file principale dell’applicazione. Nel prossimo passo, testerai l’applicazione.

Passaggio 6 – Test dell’applicazione

In questo passaggio, eseguirai l’applicazione per verificare se il meccanismo di caching di Redis funziona. Esegui i comandi seguenti per testare l’applicazione:

  1. Utilizza il seguente comando python3 per eseguire l’applicazione:

    python3 index.py
    
  2. Assicurati che il server web personalizzato dell’applicazione sia in esecuzione:

    Output
    Il server web è in esecuzione sulla porta 8080...
  3. Stabilisci una nuova connessione SSH al tuo server in una nuova finestra del terminale e esegui i seguenti comandi curl per inviare quattro richieste GET utilizzando le credenziali di john_doe. Aggiungi [1-4] alla fine dell’URL http://localhost:8080/ per inviare le quattro richieste in un singolo comando:

    curl -X GET -u john_doe:password_1  http://localhost:8080/[1-4]
    
  4. Verifica i seguenti output. Il server MySQL serve solo la prima richiesta di autenticazione. Successivamente, il database Redis gestisce le tre richieste successive.

    Output
    [1/4] { "time": "2023-11-07 10:04:38", "authorized by": "server MySQL" } [4/4] { "time": "2023-11-07 10:04:38", "authorized by": "server Redis" }

La logica dell’applicazione sta ora funzionando come previsto.

Conclusione

In questa guida, hai costruito un’applicazione Python che utilizza il server Redis per memorizzare nella cache le credenziali di accesso degli utenti. Redis è un server di database altamente disponibile e scalabile in grado di eseguire migliaia di transazioni al secondo. Con il meccanismo di memorizzazione nella cache Redis nella tua applicazione, puoi ridurre notevolmente il traffico nel tuo server di database di backend. Per saperne di più sulle applicazioni Redis, consulta i nostri tutorial Redis.

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