武功秘籍之发号器

武功秘籍之发号器

目录

“ 发号器是什么?为什么要用发号器?可以解决什么问题?”

发号器:
在分布式环境下,能够快速生成全局唯一标识(主键)非常重要,比如用户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
        }
    ]
}

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/zk18286047195/article/details/106052350