共计 2216 个字符,预计需要花费 6 分钟才能阅读完成。
背景分析:Ruoyi 权限模块的架构与痛点
Ruoyi 框架默认采用 Shiro+RBAC 模型实现权限管理,其核心流程包含:

- 用户登录时查询
sys_menu和sys_role_menu表构建权限树 - 每次接口访问时通过
ShiroFilter校验权限标识符 - 基于数据库实时查询判断角色 - 菜单关联关系
常见性能瓶颈集中体现在:
- 高频数据库访问:每个请求至少触发 2 - 3 次权限校验 SQL
- 冗余计算:相同用户的权限数据在会话期内重复加载
- 全量加载:即使只校验单个权限点也会加载完整菜单树
技术方案:三层优化策略
1. Redis 缓存权限数据
核心思路:将会话级权限数据迁移到 Redis
- 使用 Hash 结构存储用户 - 角色 - 菜单关系
- 设置合理的 TTL(建议≥会话超时时间)
- 采用
Cache-Aside模式保证一致性
2. SQL 查询优化
关键改进点:
- 将 N + 1 查询改为单次 JOIN 查询
- 添加复合索引
(role_id, menu_id) - 使用
@Cacheable注解缓存基础数据
3. 懒加载策略
实现方案:
- 首次仅加载权限标识符集合
- 按需加载菜单树元数据
- 使用 Guava Cache 做本地二级缓存
代码实现:改造 Shiro 校验逻辑
缓存层实现(示例)
// Redis 权限服务封装
@Service
public class PermissionCacheService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
private static final String CACHE_KEY_PREFIX = "auth:perms:";
// 写入权限缓存
public void cacheUserPermissions(Long userId, Set<String> permissions) {
String key = CACHE_KEY_PREFIX + userId;
redisTemplate.opsForHash().put(key, "permissions", permissions);
redisTemplate.expire(key, 2, TimeUnit.HOURS); // 建议 TTL
}
// 带降级的缓存查询
public Set<String> getPermissions(Long userId, Supplier<Set<String>> fallback) {
String key = CACHE_KEY_PREFIX + userId;
Object cached = redisTemplate.opsForHash().get(key, "permissions");
if (cached != null) {return (Set<String>) cached;
}
// 缓存未命中时回源查询
Set<String> permissions = fallback.get();
if (!permissions.isEmpty()) {this.cacheUserPermissions(userId, permissions);
}
return permissions;
}
}
Shiro Realm 改造
// 优化后的自定义 Realm
public class ShiroRealm extends AuthorizingRealm {
@Autowired
private PermissionCacheService permissionCacheService;
// 授权方法优化
@Override
protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {SysUser user = (SysUser) principals.getPrimaryPrincipal();
SimpleAuthorizationInfo info = new SimpleAuthorizationInfo();
// 从缓存获取权限(降级到数据库查询)Set<String> permissions = permissionCacheService.getPermissions(user.getUserId(),
() -> menuService.selectPermsByUserId(user.getUserId())
);
info.setStringPermissions(permissions);
return info;
}
}
性能对比:量化优化效果
测试环境:4C8G 服务器,MySQL 5.7,Redis 6.2
| 场景 | QPS | 平均响应时间 | 数据库 QPS |
|---|---|---|---|
| 原始方案 | 128 | 78ms | 320 |
| 优化后方案 | 2100 | 4.2ms | 8 |
| 提升幅度 | 16.4x | 18.6x | 97%↓ |
避坑指南:生产环境注意事项
- 缓存一致性
- 权限变更时采用
@CacheEvict清除相关缓存 -
考虑引入消息队列做集群间缓存同步
-
并发竞争
- 使用 Redis 分布式锁控制缓存重建过程
-
设置合理的锁超时时间(建议≤500ms)
-
缓存穿透
- 对 null 值进行短时间缓存(建议 1 - 2 分钟)
- 使用 BloomFilter 过滤无效用户 ID
总结与延伸
本次优化通过缓存 +SQL 优化 + 懒加载的组合拳,实现了权限模块的性能飞跃。这种优化思路可以复用到其他场景:
- 对于频繁访问的字典数据,采用多级缓存策略
- 列表查询实现分页缓存(基于参数 hash)
- 热点数据使用本地缓存 +Redis 的双缓存架构
建议在实施前做好基准测试,通过 Arthas 等工具准确定位瓶颈点。优化过程中要特别注意监控缓存命中率和数据库负载变化。
正文完
