我正在使用 Spring Data Redis 模块中的 CrudRepository 接口来访问 Redis 中的数据。 我的Redis连接配置如下: 这是我的代码:
redis:
database: 1
host: my.redis.com
port: 6380
password: redis
timeout: 5000ms
lettuce:
pool:
max-active: 50
max-idle: 10
我的Redis配置类如下:
@Configuration
@Slf4j
@EnableCaching
public 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;
}
}
以下是我需要保存在Redis中的数据类:
@Data
@AllArgsConstructor
@NoArgsConstructor
@RedisHash(RedisKeyPrefix.UNIT_CONNECT_STATUS)
@Builder
public class UnitConnectStatus implements Serializable {
@Id
private Integer unitId;
private Integer unitType;
private Boolean server2gateway;
private Boolean gateway2unit;
}
我的repo类写成如下。正如你所看到的,它只是扩展了一个 CrudRepository,这非常简单。这就是我使用这种方法与 Redis 交互的原因:
public interface ConnectRepo extends CrudRepository<UnitConnectStatus,Integer> {
}
在我的整个项目中,我只有像saveOnline这样的几个方法来保存设备的在线状态,如下所示。值得注意的是,我使用@Async注解将数据异步存储在Redis中。正如你所看到的,我没有为 Redis 值设置过期时间:
@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());
}
我可以确认我的项目中没有代码执行删除键“RedisKeyPrefix.UNIT_CONNECT_STATUS”的操作。现在,奇怪的事情正在发生。我注意到,在项目运行时,某些 unitConnectStatus 对象偶尔会被读取为 null。我使用 id=57 的 unitConnectStatus 作为示例创建了一个简单的再现测试方法:
@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 - null
2024-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)
从一系列输出中可以看到,findById 得到的结果偶尔会为 null。理论上,我的密钥没有过期、失效或删除的逻辑。为什么偶尔查询会返回null?
我认为我的测试方法中不应该查询空值
问题是SpringDataRedis的CrudRepository的save方法在操作RedisHash数据时不是原子性的,所以如果频繁调用findBy()方法和save()方法,可能会读取到null。