如何在Ubuntu 22.04上通过Redis加速Python/MySQL应用程序会话处理

简介

身份验证是在登录请求过程中验证用户身份的过程。在身份验证过程中,用户提交其作为用户名和密码的凭据。然后,应用程序将这些登录凭据与存储的数据库条目进行匹配。如果匹配成功,应用程序将授予用户对系统的访问权限。

在没有缓存机制的情况下,将登录凭据存储在诸如MySQL或PostgreSQL之类的关系型数据库中仍然是一种常见且实用的方法,但它具有以下限制:

  • 负载过重的数据库。每次用户提交登录请求时,应用程序必须将用户的凭据从数据库表中验证一次,这会导致数据库过载并变慢,因为数据库可能仍在处理其他读写请求。

  • 传统的基于磁盘的数据库存在可扩展性问题。当您的应用程序每秒接收数千个请求时,基于磁盘的数据库无法达到最佳性能。

为了克服上述挑战,您可以使用Redis来缓存用户的登录凭据,这样在每个登录请求期间,您的应用程序就不需要与后端数据库进行联系。Redis是一种最流行的超快速数据存储,它利用计算机的RAM以键值对的形式存储数据。在本指南中,您将使用Redis数据库来加速Python/MySQL应用程序在Ubuntu 22.04服务器上的会话处理。

先决条件

在开始本教程之前,您需要设置以下内容:

第1步 — 安装Redis和MySQL的Python数据库驱动

此应用程序永久存储用户的凭据,例如用户名和密码,在一个MySQL数据库服务器中。当用户登录应用程序时,一个Python脚本查询MySQL数据库并将详细信息与存储的值进行匹配。然后,Python脚本将用户的登录凭据缓存到Redis数据库中,以便为其他未来的请求提供服务。为了完成这个逻辑,你的Python脚本需要数据库驱动程序(Python模块)与MySQL和Redis服务器进行通信。按照以下步骤安装驱动程序:

  1. 更新您的软件包信息索引,并运行以下命令安装python3-pip,这是一个Python包管理器,允许您安装不属于Python标准库的额外模块。
sudo apt install python3-pip
  1. 安装Python的MySQL驱动程序:
pip install mysql-connector-python
  1. 安装Python的Redis驱动程序:
pip install redis

在安装与MySQL和Redis通信所需的驱动程序之后,继续下一步并初始化一个MySQL数据库。

步骤2 – 设置一个示例MySQL数据库

