【技术踩坑】Redission与Satoken的Redis数据库配置冲突实录
1. 问题现象:当Redis配置变成"狼人杀"
那天下午我刚部署完新版本,线上服务突然开始集体"失忆"——所有用户登录状态无故失效,重新登录后依然无法保持会话。作为使用Satoken做鉴权的系统,这相当于大门门锁突然失灵,整个系统陷入混乱。
通过Redis Desktop Manager查看,发现token确实安静地躺在database 1里(线上配置的库编号)。但诡异的是,当我在本地把database改成1复现时,Satoken死活读不到这些token。更魔幻的是,用redis-cli手动查询却能正常获取数据,就像一场精心设计的"狼人杀"——明明证据就在眼前,程序却集体装瞎。
2. 抽丝剥茧:当两个Redis客户端开始"打架"
在断点调试Satoken源码时,发现一个关键线索:RedisClient的database属性显示为0,而我们的配置明明是1。就像你明明订的是301房间,前台却硬把你塞进201。继续追踪发现,底层连接池竟然来自Redission而非预期的Lettuce。
这时候才恍然大悟——我们的系统同时存在两套Redis客户端:
- Spring Data Redis:通过application.yml配置,使用Lettuce作为默认客户端
- Redission:独立配置的分布式锁客户端,有自己的连接池
查看Redission的SingleServerConfig配置,果然发现其database硬编码为0。这就解释了为什么数据明明存在database 1,程序却总去database 0找人——就像两个快递员同时送货,一个按新地址投递,一个固执地使用旧地址。
3. 原理透析:配置参数的"平行宇宙"
Spring Boot的魔法配置spring.data.redis.database只对Spring体系下的客户端(Lettuce/Jedis)生效。Redission作为独立客户端,维护着自己的一套配置体系,这就形成了配置参数的"平行宇宙":
| 配置维度 | 生效范围 | 典型客户端 |
|---|---|---|
| spring.data.redis | Spring生态组件 | Lettuce/Jedis |
| redission.config | Redission独立客户端 | Redission |
这种割裂导致我们在application.yml里修改database配置时,就像在给左手戴手套,右手却依然赤裸——Redission完全感知不到Spring的配置变更。
4. 解决方案:给Redission配上"导航仪"
修复方案需要让Redission读取Spring的配置参数。这里分享我最终采用的配置方案:
@Configuration public class RedissonConfig { @Value("${spring.data.redis.host}") private String host; @Value("${spring.data.redis.port}") private String port; @Value("${spring.data.redis.database}") private int database; @Bean public RedissonClient redissonClient() { Config config = new Config(); config.useSingleServer() .setAddress("redis://" + host + ":" + port) .setDatabase(database); // 关键注入点 return Redisson.create(config); } }这个配置类像交通调度中心,确保所有Redis客户端都驶向正确的"车道"(database)。特别注意三点:
- 必须显式调用
setDatabase()方法 - 配置值要从Spring环境变量读取
- 建议把password等参数也统一管理
5. 防坑指南:多客户端共存的"交通规则"
在混合使用Redis客户端时,建议建立以下规范:
- 配置集中化:所有Redis相关参数统一在application.yml定义
- 客户端隔离:为不同用途(缓存/分布式锁)配置独立连接池
- 启动检查:添加健康检查验证各客户端连接的database是否正确
- 文档标注:在架构设计文档中明确记录各客户端的用途
一个实用的检查脚本,可以快速验证各客户端实际连接的database:
# 查看Spring Data Redis连接信息 spring.redis.database=$(grep "database:" application.yml | awk '{print $2}') echo "Spring配置的database: $spring.redis.database" # 查看Redission实际连接 redis-cli client list | grep -A 3 "redisson"6. 深度优化:连接池的"智能分流"
对于更高要求的场景,可以考虑更精细化的连接管理策略。这是我目前在用的多客户端配置模板:
@Configuration public class RedisMultiClientConfig { // 主连接池(Spring Data Redis使用) @Bean public LettuceConnectionFactory redisConnectionFactory( @Value("${spring.data.redis.host}") String host, @Value("${spring.data.redis.port}") int port, @Value("${spring.data.redis.database}") int database) { RedisStandaloneConfiguration config = new RedisStandaloneConfiguration(host, port); config.setDatabase(database); return new LettuceConnectionFactory(config); } // Redission专用连接池 @Bean public RedissonClient redissonClient( @Value("${spring.data.redis.host}") String host, @Value("${spring.data.redis.port}") String port, @Value("${spring.data.redis.database}") int database) { Config config = new Config(); config.useSingleServer() .setAddress("redis://" + host + ":" + port) .setDatabase(database) .setConnectionPoolSize(16); return Redisson.create(config); } }这种架构就像给不同车辆修建专用车道,避免发生"交通事故"。建议为不同用途设置不同的连接池参数:
- 缓存客户端:较大连接数,较短超时
- 分布式锁客户端:较小连接数,较长超时
- 会话管理客户端:中等连接数,平衡配置
7. 监控预警:给Redis配置装上"行车记录仪"
配置修复后,我增加了三道监控防线:
- 启动时校验:应用启动时主动验证各客户端连接的database
- 运行时巡检:通过Spring Boot Actuator暴露连接状态
- 异常时告警:当检测到database不匹配时触发企业微信告警
示例校验代码片段:
@Slf4j @Component public class RedisConfigValidator implements ApplicationRunner { @Autowired private RedisTemplate<String, String> redisTemplate; @Autowired private RedissonClient redissonClient; @Value("${spring.data.redis.database}") private int expectedDatabase; @Override public void run(ApplicationArguments args) { validateRedisTemplate(); validateRedissonClient(); } private void validateRedisTemplate() { try { int actualDb = redisTemplate.getConnectionFactory() .getConnection().getDbNumber(); assertDatabaseMatch("RedisTemplate", actualDb); } catch (Exception e) { log.error("RedisTemplate验证失败", e); } } private void validateRedissonClient() { try { int actualDb = redissonClient.getConfig() .useSingleServer().getDatabase(); assertDatabaseMatch("RedissonClient", actualDb); } catch (Exception e) { log.error("RedissonClient验证失败", e); } } private void assertDatabaseMatch(String clientName, int actualDb) { if (actualDb != expectedDatabase) { String msg = String.format("%s连接到了错误的database: %d (应为%d)", clientName, actualDb, expectedDatabase); throw new IllegalStateException(msg); } log.info("{} database验证通过: {}", clientName, actualDb); } }这套验证机制就像给系统装上了"防呆装置",确保配置错误在第一时间暴露,而不是等到用户报障才发现问题。在实际运行中,它已经帮我拦截了多次因配置疏忽导致的问题。
