美团Leaf——全局序列生成器

Leaf的Github地址:

https://github.com/Meituan-Dianping/Leaf

Leaf美团技术团队博客地址:

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

关于Leaf的使用手册、架构说明、Segment和Snowflake的特点和时钟回拨解决办法,参考上面的链接内容都能获得到答案。拒绝重复搬砖。

在本篇博客里我想就Leaf的Segment模式的源码实现做个简单的注释。代码分支:master。

1.Segment

Segment是SegmentBuffer的成员属性,cache中存储的是SegmentBuffer,Segment是双buffer的实现。

2.初始化Segment

在初始化Segment时,主要做两件事情。1是根据数据库表中配置的busi_tag更新缓存;2是添加定时任务,定时(一分钟间隔)更新缓存。

    @Override
    public boolean init() {
        logger.info("Init ...");
        // 确保加载到kv后才初始化成功
        updateCacheFromDb();
        initOK = true;
        updateCacheFromDbAtEveryMinute();
        return initOK;
    }

updateCacheFromDb():

private void updateCacheFromDb() {
        logger.info("update cache from db");
        StopWatch sw = new Slf4JStopWatch();
        try {
            //从配置的数据源中加载biz_tag
            List<String> dbTags = dao.getAllTags();
            if (dbTags == null || dbTags.isEmpty()) {
                return;
            }
            //cache中的biz_tag.初始为空.
            List<String> cacheTags = new ArrayList<String>(cache.keySet());
            //存储本次更新操作,要从DB中加载进cache的biz_tag.
            Set<String> insertTagsSet = new HashSet<>(dbTags);
            //存储失效的biz_tag:存在于cache,不存在于DB.
            Set<String> removeTagsSet = new HashSet<>(cacheTags);
            //过滤去重,得到需要存入进cache的biz_tag
            for(int i = 0; i < cacheTags.size(); i++){
                String tmp = cacheTags.get(i);
                if(insertTagsSet.contains(tmp)){
                    insertTagsSet.remove(tmp);
                }
            }
            //存储进cache
            for (String tag : insertTagsSet) {
                SegmentBuffer buffer = new SegmentBuffer();
                buffer.setKey(tag);
                Segment segment = buffer.getCurrent();
                segment.setValue(new AtomicLong(0));
                //这里的max、step均为0.所以这一步仅仅将biz_tag存储进了cache,并没有对SegmentBuffer执行初始化操作.
                segment.setMax(0);
                segment.setStep(0);
                cache.put(tag, buffer);
                logger.info("Add tag {} from db to IdCache, SegmentBuffer {}", tag, buffer);
            }
            //cache中已失效的biz_tag从cache删除
            for(int i = 0; i < dbTags.size(); i++){
                String tmp = dbTags.get(i);
                if(removeTagsSet.contains(tmp)){
                    removeTagsSet.remove(tmp);
                }
            }
            for (String tag : removeTagsSet) {
                cache.remove(tag);
                logger.info("Remove tag {} from IdCache", tag);
            }
        } catch (Exception e) {
            logger.warn("update cache from db exception", e);
        } finally {
            sw.stop("updateCacheFromDb");
        }
    }
View Code

 

3.获取到下一个序列号

    @Override
    public Result get(final String key) {
        if (!initOK) {
            return new Result(EXCEPTION_ID_IDCACHE_INIT_FALSE, Status.EXCEPTION);
        }
        //判断cache中是否有这个key——bizTag
        if (cache.containsKey(key)) {
            SegmentBuffer buffer = cache.get(key);
            //未初始化,
            if (!buffer.isInitOk()) {
                synchronized (buffer) {
                    //再次判断,以防重复初始化.
                    if (!buffer.isInitOk()) {
                        try {
                            //对SegmentBuffer当前的Segment进行初始化
                            updateSegmentFromDb(key, buffer.getCurrent());
                            logger.info("Init buffer. Update leafkey {} {} from db", key, buffer.getCurrent());
                            buffer.setInitOk(true);
                        } catch (Exception e) {
                            logger.warn("Init buffer {} exception", buffer.getCurrent(), e);
                        }
                    }
                }
            }
            return getIdFromSegmentBuffer(cache.get(key));
        }
        return new Result(EXCEPTION_ID_KEY_NOT_EXISTS, Status.EXCEPTION);
    }

updateSegmentFromDb();

