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

来源:互联网 发布:淘宝贷款额度可以作假 编辑:程序博客网 时间:2024/05/20 12:52
在平时的工作中如果将数据库进行了分库分表,那么肯定需要分布式唯一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-trace和https://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(pathid);
            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年,首位为标记位(javalong首位是0表示为正数)
    private static final int TIME_BITS 40;
    // worker idbit数,最多支持8192apphost的组合(即在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 id65535
    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;
    }

    /**
     * 利用twittersnowflake(做了些微修改)算法来实现
     @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;
    }

}
2 0
原创粉丝点击