高并发场景下 Redis 存储 JWT 黑名单如何优化鉴权性能?

文章导读
在高并发场景下,单纯依赖 Redis 存储 JWT 黑名单会导致每次鉴权都产生网络 RTT,成为性能瓶颈。工程实践中,最稳妥的优化方案是“短有效期 Token + 本地缓存 + 布隆过滤器 + Redis 兜底”的多级拦截策略。
📋 目录
  1. 核心代码实现
  2. 配置与部署细节
  3. 验证与压测方法
  4. 常见坑与解决方案
A A

在高并发场景下,单纯依赖 Redis 存储 JWT 黑名单会导致每次鉴权都产生网络 RTT,成为性能瓶颈。工程实践中,最稳妥的优化方案是“短有效期 Token + 本地缓存 + 布隆过滤器 + Redis 兜底”的多级拦截策略。

核心结论:不要将所有鉴权压力直接打在 Redis 上。通过多级缓存过滤大部分合法请求,仅在疑似黑名单请求时查询 Redis。

  • 先定位:使用 Arthas 或 APM 工具确认鉴权接口中 Redis 网络耗时占比。
  • 先做:实施 Access Token 短有效期(如 15 分钟),引入 Caffeine 本地缓存与 Redis Bloom Filter。
  • 再验证:对比优化前后鉴权接口 P99 延迟及 Redis CPU 使用率。

核心代码实现

以下基于 Java Spring Boot 环境,展示如何构建多级黑名单校验逻辑。关键在于布隆过滤器误判处理:当布隆过滤器返回“可能存在”时,必须二次查询 Redis 确认,避免误杀正常用户。

高并发场景下 Redis 存储 JWT 黑名单如何优化鉴权性能?
@Component
public class JwtBlacklistChecker {

    @Autowired
    private RedisTemplate<String, String> redisTemplate;
    
    @Autowired
    private RBloomFilter<String> bloomFilter;
    
    // 本地缓存:存储最近被确认黑名单的 JWT ID,减少穿透
    @Cacheable(value = "blacklistCache", expireAfterWrite = 5, unit = TimeUnit.MINUTES)
    private Cache<String, Boolean> localCache = Caffeine.newBuilder()
            .maximumSize(10000)
            .expireAfterWrite(5, TimeUnit.MINUTES)
            .build();

    public void check(String jwtId) {
        // 1. 查本地缓存 (最快)
        if (Boolean.TRUE.equals(localCache.getIfPresent(jwtId))) {
            throw new AuthenticationException("Token 已在黑名单中");
        }

        // 2. 查布隆过滤器 (过滤大部分正常请求)
        // 注意:bloomFilter.contains 返回 true 仅代表"可能存在"
        if (bloomFilter.contains(jwtId)) {
            // 3. 二次确认 Redis (解决布隆过滤器误判问题)
            Boolean isBlacklisted = redisTemplate.hasKey("blacklisted:" + jwtId);
            if (Boolean.TRUE.equals(isBlacklisted)) {
                // 确认黑名单,写入本地缓存加速后续请求
                localCache.put(jwtId, true);
                throw new AuthenticationException("Token 已在黑名单中");
            }
            // 如果 Redis 返回 false,说明是布隆过滤器误判,放行请求
        }
        // 如果布隆过滤器返回 false,则一定不在黑名单,直接放行
    }
}

配置与部署细节

1. Redis Key 过期时间设置
黑名单 Key 必须设置过期时间,过期时间应略大于 Token 剩余有效期,防止内存泄漏。

SET blacklisted:jwt_id <value> EX <seconds>

2. 布隆过滤器初始化
需预估最大元素量和误判率。例如预期存储 100 万个黑名单 ID,误判率 0.01%:

BF.RESERVE jwtBlacklist 0.001 1000000

3. Refresh Token 刷新流程
Access Token 有效期缩短后,需配合 Refresh Token 机制。用户退出时,同时将 Access Token ID 和 Refresh Token ID 加入黑名单。

高并发场景下 Redis 存储 JWT 黑名单如何优化鉴权性能?
// 注销示例
public void logout(String accessTokenId, String refreshTokenId) {
    // 异步写入黑名单,避免阻塞退出流程
    asyncExecutor.execute(() -> {
        redisTemplate.setex("blacklisted:" + accessTokenId, 15, TimeUnit.MINUTES, "revoked");
        redisTemplate.setex("blacklisted:" + refreshTokenId, 7, TimeUnit.DAYS, "revoked");
        bloomFilter.add(accessTokenId);
        bloomFilter.add(refreshTokenId);
    });
}

验证与压测方法

优化效果需通过监控数据验证,避免主观猜测:

  1. 延迟对比:使用 JMeter 压测鉴权接口,对比优化前后 P99 延迟。预期本地缓存命中场景下延迟降至毫秒级。
  2. Redis 负载:观察 Redis info stats 中的 instantaneous_ops_per_sec。优化后,鉴权相关的 READ 操作应显著下降。
  3. 慢查询监控:定期执行 slowlog get 10,确认无大量鉴权相关的慢查询堆积。

常见坑与解决方案

1. 布隆过滤器误判导致正常用户被拦截
风险:布隆过滤器存在哈希冲突,可能将正常 ID 判为存在。
解决:代码中必须实现“布隆命中 -> 查 Redis 确认”的逻辑。只有 Redis 也确认存在时,才拒绝请求。

高并发场景下 Redis 存储 JWT 黑名单如何优化鉴权性能?

2. 分布式本地缓存一致性
风险:多实例部署时,某实例加入黑名单,其他实例本地缓存未同步。
解决:本地缓存仅作为“性能加速”,TTL 设置较短(如 5 分钟)。最终一致性依赖 Redis 兜底。若需强一致,可通过 Redis Pub/Sub 广播失效消息清除本地缓存。

3. 内存爆炸
风险:黑名单 Key 未设置过期时间,随时间无限增长。
解决:写入黑名单时,TTL 必须等于 Token 剩余有效期。对于 Refresh Token 黑名单,建议设置固定较长过期时间(如 7 天)后自动清理。