调试过程

通过本地调试,经过一个半天终于确定了是 redis 缓存信息有问题导致了任务调度没有达到预期。前面调试过没有问题的业务逻辑部分忽略,涉及的部分主要在对数据库一个规则表做缓存处理,因为业务需要会频繁使用。

大概问题是这样:刷新缓存的部分出错,导致规则没有写入到 redis,业务消费时找不到 redis 存储的规则,自然不会正常执行。所以关键点在数据写入到 redis。直接调用和调试都会报错。

调用方法:

redisTemplate.opsForList().rightPushAll(key, xxxUser)

报错信息:

xxxUser cannot be cast to java.lang.String

一开始以为是 opsForList().rightPushAll() 接受到第二个参数需要是 String 类型(从报错信息来看)。API rightPushAll(K var1, V... var2),K、V 都是 ListOperations<K, V> 定义的泛型类型。从自动装配注入的 RedisTemplate<String, Object> redisTemplate 可以看出,第二个参数应当为 Object 类型。而 xxxUser 传递进来 upCasting 向上转换 Object 应该是不成问题的。

百度搜索 RedisTemplate<String, Object> redis 使用 opsForList().rightPushAll 放入对象,可以得到 AI 生成的一大串 demo 使用方法:

RedisTemplate&lt;String, Object&gt; redis 使用 opsForList().rightPushAll 放入对象 - 百度AI

不得不赞一句百度 AI 真的很强大,以后一些基础性的对象使用方法,完全可以从它这里参考。

对照存入和读取方法都没有问题。然后又百度了 opsForList 存储对象报错 User cannot be cast to java.lang.String,这里提供了一个解决方法:

ObjectMapper mapper = new ObjectMapper();
String objectAsString = mapper.writeValueAsString(yourObject);

但是读取的时候也要处理:

String valueFromList = redisTemplate.opsForList().leftPop(key);
ObjectMapper mapper = new ObjectMapper();
YourObjectType yourObject = mapper.readValue(valueFromList, YourObjectType.class);

在写入的部分修改了一下,发现存储的数据处理方式是不一样的。查看 gitlab 这个文件接近一年的时间都没有修改过,所以基本排除了当前 redis 读取代码存在问题的可能性。

回过头来发现,前面百度到的使用 demo 中,有提及到一些信息:

需要注意的是 RedisTemplate 默认使用的序列化器是 JdkSerializationRedisSerializer,它会将对象序列化后存储
如果你需要存储的是简单的字符串或数字,可以使用StringRedisTemplate,它使用 StringRedisSerializer 来序列化字符串。
如果你需要存储复杂对象,可以使用 JSON 序列化器,如 Jackson2JsonRedisSerializer,这样可以存储对象的 JSON 表示。

SpringBoot 使用本来就会有很多配置,正常都会有一个配置文件 xxConfig,是否是在配置文件里面定义了序列化器呢?进入到 RedisTemplate.class 中,按住 command 点击类名,查找使用的地方,找到了一个 RedisConfig.java 文件。

@Configuration
public class RedisConfig {
    /**
     * 注入 RedisConnectionFactory
     */
    @Autowired
    RedisConnectionFactory factory;

    /**
     * 实例化 RedisTemplate 对象
     *
     * @return
     */
    @Bean
    public RedisTemplate<String, Object> redisTemplate() {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(jackson2JsonRedisSerializer());
        redisTemplate.setValueSerializer(jackson2JsonRedisSerializer());
        redisTemplate.setConnectionFactory(factory);
        // 设置---非常重要********
        ParserConfig.getGlobalInstance().addAccept("com.xxx");
        return redisTemplate;
    }

    @Bean
    public Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer() {
        final Jackson2JsonRedisSerializer<Object> jackson2JsonRedisSerializer = new Jackson2JsonRedisSerializer(
                Object.class);
        ...
        return jackson2JsonRedisSerializer;
    }

    ...

主要关注返回了 redisTemplate 对象的方法,设置了 keySerializerhashKeySerializer 使用 StringRedisSerializer 序列化器,同时设置了 hashValueSerializervalueSerializer 使用 Jackson2JsonRedisSerializer 序列化器。

从现有配置来看,配置文件一点儿问题都没有,还非常完善。好奇注入进来的 redisTemplate 有没有使用这些序列化器,通过断点调试,果然有问题,四项 keySerializerhashKeySerializerhashValueSerializervalueSerializer 使用的都是 StringRedisSerializer。添加一项 redisTemplate.setDefaultSerializer(jackson2JsonRedisSerializer()); 再次查看 defaultSerializer 设置是生效的。然后观察到这些实例对象@Id 值是不一样的,有先后,也就是说不是同一次初始化的。这说明序列化器有重新配置过。

解决

再次查找 RedisTemplate 类的使用里,找到了一个 RedisService 的组件,里面同样注入了 RestTemplate 对象,或者说使用的是同一个实例对象。这个是我从另一个项目里 copy 过来的,主要是从 redis 中读取和存储 token 用户信息。跟一般的使用不同的是,里面还加了一个自动装配的方法:

    @Autowired(required = false)
    public void setRedisTemplate(RedisTemplate redisTemplate) {
        RedisSerializer stringSerializer = new StringRedisSerializer();
        redisTemplate.setKeySerializer(stringSerializer);
        redisTemplate.setValueSerializer(stringSerializer);
        redisTemplate.setHashKeySerializer(stringSerializer);
        redisTemplate.setHashValueSerializer(stringSerializer);
        this.redisTemplate = redisTemplate;
    }

@Autowired(required=false) 表示忽略当前要注入的 bean 如果有直接注入,没有就跳过,不会报错。所以这个方法也是会自动执行的。里面设置了四项为字符串序列化器,问题就出在这里了。redis 重复配置了。

注释掉对于 redis K、V 序列化器的重新设置,测试检查这部分 redis 操作没有报错,OK,问题解决。