public void updateSegmentFromDb(String key, Segment segment) {
        StopWatch sw = new Slf4JStopWatch();
        SegmentBuffer buffer = segment.getBuffer();
        LeafAlloc leafAlloc;
        //1.SegmentBuffer尚未初始化,则SegmentBuffer的step等于数据库中的step;
        if (!buffer.isInitOk()) {
            leafAlloc = dao.updateMaxIdAndGetLeafAlloc(key);
            buffer.setStep(leafAlloc.getStep());
            buffer.setMinStep(leafAlloc.getStep());//leafAlloc中的step为DB中的step
        } else if (buffer.getUpdateTimestamp() == 0) {
            //2.SegmentBuffer已经初始化完成:
            leafAlloc = dao.updateMaxIdAndGetLeafAlloc(key);
            buffer.setUpdateTimestamp(System.currentTimeMillis());
            buffer.setStep(leafAlloc.getStep());
            buffer.setMinStep(leafAlloc.getStep());//leafAlloc中的step为DB中的step
        } else {
            //2.SegmentBuffer已经初始化完成:
            //a)    SegmentBuffer的存活时间小于15分钟:
            //   i.    如果SegmentBuffer当前的step*2大于最大值(一百万),则什么也不做,不再扩大step
            //  ii.    否则,SegmentBuffer的step扩大为原来的2倍。
            //b)    SegmentBuffer的存活时间小于30分钟:什么也不做.
            //c)    SegmentBuffer的存活时间在15至30分钟之间:
            //   i.    如果SegmentBuffer的step/2 大于等于数据库中的step,那么就将SegmentBuffer的step值变为原来的二分之一,否则什么也不做。
            long duration = System.currentTimeMillis() - buffer.getUpdateTimestamp();
            int nextStep = buffer.getStep();
            if (duration < SEGMENT_DURATION) {
                if (nextStep * 2 > MAX_STEP) {
                    //do nothing
                } else {
                    nextStep = nextStep * 2;
                }
            } else if (duration < SEGMENT_DURATION * 2) {
                //do nothing with nextStep
            } else {
                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);
    }
View Code

 

getIdFromSegmentBuffer();

    public Result getIdFromSegmentBuffer(final SegmentBuffer buffer) {
        while (true) {
            //读锁
            buffer.rLock().lock();
            try {
                //获取到当前正在使用的Segment.
                final Segment segment = buffer.getCurrent();
                //如果下一个Segment没有初始化完成,当前Segment的ID闲置数量小于9成,并且没有在执行Segment初始化操作,就去执行对下一个Segment的初始化.
                //只要当前号段消费数量达到了10%,就对下一个号段进行初始化.
                if (!buffer.isNextReady() && (segment.getIdle() < 0.9 * segment.getStep()) && buffer.getThreadRunning().compareAndSet(false, true)) {
                    service.execute(new Runnable() {
                        @Override
                        public void run() {
                            Segment next = buffer.getSegments()[buffer.nextPos()];
                            boolean updateOk = false;
                            try {
                                //根据biz_tag,根据DB对Segment初始化
                                updateSegmentFromDb(buffer.getKey(), next);
                                updateOk = true;
                                logger.info("update segment {} from db {}", buffer.getKey(), next);
                            } catch (Exception e) {
                                logger.warn(buffer.getKey() + " updateSegmentFromDb exception", e);
                            } finally {
                                if (updateOk) {
                                    //获取写锁
                                    buffer.wLock().lock();
                                    buffer.setNextReady(true);
                                    buffer.getThreadRunning().set(false);
                                    buffer.wLock().unlock();
                                } else {
                                    buffer.getThreadRunning().set(false);
                                }
                            }
                        }
                    });
                }
                //Segment的value记录了当前已分配的ID最大值,加一后返回.原子性操作.
                long value = segment.getValue().getAndIncrement();
                //如果超过了当前Segment缓存的最大值(当前Segment号段已经消费完毕),就进入阻塞等待.
                if (value < segment.getMax()) {
                    return new Result(value, Status.SUCCESS);
                }
            } finally {
                buffer.rLock().unlock();
            }
            //当前号段已经消费完了,下个号段正在有别的线程在执行初始化,进入等待.
            //做空转.
            waitAndSleep(buffer);
            buffer.wLock().lock();
            //下面这段代码在做空转之前已经执行过,这里为什么还要再次执行呢?
            //在当前线程空转期间,可能已经有别的线程执行完毕了对下个Segment的初始化操作,并进行了切换.防止出现多次切换.
            try {
                final Segment segment = buffer.getCurrent();
                long value = segment.getValue().getAndIncrement();
                if (value < segment.getMax()) {
                    return new Result(value, Status.SUCCESS);
                }
                //当前Segment缓存的号段已经消费完了,下一个Segment初始化好了就进行切换
                if (buffer.isNextReady()) {
                    //将Segment切换为下一个.
                    buffer.switchPos();
                    buffer.setNextReady(false);
                } else {
                    //ERROR.当前Segment号段满了,下一个号段还未准备好.
                    logger.error("Both two segments in {} are not ready!", buffer);
                    return new Result(EXCEPTION_ID_TWO_SEGMENTS_ARE_NULL, Status.EXCEPTION);
                }
            } finally {
                buffer.wLock().unlock();
            }
        }
    }
View Code

 

posted @ 2020-06-03 18:29  你学会了吗  阅读(1241)  评论(0编辑  收藏  举报