服务器集群下根据年月日生成唯一编号重复?

开发环境 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

回到顶部