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

MySQL 實現 IP 訪問頻率限制:INET_ATON 數值存儲與時間窗口判斷

     編輯:PHP 相關知識

表結構設計

CREATE TABLE `ip_access_log` (
    `ip` INT UNSIGNED NOT NULL COMMENT 'IP 數值 (INET_ATON)',
    `last_access` INT UNSIGNED NOT NULL COMMENT '上次訪問時間戳',
    `access_count` SMALLINT UNSIGNED NOT NULL DEFAULT 1 COMMENT '窗口內訪問次數',
    `window_start` INT UNSIGNED NOT NULL COMMENT '窗口起始時間戳',
    PRIMARY KEY (`ip`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='IP 訪問頻率限制表';
  • ip 使用 INT UNSIGNED 存儲 INET_ATON() 轉換的數值,支持索引、範圍查詢、比較運算
  • 主鍵即 IP,保證唯一性,避免重複行

核心 SQL 操作

1. 記錄訪問 (INSERT ... ON DUPLICATE KEY UPDATE)

<?php
$ip = $_SERVER['REMOTE_ADDR'];
$now = time();
$window = 3600; // 1 小時窗口
$limit = 100;   // 最大訪問次數

$sql = "
    INSERT INTO ip_access_log (ip, last_access, access_count, window_start)
    VALUES (INET_ATON(?), ?, 1, ?)
    ON DUPLICATE KEY UPDATE
        last_access = VALUES(last_access),
        access_count = CASE 
            WHEN VALUES(last_access) - window_start >= ? THEN 1
            ELSE access_count + 1
        END,
        window_start = CASE
            WHEN VALUES(last_access) - window_start >= ? THEN VALUES(window_start)
            ELSE window_start
        END
";

$stmt = $mysqli->prepare($sql);
$stmt->bind_param('siii', $ip, $now, $now, $window, $window);
$stmt->execute();
?>

2. 判斷是否允許訪問 (SELECT)

<?php
$sql = "
    SELECT access_count, window_start
    FROM ip_access_log
    WHERE ip = INET_ATON(?)
      AND (window_start + ?) > ?
";
$stmt = $mysqli->prepare($sql);
$stmt->bind_param('sii', $ip, $window, $now);
$stmt->execute();
$result = $stmt->get_result();
$row = $result->fetch_assoc();

if ($row && $row['access_count'] > $limit) {
    http_response_code(429);
    die("訪問過於頻繁,請稍後再試。");
}
// 允許訪問,繼續業務邏輯
?>

3. 定期清理過期數據 (Cron 任務)

<?php
// 每天凌晨執行
$expireTime = time() - 86400 * 7; // 保留 7 天
$mysqli->query("DELETE FROM ip_access_log WHERE last_access < $expireTime");
?>

INET_ATON / INET_NTOA 使用詳解

<?php
// PHP 內建函數 (推薦,不依賴 MySQL)
$ip = '192.168.1.101';
$long = ip2long($ip);          // 有符號整數,可能為負
$unsigned = sprintf('%u', $long); // 轉無符號字符串
echo "數值: $unsigned\n";      // 3232235877

// 還原
$ipRestored = long2ip($long);
echo "還原: $ipRestored\n";    // 192.168.1.101

// MySQL 函數 (SQL 中使用)
/*
SELECT INET_ATON('192.168.1.101');  -- 3232235877
SELECT INET_NTOA(3232235877);       -- 192.168.1.101
*/
?>

進階:滑動窗口精確限流 (基於請求時間戳記錄)

若需**精確滑動窗口** (而非固定窗口),需記錄每次請求時間戳:

CREATE TABLE `ip_request_log` (
    `id` BIGINT UNSIGNED NOT NULL AUTO_INCREMENT,
    `ip` INT UNSIGNED NOT NULL,
    `request_time` INT UNSIGNED NOT NULL,
    PRIMARY KEY (`id`),
    KEY `idx_ip_time` (`ip`, `request_time`)
) ENGINE=InnoDB;

-- 記錄請求
INSERT INTO ip_request_log (ip, request_time) VALUES (INET_ATON(?), ?);

-- 統計窗口內請求數
SELECT COUNT(*) FROM ip_request_log
WHERE ip = INET_ATON(?)
  AND request_time > ?; -- now - window

-- 定期清理
DELETE FROM ip_request_log WHERE request_time < ?; -- now - max_window

權衡:精確度高但寫入量大、表膨脹快,需分區/定期清理。適合核心接口 (登錄、支付、API 閘道)。

性能優化建議

  1. 內存表:極高併發場景可用 ENGINE=MEMORY,重啟丟失但速度最快
  2. Redis 替代:計數器類業務 Redis INCR + EXPIRE 性能優於 MySQL 10-100 倍
  3. 批量異步寫入:應用層聚合計數,定時批量寫入 DB (如每秒刷新一次)
  4. 分區表:按時間範圍分區,快速清理過期分區

總結

MySQL 方案適合**中小流量、無 Redis 環境、需持久化審計**的場景。核心技巧:

  • INET_ATON 將 IP 轉無符號整數,利用數值索引高效查詢
  • INSERT ... ON DUPLICATE KEY UPDATE 原子化「查找或創建/更新」
  • 固定窗口邏輯在 SQL 中用 CASE WHEN 完成,減少網絡往返
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved