RedisAtomicLong分布式自增id出現重復編號
- 問題分析
- 解決方案
- 1.升級spring-data-redis版本
- 2.業務代碼加鎖
問題分析
近期公司的訂單編號出現重復編號,并且每次都是前幾個編號重復,后面就不會出現,從現象猜測八成是競態條件下出現并發問題,
幸運的是我竟然能在本地重現,啟動100個執行緒并發創建自增id,確實是會有編號0,1,2重復的情況,查看核心代碼:
生成分布式唯一自增id.
/**
* @Title: generate
* @Description: 生成分布式唯一自增id.
* @param key
* @return
*/
public long generate(String key) {
RedisAtomicLong counter = new RedisAtomicLong(key, redisTemplate.getConnectionFactory());
return counter.getAndIncrement();
}
看到這里非常奇怪,RedisAtomicLong的getAndIncrement使用的是redis的incrBy命令,這個命令是原子性的,不可能出現并發問題,
public Long increment(K key, long delta) {
byte[] rawKey = this.rawKey(key);
return (Long)this.execute((connection) -> {
return connection.incrBy(rawKey, delta);
}, true);
}
那就繼續查看RedisAtomicLong的初始化方法,發現問題所在:每次初始化的時候,判斷Redis中是否存在redisCounter這個key,如果不存在則設定這個key為0,這兩步不是原子性的,導致并發場景下,可能會有執行緒將已經增長的編號重新設定為0,從而導致id重復,
private RedisAtomicLong(String redisCounter, RedisConnectionFactory factory, Long initialValue) {
Assert.hasText(redisCounter, "a valid counter name is required");
Assert.notNull(factory, "a valid factory is required");
RedisTemplate<String, Long> redisTemplate = new RedisTemplate();
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new GenericToStringSerializer(Long.class));
redisTemplate.setExposeConnection(true);
redisTemplate.setConnectionFactory(factory);
redisTemplate.afterPropertiesSet();
this.key = redisCounter;
this.generalOps = redisTemplate;
this.operations = this.generalOps.opsForValue();
if (initialValue == null) {
//判斷Redis中是否存在redisCounter這個key,如果不存在則設定這個key為0
if (this.operations.get(redisCounter) == null) {
this.set(0L);
}
} else {
this.set(initialValue.longValue());
}
}
解決方案
1.升級spring-data-redis版本
目前官方已經修復了這個問題,受影響的版本: 1.8.16 , 2.0.11, 2.2 , 2.1.1 前的版本,2.1.2 , 2.0.12, 1.8.17 之后的版本已經修復,


2.業務代碼加鎖
判斷Redis中是否存在redisCounter這個key,如果不存在則加上Redis分布式鎖,防止并發創建,這個鎖競爭只會在剛開始的時候出現,所有對性能影響不大,
偽代碼:
/**
* @Title: generate
* @Description: 生成分布式唯一自增id.
* @param key
* @return
*/
public long generate(String key) {
RedisAtomicLong counter=null;
if (!exisit(key)){
try {
//Redis分布式鎖,注意防止超時導致死鎖
lock(key+"_lock");
counter = new RedisAtomicLong(key, redisTemplate.getConnectionFactory());
} finally {
unLock(key+"_lock");
}
}else {
counter = new RedisAtomicLong(key, redisTemplate.getConnectionFactory());
}
return counter.getAndIncrement();
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/276722.html
標籤:其他
上一篇:并發編程-初級之認識并發編程
