首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> 網頁程式設計 >> PHP 程式設計 >> PHP 綜合知識

PHP + MySQL 億級用戶登錄系統架構設計深度解析

     編輯:PHP 綜合知識

背景與挑戰

百度、騰訊、360 等億級用戶平台,面臨:

  • 單表億級行,查詢/寫入效能瓶頸
  • 多種登錄方式(用戶名、郵箱、手機、第三方)
  • 高併發登錄請求(峰值 QPS 萬級)
  • 跨業務線統一認證 (SSO)、開放平台授權

核心原則:**壓力不在 PHP(無狀態可水平擴展),瓶頸在 MySQL 寫入與熱點查詢**。

資料庫分庫分表設計

1. 登錄映射表 (用戶名/郵箱/手機 → UID)

採用 **Hash 取模分 100 表**,鍵值分離,極小行寬:

CREATE TABLE `user_login_00` (
  `login_key`   VARBINARY(64) NOT NULL COMMENT 'sha256(login_type#login_value)',
  `login_type`  TINYINT UNSIGNED NOT NULL COMMENT '1=username,2=email,3=phone',
  `login_value` VARCHAR(120) NOT NULL COMMENT '原始值,供驗證/找回',
  `uid`         BIGINT UNSIGNED NOT NULL,
  `status`      TINYINT NOT NULL DEFAULT 1 COMMENT '1=正常,0=禁用',
  `created_at`  DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`login_key`),
  KEY `idx_uid` (`uid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='登錄映射分表 00';
  • login_key = substr(hash('sha256', $type . '#' . $value), 0, 16) → 16 字節二進位,節省空間、索引極快。
  • 分表邏輯:$table = 'user_login_' . substr($login_key, -2) (取最後 2 位十六進位,00-ff,此處簡化為 100 表)。
  • 郵箱/手機加前綴區分類型:email#user@domain.com、phone#13800000000。

2. 用戶核心資料表 (UID → Profile)

同樣按 UID 分 100 表:

CREATE TABLE `user_profile_00` (
  `uid`           BIGINT UNSIGNED NOT NULL PRIMARY KEY,
  `password_hash` CHAR(60) NOT NULL COMMENT 'password_hash() 結果',
  `salt`          CHAR(16) NOT NULL COMMENT '額外鹽值(可選)',
  `nickname`      VARCHAR(50),
  `avatar`        VARCHAR(255),
  `gender`        TINYINT,
  `birthday`      DATE,
  `reg_ip`        INT UNSIGNED,
  `last_login_at` DATETIME,
  `last_login_ip` INT UNSIGNED,
  `status`        TINYINT NOT NULL DEFAULT 1,
  `created_at`    DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
  `updated_at`    DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

3. 自增 UID 服務 (替代 AUTO_INCREMENT)

MySQL 自增在分表環境下會產生重複 UID。方案:

  • Redis INCR:INCR global:uid,單機 10 萬 QPS 足夠,配合 Sentinel/Cluster 高可用。
  • Snowflake 演算法:41bit 時間 + 10bit 機器 + 12bit 序列,去中心化、趨勢遞增、無協調開銷。
  • MySQL 單獨序列表:REPLACE INTO uid_seq (stub) VALUES ('a') 取 LAST_INSERT_ID(),簡單但寫入單點。

推薦:Snowflake (PHP 端生成) 或 Redis INCR (服務端生成)。

註冊流程 (偽代碼)

function register(string $loginType, string $loginValue, string $password): int
{
    // 1. 格式驗證
    if (!validate($loginType, $loginValue)) throw new Exception('格式錯誤');

    // 2. 產生 UID
    $uid = UidGenerator::nextId(); // Snowflake 或 Redis INCR

    // 3. 組合登錄鍵
    $loginKey = hash('sha256', $loginType . '#' . $loginValue, true);
    $loginKeyHex = bin2hex($loginKey);
    $tableIdx = substr($loginKeyHex, -2); // 00-ff
    $loginTable = "user_login_{$tableIdx}";

    // 4. 寫入登錄映射 (唯一鍵衝突即表示已註冊)
    $sql = "INSERT INTO {$loginTable} (login_key, login_type, login_value, uid) VALUES (UNHEX(?), ?, ?, ?)";
    try {
        $db->execute($sql, [$loginKeyHex, $loginType, $loginValue, $uid]);
    } catch (UniqueConstraintViolation $e) {
        throw new Exception('帳號已存在');
    }

    // 5. 寫入核心資料
    $profileTable = "user_profile_" . ($uid % 100);
    $pwdHash = password_hash($password, PASSWORD_ARGON2ID); // PHP 7.2+
    $sql = "INSERT INTO {$profileTable} (uid, password_hash, salt, reg_ip) VALUES (?, ?, ?, ?)";
    $db->execute($sql, [$uid, $pwdHash, random_bytes(16), ip2long($_SERVER['REMOTE_ADDR'])]);

    // 6. 非同步寫入擴展資料、發送歡迎信、風控記錄 (MQ)
    MessageQueue::push('user.registered', ['uid' => $uid, 'type' => $loginType]);

    return $uid;
}

登錄流程 (偽代碼)

function login(string $loginValue, string $password): ?string
{
    // 1. 嘗試三種類型查找 UID (並行查詢或按優先級)
    $loginTypes = [2 => 'email', 3 => 'phone', 1 => 'username']; // 郵箱/手機優先,唯一性高
    $uid = null;

    foreach ($loginTypes as $type => $label) {
        $key = hash('sha256', $type . '#' . $loginValue, true);
        $table = "user_login_" . substr(bin2hex($key), -2);
        $sql = "SELECT uid, status FROM {$table} WHERE login_key = UNHEX(?)";
        $row = $db->fetch($sql, [bin2hex($key)]);
        if ($row && $row['status'] == 1) {
            $uid = $row['uid'];
            break;
        }
    }

    if (!$uid) return null; // 帳號不存在

    // 2. 讀取密碼雜湊
    $profileTable = "user_profile_" . ($uid % 100);
    $sql = "SELECT password_hash, status FROM {$profileTable} WHERE uid = ?";
    $user = $db->fetch($sql, [$uid]);
    if (!$user || $user['status'] != 1) return null;

    // 3. 驗證密碼
    if (!password_verify($password, $user['password_hash'])) {
        // 記錄失敗次數、風控 (Redis 計數器)
        return null;
    }

    // 4. 更新最後登錄時間 (非同步)
    MessageQueue::push('user.login', ['uid' => $uid, 'ip' => $_SERVER['REMOTE_ADDR']]);

    // 5. 簽發 Token (JWT 或 Session)
    return TokenService::issue($uid);
}

效能優化關鍵點

層面策略
查詢路由PHP 端計算分表名,直連對應分片,避免中間件轉發延遲
熱點保護登錄映射表極小,全量放入 Redis Cluster 熱點快取 (TTL 1h),登錄先查 Redis 再落庫
密碼驗證Argon2id 成本參數適中 (time=3, memory=65536, threads=2),防暴力破解不阻塞
連線池Swoole/RoadRunner 長連線 + PDO 連線池,消除 TCP/SSL 握手開銷
非同步化寫入登錄日誌、更新最後登錄、風控記錄全部推入 MQ (Kafka/RocketMQ) 異步處理
讀寫分離登錄查詢走從庫 (延遲 < 10ms 可接受),註冊寫入走主庫

擴展性設計

  • 分表擴容:採用一致性 Hash 或範圍分片,新增分片只需遷移少量資料,配合雙寫遷移方案零停機。
  • 多租戶/業務線隔離:UID 高位編碼業務線 ID,或獨立分片集群。
  • 第三方登錄:user_oauth 表 (provider, openid, uid),同樣分片邏輯。
  • 裝置指紋/風控:獨立 user_device、login_risk_log 表,不污染核心路徑。

總結

億級登錄系統的核心在於:**鍵值分離、Hash 分片、UID 全域唯一、熱點快取、非同步寫入、無狀態 PHP 水平擴展**。MySQL 單表控制在 2000 萬行以內,單分片 QPS 萬級無壓力。PHP 層只負責路由計算與協議轉換,所有有狀態操作下沉至 Redis/MySQL/MQ,實現線性擴展。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved