一、场景及代码
1.场景
卡内编号全量下发黑白名单时 如果redis中的全量名单被删除/重启后消失 当设备访问接口时 会初始化一份全量黑白名单到缓存
而这段代码执行时间太长(25分钟)
2.
报错如下
代码如下
@GetMapping("/test")
public void test(){
long before = System.currentTimeMillis();
// 获取底层的RedisConnection
List<DdCardCode> cardCodeList = cardCodeComponent.list();
List<List<DdCardCode>> cardCodeListList = CollUtil.split(cardCodeList, 32);
List<Long> integerList = cardCodeListList.stream().map(e -> {
StringBuilder sb = new StringBuilder();
for (DdCardCode ddCardCode : e) {
sb.append(ddCardCode.getRecFlag());
}
return Long.parseLong(String.valueOf(sb),2);
}).collect(Collectors.toList());
long after = System.currentTimeMillis();
log.error("数据库100w条数据到缓存所需要的时间"+ (after-before)/1000 + "s");
long redisBefore = System.currentTimeMillis();
byte[] keyBytes = "test111".getBytes();
redisTemplate.execute((RedisCallback<Object>) connection -> {
BitFieldSubCommands.BitFieldSetBuilder builder = BitFieldSubCommands.create().set(BitFieldSubCommands.BitFieldType.unsigned(32));
for (int i = 0; i < integerList.size(); i++) {
long offset = i * 32;
BitFieldSubCommands subCommand = builder.valueAt(offset).to(integerList.get(i));
connection.bitField(keyBytes, subCommand);
}
return "success";
});
long redisAfter = System.currentTimeMillis();
log.error("同步100w条数据到缓存所需要的时间"+ (redisAfter-redisBefore)/1000 + "s");
}
二、思路
1.是代码的问题还是环境的问题?
(1)在统一身份认证的项目中 运行了同样的命令 可以成功
(2)在redis的控制台中直接执行, 可以成功
推断为环境问题
2.data-redis被redisson封装
3.身份核验中配置了Redissson而没有引入springboot-data-redis 有没有可能是这个原因?
(1)百度找了一下有没有类似问题(不用看英文) 大概说了原因和解决方案 但没debug代码的过程
https://blog.csdn.net/qq_38783304/article/details/129042816
(2)github上找到Redisson的开源项目 看看issue中有没有同样的问题
有同样的问题 并且作为一个bug被fixed了
(3)在3.15.6版本中被更新 理论上将目前的版本升级到这个版本 就没问题了
(4)根据commit的标题Missed implementation of few methods in Spring Data’s RedissonConnection知道大约是连接器的问题 根据栈溢出的报错看一下源码
三、源码分析
1.报错的代码
调用DefaultedRedisConnection中的bitField命令(已被废弃)
f7进入bitField时,自己调用了自己 导致了栈溢出
2.查看调用处 bitField 抽象方法有4个实现类
3.到此为止 无法得知 springboot的自动配置使用的哪个实现类
redisson提供了spring.factories文件,文件中的类被优先加载
配置类如下
4.代码中redis回调的入参是RedisConnection RedisConnection有很多实现类 实际使用哪个要根据RedisConnectionFactory来判断
5.RedissonConnectionFactory的配置
重写了getConnection方法 返回了RedissonConnection
6.所以connection.bitField方法应该是调用的RedissonConnection被重写的方法
7.但低版本的RedissonConnection没有重写 故调用了RedisConnection的默认实现接口DefaultedRedisConnection 造成了栈溢出(以下命令都没被重写)
bitField(byte[] key, BitFieldSubCommands subCommands)
exists(byte[]... keys)
touch(byte[]... keys)
encodingOf(byte[] key)
idletime(byte[] key)
refcount(byte[] key)
bitPos(byte[] key, boolean bit, org.springframework.data.domain.Range<Long> range)
restore(byte[] key, long ttlInMillis, byte[] serializedValue, boolean replace)