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

報表快取系統設計:Redis 儲存與讀寫封裝 (Rpt/RptFinder/RptStorage)

     編輯:PHP 相關知識

設計目標

  • 統一報表資料快取存取介面
  • 支援同步/非同步讀取 (阻塞等待快取生成)
  • 自動過期、版本控制、資料實體封裝
  • 鍵值命名規範化 (報表名 + 用戶標識)

核心類別架構

<?php
/**
 * 入口門面:靜態方法快速存取
 */
class Rpt {
    public static function read(string $key, string $nick): mixed {
        return self::createFinder($key, $nick)->read();
    }

    public static function readSync(string $key, string $nick): mixed {
        return self::createFinder($key, $nick)->readSync();
    }

    public static function save(string $key, string $nick, mixed $data, int $expired = 7200): bool {
        return self::createStorage($key, $nick)->save($data, $expired);
    }

    public static function createFinder(string $key, string $nick): RptFinder {
        return new RptFinder(RptGenerate::key($key, $nick));
    }

    public static function createStorage(string $key, string $nick): RptStorage {
        return new RptStorage(RptGenerate::key($key, $nick));
    }

    private static function createRedis(): Redis {
        $redis = new Redis();
        $redis->connect(Yii::app()->params['RedisServerIP']); // 需根據實際框架調整
        return $redis;
    }
}

/**
 * 鍵值生成器:統一命名規範
 */
class RptGenerate {
    public static function key(string $key, string $nick): string {
        return $key . '.' . md5($nick); // 如: diamond.account.a1b2c3d4...
    }
}

/**
 * 資料實體:封裝快取元資料
 */
class RptData {
    public int    $expired     = 0;      // TTL 秒數
    public int    $expiredTime = 0;      // 寫入時間戳
    public mixed  $data        = null;   // 實際業務資料
    public bool   $isRead      = false;  // 是否有效資料

    public function isExpired(): bool {
        return (time() - $this->expiredTime) > $this->expired;
    }
}

/**
 * 讀取器:非同步/同步讀取策略
 */
class RptFinder {
    private string $key;

    public function __construct(string $key) { $this->key = $key; }

    // 非阻塞讀取:快取存在且未過期即返回,否則 null
    public function read(): mixed {
        $data = $this->readData();
        return ($data->isRead && !$data->isExpired()) ? $data->data : null;
    }

    // 阻塞讀取:輪詢等待快取生成 (適用於報表異步生成場景)
    public function readSync(): mixed {
        while (true) {
            $data = $this->readData();
            if ($data->isRead && !$data->isExpired()) {
                return $data->data;
            }
            sleep(1); // 避免佔滿 CPU
        }
    }

    private function readData(): RptData {
        $redis = Rpt::createRedis();
        $rptData = new RptData();

        $raw = $redis->get($this->key);
        if ($raw === false) { // 鍵不存在
            $rptData->expiredTime = time();
            $rptData->expired = 86400; // 預設 24h
            return $rptData;
        }

        $obj = json_decode($raw);
        $rptData->expired     = $obj->expired ?? 0;
        $rptData->isRead      = $obj->isRead ?? false;
        $rptData->expiredTime = $obj->expiredTime ?? time();
        $rptData->data        = $obj->data ?? null;
        return $rptData;
    }
}

/**
 * 寫入器:原子性寫入 (SETEX)
 */
class RptStorage {
    private string $key;

    public function __construct(string $key) { $this->key = $key; }

    public function save(mixed $data, int $expired = 7200): bool {
        $rptData = new RptData();
        $rptData->data        = $data;
        $rptData->expiredTime = time();
        $rptData->isRead      = true;
        $rptData->expired     = $expired;

        $redis = Rpt::createRedis();
        return $redis->setex($this->key, $rptData->expired, json_encode($rptData));
    }
}

使用範例

<?php
// 1. 簡單儲存/讀取
Rpt::save('diamond.account', $userNick, $accountData, 3600); // 1 小時
$data = Rpt::read('diamond.account', $userNick);

// 2. 同步等待 (報表異步生成中)
$data = Rpt::readSync('diamond.account', $userNick); // 阻塞直到有效資料

// 3. 物件導向風格
$finder  = Rpt::createFinder('diamond.account', $userNick);
$storage = Rpt::createStorage('diamond.account', $userNick);

$data = $finder->read();
if (!$data) {
    $storage->save($freshData, 7200);
}

關鍵設計亮點

特性實現方式效益
原子性寫入Redis SETEX (SET + EXPIRE)避免競態條件,寫入與過期單操作
資料完整性RptData 實體封裝 isRead/expiredTime區分「無快取」vs「快取過期」vs「快取有效」
同步等待readSync() 輪詢 + sleep(1)異步報表生成完成前自動阻塞,上層無感
鍵值隔離md5(nick) 避免鍵衝突、長度可控支援任意用戶標識,安全無注入風險
門面模式Rpt 靜態方法統一入口業務代碼簡潔,底層儲存可替換 (Redis→Memcached)

擴展建議

  • 標籤式清除:引入 RptTag 類別,支援按報表類別批次失效 (如 Rpt::invalidateTag('diamond.*'))。
  • 壓縮大資料:json_encode 前對 $data 使用 gzcompress,讀取時 gzuncompress,節省記憶體/頻寬。
  • 分散式鎖:快取未命中時,使用 SET NX EX 獲取鎖,僅一個進程生成報表,其餘 readSync 等待。
  • 監控指標:埋點記錄 hit/miss 率、平均延遲、同步等待耗時,接入 Prometheus/Grafana。
  • 框架解耦:將 createRedis() 抽象為介面,支援 Laravel Redis、Predis、phpredis 多驅動。
  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved