最近工作中遇到了这样一个场景
同一个外部单号生成了多张出库单,等待所有相关的出库单都出库成功后回复成功消息外部系统调用方。因为是分布式布系统,我使用了RedisAtomicInteger计数器来判断出库单是否全部完成,数量达成时回复成功消息给外部系统调用方。
在本地测试和测试环境测试时都没有发现问题,到了生产环境后,发现偶尔出现所有出库单都已经出库,但没有回复消息给调用方,如:出库单15张,但计数器只有14。
分析:
开始以为是有单据漏计算了,通过日志分析,发现所有的出库单都统计进去了。
然后通过增加打开调试日志,发现最开始的2张出库单统计后的值都为1,少了1个。
原因:
redis的increment是原子性,但new RedisAtomicInteger时会调用set方法来设置初始值,set方法是可以被后面的方法覆盖的。
edisAtomicInteger redisAtomicInt = new RedisAtomicInteger(countKey, redisTemplate.getConnectionFactory());
// spring-data-redis-1.8.13原码
public RedisAtomicInteger(String redisCounter, RedisConnectionFactory factory) {
this(redisCounter, factory, null);
}
private RedisAtomicInteger(String redisCounter, RedisConnectionFactory factory, Integer initialValue) {
RedisTemplate<String, Integer> redisTemplate = new RedisTemplate<String, Integer>();
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new GenericToStringSerializer<Integer>(Integer.class));
redisTemplate.setExposeConnection(true);
redisTemplate.setConnectionFactory(factory);
redisTemplate.afterPropertiesSet();
this.key = redisCounter;
this.generalOps = redisTemplate;
this.operations = generalOps.opsForValue();
if (initialValue == null) {
if (this.operations.get(redisCounter) == null) {
set(0);
}
} else {
set(initialValue);
}
}
解决方法:
网上看到的都是加业务锁或升级spring-data-redis版本。
但老项目升级spring-data-redis版本可能会引起兼容性问题,加业务锁又增加了代码复杂度。
那有没有更简单方法呢,有。竟然是set方法导致的值覆盖,那就不走set方法就可以了。
增加下面一行代码解决问题
// Fixed bug 前几个数累计重复问题
redisTemplate.opsForValue().setIfAbsent(countKey, 0);