I am using the CrudRepository interface from Spring Data Redis module to access data in Redis.My Redis connection configuration is as follows:Here is my code:
redis: database: 1 host: my.redis.com port: 6380 password: redis timeout: 5000ms lettuce: pool: max-active: 50 max-idle: 10
My Redis configuration class is as follows:
@Configuration@Slf4j@EnableCachingpublic class RedisConfig { @Bean public RedisTemplate<String, byte[]> byteRedisTemplate(RedisConnectionFactory connectionFactory) { RedisTemplate<String, byte[]> template = new RedisTemplate<>(); template.setConnectionFactory(connectionFactory); template.setKeySerializer(new StringRedisSerializer()); template.setValueSerializer(RedisSerializer.byteArray()); return template; } @Bean public RedisTemplate<String,Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) { RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>(); StringRedisSerializer stringRedisSerializer = new StringRedisSerializer(); //设置value hashValue值的序列化 Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>( Object.class); ObjectMapper om = new ObjectMapper(); om.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.ANY); om.activateDefaultTyping(LaissezFaireSubTypeValidator.instance,ObjectMapper.DefaultTyping.NON_FINAL, JsonTypeInfo.As.WRAPPER_ARRAY); serializer.setObjectMapper(om); redisTemplate.setValueSerializer(serializer); redisTemplate.setHashValueSerializer(serializer); //key hasKey的序列化 redisTemplate.setKeySerializer(stringRedisSerializer); redisTemplate.setHashKeySerializer(stringRedisSerializer); redisTemplate.setConnectionFactory(redisConnectionFactory); redisTemplate.afterPropertiesSet(); return redisTemplate; } @Bean public CacheManager cacheManager(RedisConnectionFactory connectionFactory) { RedisCacheConfiguration redisCacheConfiguration = RedisCacheConfiguration.defaultCacheConfig() .disableCachingNullValues() .entryTtl(Duration.ofMinutes(10)) .serializeValuesWith(RedisSerializationContext.SerializationPair.fromSerializer(new GenericJackson2JsonRedisSerializer())); RedisCacheManager redisCacheManager = RedisCacheManager.builder(connectionFactory) .cacheDefaults(redisCacheConfiguration) .build(); return redisCacheManager; }}
The following is the data class that I need to save in Redis:
@Data@AllArgsConstructor@NoArgsConstructor@RedisHash(RedisKeyPrefix.UNIT_CONNECT_STATUS)@Builderpublic class UnitConnectStatus implements Serializable { @Id private Integer unitId; private Integer unitType; private Boolean server2gateway; private Boolean gateway2unit;}
My repo class is written as follows. As you can see, it simply extends a CrudRepository, which is very straightforward. This is the reason I use this approach to interact with Redis:
public interface ConnectRepo extends CrudRepository<UnitConnectStatus,Integer> {}
In my entire project, I only have a few methods like the saveOnline method to save the online status of devices, as shown below. It's worth noting that I use the @Async annotation to asynchronously store data in Redis. As you can see, I haven't set an expiration time for Redis values:
@Async public <T extends Unit>void saveOnline(T unit){ Assert.notNull(unit, "unit is null"); Assert.notNull(unit.getId(), "unit id is null"); connectRepo.save(UnitConnectStatus.builder() .unitId(unit.getId()) .unitType(unit.getType()) .server2gateway(true) .gateway2unit(true) .build()); }
And I can confirm that there is no code in my project that executes the action of deleting the key 'RedisKeyPrefix.UNIT_CONNECT_STATUS.' Now, something strange is happening. I've noticed that during the project's runtime, some unitConnectStatus objects occasionally appear to be read as null. I've created a simple reproduction test method using the unitConnectStatus with id=57 as an example:
@Test public void testCon(){ for (int i = 0; i < 10000; i++) { log.info("{}",connectRepo.findById(56).orElse(null)); Thread.sleep(200); } }
2024-01-26 11:39:48.361 [][] INFO c.t.controller.TestLeila:20 - UnitConnectStatus(unitId=56, unitType=1, server2gateway=true, gateway2unit=true)2024-01-26 11:39:48.582 [][] INFO c.t.controller.TestLeila:20 - UnitConnectStatus(unitId=56, unitType=1, server2gateway=true, gateway2unit=true)2024-01-26 11:39:48.803 [][] INFO c.t.controller.TestLeila:20 - UnitConnectStatus(unitId=56, unitType=1, server2gateway=true, gateway2unit=true)2024-01-26 11:39:49.023 [][] INFO c.t.controller.TestLeila:20 - null2024-01-26 11:39:49.235 [][] INFO c.t.controller.TestLeila:20 - UnitConnectStatus(unitId=56, unitType=1, server2gateway=true, gateway2unit=true)2024-01-26 11:39:49.453 [][] INFO c.t.controller.TestLeila:20 - UnitConnectStatus(unitId=56, unitType=1, server2gateway=true, gateway2unit=true)2024-01-26 11:39:49.673 [][] INFO c.t.controller.TestLeila:20 - UnitConnectStatus(unitId=56, unitType=1, server2gateway=true, gateway2unit=true)2024-01-26 11:39:49.906 [][] INFO c.t.controller.TestLeila:20 - UnitConnectStatus(unitId=56, unitType=1, server2gateway=true, gateway2unit=true)
As you can see from a series of outputs, the result obtained by findById is occasionally null. In theory, there is no logic for expiration, invalidation, or deletion of my key. Why does the occasional query return null?
I don't think there should be a query for null values in my test method