PHP项目权限缓存如何提升校验速度

wen PHP项目 25

本文目录导读:

PHP项目权限缓存如何提升校验速度

  1. 缓存粒度与结构设计
  2. 多级缓存架构
  3. 缓存失效策略
  4. 批量预加载优化
  5. 基准测试数据
  6. 生产环境最佳实践
  7. 总结建议

在PHP项目中,权限校验往往是性能瓶颈之一,尤其是当权限规则复杂、用户量大时,缓存是提升权限校验速度的核心手段,以下是几种高效的缓存策略及实现方案:

缓存粒度与结构设计

1 按用户缓存(推荐)

// 缓存结构:user_id => [permissions]
$cacheKey = "user_permissions:{$userId}";
$permissions = $redis->get($cacheKey);
if ($permissions === null) {
    // 从数据库加载权限
    $permissions = $db->query("
        SELECT p.code 
        FROM user_roles ur
        JOIN role_permissions rp ON ur.role_id = rp.role_id
        JOIN permissions p ON rp.permission_id = p.id
        WHERE ur.user_id = ?
    ", [$userId]);
    // 缓存7200秒,并根据数据变动设置失效标记
    $redis->setex($cacheKey, 7200, serialize($permissions));
}

2 位图缓存(极高性能)

// 预定义权限位图映射
$permissionMap = [
    'create_user' => 1,  // 第1位
    'edit_user'   => 2,  // 第2位
    'delete_user' => 4,  // 第3位
    // ... 按2的幂分配
];
// 缓存用户权限位图值
$bitmapKey = "user_bitmap:{$userId}";
$userBitmap = $redis->get($bitmapKey);
if ($userBitmap === null) {
    $permissions = loadUserPermissions($userId);
    $bitmap = 0;
    foreach ($permissions as $perm) {
        if (isset($permissionMap[$perm['code']])) {
            $bitmap |= $permissionMap[$perm['code']];
        }
    }
    $redis->setex($bitmapKey, 3600, $bitmap);
}
// 校验权限:O(1)复杂度
function checkPermission($userId, $permissionCode) {
    $bitmap = $redis->get("user_bitmap:{$userId}");
    return ($bitmap & $permissionMap[$permissionCode]) !== 0;
}

多级缓存架构

1 本地内存 + Redis 二级缓存

class PermissionCache {
    private static $localCache = [];
    public function check($userId, $permission) {
        // 第一级:本地内存缓存(毫秒级)
        if (isset(self::$localCache[$userId])) {
            return in_array($permission, self::$localCache[$userId]);
        }
        // 第二级:Redis缓存
        $key = "user_perms:{$userId}";
        $cached = $this->redis->get($key);
        if ($cached !== null) {
            self::$localCache[$userId] = unserialize($cached);
            return in_array($permission, self::$localCache[$userId]);
        }
        // 第三级:数据库查询
        $permissions = $this->loadFromDB($userId);
        $this->redis->setex($key, 3600, serialize($permissions));
        self::$localCache[$userId] = $permissions;
        return in_array($permission, $permissions);
    }
}

2 使用共享内存(APCu)

// 适用于单机部署,速度极快(纳秒级)
function checkPermissionAPCu($userId, $permissionCode) {
    $cacheKey = "perm:{$userId}";
    if (apcu_exists($cacheKey)) {
        $bitmap = apcu_fetch($cacheKey);
    } else {
        $bitmap = buildUserBitmap($userId);
        apcu_store($cacheKey, $bitmap, 300); // 5分钟过期
    }
    return ($bitmap & $permissionCode) !== 0;
}

缓存失效策略

1 基于版本号的缓存失效

class VersionedPermissionCache {
    // 全局版本号,权限变更时递增
    private $globalVersionKey = "perm_global_version";
    public function check($userId, $permission) {
        $globalVersion = $this->redis->get($this->globalVersionKey);
        $userCacheKey = "user_perm:{$userId}:v{$globalVersion}";
        $permissions = $this->redis->get($userCacheKey);
        if ($permissions === null) {
            $permissions = $this->loadPermissions($userId);
            $this->redis->setex($userCacheKey, 3600, serialize($permissions));
        }
        return in_array($permission, unserialize($permissions));
    }
    // 权限变更时调用
    public function invalidateAll() {
        $this->redis->incr($this->globalVersionKey);
    }
}

2 延迟失效 + 消息队列

// 权限变更时,发送异步失效消息
class PermissionInvalidator {
    public function invalidate($userId) {
        // 1.立即清除Redis缓存(同步)
        $this->redis->del("user_perms:{$userId}");
        // 2.发送异步消息清除本地缓存(可选)
        $this->messageQueue->send('clear_permission_cache', [
            'user_id' => $userId,
            'timestamp' => time()
        ]);
    }
}
// 消费者进程
class CacheClearWorker {
    public function process($message) {
        // 清除所有服务器节点的本地缓存
        apcu_delete("perm:{$message['user_id']}");
    }
}

批量预加载优化

1 请求入口处批量加载

// 在中间件或框架启动时
class PermissionMiddleware {
    public function handle($request, $next) {
        $userId = $request->user()->id;
        // 一次性加载所有权限到本地缓存
        $permissions = $this->loadUserPermissions($userId);
        // 存储到请求上下文
        $request->attributes->set('_permissions', $permissions);
        return $next($request);
    }
}
// 后续校验直接使用本地数组
function checkPermission($request, $code) {
    $permissions = $request->attributes->get('_permissions');
    return in_array($code, $permissions);
}

2 按模块分组缓存

class ModulePermissionCache {
    // 只缓存常访问的模块
    private $hotModules = ['dashboard', 'user', 'order'];
    public function checkModule($userId, $module) {
        $key = "perm_module:{$userId}:{$module}";
        // 热点模块使用长时间缓存
        $ttl = in_array($module, $this->hotModules) ? 7200 : 600;
        return $this->cache->remember($key, $ttl, function() use ($userId, $module) {
            return $this->db->checkModulePermission($userId, $module);
        });
    }
}

基准测试数据

方案 平均耗时 每秒请求数 适用场景
无缓存(直接查库) 50-200ms 50-200 极小规模
Redis(字符串) 1-5ms 2000-10000 中大规模
Redis(位图) 5-2ms 5000-20000 大规模,权限固定
APCu(位图) 01-0.1ms 50000+ 单机部署
本地内存+Redis 1-1ms 10000-50000 高并发场景

生产环境最佳实践

class OptimizedPermissionService {
    private $redis;
    private $localCache = [];
    private $useBitMap;
    public function __construct() {
        $this->useBitMap = count(PERMISSION_DEFINITIONS) <= 64; // 64位以内用位图
    }
    public function check($userId, $permissionCode) {
        // 1. 本地缓存(当前请求周期)
        if (isset($this->localCache[$userId])) {
            return $this->checkInCache($this->localCache[$userId], $permissionCode);
        }
        // 2. Redis缓存
        $cacheKey = $this->useBitMap 
            ? "perm_bitmap:{$userId}" 
            : "perm_list:{$userId}";
        $cached = $this->redis->get($cacheKey);
        if ($cached !== null) {
            $data = $this->useBitMap ? (int)$cached : unserialize($cached);
            $this->localCache[$userId] = $data;
            return $this->checkInCache($data, $permissionCode);
        }
        // 3. 数据库加载
        $permissions = $this->loadFromDatabase($userId);
        $cacheData = $this->useBitMap 
            ? $this->buildBitmap($permissions)
            : $permissions;
        // 缓存并设置失效监听
        $this->redis->setex($cacheKey, 3600, 
            $this->useBitMap ? $cacheData : serialize($cacheData));
        $this->localCache[$userId] = $cacheData;
        return $this->checkInCache($cacheData, $permissionCode);
    }
    private function checkInCache($cacheData, $permissionCode) {
        if ($this->useBitMap) {
            return ($cacheData & PERMISSION_MAP[$permissionCode]) !== 0;
        }
        return in_array($permissionCode, $cacheData);
    }
}

总结建议

  1. 优先选择位图缓存:如果权限数量小于64个,位图是性能最优方案
  2. 使用多级缓存:本地内存 + Redis 组合能覆盖大部分场景
  3. 合理设置TTL:热点权限7200秒,普通权限3600秒,非核心模块600秒
  4. 异步失效:权限变更时使用消息队列异步清除缓存
  5. 避免缓存穿透:对空结果也进行短暂缓存(比如60秒)
  6. 监控与告警:监控缓存命中率,低于90%时检查配置

根据实际业务场景选择合适的方案,通常位图缓存+Redis+本地内存的组合能满足99%的性能需求。

抱歉,评论功能暂时关闭!