Ruoyi框架实战:如何解决权限管理模块的性能瓶颈问题

1次阅读
没有评论

共计 2216 个字符,预计需要花费 6 分钟才能阅读完成。

image.webp

背景分析:Ruoyi 权限模块的架构与痛点

Ruoyi 框架默认采用 Shiro+RBAC 模型实现权限管理,其核心流程包含:

Ruoyi 框架实战:如何解决权限管理模块的性能瓶颈问题

  • 用户登录时查询 sys_menusys_role_menu表构建权限树
  • 每次接口访问时通过 ShiroFilter 校验权限标识符
  • 基于数据库实时查询判断角色 - 菜单关联关系

常见性能瓶颈集中体现在:

  1. 高频数据库访问:每个请求至少触发 2 - 3 次权限校验 SQL
  2. 冗余计算:相同用户的权限数据在会话期内重复加载
  3. 全量加载:即使只校验单个权限点也会加载完整菜单树

技术方案:三层优化策略

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%↓

避坑指南:生产环境注意事项

  1. 缓存一致性
  2. 权限变更时采用 @CacheEvict 清除相关缓存
  3. 考虑引入消息队列做集群间缓存同步

  4. 并发竞争

  5. 使用 Redis 分布式锁控制缓存重建过程
  6. 设置合理的锁超时时间(建议≤500ms)

  7. 缓存穿透

  8. 对 null 值进行短时间缓存(建议 1 - 2 分钟)
  9. 使用 BloomFilter 过滤无效用户 ID

总结与延伸

本次优化通过缓存 +SQL 优化 + 懒加载的组合拳,实现了权限模块的性能飞跃。这种优化思路可以复用到其他场景:

  • 对于频繁访问的字典数据,采用多级缓存策略
  • 列表查询实现分页缓存(基于参数 hash)
  • 热点数据使用本地缓存 +Redis 的双缓存架构

建议在实施前做好基准测试,通过 Arthas 等工具准确定位瓶颈点。优化过程中要特别注意监控缓存命中率和数据库负载变化。

正文完
 0
评论(没有评论)