Redis 在权限系统多场景应用实践

作者:忆笙智云官方 | 发布时间:2026-06-13 09:15 | 更新时间:2026-07-13 09:15

Redis 在权限系统多场景应用实践

引言

Redis 作为高性能内存数据库,在企业级权限系统中扮演着不可替代的角色。权限系统对响应速度和数据一致性要求极高——每次API请求都需要校验权限,如果每次都查询数据库,将成为系统瓶颈。通过 Redis 的多场景应用,可以实现毫秒级权限校验、分布式环境下的会话共享、登录安全防护等关键能力。

本文将系统介绍 Redis 在权限系统中的六大应用场景,涵盖配置方法、代码示例和最佳实践。

核心内容

Redis 在权限系统中的六大应用场景

graph TD
    A[Redis在权限系统中的应用] --> B[会话共享]
    A --> C[权限缓存]
    A --> D[分布式锁]
    A --> E[登录失败计数]
    A --> F[字典缓存]
    A --> G[向量存储]

    B --> B1[多实例会话同步]
    B --> B2[单点登录控制]
    C --> C1[用户权限缓存]
    C --> C2[角色权限缓存]
    D --> D1[防止重复操作]
    D --> D2[定时任务互斥]
    E --> E1[暴力破解防护]
    E --> E2[账号锁定机制]
    F --> F1[数据字典缓存]
    F --> F2[配置参数缓存]
    G --> G1[AI向量检索]
    G --> G2[知识库RAG]

一、基础配置

Redis 连接配置

# application.yml
spring:
  data:
    redis:
      host: 127.0.0.1
      port: 6379
      password: ${REDIS_PASSWORD:}
      database: 0
      timeout: 5000ms
      lettuce:
        pool:
          max-active: 20
          max-idle: 10
          min-idle: 5
          max-wait: 3000ms

Redis 配置类

@Configuration
public class RedisConfig {

    /**
     * 自定义RedisTemplate,配置序列化策略
     * Key使用String序列化,Value使用JSON序列化
     */
    @Bean
    public RedisTemplate<String, Object> redisTemplate(
            RedisConnectionFactory connectionFactory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(connectionFactory);

        // Key序列化:String
        StringRedisSerializer stringSerializer = new StringRedisSerializer();
        template.setKeySerializer(stringSerializer);
        template.setHashKeySerializer(stringSerializer);

        // Value序列化:JSON
        Jackson2JsonRedisSerializer<Object> jsonSerializer =
            new Jackson2JsonRedisSerializer<>(Object.class);
        ObjectMapper om = new ObjectMapper();
        om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY);
        om.activateDefaultTyping(
            LaissezFaireSubTypeValidator.instance,
            ObjectMapper.DefaultTyping.NON_FINAL
        );
        jsonSerializer.setObjectMapper(om);

        template.setValueSerializer(jsonSerializer);
        template.setHashValueSerializer(jsonSerializer);
        template.afterPropertiesSet();

        return template;
    }
}

二、场景一:会话共享

在多实例部署环境下,用户登录状态需要跨实例共享。结合 Sa-Token 框架,将 Token 数据存储到 Redis 中。

sequenceDiagram
    participant 用户
    participant 网关
    participant 实例A
    participant 实例B
    participant Redis

    用户->>网关: 登录请求
    网关->>实例A: 转发请求
    实例A->>Redis: 存储Token信息
    实例A-->>用户: 返回Token

    用户->>网关: 业务请求(携带Token)
    网关->>实例B: 转发请求
    实例B->>Redis: 读取Token信息
    Redis-->>实例B: 返回Token数据
    实例B-->>用户: 业务响应

Sa-Token Redis 集成配置

<!-- Sa-Token 整合 Redis -->
<dependency>
    <groupId>cn.dev33</groupId>
    <artifactId>sa-token-redis-jackson</artifactId>
    <version>${sa-token.version}</version>
</dependency>
# Sa-Token 配置
sa-token:
  token-name: Authorization
  timeout: 86400          # Token有效期24小时
  active-timeout: 1800    # 活跃超时30分钟
  is-concurrent: true     # 允许多端同时登录
  is-share: false         # 不共享Token
  token-style: uuid
  is-log: false

