前言

关于为什么叫leaf

There are no two identical leaves in the world.

世界上没有两片完全相同的树叶。

Leaf默认提供了HTTP方式暴露生成唯一id的端口,在部署Leaf完成之后,可以直接通过HTTP接口调用的方式获取生成的唯一ID

当然,如果想要获得更好的性能,可以通过RPC方式暴露,不过这就需要自己基于Leaf-core依赖进行封装。

Leaf提供了两种uuid的实现方式:

  • Segment号段模式
  • SnakeFlake雪花算法模式

使用方式

官方文档写的很直观,这里不再赘述,直接贴出链接

https://github.com/Meituan-Dianping/Leaf/blob/master/README_CN.md

什么是雪花算法SnakeFlake?

可以参考分布式唯一ID生成—雪花算法

在这里插入图片描述

SnakeFlake方式架构

雪花算法需要workerID来唯一表示不同机器,可以给每台机器手动配置,但如果机器过多,手动注册是十分不方便的,所以leaf基于ZK做了workerID的获取。

并且leaf-snakeFlake弱依赖于ZK,即从ZK上获取workerID的时候,会本地也缓存一份workerID文件
在这里插入图片描述

时钟问题

在这里插入图片描述
参见上图整个启动流程图,服务启动时首先检查自己是否写过ZooKeeper leaf_forever节点:

  • 若写过,则用自身系统时间与leaf_forever节点记录时间做比较,若小于leaf_forever时间则认为机器时间发生了大步长回拨,服务启动失败并报警。
  • 若未写过,证明是新服务节点,直接创建持久节点leaf_forever并写入自身系统时间,接下来综合对比其余Leaf节点的系统时间来判断自身系统时间是否准确,具体做法是取leaf_temporary下的所有临时节点(所有运行中的Leaf-snowflake节点)的服务IP:Port,然后通过RPC请求得到所有节点的系统时间,计算sum(time)/nodeSize
    abs( 系统时间-sum(time)/nodeSize ) < 阈值,认为当前系统时间准确,正常启动服务,同时写临时节点leaf_temporary维持租约。否则认为本机系统时间发生大步长偏移,启动失败并报警。
  • 每隔一段时间(3s)上报自身系统时间写入leaf_forever。
  • 由于强依赖时钟,对时间的要求比较敏感,在机器工作时NTP同步也会造成秒级别的回退,建议可以直接关闭NTP同步。要么在时钟回拨的时候直接不提供服务直接返回ERROR_CODE,等时钟追上即可。
  • 或者做一层重试,然后上报报警系统,更或者是发现有时钟回拨之后自动摘除本身节点并报警。

SnakeFlake方式—初始化环境

Leaf的SnakeFlake算法基于ZK实现,所以在服务启动的时候,需要先初始化ZK的环境。

public SnowflakeIDGenImpl(String zkAddress, int port, long twepoch) {
    this.twepoch = twepoch;
    Preconditions.checkArgument(timeGen() > twepoch, "Snowflake not support twepoch gt currentTime");
    final String ip = Utils.getIp();
    SnowflakeZookeeperHolder holder = new SnowflakeZookeeperHolder(ip, String.valueOf(port), zkAddress);
    LOGGER.info("twepoch:{} ,ip:{} ,zkAddress:{} port:{}", twepoch, ip, zkAddress, port);
    boolean initFlag = holder.init();
    if (initFlag) {
        workerId = holder.getWorkerID();
        LOGGER.info("START SUCCESS USE ZK WORKERID-{}", workerId);
    } else {
        Preconditions.checkArgument(initFlag, "Snowflake Id Gen is not init ok");
    }
    Preconditions.checkArgument(workerId >= 0 && workerId <= maxWorkerId, "workerID must gte 0 and lte 1023");
}

SnakeFlow方式—核心逻辑

    @Override
    public synchronized Result get(String key) {
        long timestamp = timeGen();
        if (timestamp < lastTimestamp) {
            long offset = lastTimestamp - timestamp;
            if (offset <= 5) {
                try {
                    wait(offset << 1);
                    timestamp = timeGen();
                    if (timestamp < lastTimestamp) {
                        return new Result(-1, Status.EXCEPTION);
                    }
                } catch (InterruptedException e) {
                    LOGGER.error("wait interrupted");
                    return new Result(-2, Status.EXCEPTION);
                }
            } else {
                return new Result(-3, Status.EXCEPTION);
            }
        }
        if (lastTimestamp == timestamp) {
            sequence = (sequence + 1) & sequenceMask;
            if (sequence == 0) {
                //seq 为0的时候表示是下一毫秒时间开始对seq做随机
                sequence = RANDOM.nextInt(100);
                timestamp = tilNextMillis(lastTimestamp);
            }
        } else {
            //如果是新的ms开始
            sequence = RANDOM.nextInt(100);
        }
        lastTimestamp = timestamp;
        long id = ((timestamp - twepoch) << timestampLeftShift) | (workerId << workerIdShift) | sequence;
        return new Result(id, Status.SUCCESS);

    }

leaf会定期(间隔周期是3秒)上报更新timestamp。并且上报时,如果发现当前时间戳少于最后一次上报的时间戳,那么会放弃上报。之所以这么做的原因是,防止在leaf实例重启过程中,由于时钟回拨导致可能产生重复ID的问题

private void ScheduledUploadData(final CuratorFramework curator, final String zk_AddressNode) {
    Executors.newSingleThreadScheduledExecutor(new ThreadFactory() {
        @Override
        public Thread newThread(Runnable r) {
            Thread thread = new Thread(r, "schedule-upload-time");
            thread.setDaemon(true);
            return thread;
        }
    }).scheduleWithFixedDelay(new Runnable() {
        @Override
        public void run() {
            updateNewData(curator, zk_AddressNode);
        }
    }, 1L, 3L, TimeUnit.SECONDS);//每3s上报数据

}

参考

https://tech.meituan.com/2017/04/21/mt-leaf.html

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