如何在 Ubuntu 22.04 上使用 Redis 加快 Python/MySQL 應用程序會話處理

介紹

驗證是在登錄請求期間驗證使用者身份的過程。在驗證過程中,使用者提交他們的憑證,如用戶名和密碼。然後,應用程序將這些登錄憑證與存儲的數據庫項目進行匹配。如果匹配成功,應用程序將授予使用者訪問系統的權限。

將登錄憑證存儲在像MySQL或PostgreSQL這樣的關聯式數據庫中,並且沒有緩存機制,仍然是一種常見且實用的方法,但其存在以下限制:

  • 過度加載數據庫。每次使用者提交登錄請求時,應用程序必須從數據庫表中進行一次往返到數據庫服務器以驗證使用者的憑證。由於數據庫可能還需要處理其他讀/寫請求,整個過程會過載數據庫並使其變慢。

  • 傳統基於磁盤的數據庫存在可擴展性問題。當您的應用程序每秒接收到數千個請求時,基於磁盤的數據庫的性能不佳。

為了克服上述挑戰,您可以使用Redis來緩存用戶的登錄憑據,這樣您的應用程序就不必在每次登錄請求中與後端數據庫進行通信。Redis是一個使用計算機的RAM來存儲鍵值對數據的最受歡迎的超高速數據存儲庫之一。在本指南中,您將在Ubuntu 22.04服務器上使用Redis數據庫來加快Python/MySQL應用程序的會話處理速度。

前提條件

在開始本教程之前,您需要設置以下內容:

步驟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. 通過驗證以下輸出來確認您已連接到新的資料庫:

    輸出
    資料庫已變更
  4. 創建一個system_users表。 user_id列用作唯一識別每個用戶的PRIMARY KEYusernamepassword列是用戶必須提交的登錄憑據,以便登錄應用程序。 first_namelast_name列存儲用戶的名字:

    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. 通過驗證以下輸出,確保您已創建新表:

    輸出
    查詢已成功,影響行數:00.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 | +---------+------------+-----------+----------------------------------+ 3 rows in set (0.00 sec)
  10. 從MySQL資料庫登出:

    1. QUIT;

您現在已經設定了適合您應用程式的正確MySQL資料庫。在下一個步驟中,您將建立一個與您的範例資料庫通信的Python模組。

第3步 – 為Python創建一個中央MySQL Gateway模組

在編寫任何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 模組。

第 4 步-創建 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("網頁伺服器正在執行,監聽埠號 8080...")
    
    try:
        httpd.serve_forever()
    except KeyboardInterrupt:
        httpd.server_close()
        print("網頁伺服器已停止執行。")
    
  3. 儲存並關閉index.py檔案。

  • index.py檔案中的import...區段中加入以下模組到您的專案中:

    • utf_8base64md5json,文字編碼和格式化模組。

    • http.serverHTTPStatussocketserver,網頁伺服器模組。

    • 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 服务器正在端口 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