单点登录控制

@Service
public class SsoService {

    /**
     * 登录并控制单设备在线
     * 同一账号只允许一个设备在线,新登录会踢掉旧会话
     */
    public LoginResult login(String username, String password) {
        // 校验账号密码
        User user = userService.validateUser(username, password);

        // 获取当前账号已登录的Token
        StpUtil.login(user.getId());

        // 踢掉之前登录的设备
        List<String> tokenList = StpUtil.getTokenValueListByLoginId(user.getId());
        for (String oldToken : tokenList) {
            if (!StpUtil.getTokenValue().equals(oldToken)) {
                StpUtil.kickoutByTokenValue(oldToken);
            }
        }

        return new LoginResult(StpUtil.getTokenValue(), user);
    }
}

三、场景二:权限缓存

权限数据是高频访问但低频变更的典型缓存场景。每次请求都需要校验权限,缓存命中可大幅减少数据库查询。

权限缓存设计

graph TD
    A[API请求] --> B{Redis缓存命中?}
    B -->|是| C[返回缓存权限]
    B -->|否| D[查询数据库]
    D --> E[写入Redis缓存]
    E --> C
    C --> F[权限校验]

    G[权限变更事件] --> H[清除相关缓存]
    H --> I[下次请求重新加载]

    style B fill:#fff9c4
    style H fill:#ffcdd2
@Service
public class PermissionCacheService {

    private static final String PERM_CACHE_PREFIX = "perm:user:";
    private static final String ROLE_CACHE_PREFIX = "perm:role:";
    private static final long CACHE_TTL_HOURS = 2;

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    /**
     * 获取用户权限列表(带缓存)
     * @param userId 用户ID
     * @return 权限标识集合
     */
    @SuppressWarnings("unchecked")
    public Set<String> getUserPermissions(Long userId) {
        String cacheKey = PERM_CACHE_PREFIX + userId;

        // 1. 尝试从缓存获取
        Object cached = redisTemplate.opsForValue().get(cacheKey);
        if (cached != null) {
            return (Set<String>) cached;
        }

        // 2. 缓存未命中,查询数据库
        Set<String> permissions = permissionMapper.selectPermissionsByUserId(userId);

        // 3. 写入缓存,设置过期时间
        redisTemplate.opsForValue().set(cacheKey, permissions,
            CACHE_TTL_HOURS, TimeUnit.HOURS);

        return permissions;
    }

    /**
     * 清除用户权限缓存
     * 在用户角色变更、权限变更时调用
     */
    public void clearUserPermissionCache(Long userId) {
        redisTemplate.delete(PERM_CACHE_PREFIX + userId);
    }

    /**
     * 清除角色关联的所有用户权限缓存
     * 角色权限变更时,需要清除该角色下所有用户的缓存
     */
    public void clearRoleUsersCache(Long roleId) {
        List<Long> userIds = userRoleMapper.selectUserIdsByRoleId(roleId);
        List<String> keys = userIds.stream()
            .map(id -> PERM_CACHE_PREFIX + id)
            .toList();
        redisTemplate.delete(keys);
    }
}

四、场景三:分布式锁

分布式锁用于防止分布式环境下的重复操作,如防止重复提交、定时任务互斥执行等。

@Service
public class DistributedLockService {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    /**
     * 尝试获取分布式锁
     * @param lockKey 锁的key
     * @param requestId 请求标识(用于安全释放锁)
     * @param expireSeconds 锁的过期时间(秒)
     * @return 是否获取成功
     */
    public boolean tryLock(String lockKey, String requestId, long expireSeconds) {
        Boolean result = redisTemplate.opsForValue()
            .setIfAbsent(lockKey, requestId, expireSeconds, TimeUnit.SECONDS);
        return Boolean.TRUE.equals(result);
    }

