美团Leaf之分布式ID

关于美团leaf的相关知识请到官方查看文档,有了一定了解之后,再根据本文实操。 

美团leaf官网

美团leaf实现方式分为两种:Leaf-segment数据库方案(号段模式)、Leaf-snowflake方案(雪花算法模式)

源码拉取

一、Leaf-segment数据库方案

源码目录:

 配置以下内容到:leaf-server------》resources------》leaf.properties

leaf.name=leaf服务名
leaf.segment.enable=true
leaf.jdbc.url=jdbc:mysql://localhost:3306/leaf?autoReconnect=true&useUnicode=true&useSSL=false&haracterEncoding=utf-8&&zeroDateTimeBehavior=convertToNull&&serverTimezone=GMT%2B8
leaf.jdbc.username=root
leaf.jdbc.password=你的数据库密码

接下来创建数据库、建表、插入数据:

CREATE DATABASE leaf
CREATE TABLE `leaf_alloc` (
  `biz_tag` varchar(128)  NOT NULL DEFAULT '',
  `max_id` bigint(20) NOT NULL DEFAULT '1',
  `step` int(11) NOT NULL,
  `description` varchar(256)  DEFAULT NULL,
  `update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`biz_tag`)
) ENGINE=InnoDB;

insert into leaf_alloc(biz_tag, max_id, step, description) values('leaf-segment-test', 1, 10, 'Test leaf Segment Mode Get Id')

为了演示效果明显,步长设置为10 ,实际生产时根据需要设定但最大步长不超过1000000

 配置好以上信息后运行出现如下错误:

到leaf-parent下的pom文件中修改mysql-connector的版本即可: 

 运行成功后浏览器请求:localhost:8080/api/segment/get/mykey得到如下结果:

这里要注意localhost:8080/api/segment/get/{key},这里的key必须要与你数据库中保存的biz_tag下的值一致,否则请求失败。下面我们来一次正确的请求:localhost:8080/api/segment/get/leaf-segment-test  可以看到请求成功!

美团leaf提供了监控页面:http://localhost:8080/cache

其中init=true表示第一个segmentbuffer已准备好,next=false表示下一个segmentbuffer还没准备好,pos=0表示当前在segments[0],max0=11表示当前最大id为11,max0=max_id(数据库的值)+step=11。value0=max_id+1所以这里你会看到value0=2之后每次加1,当用完最后一个id的时候value0=value1+1(这里看不懂的读者可以不必理会,不影响请求结果,请求结果还是递增的)。这里还需要注意一个点当号段使用10%时会初始化下一个segmentbuffer。如图所示: 

这里并不是达到10%时立马就更新进来,当达到10%时才fork一个新的进程去初始化下一个segment。所以你会看到当请求到id=3即value0=4时才更新下一个buffer如图所示。

 当我用完第二个segment后,继续初始化第一个segment的时候发现步长竟然变成20了?

按道理来说步长是固定的,怎么翻倍增长了?我们来查看一下源码并解析:(想要快的读者直接跳过源码解析看结论)

 public void updateSegmentFromDb(String key, Segment segment) {
        StopWatch sw = new Slf4JStopWatch();
        SegmentBuffer buffer = segment.getBuffer();
        LeafAlloc leafAlloc;
        if (!buffer.isInitOk()) {//判断是否初始化
            leafAlloc = dao.updateMaxIdAndGetLeafAlloc(key);
            buffer.setStep(leafAlloc.getStep());
            //leafAlloc中的step为DB中的step
            buffer.setMinStep(leafAlloc.getStep());
        } else if (buffer.getUpdateTimestamp() == 0) {//判断当前时间戳是否为0
            leafAlloc = dao.updateMaxIdAndGetLeafAlloc(key);
            //若时间时间戳为0将当前时间戳作为Timestamp的值
            buffer.setUpdateTimestamp(System.currentTimeMillis());
            buffer.setStep(leafAlloc.getStep());
            //leafAlloc中的step为DB中的step
            buffer.setMinStep(leafAlloc.getStep());
        } else {
             //若当前时间戳不为0则用当前时间戳减去最近一次更新的时间戳。得到duration的值。
         long duration = System.currentTimeMillis() - 
         buffer.getUpdateTimestamp();
            int nextStep = buffer.getStep();
            if (duration < SEGMENT_DURATION) {//SEGMENT_DURATION=15分钟
          //当用当前时间戳减去最近一次更新的时间戳的差值小于15分钟时
          //即duration<15*60*1000
          //nextStep扩大一倍但不能超过最大步长1000000。MAX_STEP=1000000
                if (nextStep * 2 > MAX_STEP) {
           //当超过nextStep的两倍大于1000000时不做扩充。即按照原来的步长增长。
                    //do nothing
                } else {
              //duration小于15分钟,即一个segment的id15分钟内使用完了,
              //且nextSrep*2<1000000时则扩大两倍
                    nextStep = nextStep * 2;
                }
            } else if (duration < SEGMENT_DURATION * 2) {
               //如果15分钟<duration<30分钟时则按照原来的步长增长。
                //do nothing with nextStep
            } else {
               //duration大于30分钟时则认为segment太大需要压缩到原来的1/2。
               //但不能小于当期数据库存储的最大步长。
               //getMinStep()获取的当前数据库的最大步长。
               //若压缩一半小于当前数据库存储的最大step。
               //则按照原来的步长增长,不做压缩。
         nextStep = nextStep / 2 >= buffer.getMinStep() ? nextStep / 2 : nextStep;
            }
            logger.info("leafKey[{}], step[{}], duration[{}mins], nextStep[{}]", key, buffer.getStep(), String.format("%.2f",((double)duration / (1000 * 60))), nextStep);
            LeafAlloc temp = new LeafAlloc();
            temp.setKey(key);
            temp.setStep(nextStep);
            leafAlloc = dao.updateMaxIdByCustomStepAndGetLeafAlloc(temp);
            buffer.setUpdateTimestamp(System.currentTimeMillis());
            buffer.setStep(nextStep);
            buffer.setMinStep(leafAlloc.getStep());//leafAlloc的step为DB中的step
        }
        // must set value before set max
        long value = leafAlloc.getMaxId() - buffer.getStep();
        segment.getValue().set(value);
        segment.setMax(leafAlloc.getMaxId());
        segment.setStep(buffer.getStep());
        sw.stop("updateSegmentFromDb", key + " " + segment);
    }

结论:一个segment 使用时间是15分钟-30分钟如果在15分钟就用完了一个segment的id则认为segment的步长太小会对其进行扩充,扩大为原来的两倍但上限不会超出1000000,如果步长的两倍超过1000000则按照原来的步长。如果超出30分钟才更新segment,则认为segment的步长太大,需要对其进行压缩,压缩为原来的1/2。但如果压缩1/2后比当前数库储存的最大步长小则不进行压缩。按照原来的步长增长。

总结:

Leaf-Segment模式有以下优缺点:

优点:

  • Leaf服务可以很方便的线性扩展,性能完全能够支撑大多数业务场景。
  • ID号码是趋势递增的8byte的64位数字,满足上述数据库存储的主键要求。
  • 容灾性高:Leaf服务内部有号段缓存,即使DB宕机,短时间内Leaf仍能正常对外提供服务。
  • 可以自定义max_id的大小,非常方便业务从原有的ID方式上迁移过来。

缺点:

  • ID号码不够随机,能够泄露发号数量的信息,不太安全。
  • TP999数据波动大,当号段使用完之后还是会hang在更新数据库的I/O上,tg999数据会出现偶尔的尖刺。
  • DB宕机会造成整个系统不可用。

二、Leaf-snowflake方案(雪花算法模式)

首先在leaf-server---》resources----》leaf.properties开启雪花模式并关闭Leaf-Segment模式。

两种模式不能同时开启。只能开启其中一种模式。

leaf.snowflake.enable=true 
leaf.snowflake.zk.address=127.0.0.1  //zk服务器地址
leaf.snowflake.port=2181 //zk端口号。默认2181

启动leaf前需要先启动zookeeper,注意如果你是第一次下载zookeeper,同时也是第一次实践leaf,要注意端口号问题。zookeeper和Leaf会有端口占用问题,把zk或者leaf的其中一个端口号改掉即可。启动zk成功后启动Leaf。请求:localhost:8080/api/snowflake/get/key(这里的key可以随便填)可以看到请求成功!!!

 请求结果是一个19位长度的long型数字。为什么是19位?2的10次方约等于10的3次方。雪花算法共64位。第1位符号位。随后41位时间戳。再后5位datacenterid,接着5位workid。后面12位为序号2的12次方等于4096即一毫秒能产生4096个不同的id即0-4095。一秒钟能产生4096000个不同id。2的64次方时间戳=69年。即能保证69年内不会出现id重复的情况。前提是不要发生时钟回拨或闰秒的情况,为了防止此类情况发生可以关闭NTP协议,不校对时间。

Leaf-snowflake方案有以下优缺点:

优点:

  • 毫秒数在高位,自增序列在低位,整个ID都是趋势递增的。
  • 不依赖数据库等第三方系统,以服务的方式部署,稳定性更高,生成ID的性能也是非常高的。
  • 可以根据自身业务特性分配bit位,非常灵活。

缺点:

  • 强依赖机器时钟,如果机器上时钟回拨,会导致发号重复或者服务会处于不可用状态。

什么是时间回拨?

时钟回拨是硬件时钟可能会因为各种原因发生不准的情况,网络中提供了ntp服务来做时间校准,做校准的时候就会发生时钟的跳跃或者回拨的问题。即时间倒流或者时间增加。

 什么是闰秒?闰秒

面试: 

snowflake算法的时钟回拨问题如何解决(面试)

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值