• 雪花算法生成ID


    前言
    我们的数据库在设计时一般有两个ID,自增的id为主键,还有一个业务ID使用UUID生成。自增id在需要分表的情况下做为业务主键不太理想,所以我们增加了uuid作为业务ID,有了业务id仍然还存在自增id的原因具体我也说不清楚,只知道和插入的性能以及db的要求有关。

    我个人一直想将这两个ID换成一个字段来处理,所以要求这个id是数字类似的,且是趋抛增长的,这样mysql创建索引以及查询时性能会比较好。于时网上找到了雪花算法.关于雪花算法大家可以看一下我后面引用的资料。

    ID生成器代码:
    从网上抄的,自己改的,目前我还没有应用到实际项目中,如需应用,请先进行严格自测

      1  
      2 import java.time.LocalDateTime;
      3 import java.time.ZoneOffset;
      4 import java.time.format.DateTimeFormatter;
      5  
      6 /**
      7  * <p>
      8  *  在雪花算法基础生稍做改造生成Long Id
      9  *  https://www.jianshu.com/p/d3881a6a895e
     10  * </p>
     11  * 1 - 41位 - 10位 - 12位
     12  * 0 - 41位 - 10位 - 12位
     13  * <p>
     14  * <PRE>
     15  * <BR>    修改记录
     16  * <BR>-----------------------------------------------
     17  * <BR>    修改日期         修改人          修改内容
     18  * </PRE>
     19  *
     20  * @author cuiyh9
     21  * @version 1.0
     22  * @Date Created in 2018年11月29日 20:46
     23  * @since 1.0
     24  */
     25 public final class ZfIdGenerator {
     26  
     27     /**
     28      * 起始的时间戳
     29      */
     30     private static final long START_TIME_MILLIS;
     31  
     32     /**
     33      * 每一部分占用的位数
     34      */
     35     private final static long SEQUENCE_BIT = 12; //序列号占用的位数
     36     private final static long WORKID_BIT = 10;   //机器标识占用的位数
     37  
     38     /**
     39      * 每一部分的最大值
     40      */
     41     private final static long MAX_WORK_NUM = -1L ^ (-1L << WORKID_BIT);
     42     private final static long MAX_SEQUENCE = -1L ^ (-1L << SEQUENCE_BIT);
     43  
     44     /**
     45      * 每一部分向左的位移
     46      */
     47     private final static long WORKID_SHIFT = SEQUENCE_BIT;
     48     private final static long TIMESTMP_SHIFT = WORKID_SHIFT + WORKID_BIT;
     49  
     50     private long sequence = 0L; //序列号
     51     private long lastStmp = -1L;
     52  
     53     /** workId */
     54     private long workId;
     55  
     56     static {
     57         String startDate = "2018-01-01 00:00:00";
     58         DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
     59         LocalDateTime localDateTime = LocalDateTime.parse(startDate, df);
     60         START_TIME_MILLIS = localDateTime.toInstant(ZoneOffset.of("+8")).toEpochMilli();
     61  
     62     }
     63  
     64  
     65  
     66  
     67     /**
     68      * 获取分部署式发号器
     69      * @param workId 每台服务需要传一个服务id
     70      * @return
     71      */
     72     public static synchronized ZfIdGenerator getDistributedIdGenerator(long workId) {
     73         return new ZfIdGenerator(workId);
     74     }
     75  
     76     public static synchronized ZfIdGenerator getStandAloneIdGenerator() {
     77         long workId = MAX_WORK_NUM;
     78         return new ZfIdGenerator(workId);
     79     }
     80  
     81  
     82     private ZfIdGenerator(long workId) {
     83         if (workId > MAX_WORK_NUM || workId <= 0) {
     84             throw  new RuntimeException("workdId的值设置错误");
     85         }
     86         this.workId = workId;
     87     }
     88  
     89     /**
     90      * 生成id
     91      * @return
     92      */
     93     public synchronized long nextId() {
     94         long currStmp = System.currentTimeMillis();
     95         if (currStmp < START_TIME_MILLIS) {
     96             throw new RuntimeException("机器时间存在问题,请注意查看");
     97         }
     98  
     99         if (currStmp == lastStmp) {
    100             sequence =  (sequence + 1) & MAX_SEQUENCE;
    101             if (sequence == 0L) {
    102                 currStmp = getNextMillis(currStmp);
    103             }
    104         } else {
    105             sequence = 0L;
    106         }
    107         lastStmp = currStmp;
    108  
    109         return ((currStmp - START_TIME_MILLIS) << TIMESTMP_SHIFT)
    110                 | (workId << WORKID_SHIFT)
    111                 | (sequence);
    112     }
    113  
    114     public long getNextMillis(long currStmp) {
    115         long millis = System.currentTimeMillis();
    116         while (millis <= currStmp) {
    117             millis = System.currentTimeMillis();
    118         }
    119         return millis;
    120     }
    121  
    122     /**
    123      * 获取最大的工作数量
    124      * @return
    125      */
    126     public static long getMaxWorkNum() {
    127         return MAX_WORK_NUM;
    128     }
    129  
    130     public static void main(String[] args) {
    131         ZfIdGenerator idGenerator1 = ZfIdGenerator.getDistributedIdGenerator(1);
    132 //        ZfIdGenerator idGenerator2 = ZfIdGenerator.getDistributedIdGenerator(2);
    133         for (int i = 0; i < 1000000; i++) {
    134             System.out.println(idGenerator1.nextId());
    135         }
    136  
    137 //        System.out.println(idGenerator2.nextId());
    138  
    139  
    140  
    141     }
    142  
    143 }

    分布式情况
    上面的ID生成器在单机情况下使用没有问题,但如果在分布下使用,就需要分配不同的workId,如果workId相同,可能会导致生成的id相同。

    解决方案:

    1、使用java环境变量,人为通过-D预先设置workid.这种方案简单,不会出现重复情况,但需要每个服务的启动脚本不同.

    2、使用sharding-jdbc中的算法,使用IP后几位来做workId,这种方案也很简单,不需要修改服务的启动脚本,但在某些情况下会出现生成重复ID的情况,详细见我下面的参考资料

    3、使用zk,在启动时给每个服务分配不同的workId,缺点:多了依赖,需要zk,优点:不会出现重复情况,且不需要修改服务的启动脚本。这个是我个人使用的方案,实现思路为,系统启动时创建一个永久性的结点(zookeeper保证原子性),然后在这个永久性的节点下,遍历workId去zookeeper创建临时结点,zookeeper会保证相同路径只会有一个可能创建成功,如果创建失败继续遍历即可。详细可看一下代码

    实例化ID生成器如下(Spring boot项目):

     1  
     2 import lombok.extern.slf4j.Slf4j;
     3 import org.springframework.beans.factory.annotation.Autowired;
     4 import org.springframework.beans.factory.annotation.Value;
     5 import org.springframework.boot.SpringBootConfiguration;
     6 import org.springframework.context.annotation.Bean;
     7  
     8 /**
     9  * <p>TODO</p>
    10  * <p>
    11  * <PRE>
    12  * <BR>    修改记录
    13  * <BR>-----------------------------------------------
    14  * <BR>    修改日期         修改人          修改内容
    15  * </PRE>
    16  *
    17  * @author cuiyh9
    18  * @version 1.0
    19  * @Date Created in 2018年11月30日 16:37
    20  * @since 1.0
    21  */
    22 @Slf4j
    23 @SpringBootConfiguration
    24 public class IdGeneratorConfig {
    25  
    26     @Autowired
    27     private ZkClient zkClient;
    28  
    29     @Value("${idgenerator.zookeeper.parent.path}")
    30     private String IDGENERATOR_PARENT_PATH;
    31  
    32     @Bean
    33     public ZfIdGenerator idGenerator() {
    34         boolean flag = zkClient.createParent(IDGENERATOR_PARENT_PATH);
    35         if (!flag) {
    36             throw new RuntimeException("创建发号器父节点失败");
    37         }
    38  
    39         // 获取workId
    40         long workId = 0;
    41         long maxWorkNum = ZfIdGenerator.getMaxWorkNum();
    42         for (long i = 1; i < maxWorkNum; i++) {
    43             String workPath = IDGENERATOR_PARENT_PATH + "/" + i;
    44             flag = zkClient.createNotExistEphemeralNode(workPath);
    45             if (flag) {
    46                 workId =  i;
    47                 break;
    48             }
    49         }
    50  
    51         if (workId == 0) {
    52             throw new RuntimeException("获取机器id失败");
    53         }
    54         log.warn("idGenerator workId:{}", workId);
    55         return ZfIdGenerator.getDistributedIdGenerator(workId);
    56  
    57     }
    58 }

    ZkClient代码(基于apache curator)

    注意apache curator版本,我最初使用的是4.x版本,程序执行到forPath()方法就会阻塞,后来查到是与zookeeper版本不匹配导致.

     1  
     2 import lombok.extern.slf4j.Slf4j;
     3 import org.apache.curator.framework.CuratorFramework;
     4 import org.apache.zookeeper.CreateMode;
     5 import org.apache.zookeeper.KeeperException;
     6 import org.springframework.beans.factory.annotation.Autowired;
     7 import org.springframework.stereotype.Component;
     8  
     9 /**
    10  * <p>TODO</p>
    11  * <p>
    12  * <PRE>
    13  * <BR>    修改记录
    14  * <BR>-----------------------------------------------
    15  * <BR>    修改日期         修改人          修改内容
    16  * </PRE>
    17  *
    18  * @author cuiyh9
    19  * @version 1.0
    20  * @Date Created in 2018年11月30日 16:36
    21  * @since 1.0
    22  */
    23 @Slf4j
    24 @Component
    25 public class ZkClient {
    26  
    27     @Autowired
    28     private CuratorFramework client;
    29  
    30     /**
    31      * 创建父节点,创建成功或存在都返回成功
    32      * @param path
    33      * @return
    34      */
    35     public boolean createParent(String path) {
    36         try {
    37             client.create().creatingParentsIfNeeded().forPath(path);
    38             return true;
    39         } catch (KeeperException.NodeExistsException e) {
    40             return true;
    41         } catch (Exception e) {
    42             log.error("createParent fail path:{}", path, e);
    43         }
    44         return false;
    45     }
    46  
    47     /**
    48      * 创建不存在的节点。如果存在或创建失败,返回false
    49      * @param path
    50      * @throws Exception
    51      */
    52     public boolean createNotExistEphemeralNode(String path) {
    53         try {
    54             client.create().withMode(CreateMode.EPHEMERAL).forPath(path);
    55             return true;
    56         } catch (KeeperException.NodeExistsException e) {
    57             return false;
    58         }  catch (Exception e) {
    59             log.error("createNotExistNode fail path:{}", path, e);
    60         }
    61         return false;
    62     }
    63 }
  • 相关阅读:
    [分享]解决Ubuntu 16.04安装Vitis 2019.2失败的问题
    ZCU102 休眠到内存(suspend-to-ram)对DDR复位信号的设计
    PetaLinux使用bitbake提前下载所有软件包
    Ubuntu 16.04执行基本命令失败,恢复Python版本后正常
    Upgrade Zynq-7000 XIP reference design to Xilinx SDK 2018.3
    区块链入门到实战(26)之以太坊(Ethereum) – 挖矿
    区块链入门到实战(25)之以太坊(Ethereum) – 以太币单位
    区块链入门到实战(24)之以太坊(Ethereum) – 网络节点
    区块链入门到实战(23)之以太坊(Ethereum) – 虚拟机架构
    区块链入门到实战(22)之以太坊(Ethereum) – 账号(地址)
  • 原文地址:https://www.cnblogs.com/Jeremy2001/p/10557693.html
Copyright © 2020-2023  润新知