本文目录导读:

在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);
}
}
总结建议
- 优先选择位图缓存:如果权限数量小于64个,位图是性能最优方案
- 使用多级缓存:本地内存 + Redis 组合能覆盖大部分场景
- 合理设置TTL:热点权限7200秒,普通权限3600秒,非核心模块600秒
- 异步失效:权限变更时使用消息队列异步清除缓存
- 避免缓存穿透:对空结果也进行短暂缓存(比如60秒)
- 监控与告警:监控缓存命中率,低于90%时检查配置
根据实际业务场景选择合适的方案,通常位图缓存+Redis+本地内存的组合能满足99%的性能需求。