服务器集群下根据年月日生成唯一编号重复?
开发环境 springboot + mybatis-plus
实现类伪代码如下
@Transactional(rollbackFor = Exception.class) public String getSeq(int id) {
SequenceMapper sequenceMapper = SpringContextUtis.getBean("sequenceMapper", SequenceMapper.class);
// 获取当前时间并格式化 yyyyMMddHHmm
String formatDate = dateFormatter.format(now);
// 取出数据库中的序列化对象
// 获取上一次的序列对象
Sequence sequence = sequenceMapper.selectOne(Wrappers.<Sequence>lambdaQuery().eq(Sequence::getId, 1));
// 如果数据库中存在
if (sequence != null) {
// 时间相同则将排序号加1
if (sequence中日期字符串 == formatDate) {
int last = sequence.last += 1;
sequence.last = last;
} else {
// 时间不同 则直接创建一个 当前时间 + 排序号的 序列
sequence.formatDate = formatDate;
sequence.last = last;
}
} else {
// 如果这种类型的序列从未创建过 则创建一个 当前时间 + 排序号的序列
sequence = new Sequence();
sequence.id = 1;
sequence.formatDate = formatDate;
sequence.last = formatDate + "01";
// 插入到数据库
if(sequenceMapper.insert(sequence) > 0)
return sequence.format + last;
else
throw new RuntimeException();
}
// 保存到数据库中 并记录上一次的 时间 和 排序号
if(sequenceMapper.update(sequence, Wrappers.<Sequence>lambdaUpdate().eq(Sequence::getId(), sequence.id)) > 0)
return sequence.format + last;
else
throw new RuntimeException();
调用类伪代码如下
public String getUserSeq(){ // 这里使用 redisson 作为分布式锁
RedissonClient redissonClient = SpringContextUtis.getBean("redissonClient", RedissonClient.class);
RLock lock = redissonClient.getLock("sequence");
String value = null;
// 只有真正获取到值才停止
while(value == null) {
lock.lock();
try {
value = sequenceGenerate.getSeq(1);
}catch(Exception exception){
log.error("获取失败,继续获取");
}finally {
lock.unlock();
}
}
return value;
}
代码基本就上面那些,在本地单机环境下没出现过 序列字符串 重复的问题;在集群环境下时而出现,按照我的理解来说,在创建时已经使用redisson锁住了,应该是不会出现数据脏读的问题,这样应该是不会重复的。求各位大佬帮忙解答一下,十分感谢。
回答:
更新
经过验证和讨论,并不是没有提交或者是锁有问题,而是代码里的逻辑导致,在集群中机器时间不一致的情况下,会出现重复。
历史回答
你这样还是会有脏数据的。
因为在 unlock 的时候,事务是没有提交的,此时数据还没写进去,其它进程又进来读数据了。
换种说法,目前你的做法,事务应该是整个线程结束后才被提交,而不是执行完 getSeq 就提交。
你这种情况我之前遇到,单纯的使用 @Transactional(rollbackFor = Exception.class)
,不论嵌套再多方法,使用的实际上都是同一个事务,当你打开一个事物的时候。
试想一下,当你打开同一个事务时,你如何让程序知道去主动提交事务呢?spring 框架并没有给你获取当前线程事务,手动提交的接口。
因此唯一的可能就是当线程结束后,spring 才知道此时要提交事务了。
方法一:
@Transactional(rollbackFor = Exception.class)
改成:
@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRES_NEW)
让方法结束时,强行提交到数据库。
Propagation.REQUIRES_NEW
:含义是,参数表示当前方法需要在新的事务中运行,加上之后,才能让方法结束后就立即提交事务。
方法二:
更加彻底的方法是使用 TransactionTemplate
新开一个事务,这样掌控起来更加容易和便于理解。
@Autowired TransactionTemplate template;
public void test() {
template.execute(status -> {
// 数据库更新
return null;
});
}
以上是 服务器集群下根据年月日生成唯一编号重复? 的全部内容, 来源链接: utcz.com/p/945312.html