Twitter-Snowflake,64位自增ID算法詳解

Twitter-Snowflake算法產生的背景至關簡單,爲了知足Twitter每秒上萬條消息的請求,每條消息都必須分配一條惟一的id,這些id還須要一些大體的順序(方便客戶端排序),而且在分佈式系統中不一樣機器產生的id必須不一樣。算法

snowflake把時間戳工做機器id序列號組合在一塊兒。less

 

snowflake-64bit

 

除了最高位bit標記爲不可用之外,其他三組bit佔位都可浮動,看具體的業務需求而定。如下關於此算法的可行性研究分佈式

 Console.WriteLine("41bit的時間戳能夠支持該算法使用年限:{0}", (1L << 41) / (3600L * 24 * 365 * 1000.0));
 Console.WriteLine("10bit的工做機器id數量:{0}", (1L << 10) - 1);
 Console.WriteLine("12bit序列id數量:{0}", (1L << 12) - 1);

運行結果:spa

41bit的時間戳能夠支持該算法使用年限:69.7305700010147
10bit的工做機器id數量:1023
12bit序列id數量:4095

默認狀況下41bit的時間戳(從當前開始計算)能夠支持該算法使用近70年,10bit的工做機器id能夠支持1023臺機器,序列號支持1毫秒產生4095個自增序列id。那麼理論上,一個應用1秒鐘能夠產生409萬條自增ID,此算法可持續使用近70年。徹底能知足咱們平常開發項目的需求。code

工做機器id嚴格意義上來講這個bit段的使用能夠是進程級,機器級的話你可使用MAC地址來惟一標示工做機器,工做進程級可使用IP+Path來區分工做進程。若是工做機器比較少,可使用配置文件來設置這個id是一個不錯的選擇,若是機器過多配置文件的維護是一個災難性的事情。這個工做機器id的bit段也能夠進一步拆分,好比用前5個bit標記workerid,後5個bit標記datacenterid,具體代碼以下:orm

    class Snowflake
    {

        //工做機器id的bit段拆分爲前5個bit標記workerid,後5個bit標記datacenterid
        const int WorkerIdBits = 5;
        const int DatacenterIdBits = 5;
        //序列號bit數
        const int SequenceBits = 12;
        //最大編號限制
        const long MaxWorkerId = -1L ^ (-1L << WorkerIdBits);
        const long MaxDatacenterId = -1L ^ (-1L << DatacenterIdBits);
        private const long SequenceMask = -1L ^ (-1L << SequenceBits);
        //位左運算移動量
        public const int WorkerIdShift = SequenceBits;
        public const int DatacenterIdShift = SequenceBits + WorkerIdBits;
        public const int TimestampLeftShift = SequenceBits + WorkerIdBits + DatacenterIdBits;

        //序列號記錄
        private long _sequence = 0L;
        //時間戳記錄
        private long _lastTimestamp = -1L;


        public long WorkerId { get; protected set; }
        public long DatacenterId { get; protected set; }

        public Snowflake(long workerId, long datacenterId, long sequence = 0L) 
        {
            WorkerId = workerId;
            DatacenterId = datacenterId;
            _sequence = sequence;
        
            // sanity check for workerId
            if (workerId > MaxWorkerId || workerId < 0) 
            {
                throw new ArgumentException( String.Format("worker Id can't be greater than {0} or less than 0", MaxWorkerId) );
            }

            if (datacenterId > MaxDatacenterId || datacenterId < 0)
            {
                throw new ArgumentException( String.Format("datacenter Id can't be greater than {0} or less than 0", MaxDatacenterId));
            }

        }
        /// <summary>
        /// 格林時間戳
        /// </summary>
        /// <returns></returns>
        public long TimeGen()
        {
            DateTime Jan1st1970 = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc);//
            return (long)(DateTime.UtcNow - Jan1st1970).TotalMilliseconds;
        }

        readonly object _lock = new Object();

        public virtual long NextId()
        {
            lock (_lock)
            {
                var timestamp = TimeGen();

                if (timestamp < _lastTimestamp)
                {
                    throw new InvalidSystemClock(String.Format(
                        "發現最新時間戳少{0}毫秒的異常", _lastTimestamp - timestamp));
                }

                if (_lastTimestamp == timestamp)
                {
                    _sequence = (_sequence + 1) & SequenceMask;
                    if (_sequence == 0)
                    {
                        //序列號超過限制,從新取時間戳
                        timestamp = TilNextMillis(_lastTimestamp);
                    }
                }
                else
                {
                    _sequence = 0;
                }

                _lastTimestamp = timestamp;
                //snowflake算法
                var id = (timestamp << TimestampLeftShift) |
                         (DatacenterId << DatacenterIdShift) |
                         (WorkerId << WorkerIdShift) | _sequence;

                return id;
            }
        }
        /// <summary>
        /// 從新取時間戳
        /// </summary>
        protected virtual long TilNextMillis(long lastTimestamp)
        {
            var timestamp = TimeGen();
            while (timestamp <= lastTimestamp)
            {
                //新的時間戳要大於舊的時間戳,纔算做有效時間戳
                timestamp = TimeGen();
            }
            return timestamp;
        }
    }
相關文章
相關標籤/搜索