上篇我讲了在mybatis中,新增数据时如何返回自增主键,依靠的是数据库可设置主键自动递增的机制,但是这种方法生成的主键扩展性比较差,如在一个分布式的系统中,会造成主键重复的问题。今天这篇文章讲下在分布式系统中如何生成全局唯一主键ID。
常见的解决方案大家可以参考下这篇文章,作者基于漫画的方式讲解的很清晰;
本文主要讲下在spring boot中如何集成SnowFlake算法,生成全局主键
SnowFlake算法github的网址: https://github.com/twitter/snowflake 但貌似已经不维护了 ~~
好了在上一篇文章代码的基础上,开始撸代码~~
该类是SnowFlake算法的核心,这是我在网上找的一个java版本
/**
* From: https://github.com/twitter/snowflake
* An object that generates IDs.
* This is broken into a separate class in case
* we ever want to support multiple worker threads
* per process
*/
public class IdWorker {
// 时间起始标记点,作为基准,一般取系统的最近时间(一旦确定不能变动)2017/1/1 0:0:0
private final static long twepoch = 1483200000000L;
// 机器标识位数
private final static long workerIdBits = 5L;
// 数据中心标识位数
private final static long datacenterIdBits = 5L;
// 机器ID最大值
private final static long maxWorkerId = -1L ^ (-1L << workerIdBits);
// 数据中心ID最大值
private final static long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
// 毫秒内自增位
private final static long sequenceBits = 12L;
// 机器ID偏左移12位
private final static long workerIdShift = sequenceBits;
// 数据中心ID左移17位
private final static long datacenterIdShift = sequenceBits + workerIdBits;
// 时间毫秒左移22位
private final static long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
private final static long sequenceMask = -1L ^ (-1L << sequenceBits);
/* 上次生产id时间戳 */
private static long lastTimestamp = -1L;
// 0,并发控制
private long sequence = 0L;
private final long workerId;
// 数据标识id部分
private final long datacenterId;
/**
* @param workerId
* 工作机器ID
* @param datacenterId
* 序列号
*/
public IdWorker(long workerId, long datacenterId) {
if (workerId > maxWorkerId || workerId < 0) {
throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
}
if (datacenterId > maxDatacenterId || datacenterId < 0) {
throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));
}
this.workerId = workerId;
this.datacenterId = datacenterId;
}
/**
* 获取下一个ID
* @return
*/
public synchronized long nextId() {
long timestamp = timeGen();
if (timestamp < lastTimestamp) {
throw new RuntimeException(String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
}
if (lastTimestamp == timestamp) {
// 当前毫秒内,则+1
sequence = (sequence + 1) & sequenceMask;
if (sequence == 0) {
// 当前毫秒内计数满了,则等待下一秒
timestamp = tilNextMillis(lastTimestamp);
}
} else {
sequence = 0L;
}
lastTimestamp = timestamp;
// ID偏移组合生成最终的ID,并返回ID
long nextId = ((timestamp - twepoch) << timestampLeftShift)
| (datacenterId << datacenterIdShift)
| (workerId << workerIdShift) | sequence;
return nextId;
}
private long tilNextMillis(final long lastTimestamp) {
long timestamp = this.timeGen();
while (timestamp <= lastTimestamp) {
timestamp = this.timeGen();
}
return timestamp;
}
private long timeGen() {
return System.currentTimeMillis();
}
}
导入idworker类后要想正常使用,肯定是要先配置一下:
首先在application.yml中新建如下2个配置参数,分别对应机器ID和序列号,多实例部署的话,不同实例该参数需配置不同值
util:
#工作机器ID
workerId: 5
#序列号
datacenterId: 10
然后创建配置类CustomerConfig,将IdWorker注入为bean
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
@ConfigurationProperties(prefix = "util")
@Data
public class CustomerConfig {
private Long workerId;
private Long datacenterId;
@Bean
public IdWorker createIdWorker() {
IdWorker worker = new IdWorker(workerId, datacenterId);
return worker;
}
}
配置好IdWorker后,使用非常简单,只要每次在调用insert操作时,我们显示的调用idworker的nextId()方法获取一个全局ID,然后将生成的ID赋值给即将插入的对象即可,具体代码如下:
DepartmentController.java
@Api(description = "department")
@RestController
@RequestMapping("dept")
public class DepartmentController {
@Autowired
public DepartmentService departmentService;
@Autowired
public IdWorker idWorker;
@ApiOperation(value = "新增部门")
@PostMapping("new")
public ResultMsg newDepartment(@RequestBody Department department)
{
//每次插入数据时,调用nextId()获取一个全局ID
department.setId(idWorker.nextId());
int result = departmentService.insertDept(department);
return ResultMsg.getMsg(result);
}
}
打开浏览器输入:http://localhost:9292/mybatis/swagger-ui.html 进入我们Swagger接口测试界面找到dept->new方法,输入测试参数,然后点击按钮try it out 我们连续插入多条看看效果: 源码地址
相比较上篇文章中自增主键的方式,每条涉及插入的SQL语句都要设置useGeneratedKeys和keyProperty,全局获取ID的方式只要一次配置后,后边每次插入操作只需调用下idWorker.nextId()方法,简直不要太简单
建议大家在在实际开发过程中都采用这种方式来生成主键ID,可谓一劳永逸
评论