分布式环境利用数据库生成连续唯一序列
生成连续唯一的序列号在很多业务场景都会需要,本文分享一个在分布式环境利用数据库生成连续唯一序列的例子(按天生成),在并发不是特别高的大型场景还是值得一干。文中采用版本(version)机制,结合自旋锁 + 乐观锁生成连续的唯一的数字。
直接上代码
1. 创建表test_no
CREATE TABLE `test_no` (
`id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
`date` varchar(10) NOT NULL COMMENT '格式yyyyMMdd',
`number` bigint(20) unsigned DEFAULT NULL COMMENT '序列号',
`version` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '版本',
PRIMARY KEY (`id`),
UNIQUE KEY `uniq_date` (`date`)
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8mb4;
date字段建有唯一索引。
2. MyBaties持久层代码TestNoMapper
package com.mingo.exp.generate_number.single_db_cas;
import com.mingo.exp.generate_number.single_db_cas.dto.TestNoDO;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
import org.apache.ibatis.annotations.Update;
/**
* @author Doflamingo
*/
public interface TestNoMapper {
/**
* 插入一条数据。主要用于初始值插入
*
* @param noDO
*/
@Insert("INSERT INTO test_no(`date`,number,version) VALUES(#{date}, #{number}, #{version})")
void insert(TestNoDO noDO);
/**
* 查询数据。date 具有唯一键
*
* @param date
* @return
*/
@Select("SELECT date,number,version FROM test_no WHERE `date` = #{date}")
TestNoDO select(@Param("date") String date);
/**
* 只有与当前版本号一样才能更新
*
* @param date
* @param version
* @return
*/
@Update("UPDATE test_no SET number = number + 1, version = version + 1 WHERE `date` = #{date} AND version = #{version}")
int update(@Param("date") String date, @Param("version") Integer version);
}
3. 生成连续唯一序列代码DistributeSerialNumber
package com.mingo.exp.generate_number.single_db_cas;
import com.mingo.exp.generate_number.single_db_cas.dto.TestNoDO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* 分布式环境利用数据库生成连续唯一序列
*
* @author Doflamingo
*/
@Component
public class DistributeSerialNumber {
@Autowired
private TestNoMapper testNoMapper;
/**
* 查询唯一号码
*
* @param date 格式 yyyyMMdd
* @return
*/
public int get(String date) {
TestNoDO testNoDO = this.selectOrInsert(date);
boolean flag = this.update(testNoDO);
if (flag) {
return testNoDO.getNumber();
}
// 自旋锁 + 乐观锁
while (!flag) {
testNoDO = testNoMapper.select(date);
// 更新number
flag = this.update(testNoDO);
}
return testNoDO.getNumber();
}
/**
* 这里主要用于当前首次查询和插入数据,保证直插入一条数据,date建有唯一索引;
* 并发度不大也可以不用双重校验锁,直接插入,异常再查询即可
*
* @Param date yyyyMMdd
*/
private TestNoDO selectOrInsert(String date) {
TestNoDO testNoDO = testNoMapper.select(date);
if (null == testNoDO) {
try {
testNoDO = new TestNoDO(date, 1, 0);
synchronized (this) {
TestNoDO testNoDO2 = testNoMapper.select(date);
if (null != testNoDO2) {
return testNoDO2;
}
testNoMapper.insert(testNoDO);
}
} catch (Exception e) {
// 插入失败,其他机器已经插入了
testNoDO = testNoMapper.select(date);
}
}
return testNoDO;
}
/**
* 按版本号更新数据
*
* @param testNoDO
* @return true 即 成功
*/
private boolean update(TestNoDO testNoDO) {
return 1 == testNoMapper.update(testNoDO.getDate(), testNoDO.getVersion());
}
}
4. 测试类DistributeSerialNumberTest
为了测试效果,我用20线程生成序列号
package com.mingo.exp.generate_number.single_db_cas;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
@SpringBootTest
@RunWith(SpringRunner.class)
public class DistributeSerialNumberTest {
@Autowired
private DistributeSerialNumber serialNumber;
@Test
public void test() throws Exception {
System.out.println("\n\n==============================\n");
// 20个线程
ExecutorService executorService =
new ThreadPoolExecutor(
20,
20,
0,
TimeUnit.MILLISECONDS,
new LinkedBlockingDeque<>()
);
for (int i = 30; i > 0; i--) {
// 启动
executorService.execute(() -> {
int number = serialNumber.get("20200608");
System.out.println("获得的数:" + number);
});
}
executorService.shutdown();
executorService.awaitTermination(10, TimeUnit.SECONDS);
}
}
5. 测试结果
获得的数:2
获得的数:1
获得的数:3
获得的数:4
获得的数:6
获得的数:5
获得的数:13
获得的数:8
获得的数:10
获得的数:14
获得的数:9
获得的数:11
获得的数:7
获得的数:12
获得的数:15
获得的数:18
获得的数:17
获得的数:16
获得的数:20
获得的数:26
获得的数:24
获得的数:21
获得的数:25
获得的数:27
获得的数:28
获得的数:23
获得的数:19
获得的数:22
获得的数:30
获得的数:29
表
原创
Doflamingo
https://www.cnblogs.com/doflamingo