    /**
     * 释放分布式锁(Lua脚本保证原子性)
     * 只有锁的持有者才能释放锁
     */
    public boolean unlock(String lockKey, String requestId) {
        String script = """
            if redis.call('get', KEYS[1]) == ARGV[1] then
                return redis.call('del', KEYS[1])
            else
                return 0
            end
            """;
        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>(script, Long.class);
        Long result = redisTemplate.execute(redisScript,
            Collections.singletonList(lockKey), requestId);
        return Long.valueOf(1L).equals(result);
    }
}

防重复提交注解

/** 防重复提交注解 */
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface RepeatSubmit {
    /** 间隔时间(秒),默认3秒 */
    int interval() default 3;
}

/** 防重复提交切面 */
@Aspect
@Component
public class RepeatSubmitAspect {

    @Autowired
    private DistributedLockService lockService;

    @Around("@annotation(repeatSubmit)")
    public Object around(ProceedingJoinPoint point, RepeatSubmit repeatSubmit) throws Throwable {
        HttpServletRequest request = ((ServletRequestAttributes)
            RequestContextHolder.getRequestAttributes()).getRequest();
        String lockKey = "repeat:" + request.getRequestURI() + ":" + getUserId();
        String requestId = UUID.randomUUID().toString();

        if (!lockService.tryLock(lockKey, requestId, repeatSubmit.interval())) {
            throw new BusinessException("请勿重复提交");
        }

        try {
            return point.proceed();
        } finally {
            lockService.unlock(lockKey, requestId);
        }
    }
}

五、场景四:登录失败计数

通过 Redis 的原子递增操作实现登录失败计数,配合过期时间实现自动解锁。

@Service
public class LoginSecurityService {

    private static final String FAIL_COUNT_PREFIX = "login:fail:";
    private static final String LOCK_PREFIX = "login:lock:";
    private static final int MAX_FAIL_COUNT = 5;
    private static final long LOCK_DURATION_MINUTES = 30;

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    /**
     * 记录登录失败
     * @param username 用户名
     * @return 当前失败次数
     */
    public int recordLoginFail(String username) {
        String countKey = FAIL_COUNT_PREFIX + username;

        // 原子递增失败次数
        Long failCount = redisTemplate.opsForValue().increment(countKey);

        // 首次失败时设置过期时间(15分钟窗口)
        if (failCount != null && failCount == 1) {
            redisTemplate.expire(countKey, 15, TimeUnit.MINUTES);
        }

        // 达到最大失败次数,锁定账号
        if (failCount != null && failCount >= MAX_FAIL_COUNT) {
            String lockKey = LOCK_PREFIX + username;
            redisTemplate.opsForValue().set(lockKey, "locked",
                LOCK_DURATION_MINUTES, TimeUnit.MINUTES);
        }

        return failCount != null ? failCount.intValue() : 1;
    }

    /**
     * 检查账号是否被锁定
     */
    public boolean isAccountLocked(String username) {
        String lockKey = LOCK_PREFIX + username;
        return Boolean.TRUE.equals(redisTemplate.hasKey(lockKey));
    }

    /**
     * 登录成功,清除失败计数
     */
    public void clearLoginFailCount(String username) {
        redisTemplate.delete(FAIL_COUNT_PREFIX + username);
    }
}

六、场景五:字典缓存

数据字典是系统中频繁访问的配置数据,适合使用 Redis Hash 结构缓存。

@Service
public class DictCacheService {

    private static final String DICT_CACHE_PREFIX = "dict:";

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    /**
     * 根据字典类型获取字典项列表
     * @param dictType 字典类型编码
     * @return 字典项列表
     */
    @SuppressWarnings("unchecked")
    public List<DictItem> getDictItems(String dictType) {
        String cacheKey = DICT_CACHE_PREFIX + dictType;

        // 从Hash缓存中获取
        Map<Object, Object> entries = redisTemplate.opsForHash().entries(cacheKey);
        if (!entries.isEmpty()) {
            return entries.values().stream()
                .map(v -> (DictItem) v)
                .toList();
        }

        // 缓存未命中,查询数据库
        List<DictItem> items = dictMapper.selectItemsByType(dictType);

        // 写入Hash缓存
        Map<String, DictItem> itemMap = items.stream()
            .collect(Collectors.toMap(DictItem::getValue, Function.identity()));
        redisTemplate.opsForHash().putAll(cacheKey, itemMap);
        redisTemplate.expire(cacheKey, 24, TimeUnit.HOURS);

        return items;
    }

