在任何一个系统中,都有需要唯一的一个编号标识一条记录或一个消息,例如一个订单记录或者消息中间件发送的消息这样的需求。在微服务系统,每一个微服务生成的id不能重复,最好保证一定的自增,目前的解决方案有UUID,数据库id自增,数据库生成id,雪花算法,然后根据这些解决方案对应的工具有美团的leaf,百度的UidGenerator。

  • UUID:是java自带的一种id生成方式,uuid能够保证同一时空所有机器都能够保持唯一。

    //java使用方式
    UUID uuid = UUID.randomUUID();
    String id = uuid.toString();
  • 数据库id自增:当多个服务在自身系统中能够保持唯一性,但在全局不能保证唯一性的时候,例如使用各个服务中的当前系统时间毫秒数作为id,那么就会导致在某一个毫秒中,生成重复的id.如果所有服务都去通过数据库获取id编号,那么就可以保证id不能重复。

      通过维护一张表,表中记录什么类型的自增,自增起始值、自增间隔、下一次自增起始值。例如对人员编号进行从1开始逐一自增。
      --type---start---inteval---one_time_total--
      --people---1--------1----------1000------
      
      
      A.在程序中维护startNum,currentNum,endNum,inteval;
      一次获取人员编号的过程: 
      select start,inteval,one_time_total from t_number where type = 'people';//获取人员当前编号信息
      update start = start + inteval * one_time_total where type = 'people';//更新人员编号信息
      得到startNum = start、endNum = start + inteval * oneTimeTotal、inteval = inteval;当前编号从startNum开始,endNum结束,currentNum代表当前编号。
      
      B.通过数据库触发器实现
      --biz_type--max_id---step---update_time---
      ---people----0-------1000-----currentTime-
      
      创建触发器或者存储过程
      Begin UPDATE table 
        SET max_id=max_id+step WHERE biz_tag='people'
        SELECT tag, max_id, step FROM table WHERE biz_tag='people' 
      Commit
  • 雪花算法: 雪花算法为Twitter开源的一种生成id的算法。

    其由64位bit组成,第1位为标识位,第2位到第42位为毫秒数,记录当前时间与起始时间的毫秒数差值,记录长度为69年,第43位到52位表示机器id,Twitter是将前5位分配为idc标识号,后5位分配为机器标识号,53位到64位表示为递增序列号。

    image.png



        java实现代码

       package com.xiayu.config;
    
       public class SnowFlake{
    
       //下面两个每个5位,加起来就是10位的工作机器id
       private long workerId;
       //工作id,每一个服务对应唯一一个workerId,这个id当服务比较少的时候,可以写在配置文件中,如果比较多的话可以使用zk的持久顺序节点
       private long datacenterId;   //数据id
       //12位的序列号
       private long sequence;
    
       public SnowFlake(long workerId, long datacenterId, long sequence){
           // 校验wokerId值
           if (workerId > maxWorkerId || workerId < 0) {
               throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0",maxWorkerId));
           }
           //校验数据中心id值
           if (datacenterId > maxDatacenterId || datacenterId < 0) {
               throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0",maxDatacenterId));
           }
           System.out.printf("worker starting. timestamp left shift %d, datacenter id bits %d, worker id bits %d, sequence bits %d, workerid %d",
                   timestampLeftShift, datacenterIdBits, workerIdBits, sequenceBits, workerId);
    
           this.workerId = workerId;
           this.datacenterId = datacenterId;
           this.sequence = sequence;
       }
    
       //初始时间戳
       private long twepoch = 1288834974657L;
    
       //长度为5位
       private long workerIdBits = 5L;
       private long datacenterIdBits = 5L;
       //最大值
       private long maxWorkerId = -1L ^ (-1L << workerIdBits);
       private long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);
       //序列号id长度
       private long sequenceBits = 12L;
       //序列号最大值
       private long sequenceMask = -1L ^ (-1L << sequenceBits);
    
       //工作id需要左移的位数,12位
       private long workerIdShift = sequenceBits;
       //数据id需要左移位数 12+5=17位
       private long datacenterIdShift = sequenceBits + workerIdBits;
       //时间戳需要左移位数 12+5+5=22位
       private long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
    
       //上次时间戳,初始值为负数
       private long lastTimestamp = -1L;
    
       public long getWorkerId(){
           return workerId;
       }
    
       public long getDatacenterId(){
           return datacenterId;
       }
    
       public long getTimestamp(){
           return System.currentTimeMillis();
       }
    
       //下一个ID生成算法,线程安全的
       public synchronized long nextId() {
           long timestamp = timeGen();
    
           //获取当前时间戳如果小于上次时间戳,则表示时间戳获取出现异常
           if (timestamp < lastTimestamp) {
               System.err.printf("clock is moving backwards.  Rejecting requests until %d.", lastTimestamp);
               throw new RuntimeException(String.format("Clock moved backwards.  Refusing to generate id for %d milliseconds",
                       lastTimestamp - timestamp));
           }
    
           //获取当前时间戳如果等于上次时间戳(同一毫秒内),则在序列号加一;否则序列号赋值为0,从0开始。
           if (lastTimestamp == timestamp) {
               sequence = (sequence + 1) & sequenceMask; //序列号+1
               if (sequence == 0) {
                   timestamp = tilNextMillis(lastTimestamp);
               }
           } else {
               sequence = 0;
           }
    
           //将上次时间戳值刷新
           lastTimestamp = timestamp;
           return ((timestamp - twepoch) << timestampLeftShift) |
                   (datacenterId << datacenterIdShift) |
                   (workerId << workerIdShift) |
                   sequence;
       }
    
       //获取时间戳,并与上次时间戳比较
       private long tilNextMillis(long lastTimestamp) {
           long timestamp = timeGen();
           while (timestamp <= lastTimestamp) {
               timestamp = timeGen();
           }
           return timestamp;
       }
    
       //获取系统时间戳
       private long timeGen(){
           return System.currentTimeMillis();
       }
    
       public static void main(String[] args) {
           SnowFlake worker = new SnowFlake(1,1,1);
           for (int i = 0; i < 20; i++) {
               System.out.println(worker.nextId());
           }
       }
    }

    大厂id生成工具有美团的Leaf(树叶),和百度的UidGenerator,其原理同上。


你若安好便是晴天
82 声望10 粉丝