“ 发号器是什么?为什么要用发号器?可以解决什么问题?”
发号器:
在分布式环境下,能够快速生成全局唯一标识(主键)非常重要,比如用户ID、订单ID、消息ID等等,这类服务虽然业务简单,但是生成的ID经常会用来作为数据分库分表关键Key,所以又显得特别重要。目前常用技术方案有使用数据库自增ID、UUID、自定义ID(包括雪花算法等)三种方式。本文主要介绍Twitter的SnowFlake(雪花算法),并基于雪花算法,设计一个支持多业务线通用的ID生成服务。
01
—
SnowFlake雪花算法
需求描述:
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 } ]}
周星星 互联网从业人员 资深峡谷战士
本文内容为原创内容
未经授权,禁止转载