    /**
     * 刷新字典缓存
     * 在字典数据变更时调用
     */
    public void refreshDictCache(String dictType) {
        redisTemplate.delete(DICT_CACHE_PREFIX + dictType);
        // 预热缓存
        getDictItems(dictType);
    }
}

七、场景六:向量存储

Redis 7.x+ 支持向量搜索功能,可用于 AI 知识库的语义检索(RAG)场景。

@Service
public class VectorSearchService {

    @Autowired
    private RedisTemplate<String, Object> redisTemplate;

    private static final String VECTOR_INDEX_PREFIX = "vector:knowledge:";

    /**
     * 存储文档向量
     * @param docId 文档ID
     * @param content 文档内容
     * @param embedding 向量数据
     */
    public void storeEmbedding(String docId, String content, float[] embedding) {
        String key = VECTOR_INDEX_PREFIX + docId;
        Map<String, Object> fields = new HashMap<>();
        fields.put("content", content);
        fields.put("embedding", convertToBytes(embedding));
        fields.put("timestamp", System.currentTimeMillis());

        redisTemplate.opsForHash().putAll(key, fields);
    }

    /**
     * 向量相似度搜索
     * @param queryEmbedding 查询向量
     * @param topK 返回最相似的K个结果
     * @return 相似文档列表
     */
    public List<SearchResult> searchSimilar(float[] queryEmbedding, int topK) {
        // 使用Redis Search模块的向量搜索能力
        // 实际项目中可结合RedisSearch客户端实现
        String query = String.format(
            "*=>[KNN %d @embedding $query_vec]",
            topK
        );

        // 返回搜索结果
        return executeVectorSearch(query, queryEmbedding);
    }

    /** float数组转字节数组 */
    private byte[] convertToBytes(float[] embedding) {
        ByteBuffer buffer = ByteBuffer.allocate(embedding.length * 4);
        for (float f : embedding) {
            buffer.putFloat(f);
        }
        return buffer.array();
    }
}

八、缓存一致性策略

graph TD
    A[数据变更] --> B{变更类型}
    B -->|单条更新| C[删除对应缓存Key]
    B -->|批量更新| D[删除相关缓存Key集合]
    B -->|结构变更| E[清除整个缓存分组]

    C --> F[下次请求触发缓存重建]
    D --> F
    E --> F

    G[缓存异常] --> H{降级策略}
    H -->|缓存穿透| I[缓存空值+短TTL]
    H -->|缓存雪崩| J[随机TTL偏移]
    H -->|缓存击穿| K[互斥锁重建]

    style C fill:#c8e6c9
    style D fill:#c8e6c9
    style E fill:#fff9c4
    style I fill:#e1f5fe
    style J fill:#e1f5fe
    style K fill:#e1f5fe

结论与建议

Redis Key 命名规范

场景 Key格式 TTL 示例
会话 session:{token} 24h session:abc123
权限缓存 perm:user:{userId} 2h perm:user:1001
分布式锁 lock:{业务}:{标识} 业务决定 lock:order:2001
登录计数 login:fail:{username} 15min login:fail:admin
字典缓存 dict:{dictType} 24h dict:gender
向量数据 vector:knowledge:{docId} 永久 vector:knowledge:doc001

最佳实践建议

  1. 缓存穿透防护:对查询结果为空的Key缓存空值,设置短TTL(如5分钟),避免恶意请求穿透到数据库。

  2. 缓存雪崩防护:同类缓存的TTL添加随机偏移量(如2小时±10分钟),避免大量缓存同时过期。

  3. 缓存击穿防护:对热点Key使用互斥锁重建缓存,避免大量请求同时穿透到数据库。

  4. 序列化选择:内部系统推荐JSON序列化(可读性好),高性能场景推荐Protobuf或Kryo。

  5. 监控告警:监控Redis内存使用率、命中率、慢查询,设置合理的告警阈值。

相关资源