目录
“ 发号器是什么?为什么要用发号器?可以解决什么问题?”
发号器:
在分布式环境下,能够快速生成全局唯一标识(主键)非常重要,比如用户ID、订单ID、消息ID等等,这类服务虽然业务简单,但是生成的ID经常会用来作为数据分库分表关键Key,所以又显得特别重要。目前常用技术方案有使用数据库自增ID、UUID、自定义ID(包括雪花算法等)三种方式。
本文主要介绍Twitter的SnowFlake(雪花算法),并基于雪花算法,设计一个支持多业务线通用的ID生成服务。
发号器-雪花算法
需求描述:
1、分布式环境下全局生成ID唯一
2、可区分不同业务调用
3、可支持快速分库分表使用,ID生成比较均衡
4、可通过ID反查是什么时候,什么机器,什么业务生成的ID
解决方案:
提供两个接口:
ID生成接口:根据传入数量生成指定数量条件的ID,主要使用雪花算法,并对其中工作机器ID和序列号的位数进行调整,用来标识业务方与ID随机数。
ID校验接口:根据传入生成的ID,返回ID生成时间、机器、业务信息,根据雪花算法的位置逆序输出时间、机器、业务信息。
上图为雪花算法官方版本的结构图,根据上述规则可以生成一个64位的Long型数字其中位数调整为如下所示:
1 bits 标志位|41 bits: 时间戳(毫秒)| 6 bits: 业务线| 6 bits: 机器编号 | 10 bits: 序列号
实现过程:
1、定义实体类
package com.chow.kayadmin.modules.generateid.entity;
import java.io.Serializable;
/**
*
* 生成ID反解析实体类
* @author zhoukai
* @date 2020/3/23 19:56
*/
public class GenerateId implements Serializable {
private static final long serialVersionUID = 1L;
/**
* ID值
*/
private Long id;
/**
* 业务类型
*/
private Long serviceId;
/**
* 机器ID
*/
private Long wordId;
/**
* ID序号
*/
private Long sequence;
/**
* 生成时间
*/
private Long time;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Long getServiceId() {
return serviceId;
}
public void setServiceId(Long serviceId) {
this.serviceId = serviceId;
}
public Long getWordId() {
return wordId;
}
public void setWordId(Long wordId) {
this.wordId = wordId;
}
public Long getSequence() {
return sequence;
}
public void setSequence(Long sequence) {
this.sequence = sequence;
}
public Long getTime() {
return time;
}
public void setTime(Long time) {
this.time = time;
}
@Override
public String toString() {
return "GenerateId{" + "id=" + id + ", serviceId=" + serviceId + ", wordId=" + wordId
+ ", sequence=" + sequence + ", time=" + time + '}';
}
}
package com.chow.kayadmin.modules.generateid.entity;
import java.io.Serializable;
import java.util.List;
/**
*
* UID生成请求实体类
* @author zhoukai
* @date 2020/3/23 19:06
*/
public class GenerateIdVO implements Serializable{
private static final long serialVersionUID = 1L;
/**
* 请求生成数量
*/
private Integer nums;
/**
* 请求生成业务ID
*/
private Integer serviceId;
/**
* 请求检测的ID
*/
private List<Long> ids;
public Integer getNums() {
return nums;
}
public void setNums(Integer nums) {
this.nums = nums;
}
public Integer getServiceId() {
return serviceId;
}
public void setServiceId(Integer serviceId) {
this.serviceId = serviceId;
}
public List<Long> getIds() {
return ids;
}
public void setIds(List<Long> ids) {
this.ids = ids;
}
@Override
public String toString() {
return "GenerateIdVO{" + "nums=" + nums + ", serviceId=" + serviceId + ", ids=" + ids + '}';
}
}
2、接口控制层
package com.chow.kayadmin.modules.generateid.controller;
import com.chow.kayadmin.core.exception.ParamsException;
import com.chow.kayadmin.modules.generateid.entity.GenerateId;
import com.chow.kayadmin.modules.generateid.entity.GenerateIdVO;
import com.chow.kayadmin.modules.generateid.utils.GenerateIdUtils;
import com.chow.kaycommon.result.Result;
import com.chow.kaycommon.result.ResultUtils;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.springframework.util.ObjectUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
*
* 发号器-控制器层
* @author zhoukai
* @date 2020/3/23 19:00
*/
@RestController
@RequestMapping("/service/")
public class GenerateIdController {
/** 单次请求最大数量*/
private static final Integer MAX_NUMS = 1000;
/** 机器ID*/
private static final Integer WORK_ID = 1;
@PostMapping("/get/ids")
public static Result genrateId(@RequestBody GenerateIdVO generateIdVO){
if(ObjectUtils.isEmpty(generateIdVO.getNums())){
throw new ParamsException("params nums is empty");
}
if(ObjectUtils.isEmpty(generateIdVO.getServiceId())){
throw new ParamsException("params serviceId is empty");
}
if(generateIdVO.getNums() > MAX_NUMS || generateIdVO.getNums() <= 0){
throw new ParamsException("nums cannot be greater than "+ MAX_NUMS+" or less than or equal 0");
}
if(generateIdVO.getServiceId() > GenerateIdUtils.MAX_SERVICE_ID
||generateIdVO.getServiceId() <0){
throw new ParamsException("serviceId cannot be greater than "+GenerateIdUtils.MAX_SERVICE_ID+" or less than 0");
}
Set<Long> uidList = new HashSet<>();
GenerateIdUtils generateId = new GenerateIdUtils(WORK_ID, generateIdVO.getServiceId());
for (int i = 0; i < generateIdVO.getNums(); i++) {
uidList.add(generateId.nextId());
}
return ResultUtils.success(uidList);
}
@PostMapping("/check/ids")
public static Result checkIds(@RequestBody GenerateIdVO generateIdVO){
if(ObjectUtils.isEmpty(generateIdVO.getIds())){
throw new ParamsException("params nums is empty");
}
if(ObjectUtils.isEmpty(generateIdVO.getIds().size())){
throw new ParamsException("nums cannot be greater than "+ MAX_NUMS);
}
List<GenerateId> generateIdList = new ArrayList<>(16);
int idSize = generateIdVO.getIds().size();
for (int i = 0; i < idSize; i++) {
GenerateId generateId = new GenerateId();
generateId.setId(generateIdVO.getIds().get(i));
generateId.setServiceId(GenerateIdUtils.getServiceId(generateIdVO.getIds().get(i)));
generateId.setWordId(GenerateIdUtils.getWorkerId(generateIdVO.getIds().get(i)));
generateId.setSequence(GenerateIdUtils.getSequence(generateIdVO.getIds().get(i)));
generateId.setTime(GenerateIdUtils.getTime(generateIdVO.getIds().get(i)));
generateIdList.add(generateId);
}
return ResultUtils.success(generateIdList);
}
}
3、雪花算法工具类
package com.chow.kayadmin.modules.generateid.utils;
import java.security.SecureRandom;
/**
* 自定义 ID 生成器 17-19位数据 最长使用67年
* 1 bits 标志位|41 bits: Timestamp (毫秒)| 6 bits: 业务线标号| 6 bits: 机器编号 | 10 bits: 序列号
* 支持63个业务线 63个机器 1023个序列号
* @author zhoukai
* @date 2020/3/23 19:50
*/
public class GenerateIdUtils {
/**
* 基准时间 2020-03-20 13:14:27
*/
private final static long BASE_TIME = 1584681267000L;
/**
* 业务标志位数
*/
private final static long SERVICE_ID_BIT = 6L;
/**
* 机器标识位数
*/
private final static long WORKER_ID_BIT = 6L;
/**
* 序列号识位数
*/
private final static long SEQUENCE_ID_BIT = 10L;
/**
* 业务标志ID最大值
*/
public final static long MAX_SERVICE_ID = -1L ^ (-1L << SERVICE_ID_BIT);
/**
* 机器ID最大值
*/
private final static long MAX_WORKER_ID = -1L ^ (-1L << WORKER_ID_BIT);
/**
* 序列号ID最大值
*/
private final static long MAX_SEQUENCE_ID = -1L ^ (-1L << SEQUENCE_ID_BIT);
/**
* 机器ID偏左移
*/
private final static long WORKER_ID_SHIFT = SEQUENCE_ID_BIT;
/**
* 业务ID偏左移
*/
private final static long SERVICE_ID_SHIFT = SEQUENCE_ID_BIT + WORKER_ID_BIT;
/**
* 时间毫秒左移
*/
private final static long TIME_SHIFT = SEQUENCE_ID_BIT + WORKER_ID_BIT + SERVICE_ID_BIT ;
/**
* 最后一次的时间戳
**/
private static long lastTimestamp = -1L;
/**
* 序列号初始值
*/
private long sequence = 0L;
/**
* 机器号
*/
private final long workerId;
/**
* 业务线ID
*/
private final long serviceId;
public GenerateIdUtils(long workerId, long serviceId) {
// 如果超出范围就抛出异常
if (workerId > MAX_WORKER_ID || workerId < 0) {
throw new IllegalArgumentException("worker Id can't be greater than %d or less than 0");
}
if (serviceId > MAX_SERVICE_ID || serviceId < 0) {
throw new IllegalArgumentException(
"service Id can't be greater than %d or less than 0");
}
this.serviceId = serviceId;
this.workerId = workerId;
}
/**
* 实际产生代码的
*/
public synchronized long nextId() {
long timestamp = timeGen();
if (timestamp < lastTimestamp) {
try {
throw new Exception(
"Clock moved backwards. Refusing to generate id for " + (lastTimestamp
- timestamp) + " milliseconds");
} catch (Exception e) {
e.printStackTrace();
}
}
//如果上次生成时间和当前时间相同,在同一毫秒内
if (lastTimestamp == timestamp) {
//sequence自增,因为sequence只有10bit,所以和sequenceMask相与一下,去掉高位
sequence = (sequence + 1) & MAX_SEQUENCE_ID;
//判断是否溢出,也就是每毫秒内超过1024,当为1024时,与sequenceMask相与,sequence就等于0
if (sequence == 0) {
//自旋等待到下一毫秒
timestamp = tailNextMillis(lastTimestamp);
}
} else {
// 如果和上次生成时间不同,重置sequence,就是下一毫秒开始,sequence计数重新从0开始累加,
// 为了保证尾数随机性更大一些,最后一位设置一个随机数
sequence = new SecureRandom().nextInt(100);
}
lastTimestamp = timestamp;
return ((timestamp - BASE_TIME) << TIME_SHIFT)
| (serviceId << SERVICE_ID_SHIFT) | (
workerId << WORKER_ID_SHIFT) | sequence;
}
/**
* 防止产生的时间比之前的时间还要小(由于NTP回拨等问题),保持增量的趋势.
* @param lastTimestamp
* @author zhoukai
* @date 2020/3/23 20:07
*/
private long tailNextMillis(final long lastTimestamp) {
long timestamp = this.timeGen();
while (timestamp <= lastTimestamp) {
timestamp = this.timeGen();
}
return timestamp;
}
/**
*
* 获取当前的时间戳
* @author zhoukai
* @date 2020/3/23 20:08
*/
private long timeGen() {
return System.currentTimeMillis();
}
/**
* 根据传入生成的ID,获取生成的时间
* @param id
* @author zhoukai
* @date 2020/3/23 20:08
*/
public static Long getTime(Long id) {
String str = Long.toBinaryString(id);
int size = str.length();
int startIndex = 0;
int endIndex = Math.toIntExact(size - SEQUENCE_ID_BIT - WORKER_ID_BIT - SERVICE_ID_BIT);
String sequenceBinary = str.substring(startIndex, endIndex);
return Long.parseLong(sequenceBinary, 2)+BASE_TIME;
}
/**
* 根据传入生成的ID,获取生成的序列号
* @param id
* @author zhoukai
* @date 2020/3/23 20:08
*/
public static Long getSequence(Long id) {
String str = Long.toBinaryString(id);
int size = str.length();
int startIndex = Math.toIntExact(size - SEQUENCE_ID_BIT);
String sequenceBinary = str.substring(startIndex, size);
return Long.parseLong(sequenceBinary, 2);
}
/**
* 根据传入生成的ID,获取生成的机器编号
* @param id
* @author zhoukai
* @date 2020/3/23 20:08
*/
public static Long getWorkerId(Long id) {
String str = Long.toBinaryString(id);
int size = str.length();
int startIndex = Math.toIntExact(size - SEQUENCE_ID_BIT - WORKER_ID_BIT);
int endIndex = Math.toIntExact(size - SEQUENCE_ID_BIT);
String sequenceBinary = str.substring(startIndex, endIndex);
return Long.parseLong(sequenceBinary, 2);
}
/**
* 根据传入生成的ID,获取生成的业务号
* @param id
* @author zhoukai
* @date 2020/3/23 20:08
*/
public static Long getServiceId(Long id) {
String str = Long.toBinaryString(id);
int size = str.length();
int startIndex = Math.toIntExact(size - SEQUENCE_ID_BIT - WORKER_ID_BIT - SERVICE_ID_BIT);
int endIndex = Math.toIntExact(size - SEQUENCE_ID_BIT - WORKER_ID_BIT );
String sequenceBinary = str.substring(startIndex, endIndex);
return Long.parseLong(sequenceBinary, 2);
}
public static void main(String[] args) {
GenerateIdUtils generateId = new GenerateIdUtils(62,1);
for (int i = 0; i < 10; i++) {
System.out.println(generateId.nextId());
}
System.out.println(getTime(1066693619218473L));
System.out.println(getServiceId(1066693619218473L));
System.out.println(getWorkerId(1066693619218473L));
System.out.println(getSequence(1066693619218473L));
System.out.println(MAX_SERVICE_ID);
System.out.println(MAX_WORKER_ID);
System.out.println(MAX_SEQUENCE_ID);
}
}
4、结果演示
请求参数:
127.0.0.1:9200/service/get/ids
{
"nums":10,
"serviceId":"1"
}
返回结果:
{
"code": 200,
"msg": "成功",
"data": [
1073539071411206,
1073539071411207,
1073539071411205,
1073539071411210,
1073539071411211,
1073539071411208,
1073539071411209,
1073539071411212,
1073539067216925,
1073539071411213
]
}
请求参数:
127.0.0.1:9200/service/check/ids
{
"ids":[1073539071411206,1073539071411207]
}
返回结果:
{
"code": 200,
"msg": "成功",
"data": [
{
"id": 1073539071411206,
"serviceId": 1,
"wordId": 1,
"sequence": 6,
"time": 1584937218660
},
{
"id": 1073539071411207,
"serviceId": 1,
"wordId": 1,
"sequence": 7,
"time": 1584937218660
}
]
}