• C++实现雪花算法(处理时间回跳)


    toc

    雪花算法介绍

    雪花算法是Twitter开源的唯一ID生成算法。ID的有效部分有三个:

    • 41位时间戳部分:此部分是雪花算法的关键部分,因为时间是唯一且单调递增的,以时间作为关键部分,理论上ID便不会重复(但计算机上的时间计量却可能不是唯一且单调递增的,存在时间回跳或前跳现象),时间戳精度为毫秒
    • 10位机器ID部分:此部分唯一后,允许分布式环境中每个节点生成的ID唯一
    • 12位序列号部分:此部分允许同一节点同一毫秒生成多个ID(通过递增实现唯一),相当于通过编号的形式,把时间戳粒度再次细分

    结合上面的文字描述,看下下面的图,会更好理解

    /*-------高位---------------------------------------------共64位---------------------------------------------低位-------|
    |-----------------------------------------------------------------------------------------------------------------------|
    |    0    |    0000000000 0000000000 0000000000 0000000000 0    |         00000        |        00000        |   000000000000    |
    |未使用 |                    41位时间戳                        |    5位DataCenterID    |    5位WorkerID        |    12位序列号        |
    |-----------------------------------------------------------------------------------------------------------------------|
    |未使用 |                    41位时间戳                        |              10位机器ID                |    12位序列号        |
    |----------------------------------------------------------------------------------------------------------------------*/

    单从设计上看,雪花算法理论上存在如下特点:

    • ID单调递增
    • 系统内全局唯一(前提是每个节点机器ID唯一)
    • 内含时间戳,可计算ID生成时间
    • ID非常紧凑,仅有64位
    • 时间戳部分可容纳(2 ^ 41) / (1000 * 60 * 60* 24 * 365) = 69.7年
    • 可支持2 ^ 10 = 1024个节点
    • 每个节点一毫秒内最大能产生2 ^ 12 = 4096个ID
    • 机器性能允许情况下,每秒可生成4096 * 1000 = 4096000个ID

    在实际使用时,会发现雪花算法格外依赖计算机系统时间,一旦系统时间回退,将会导致重复ID的出现

    带时间回退处理实现一

    雪花算法ID生成源码在这里
    根据对算法的理解,我实现了自己的C++版本,增加了时间回退处理、单例、按需加锁,代码如下:

    #ifndef __IDGENERATER_H_
    #define __IDGENERATER_H_
    
    #include <mutex>
    #include <string>
    #include <chrono>
    #include <thread>
    #include <cstdint>
    #include <stdexcept>
    
    /*
        Twitter雪花算法
    */
    /*-------高位---------------------------------------------共64位---------------------------------------------低位-------|
    |-----------------------------------------------------------------------------------------------------------------------|
    |    0    |    0000000000 0000000000 0000000000 0000000000 0    |         00000        |        00000        |   000000000000    |
    |未使用 |                    41位时间戳                        |    5位DataCenterID    |    5位WorkerID        |    12位序列号        |
    |-----------------------------------------------------------------------------------------------------------------------|
    |未使用 |                    41位时间戳                        |              10位机器ID                |    12位序列号        |
    |----------------------------------------------------------------------------------------------------------------------*/
    //各部分所占大小
    constexpr int SEQUENCE_BITS = 12;                //12位序列号,毫秒内计数,一个机器上1毫秒内最多能产生4096个ID
    constexpr int WORKER_ID_BITS = 5;
    constexpr int DATA_CENTER_ID_BITS = 5;            //5位DataCenterID与5位WorkerID合在一起,是机器ID,共10位,最大能支持1024个节点
    constexpr int TIMESTAMP_BITS = 41;                //41位时间戳,能容纳69.7年 ==> (2 ^ 41) / (1000 * 60 * 60* 24 * 365) = 69.7
    
    //各部分偏移                                    根据前一部分所占位宽度决定后一部分偏移量
    constexpr int SEQUENCE_ID_SHIFT = 0;
    constexpr int WORK_ID_SHIFT = SEQUENCE_BITS;
    constexpr int DATA_CENTER_ID_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS;
    constexpr int TIMESTAMP_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS + DATA_CENTER_ID_BITS;
    
    //DataCenterID与WorkerID最大取值                根据所占位宽度计算最大值
    constexpr std::int64_t MAX_WORKER_ID = (1 << WORKER_ID_BITS) - 1;
    constexpr std::int64_t MAX_DATA_CENTER_ID = (1 << DATA_CENTER_ID_BITS) - 1;
    
    //序列号掩码                                    用于控制序列号取值范围
    constexpr std::int64_t SEQUENCE_MASK = (1 << SEQUENCE_BITS) - 1;
    
    //时间起点                                        时间戳虽然能容纳69.7年,但如果直接存Unix时间戳(1970.1.1),最大只能支持到2039年
    //                                                所以添加一个比Unix纪元时间晚的开始时间,存相对于开始时间的偏移,那么支持的最大时间则为开始时间 + 69.7年
    constexpr std::int64_t START_POINT = 1625068800000LL; //2021.7.1 00:00:00  ==> 41位时间支持到 2090年
    
    //无锁类                                        符合基本可锁定要求,但是不添加锁操作
    class NonLockType{
    public:
        constexpr void lock(){
        }
        constexpr void unlock(){
        }
    };
    
    template<typename LockType = NonLockType>
    class IDGenerater final{
    public:
        static IDGenerater *GetInstance(int iDataCenterID = 0, int iWorkerID = 0){
            if(iDataCenterID < 0 || MAX_DATA_CENTER_ID < iDataCenterID){
                throw std::invalid_argument(std::string("iDataCenterID不应小于0或大于") + std::to_string(MAX_DATA_CENTER_ID));
            }
            if(iWorkerID < 0 || MAX_WORKER_ID < iWorkerID){
                throw std::invalid_argument(std::string("iWorkerID不应小于0或大于") + std::to_string(MAX_WORKER_ID));
            }
    
            static IDGenerater GeneraterInstance(iDataCenterID, iWorkerID);            //magic static            C++11后静态局部变量初始化已经是线程安全的
            return &GeneraterInstance;
        }
    
        std::int64_t NextID(){
            std::lock_guard<LockType> lock(m_lock);
            auto i64CurTimeStamp = GetCurrentTimeStamp();
    
            if(i64CurTimeStamp < m_i64LastTimeStamp){                                //时间回退,睡眠到下一个毫秒再生成
                i64CurTimeStamp = GetNextTimeStampBySleep();
    
            } else if(i64CurTimeStamp == m_i64LastTimeStamp){                        //一毫秒内生成多个ID
                m_i64SequenceID = (m_i64SequenceID + 1) & SEQUENCE_MASK;            //更新序列号
    
                if(0 == m_i64SequenceID){                                            //达到该毫秒能生成的最大ID数量,循环到下一个毫秒再生成
                    i64CurTimeStamp = GetNextTimeStampByLoop(i64CurTimeStamp);
                }
            } else{                                                                    //新时间,序列号从头开始
                m_i64SequenceID = 0;
            }
            m_i64LastTimeStamp = i64CurTimeStamp;
            return ((i64CurTimeStamp - START_POINT) << TIMESTAMP_SHIFT)
                | (m_i64DataCenterID << DATA_CENTER_ID_SHIFT)
                | (m_i64WorkerID << WORK_ID_SHIFT)
                | (m_i64SequenceID << SEQUENCE_ID_SHIFT);
        }
    
    private:
        std::int64_t GetCurrentTimeStamp(){
            auto tpTimePoint = std::chrono::time_point_cast<std::chrono::milliseconds>(std::chrono::system_clock::now());    //获取时间并降低精度到毫秒
            return tpTimePoint.time_since_epoch().count();                                                                    //得到时间戳
        }
    
        std::int64_t GetNextTimeStampByLoop(std::int64_t i64CurTimeStamp){
            while(i64CurTimeStamp <= m_i64LastTimeStamp)
            {
                i64CurTimeStamp = GetCurrentTimeStamp();
            }
            return i64CurTimeStamp;
        }
    
        std::int64_t GetNextTimeStampBySleep(){
            auto dDuration = std::chrono::milliseconds(m_i64LastTimeStamp);                                                    //时间纪元到现在经历的时间段
            auto tpTime = std::chrono::time_point<std::chrono::system_clock, std::chrono::milliseconds>(dDuration);            //得到时间点
            std::this_thread::sleep_until(tpTime);
            return GetCurrentTimeStamp();
        }
    
    private:
        IDGenerater(int iDataCenterID, int iWorkerID) :m_i64DataCenterID(iDataCenterID), m_i64WorkerID(iWorkerID), m_i64SequenceID(0), m_i64LastTimeStamp(0){
        }
        IDGenerater() = delete;
        ~IDGenerater() = default;
    
        IDGenerater(const IDGenerater& rhs) = delete;
        IDGenerater(IDGenerater&& rhs) = delete;
        IDGenerater& operator=(const IDGenerater& rhs) = delete;
        IDGenerater& operator=(IDGenerater&& rhs) = delete;
    
    private:
        std::int64_t m_i64DataCenterID;
        std::int64_t m_i64WorkerID;
        std::int64_t m_i64SequenceID;
        std::int64_t m_i64LastTimeStamp;
        LockType m_lock;
    };
    
    using NonLockIDGenerater = IDGenerater<>;
    
    #endif    //!__IDGENERATER_H_
    
    • 使用单例模式处理ID生成器类,保证生成器全局唯一,每个线程拿到的生成器均是同一个
      • 在C++11中,静态局部变量的初始化是线程安全,且仅初始化仅会被调用一次,非常适合实现懒汉单例
    • 使用带默认参数的类模板,搭配空锁定/解锁实现的锁,在实例化时可根据需求选择是否使用锁,以及使用何种锁
      • 如果多个线程均需生成ID,实例化生成器类模板时,必须传入非空实现锁类型,使NextID操作互斥,否则可能产生重复ID
      • 如果仅有一个线程生成ID,实例化生成器类模板时,建议使用模板默认参数,以得到最佳性能
    • 通过sleep的方式勉强处理了时间回退,虽然处理了ID重复问题,但影响了可用性与ID生成效率,这种解决办法存在缺陷

    带时间回退处理实现二

    steady_clock介绍

    C++11引入了单调时钟std::chrono::steady_clock,他与操作系统时钟无关,机器开机状态下不会回退,一般用作间隔时间计算。
    但从steady_clock获取到的时间却不是当前时间,而是开机到现在经过的时间, 也就是操作系统运行时间,这就导致一旦重新开机,steady_clock时间又将重0开始
    可以用以下代码片段,对比系统平台运行时间(Linux下命令cat /proc/uptime Windows下任务管理器),来验证steady_clock时间

        auto tpTime = std::chrono::steady_clock::now();                                        //获取当前时间time_point
        auto tpTimePoint = std::chrono::time_point_cast<std::chrono::milliseconds>(tpTime);    //降低精度到毫秒
        auto dDuration = tpTimePoint.time_since_epoch();                                    //返回时间纪元到现在经历的时间段
        auto tsTimeStamp = dDuration.count();                                               //得到时间戳

    处理时间回退

    可以利用steady_clock开机时间戳不单调递增且不回退的特性,想办法处理它重启时间置0带来的影响
    根据IDGenerater初始化时刻system_clock时间往前推算启动时间(推算出的时间可能不准确,依赖于初始化时刻system_clock是否准确),每次计算时间戳时,以启动时间+运行时间作为当前时间戳,便可以得到一个回退概率更低的时间戳

    • 如果单用steady_clock,每次重启后生成的ID必然重复
    • 如果单用system_clock,无法处理进程重启情况下,时间回退导致的ID重复
    • 如果steady_clock配合system_clock,除非重启系统,并控制IDGenerater实例化时系统运行时间、以及实例化时的系统时间,使本次计算当前时间戳小于、等于上次开机的当前时间戳,才会生成重复ID

    steady_clock与system_clock配合后,雪花算法仍然满足上述8个理论特点,但降低了对系统时间的依赖,同时避免了时间回退时,sleep或while生成不了ID的尴尬

    #ifndef __IDGENERATER_H_
    #define __IDGENERATER_H_
    
    #include <mutex>
    #include <string>
    #include <chrono>
    #include <thread>
    #include <cstdint>
    #include <stdexcept>
    
    /*
        Twitter雪花算法
    */
    /*-------高位---------------------------------------------共64位---------------------------------------------低位-------|
    |-----------------------------------------------------------------------------------------------------------------------|
    |    0    |    0000000000 0000000000 0000000000 0000000000 0    |         00000        |        00000        |   000000000000    |
    |未使用 |                    41位时间戳                        |    5位DataCenterID    |    5位WorkerID        |    12位序列号        |
    |-----------------------------------------------------------------------------------------------------------------------|
    |未使用 |                    41位时间戳                        |              10位机器ID                |    12位序列号        |
    |----------------------------------------------------------------------------------------------------------------------*/
    //各部分所占大小
    constexpr int SEQUENCE_BITS = 12;                //12位序列号,毫秒内计数,一个机器上1毫秒内最多能产生4096个ID
    constexpr int WORKER_ID_BITS = 5;
    constexpr int DATA_CENTER_ID_BITS = 5;            //5位DataCenterID与5位WorkerID合在一起,是机器ID,共10位,最大能支持1024个节点
    constexpr int TIMESTAMP_BITS = 41;                //41位时间戳,能容纳69.7年 ==> (2 ^ 41) / (1000 * 60 * 60* 24 * 365) = 69.7
    
    //各部分偏移                                    根据前一部分所占位宽度决定后一部分偏移量
    constexpr int SEQUENCE_ID_SHIFT = 0;
    constexpr int WORK_ID_SHIFT = SEQUENCE_BITS;
    constexpr int DATA_CENTER_ID_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS;
    constexpr int TIMESTAMP_SHIFT = SEQUENCE_BITS + WORKER_ID_BITS + DATA_CENTER_ID_BITS;
    
    //DataCenterID与WorkerID最大取值                根据所占位宽度计算最大值
    constexpr std::int64_t MAX_WORKER_ID = (1 << WORKER_ID_BITS) - 1;
    constexpr std::int64_t MAX_DATA_CENTER_ID = (1 << DATA_CENTER_ID_BITS) - 1;
    
    //序列号掩码                                    用于控制序列号取值范围
    constexpr std::int64_t SEQUENCE_MASK = (1 << SEQUENCE_BITS) - 1;
    
    //时间起点                                        时间戳虽然能容纳69.7年,但如果直接存Unix时间戳(1970.1.1),最大只能支持到2039年
    //                                                所以添加一个比Unix纪元时间晚的开始时间,存相对于开始时间的偏移,那么支持的最大时间则为开始时间 + 69.7年
    constexpr std::int64_t START_POINT = 1625068800000LL; //2021.7.1 00:00:00  ==> 41位时间支持到 2090年
    
    //无锁类                                        符合基本可锁定要求,但是不添加锁操作
    class NonLockType{
    public:
        constexpr void lock(){
        }
        constexpr void unlock(){
        }
    };
    
    template<typename LockType = NonLockType>
    class IDGenerater final{
    public:
        static IDGenerater *GetInstance(int iDataCenterID = 0, int iWorkerID = 0){
            if(iDataCenterID < 0 || MAX_DATA_CENTER_ID < iDataCenterID){
                throw std::invalid_argument(std::string("iDataCenterID不应小于0或大于") + std::to_string(MAX_DATA_CENTER_ID));
            }
            if(iWorkerID < 0 || MAX_WORKER_ID < iWorkerID){
                throw std::invalid_argument(std::string("iWorkerID不应小于0或大于") + std::to_string(MAX_WORKER_ID));
            }
    
            static IDGenerater GeneraterInstance(iDataCenterID, iWorkerID);            //magic static            C++11后静态局部变量初始化已经是线程安全的
            return &GeneraterInstance;
        }
    
        std::int64_t NextID(){
            std::lock_guard<LockType> lock(m_lock);
            auto i64CurTimeStamp = m_i64BootTimeStamp + GetCurrentTimeStamp<std::chrono::steady_clock>();    //以m_i64BootTimeStamp单调递增时间
    
            if(i64CurTimeStamp < m_i64LastTimeStamp){                                //时间回退,睡眠到下一个毫秒再生成
                i64CurTimeStamp = GetNextTimeStampBySleep();
    
            } else if(i64CurTimeStamp == m_i64LastTimeStamp){                        //一毫秒内生成多个ID
                m_i64SequenceID = (m_i64SequenceID + 1) & SEQUENCE_MASK;            //更新序列号
    
                if(0 == m_i64SequenceID){                                            //达到该毫秒能生成的最大ID数量,循环到下一个毫秒再生成
                    i64CurTimeStamp = GetNextTimeStampByLoop(i64CurTimeStamp);
                }
            } else{                                                                    //新时间,序列号从头开始
                m_i64SequenceID = 0;
            }
            m_i64LastTimeStamp = i64CurTimeStamp;
            return ((i64CurTimeStamp - START_POINT) << TIMESTAMP_SHIFT)
                | (m_i64DataCenterID << DATA_CENTER_ID_SHIFT)
                | (m_i64WorkerID << WORK_ID_SHIFT)
                | (m_i64SequenceID << SEQUENCE_ID_SHIFT);
        }
    
    private:
        template<typename ClockType>
        std::int64_t GetCurrentTimeStamp(){
            auto tpTimePoint = std::chrono::time_point_cast<std::chrono::milliseconds>(ClockType::now());    //获取时间并降低精度到毫秒
            return tpTimePoint.time_since_epoch().count();                                                                    //得到时间戳
        }
    
        std::int64_t GetNextTimeStampByLoop(std::int64_t i64CurTimeStamp){
            while(i64CurTimeStamp <= m_i64LastTimeStamp)
            {
                i64CurTimeStamp = m_i64BootTimeStamp + GetCurrentTimeStamp<std::chrono::steady_clock>();
            }
            return i64CurTimeStamp;
        }
    
        std::int64_t GetNextTimeStampBySleep(){
            auto dDuration = std::chrono::milliseconds(m_i64LastTimeStamp);                                                    //时间纪元到现在经历的时间段
            auto tpTime = std::chrono::time_point<std::chrono::system_clock, std::chrono::milliseconds>(dDuration);            //得到时间点
            std::this_thread::sleep_until(tpTime);
            return m_i64BootTimeStamp + GetCurrentTimeStamp<std::chrono::steady_clock>();
        }
    
    private:
        IDGenerater(int iDataCenterID, int iWorkerID) :m_i64DataCenterID(iDataCenterID), m_i64WorkerID(iWorkerID), m_i64SequenceID(0), m_i64LastTimeStamp(0){
            m_i64BootTimeStamp = GetCurrentTimeStamp<std::chrono::system_clock>() - GetCurrentTimeStamp<std::chrono::steady_clock>();    //系统开机时间(可能有误,取决于实例化时系统时间)
        }
        IDGenerater() = delete;
        ~IDGenerater() = default;
    
        IDGenerater(const IDGenerater& rhs) = delete;
        IDGenerater(IDGenerater&& rhs) = delete;
        IDGenerater& operator=(const IDGenerater& rhs) = delete;
        IDGenerater& operator=(IDGenerater&& rhs) = delete;
    
    private:
        std::int64_t m_i64DataCenterID;
        std::int64_t m_i64WorkerID;
        std::int64_t m_i64SequenceID;
        std::int64_t m_i64LastTimeStamp;
        std::int64_t m_i64BootTimeStamp;
        LockType m_lock;
    };
    
    using NonLockIDGenerater = IDGenerater<>;
    
    #endif    //!__IDGENERATER_H_
    




    原创不易,转载请注明出处,谢谢
  • 相关阅读:
    10.6比赛 T1
    10.5比赛 T3
    10.5比赛 T2
    10.5比赛 T1
    最大子序列问题
    Python编程快速上手_第 4 章 列表
    Python编程快速上手_第 3 章 函数
    Python编程快速上手_第 2 章 控制流
    Python编程快速上手_第 1 章 Python基础
    Python 编程快速上手 ——让繁琐工作自动化
  • 原文地址:https://www.cnblogs.com/Keeping-Fit/p/15025402.html
Copyright © 2020-2023  润新知