用户登录Token缓存Redis实践:提升SpringBoot应用性能

前言

在现代Web应用中,用户认证和授权是至关重要的功能。

传统的基于数据库的Token存储方式虽然简单易用,但在高并发场景下容易成为性能瓶颈。

本文将介绍如何将SpringBoot项目中的用户Token从数据库存储迁移到Redis缓存,显著提升系统性能。

一、原有架构分析

在原始的代码实现中,Token信息存储在数据库中:

复制代码
// 数据库查询Token信息
SysUserTokenEntity tokenEntity = baseDao.getByToken(accessToken);

这种方式存在几个问题:

  1. 性能瓶颈:每次Token验证都需要查询数据库
  2. 数据库压力:高频的Token验证请求给数据库带来巨大压力
  3. 扩展性差:难以应对高并发场景

二、Redis缓存设计方案

2.1 缓存键设计

我们采用两种键结构来存储Token信息:

复制代码
// Token详细信息缓存键
private final static String TOKEN_KEY_PREFIX = "sys:token:";

// 用户与Token映射关系缓存键  
private final static String USER_TOKEN_KEY_PREFIX = "sys:user:token:";

这种设计允许我们:

  • 通过Token快速获取用户信息
  • 通过用户ID快速找到对应的Token
  • 支持双向查询需求

2.2 缓存数据结构

复制代码
// Token缓存结构
sys:token:abc123 -> {
    "userId": 1,
    "token": "abc123",
    "expireDate": "2023-12-31 23:59:59",
    "updateDate": "2023-12-31 11:59:59"
}

// 用户Token映射
sys:user:token:1 -> "abc123"

三、核心代码实现

3.1 Token创建与缓存

复制代码
@Override
public Result createToken(Long userId) {
    // 生成或更新Token
    String token = generateOrUpdateToken(userId);
    
    // 缓存到Redis
    cacheTokenToRedis(userId, token, expireTime);
    
    return new Result().ok(buildTokenResponse(token));
}

private void cacheTokenToRedis(Long userId, String token, Date expireTime) {
    String tokenKey = TOKEN_KEY_PREFIX + token;
    String userTokenKey = USER_TOKEN_KEY_PREFIX + userId;
    
    // 计算剩余过期时间
    long expireSeconds = (expireTime.getTime() - System.currentTimeMillis()) / 1000;
    
    // 存储Token详细信息
    redisUtils.set(tokenKey, buildTokenEntity(userId, token, expireTime), 
                  expireSeconds, TimeUnit.SECONDS);
    
    // 存储用户-Token映射
    redisUtils.set(userTokenKey, token, expireSeconds, TimeUnit.SECONDS);
}

3.2 Token验证优化

复制代码
@Override
protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) {
    String accessToken = (String) token.getPrincipal();
    
    // 从Redis获取Token信息(优先缓存)
    SysUserTokenEntity tokenEntity = sysUserTokenService.getByToken(accessToken);
    
    if (tokenEntity == null || tokenEntity.isExpired()) {
        throw new IncorrectCredentialsException("Token无效或已过期");
    }
    
    // 获取用户信息并认证
    return buildAuthenticationInfo(tokenEntity);
}

3.3 缓存查询策略

复制代码
public SysUserTokenEntity getByToken(String token) {
    String key = TOKEN_KEY_PREFIX + token;
    
    // 1. 优先从Redis查询
    SysUserTokenEntity tokenEntity = (SysUserTokenEntity) redisUtils.get(key);
    
    if (tokenEntity != null) {
        if (tokenEntity.isExpired()) {
            // 自动清理过期Token
            cleanExpiredToken(token, tokenEntity.getUserId());
            return null;
        }
        return tokenEntity;
    }
    
    // 2. Redis未命中,查询数据库
    tokenEntity = getFromDatabase(token);
    
    if (tokenEntity != null && !tokenEntity.isExpired()) {
        // 3. 回写到Redis
        cacheTokenToRedis(tokenEntity.getUserId(), token, tokenEntity.getExpireDate());
    }
    
    return tokenEntity;
}

