美文网首页
UidGenerator

UidGenerator

作者: liujianhuiouc | 来源:发表于2019-04-19 18:02 被阅读0次

    UidGenerator是什么

    UidGenerator是百度开源的一款分布式高性能的唯一ID生成器,更详细的情况可以查看github:uid-generator,里面有更详细的介绍文档说明,其也是基于snowflake模型的一种ID生成器,我们再来回顾以下雪花模型。


    Snowflake算法描述:指定机器 & 同一时刻 & 某一并发序列,是唯一的。据此可生成一个64 bits的唯一ID(long)。默认采用上图字节分配方式:

    sign(1bit)
    固定1bit符号标识,即生成的UID为正数。

    delta seconds (28 bits)
    当前时间,相对于时间基点"2016-05-20"的增量值,单位:秒,最多可支持约8.7年

    worker id (22 bits)
    机器id,最多可支持约420w次机器启动。内置实现为在启动时由数据库分配,默认分配策略为用后即弃,后续可提供复用策略。

    sequence (13 bits)
    每秒下的并发序列,13 bits可支持每秒8192个并发。

    这些字段的长度可以根据具体的应用需要进行动态的调整,满足总长度为64位即可
    百度的worker id的生成策略和美团的生成策略不太一样,美团的snowflake主要利用本地配置的port和IP来唯一确定一个workid,美团的这种生成方式还是可以由于手工配置错误造成port重复,最终产生重复ID的风险,百度的这种生成方式每次都是新增的,可能会一段时间后worker id用完的情况,人工配置错误的可能性很小了。

    DefaultUidGenerator

    nextId方法主要负责ID的生成,这种实现方式很简单,如果毫秒数未发生变化,在序列号加一即可,毫秒数发生变化,重置Sequence为0(Leaf文章中讲过,重置为0会造成如果利用这个ID分表的时候,并发量不大的时候,sequence字段会一直为0等,会出现数据倾斜)

     protected synchronized long nextId() {
            long currentSecond = getCurrentSecond();
    
            // Clock moved backwards, refuse to generate uid
            if (currentSecond < lastSecond) {
                long refusedSeconds = lastSecond - currentSecond;
                throw new UidGenerateException("Clock moved backwards. Refusing for %d seconds", refusedSeconds);
            }
    
            // At the same second, increase sequence
            if (currentSecond == lastSecond) {
                sequence = (sequence + 1) & bitsAllocator.getMaxSequence();
                // Exceed the max sequence, we wait the next second to generate uid
                if (sequence == 0) {
                    currentSecond = getNextSecond(lastSecond);
                }
    
            // At the different second, sequence restart from zero
            } else {
                sequence = 0L;
            }
    
            lastSecond = currentSecond;
    
            // Allocate bits for UID
            return bitsAllocator.allocate(currentSecond - epochSeconds, workerId, sequence);
        }
    
    CachedUidGenerator

    正如名字体现的那样,这是一种缓存型的ID生成方式,当剩余ID不足的时候,会异步的方式重新生成一批ID缓存起来,后续请求的时候直接的时候直接返回现成的ID即可。
    我们来看一下这种方式下几个重要的数据结构,采用了RingBuffer的方式来缓存相关UID信息。
    Tail: 指向当前最后一个可用的UID位置
    Cursor: 指向下一个获取UID的位置,其一定是小于Tail
    Tail - Cursor表示的是现在可用的UID数量,当可用UID数量小于一定阈值的时候会重新添加一批新的UID在RingBuffer中。


    代码层面的优化
    代码中通过字节的填充,来避免伪共享的产生。

    多核处理器处理相互独立的变量时,一旦这些变量处于同一个缓存行,不同变量的操作均会造成这一个缓存行失效,影响缓存的实际效果,造成很大的缓存失效的性能问题。下面图中线程处理不同的两个变量,但这两个变量的修改都会造成整个整个缓存行的失效,导致无效的加载、失效,出现了伪共享的问题

    RingBuffer中通过定义一个PaddedAtomicLong来独占一个缓存行,代码中的实现填充可能需要根据具体的执行系统做一些调整,保证其独占一个缓存行即可。
    下面我们来看下如何获取相关的UID

    public long take() {
            // spin get next available cursor
            long currentCursor = cursor.get();
            long nextCursor = cursor.updateAndGet(old -> old == tail.get() ? old : old + 1);
    
            // check for safety consideration, it never occurs
            Assert.isTrue(nextCursor >= currentCursor, "Curosr can't move back");
    
            // trigger padding in an async-mode if reach the threshold
            long currentTail = tail.get();
            if (currentTail - nextCursor < paddingThreshold) {
                LOGGER.info("Reach the padding threshold:{}. tail:{}, cursor:{}, rest:{}", paddingThreshold, currentTail,
                        nextCursor, currentTail - nextCursor);
                bufferPaddingExecutor.asyncPadding();
            }
    
            // cursor catch the tail, means that there is no more available UID to take
            if (nextCursor == currentCursor) {
                rejectedTakeHandler.rejectTakeBuffer(this);
            }
    
            // 1. check next slot flag is CAN_TAKE_FLAG
            int nextCursorIndex = calSlotIndex(nextCursor);
            Assert.isTrue(flags[nextCursorIndex].get() == CAN_TAKE_FLAG, "Curosr not in can take status");
    
            // 2. get UID from next slot
            // 3. set next slot flag as CAN_PUT_FLAG.
            long uid = slots[nextCursorIndex];
            flags[nextCursorIndex].set(CAN_PUT_FLAG);
    
            // Note that: Step 2,3 can not swap. If we set flag before get value of slot, the producer may overwrite the
            // slot with a new UID, and this may cause the consumer take the UID twice after walk a round the ring
            return uid;
        }
    
    1. 通过AtomicLong.updateAndGet来避免对整个方法进行加锁,获取一个可以访问的UID的游标值,根据这个下标获取slots中相关的uid直接返回
    2. 缓存中可用的uid(Tail - Cursor)小于一定阈值的时候,需要启动另外一个线程来生成一批UID

    UID 的生成

    public synchronized boolean put(long uid) {
            long currentTail = tail.get();
            long currentCursor = cursor.get();
    
            // tail catches the cursor, means that you can't put any cause of RingBuffer is full
            long distance = currentTail - (currentCursor == START_POINT ? 0 : currentCursor);
            if (distance == bufferSize - 1) {
                rejectedPutHandler.rejectPutBuffer(this, uid);
                return false;
            }
    
            // 1. pre-check whether the flag is CAN_PUT_FLAG
            int nextTailIndex = calSlotIndex(currentTail + 1);
            if (flags[nextTailIndex].get() != CAN_PUT_FLAG) {
                rejectedPutHandler.rejectPutBuffer(this, uid);
                return false;
            }
    
            // 2. put UID in the next slot
            // 3. update next slot' flag to CAN_TAKE_FLAG
            // 4. publish tail with sequence increase by one
            slots[nextTailIndex] = uid;
            flags[nextTailIndex].set(CAN_TAKE_FLAG);
            tail.incrementAndGet();
    
            // The atomicity of operations above, guarantees by 'synchronized'. In another word,
            // the take operation can't consume the UID we just put, until the tail is published(tail.incrementAndGet())
            return true;
        }
    
    1. 获取Tail的下标值,如果缓存区满的话直接调用RejectedPutHandler.rejectPutBuffer方法
    2. 未满的话将UID放置在slots数组相应的位置上,同时将Flags数组相应的位置改为CAN_TAKE_FLAG

    CachedUidGenerator通过缓存的方式预先生成一批UID列表,可以解决UID获取时候的耗时,但这种方式也有不好点,一方面需要耗费内存来缓存这部分数据,另外如果访问量不大的情况下,提前生成的UID中的时间戳可能是很早之前的,DefaultUidGenerator应该在大部分的场景中就可以满足相关的需求了。

    相关文章

      网友评论

          本文标题:UidGenerator

          本文链接:https://www.haomeiwen.com/subject/olyxgqtx.html