对于本指南,您需要一个MySQL表。在生产环境中,您可以拥有数十个表来处理其他请求。通过执行以下命令来设置一个数据库并创建表:

  1. root用户登录到MySQL数据库服务器:

    sudo mysql -u root -p
    
  2. 输入MySQL服务器的root密码并按下ENTER键继续。然后,运行以下命令创建一个示例的company数据库和一个company_user账户。将example-mysql-password替换为强密码:

  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. 确保你收到以下输出,以确认前面的命令已成功运行:

    输出
    Query OK, 1 row affected (0.01 sec)
  2. 切换到新的company数据库:

    1. USE company;
  3. 通过验证以下输出确认您已连接到新的数据库:

    输出
    Database changed
  4. 创建一个system_users表。user_id列作为PRIMARY KEY,用于唯一标识每个用户。usernamepassword列是用户必须提交的登录凭据,用于登录应用程序。first_namelast_name列存储用户的姓名:

    自定义前缀(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. 通过验证以下输出来确保您已创建新表:

    输出
    Query OK,受影响的行数为0(0.03秒)
  6. 使用MySQL内置的MD5(…)函数对密码进行哈希处理,将样本数据填充到system_users表中:

    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. 验证下面的输出:

    输出
    查询成功,影响1行(0.00秒)
  8. 查询system_users表以确保数据已经放置:

    1. SELECT
    2. user_id,
    3. first_name,
    4. last_name,
    5. password
    6. FROM system_users;
  9. 验证以下输出:

    输出
    +---------+------------+-----------+----------------------------------+ | user_id | first_name | last_name | password | +---------+------------+-----------+----------------------------------+ | 1 | JOHN | DOE | 57210b12af5e06ad2e6e54a93b1465aa | | 2 | MARY | HENRY | 259640f97ac2b4379dd540ff4016654c | | 3 | PETER | JADE | 48ef85c894a06a4562268de8e4d934e1 | +---------+------------+-----------+----------------------------------+ 3in set (0.00)
  10. 从MySQL数据库注销:

    1. QUIT;

您现在已经为应用程序设置了正确的MySQL数据库。在下一步中,您将构建一个与示例数据库通信的Python模块。

第3步 – 创建一个用于Python的中央MySQL网关模块

在编写任何Python项目时,应为每个任务创建一个单独的模块以促进代码的可重用性。在本步骤中,您将设置一个中央模块,该模块允许您从Python脚本连接和查询MySQL数据库。请按照以下步骤操作:

  1. 创建一个project目录。该目录将您的Python源代码文件与其他系统文件分开:

    1. mkdir project
  2. 切换到新的project目录:

    1. cd project
  3. 使用nano文本编辑器打开一个新的mysql_db.py文件。这个文件托管了与MySQL数据库交互的Python模块:

    nano mysql_db.py
    
  4. 将以下信息输入到mysql_db.py文件中。将example-mysql-password替换为company_user账户的正确MySQL密码:

    ~/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. 保存并关闭mysql_db.py文件。

mysql_db.py模块文件有一个类(MysqlDb:),包含两个方法:
db_con(self):连接到之前创建的示例company数据库,并使用return mysql_con语句返回可重用的MySQL连接。
query(self, username, password):一个接受usernamepassword参数的方法,查询system_users表以查找是否有匹配。条件语句if row_count < 1: ... else: return result[1]如果用户不在表中,则返回布尔值False;如果应用程序找到匹配,则返回用户的密码(result[1])。

MySQL模块准备就绪后,按照下一步设置一个类似的与Redis键值存储通信的Redis模块。

第四步 – 创建Python的中央Redis模块

在这一步中,您将编写一个连接到Redis服务器的模块。执行以下步骤:

  1. 打开一个新的redis_db.py文件:

    nano redis_db.py
    
  2. 将以下信息输入到redis_db.py文件中。将example-redis-password替换为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. 保存并关闭redis_db.py文件。

  • 上述文件有一个类(RedisDb:)。

  • 在这个类下面,db_con(self):方法使用提供的凭证连接到Redis服务器,并使用return redis_con语句返回可重复使用的连接。

设置完Redis类后,下一步创建项目的主文件。

步骤5 – 创建应用程序入口点

每个Python应用程序都必须有一个入口点或主文件,当应用程序运行时执行。在这个文件中,您将创建一段代码,用于为经过验证的用户显示当前服务器的时间。该文件使用您创建的自定义MySQL和Redis模块来验证用户。按照以下步骤创建文件:

  1. 打开一个新的index.py文件:

    nano index.py
    
  2. 将以下信息输入到index.py文件中:

    ~/project/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": "用户名/密码无效。"}
                else:
                    resp = {"time": current_time, "授权来源": "Redis 服务器"}
            else:
                mysql_resp  = mysql_server.query(auth_user, auth_password)
                if mysql_resp == False:
                     resp =  {"error": "用户名/密码无效。"}
                else:
                    resp = {"time": current_time, "授权来源": "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("Web服务器正在运行于端口8080...")
    
    try:
        httpd.serve_forever()
    except KeyboardInterrupt:
        httpd.server_close()
        print("Web服务器已停止运行。")
    
  3. 保存并关闭index.py文件。

  • index.py文件中,import...部分添加以下模块到你的项目中:

    • utf_8base64md5json,文本编码和格式化模块。

    • http.serverHTTPStatussocketserver,web服务器模块。

    • datetime,时间/日期模块。

    • mysql_dbredis_db,之前创建的用于访问MySQL和Redis服务器的自定义模块。

  • HttpHandler(http.server.SimpleHTTPRequestHandler)是HTTP服务器的处理程序类。在该类下,do_GET(self)方法用于处理HTTP GET请求,并为经过身份验证的用户显示系统的日期/时间。

  • if ... : else: ...逻辑中,Python脚本运行逻辑语句if redis_client.exists(auth_user)来检查用户凭据是否存在于Redis服务器中。如果用户详细信息存在且Redis存储的密码与用户提交的密码不匹配,则应用程序返回{"error": "Invalid username/password."}错误。

如果Redis服务器中不存在用户详细信息,则应用程序使用mysql_resp = mysql_server.query(auth_user, auth_password)语句查询MySQL数据库服务器。如果用户提供的密码与数据库中存储的值不匹配,则应用程序返回错误{"error": "用户名/密码无效。"}。否则,应用程序使用redis_client.set(auth_user, mysql_resp)语句将用户详细信息缓存到Redis服务器中。

  • 在所有情况下,当用户的凭据与Redis/MySQL的详细信息匹配时,应用程序使用{"time": current_time, ...}语句显示系统的当前日期/时间。输出中的authorized by条目可以让您看到在应用程序中对用户进行身份验证的数据库服务器。

      if redis_client.exists(auth_user):
          if md5(auth_password.encode('utf8')).hexdigest() != redis_client.get(auth_user).decode('utf8'):
              resp = {"error": "用户名/密码无效。"}
          else:
              resp = {"time": current_time, "authorized by": "Redis服务器"}
      else:
          mysql_resp  = mysql_server.query(auth_user, auth_password)
          if mysql_resp == False:
               resp =  {"error": "用户名/密码无效。"}
          else:
              resp = {"time": current_time, "authorized by": "MySQL服务器"}
              redis_client.set(auth_user, mysql_resp)   
    

您现在已经设置了应用程序的主文件。在下一步中,您将测试应用程序。

第6步 – 测试应用程序

在这一步中,您将运行应用程序以查看Redis缓存机制是否正常工作。执行以下命令以测试应用程序:

  1. 使用以下python3命令运行应用程序:

    python3 index.py
    
  2. 确保应用程序的自定义Web服务器正在运行:

    输出
    Web服务器正在端口8080..运行。
  3. 在新的终端窗口上建立另一个SSH连接到您的服务器,并运行以下curl命令,使用john_doe的凭据发送四个GET请求。在http://localhost:8080/的URL末尾附加[1-4]以一条命令发送四个请求:

    curl -X GET -u john_doe:password_1  http://localhost:8080/[1-4]
    
  4. 请验证以下输出。MySQL服务器只处理第一个身份验证请求。然后,Redis数据库处理接下来的三个请求。

    输出
    [1/4] { "time": "2023-11-07 10:04:38", "authorized by": "MySQL服务器" } [4/4] { "time": "2023-11-07 10:04:38", "authorized by": "Redis服务器" }

您的应用程序逻辑现在按预期工作。

结论

在这个指南中,您构建了一个使用Redis服务器来缓存用户登录凭据的Python应用程序。Redis是一个高可用性和可扩展性的数据库服务器,可以每秒执行数千个事务。通过在应用程序中使用Redis缓存机制,您可以大大减少后端数据库服务器的流量。要了解更多关于Redis应用程序的信息,请参考我们的Redis教程

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