从零到日志采集索引可视化、监控报警、rpc trace跟踪-分布式唯一ID生成

在平时的工作中如果将数据库进行了分库分表,那么肯定需要分布式唯一ID的生成策略。rpc trace跟踪模块也需要一个分布式唯一ID的生成器,本文介绍基于snowflake改造的分布式唯一ID生成策略。
根据snowflake的算法,做了些许改变,唯一ID是一个int64的值,第一位占位,接下来40位为毫秒级的时间,接下来13位(支持8192个)为app和host组合的分配的值(即在N个服务器上每个服务器部署M个项目,总共部署N*M=8192),接下去是该毫秒产生的序列号10位(每毫秒生成1024个,每秒生成1024*1000,并发很高)
该算法中最重要的就是如何为app和host进行编号,我们采用zk写数据version自增的方式来为app和host编号,这块代码只会在初始化的时候执行一次,所以不会影响效率,而且在生成ID的时候是作为jar包在项目中生成的,并不会请求任何一个ID生成中心,减少了网络消耗。具体代码如下(见: https://github.com/JThink/SkyEye/tree/master/skyeye-tracehttps://github.com/JThink/SkyEye/tree/master/skyeye-client):

public class IncrementIdGen  implements IdGen {

    //  为某台机器上的某个项目分配的 serviceId (注意区分 Span 中的 serviceId
    private static String  serviceId null;

    // register info
    private RegisterDto  registerDto ;

    /**
     *  利用 zookeeper
     *  @return
      */
    @Override
    public String  nextId() {
        String app =  this. registerDto.getApp() ;
        String host =  this. registerDto.getHost() ;
        ZkClient zkClient =  this. registerDto.getZkClient() ;
        String path = Constants. ZK_REGISTRY_ID_ROOT_PATH + Constants. SLASH + app + Constants. SLASH + host ;
        if (zkClient.exists(path)) {
            //  如果已经有该节点,表示已经为当前的 host 上部署的该 app 分配的编号(应对某个服务重启之后编号不变的问题),直接获取该 id ,而无需生成
            return zkClient.readData(Constants. ZK_REGISTRY_ID_ROOT_PATH + Constants. SLASH + app + Constants. SLASH + host) ;
        else {
            //  节点不存在,那么需要生成 id ,利用 zk 节点的版本号每写一次就自增的机制来实现
            Stat stat = zkClient.writeDataReturnStat(Constants. ZK_REGISTRY_SEQ , new byte[ 0] - 1) ;
            //  生成 id
            String id = String. valueOf(stat.getVersion()) ;
            //  将数据写入节点
            zkClient.createPersistent(path , true) ;
            zkClient.writeData(path id) ;
            return id ;
        }
    }

    /**
     *  获取 ID
     *  @return
      */
    public static String  getId() {
        return  serviceId ;
    }

    /**
     *  ID 赋值
      @param  id
      @return
      */
    public static void  setId(String id) {
        serviceId = id ;
    }

    public  IncrementIdGen() {

    }

    public  IncrementIdGen(RegisterDto registerDto) {
        this. registerDto = registerDto ;
    }

    public RegisterDto  getRegisterDto() {
        return  registerDto ;
    }

    public IncrementIdGen  setRegisterDto(RegisterDto registerDto) {
        this. registerDto = registerDto ;
        return this;
    }
}


public class UniqueIdGen  implements IdGen {

    //  开始使用该算法的时间为 : 2017-01-01 00:00:00
    private static final long  START_TIME 1483200000000L ;

    //  时间戳 bit 数,最多能支持到 2050 年,首位为标记位( java long 首位是 0 表示为正数)
    private static final int  TIME_BITS 40 ;
    // worker id bit 数,最多支持 8192 app host 的组合(即在 N 个服务器上每个服务器部署 M 个项目,总共部署 N*M=8192
    private static final int  APP_HOST_ID_BITS 13 ;
    //  序列号,支持单节点最高 1000*1024 的并发
    private final static int  SEQUENCE_BITS 10 ;

    //  最大的 app host id 65535
    private final static long  MAX_APP_HOST_ID = ~(- 1L <<  APP_HOST_ID_BITS) ;
    //  最大的序列号, 127
    private final static long  MAX_SEQUENCE = ~(- 1L <<  SEQUENCE_BITS) ;

    // app host 编号的移位
    private final static long  APP_HOST_ID_SHIFT SEQUENCE_BITS ;
    //  时间戳的移位
    private final static long  TIMESTAMP_LEFT_SHIFT APP_HOST_ID_BITS APP_HOST_ID_SHIFT ;

    //  该项目的 app host id ,对应着为某台机器上的某个项目分配的 serviceId (注意区分 Span 中的 serviceId
    private long  appHostId ;
    //  上次生成 ID 的时间戳
    private long  lastTimestamp = - 1L ;
    //  当前毫秒生成的序列
    private long  sequence 0L ;

    //  单例
    private static volatile UniqueIdGen  idGen null;

    /**
     *  实例化
      @param  appHostId
      @return
      */
    public static UniqueIdGen  getInstance( long appHostId) {
        if ( idGen ==  null) {
            synchronized(UniqueIdGen. class) {
                if ( idGen ==  null) {
                    idGen new UniqueIdGen(appHostId) ;
                }
            }
        }
        return  idGen ;
    }

    private  UniqueIdGen( long appHostId) {
        if (appHostId >  MAX_APP_HOST_ID) {
            // zk 分配的 serviceId 过大 ( 基本小规模的公司不会出现这样的问题 )
            throw new IllegalArgumentException(String. format( "app host Id wrong: %d " appHostId)) ;
        }
        this. appHostId = appHostId ;
    }

    /**
     *  利用 twitter snowflake (做了些微修改)算法来实现
      @return
      */
    @Override
    public String  nextId() {
        return Long. toHexString( this.genUniqueId()) ;
    }

    /**
     *  生成唯一 id 的具体实现
      @return
      */
    private synchronized long  genUniqueId() {
        long current = System. currentTimeMillis() ;

        if (current <  lastTimestamp) {
            //  如果当前时间小于上一次 ID 生成的时间戳,说明系统时钟回退过,出现问题返回 -1
            return - 1 ;
        }

        if (current ==  lastTimestamp) {
            //  如果当前生成 id 的时间还是上次的时间,那么对 sequence 序列号进行 +1
            sequence = ( sequence 1) &  MAX_SEQUENCE ;

            if ( sequence ==  MAX_SEQUENCE) {
                //  当前毫秒生成的序列数已经大于最大值,那么阻塞到下一个毫秒再获取新的时间戳
                current =  this.nextMs( lastTimestamp) ;
            }
        }  else {
            //  当前的时间戳已经是下一个毫秒
            sequence 0L ;
        }

        //  更新上次生成 id 的时间戳
        lastTimestamp = current ;

        //  进行移位操作生成 int64 的唯一 ID
        return ((current -  START_TIME) <<  TIMESTAMP_LEFT_SHIFT)
                | ( this. appHostId <<  APP_HOST_ID_SHIFT)
                |  sequence ;
    }

    /**
     *  阻塞到下一个毫秒
      @param  timeStamp
      @return
      */
    private long  nextMs( long timeStamp) {
        long current = System. currentTimeMillis() ;
        while (current <= timeStamp) {
            current = System. currentTimeMillis() ;
        }
        return current ;
    }

}
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值