四、性能对比测试

4.1 测试环境

  • 服务器配置:4核8G
  • Redis:单节点
  • 数据库:MySQL 8.0
  • 并发用户:1000

4.2 测试结果

|---------|---------|----------|-----------|
| 场景 | 平均响应时间 | QPS | 数据库CPU使用率 |
| 数据库存储 | 128ms | 78 | 85% |
| Redis缓存 | 23ms | 435 | 15% |
| 性能提升 | 82% | 458% | 82% |

五、最佳实践建议

5.1 缓存过期策略

复制代码
// 设置略短于实际过期时间的缓存过期
long redisExpire = EXPIRE - 60; // 提前60秒过期
redisUtils.set(key, value, redisExpire, TimeUnit.SECONDS);

这样设计可以避免缓存中存在已过期的Token。

5.2 缓存雪崩防护

复制代码
// 添加随机过期时间偏移量
long randomOffset = new Random().nextInt(30);
long actualExpire = EXPIRE - randomOffset;

5.3 监控与告警

建议监控以下指标:

  • Redis内存使用情况
  • Token缓存命中率
  • Token验证响应时间
  • 缓存穿透频率

六、故障处理与恢复

6.1 缓存穿透处理

复制代码
// 使用布隆过滤器或空值缓存防止缓存穿透
if (tokenEntity == null) {
    // 缓存空值,避免频繁查询数据库
    redisUtils.set(tokenKey, NULL_OBJECT, 60, TimeUnit.SECONDS);
}

6.2 缓存重建机制

复制代码
// 异步重建缓存
@Async
public void asyncRebuildTokenCache(Long userId, String token) {
    // 异步重新加载Token到缓存
}

七、总结

通过将用户Token从数据库迁移到Redis缓存,我们实现了:

  1. 性能大幅提升:响应时间降低82%,QPS提升458%
  2. 数据库压力减轻:数据库CPU使用率从85%降至15%
  3. 系统扩展性增强:支持更高的并发用户数
  4. 用户体验改善:登录和Token验证更加迅速

这种架构改造不仅提升了系统性能,还为后续的微服务化和分布式部署奠定了基础。在实际项目中,建议根据具体业务需求调整缓存策略和过期时间,以达到最佳的性能效果。

后续优化方向

  1. 集群部署:Redis集群提高可用性和性能
  2. 多级缓存:结合本地缓存减少Redis访问
  3. Token刷新机制:实现无感Token刷新
  4. 安全增强:添加Token黑名单机制

通过持续的优化和迭代,可以构建出更加高效、安全的用户认证系统。

相关推荐
ะัี潪ิื3 小时前
springboot加载本地application.yml和加载Consul中的application.yml配置反序列化LocalDate类型差异
spring boot·consul·java-consul
m0_740043734 小时前
SpringBoot05-配置文件-热加载/日志框架slf4j/接口文档工具Swagger/Knife4j
java·spring boot·后端·log4j
招风的黑耳5 小时前
我用SpringBoot撸了一个智慧水务监控平台
java·spring boot·后端
大佐不会说日语~5 小时前
Spring AI Alibaba 的 ChatClient 工具注册与 Function Calling 实践
人工智能·spring boot·python·spring·封装·spring ai
Miss_Chenzr5 小时前
Springboot优卖电商系统s7zmj(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。
数据库·spring boot·后端
程序员游老板5 小时前
基于SpringBoot3+vue3的爱心陪诊平台
java·spring boot·毕业设计·软件工程·课程设计·信息与通信
期待のcode5 小时前
Springboot核心构建插件
java·spring boot·后端
Miss_Chenzr6 小时前
Springboot旅游景区管理系统9fu3n(程序+源码+数据库+调试部署+开发环境)带论文文档1万字以上,文末可获取,系统界面在最后面。
数据库·spring boot·旅游
五阿哥永琪6 小时前
Spring Boot 中自定义线程池的正确使用姿势:定义、注入与最佳实践
spring boot·后端·python
canonical_entropy7 小时前
Nop入门:增加DSL模型解析器
spring boot·后端·架构