• 雪花算法实验


    算法原理

    SnowFlake算法生成id的结果是一个64bit大小的整数,它的结构如下图:


     
    结构图
    1. 1bit,不用,因为二进制中最高位是符号位,1表示负数,0表示正数。生成的id一般都是用整数,所以最高位固定为0。

    2. 41bit-时间戳,用来记录时间戳,毫秒级。
      - 41位可以表示2^{41}-1个数字,
      - 如果只用来表示正整数(计算机中正数包含0),可以表示的数值范围是:0 至 2^{41}-1,减1是因为可表示的数值范围是从0开始算的,而不是1。
      - 也就是说41位可以表示2^{41}-1个毫秒的值,转化成单位年则是(2^{41}-1) / (1000 * 60 * 60 * 24 *365) = 69

    3. 10bit-工作机器id,用来记录工作机器id。
      - 可以部署在2^{10} = 1024个节点,包括5位datacenterId和5位workerId
      - 5位(bit)可以表示的最大正整数是2^{5}-1 = 31,即可以用0、1、2、3、....31这32个数字,来表示不同的datecenterId或workerId

    4. 12bit-序列号,序列号,用来记录同毫秒内产生的不同id。
      - 12位(bit)可以表示的最大正整数是2^{12}-1 = 4095,即可以用0、1、2、3、....4094这4095个数字,来表示同一机器同一时间截(毫秒)内产生的4095个ID序号。

    由于在Java中64bit的整数是long类型,所以在Java中SnowFlake算法生成的id就是long来存储的。

    SnowFlake可以保证:

    1. 所有生成的id按时间趋势递增
    2. 整个分布式系统内不会产生重复id(因为有datacenterId和workerId来做区分)


    C#版雪花算法:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace snowflake
    {
        public class SnowflakeIDcreator
        {
            private static long workerId=1; //机器ID
            private static long twepoch = 68020L; //唯一时间,这是一个避免重复的随机量,自行设定不要大于当前时间戳
            private static long sequence = 0L;
            private static int workerIdBits = 4; //机器码字节数。4个字节用来保存机器码(定义为Long类型会出现,最大偏移64位,所以左移64位没有意义)
            private static long maxWorkerId = -1L ^ -1L << workerIdBits; //最大机器ID
            private static int sequenceBits = 10; //计数器字节数,10个字节用来保存计数码
            private static int workerIdShift = sequenceBits; //机器码数据左移位数,就是后面计数器占用的位数
            private static int timestampLeftShift = sequenceBits + workerIdBits; //时间戳左移动位数就是机器码和计数器总字节数
            private static long sequenceMask = -1L ^ -1L << sequenceBits; //一微秒内可以产生计数,如果达到该值则等到下一微妙在进行生成
            private static long lastTimestamp = -1L;
            private static object lockObj = new object();
           
            /// <summary>
            /// 设置机器码
            /// </summary>
            /// <param name="id">机器码</param>
            public static void SetWorkerID(long id)
            {
                SnowflakeIDcreator.workerId = Id;
            }
    
            public static long nextId()
            {
                lock (lockObj)
                {
                    long timestamp = timeGen();
                    if (lastTimestamp == timestamp)
                    { //同一微妙中生成ID
                        SnowflakeIDcreator.sequence = (SnowflakeIDcreator.sequence + 1) & SnowflakeIDcreator.sequenceMask; //用&运算计算该微秒内产生的计数是否已经到达上限
                        if (SnowflakeIDcreator.sequence == 0)
                        {
                            //一微妙内产生的ID计数已达上限,等待下一微妙
                            timestamp = tillNextMillis(lastTimestamp);
                        }
                    }
                    else
                    { //不同微秒生成ID
                        SnowflakeIDcreator.sequence = 0; //计数清0
                    }
                    if (timestamp < lastTimestamp)
                    { //如果当前时间戳比上一次生成ID时时间戳还小,抛出异常,因为不能保证现在生成的ID之前没有生成过
                        throw new Exception(string.Format("Clock moved backwards.  Refusing to generate id for {0} milliseconds",
                            lastTimestamp - timestamp));
                    }
                    lastTimestamp = timestamp; //把当前时间戳保存为最后生成ID的时间戳
                    long nextId = (timestamp - twepoch << timestampLeftShift) | SnowflakeIDcreator.workerId << SnowflakeIDcreator.workerIdShift | SnowflakeIDcreator.sequence;
                    return nextId;
                }
            }
    
            /// <summary>
            /// 获取下一微秒时间戳
            /// </summary>
            /// <param name="lastTimestamp"></param>
            /// <returns></returns>
            private static long tillNextMillis(long lastTimestamp)
            {
                long timestamp = timeGen();
                while (timestamp <= lastTimestamp)
                {
                    timestamp = timeGen();
                }
                return timestamp;
            }
    
            /// <summary>
            /// 生成当前时间戳
            /// </summary>
            /// <returns></returns>
            private static long timeGen()
            {
                return (long)(DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalMilliseconds;
            }
        }
    }

    用法:

    全局里定义一次

     SnowflakeIDcreator.SetWorkerID(100);

    然后直接可使用:

    SnowflakeIDcreator.nextId()

    下面实验用100个线程同时并发生成id

    1.验证是否会重复;2.测试算法速度;3.与直接lock进行id生成的区别

    算法实验界面:

    Demo代码:

    using System;
    using System.Collections.Concurrent;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data;
    using System.Drawing;
    using System.Linq;
    using System.Text;
    using System.Threading;
    using System.Threading.Tasks;
    using System.Windows.Forms;
    
    namespace snowflake
    {
        public partial class Form1 : Form
        {
            public Form1()
            {
                InitializeComponent();
            }
    
            ConcurrentBag<long> ids = new ConcurrentBag<long>();
            ManualResetEvent resetEvent = new ManualResetEvent(false);
            /// <summary>
            /// 用雪花算法生成id测试
            /// </summary>
            /// <param name="sender"></param>
            /// <param name="e"></param>
            private void btnCreate_Click(object sender, EventArgs e)
            {
                int count = int.Parse(txtNum.Text);
                ids = new ConcurrentBag<long>();
                SnowflakeIDcreator.SetWorkerID(100);
                resetEvent.Reset();
    
                List<Task> tsks = new List<Task>();
                for (int i = 0; i < 100; i++)
                {
                    tsks.Add( Task.Factory.StartNew(new Action(()=>{
                        taskprocess();
                    })));
                }
                DateTime dt1 = DateTime.Now;
                resetEvent.Set();
                Task.WaitAll(tsks.ToArray());
    
                
                double takeseconds = (DateTime.Now - dt1).TotalSeconds;
                double speed = ids.Distinct().Count() / takeseconds;
                txtMsg.Text += "
    ";
                txtMsg.Text += string.Format("使用雪花算法总共生成{0}个号码,耗时{1}秒,速度{2}条/秒", ids.Distinct().Count(), takeseconds,speed.ToString("F2"));
            }
    
            
            private void taskprocess()
            {
                resetEvent.WaitOne();
                for (int i = 0; i < 100000; i++)
                {
                    ids.Add(SnowflakeIDcreator.nextId());
                }
            }
    
    
    
            /// <summary>
            /// 直接用lock生成id
            /// </summary>
            /// <param name="sender"></param>
            /// <param name="e"></param>
            private void button1_Click(object sender, EventArgs e)
            {
                int count = int.Parse(txtNum.Text);
                ids = new ConcurrentBag<long>();
                resetEvent.Reset();
    
                List<Task> tsks = new List<Task>();
                for (int i = 0; i < 100; i++)
                {
                    tsks.Add(Task.Factory.StartNew(new Action(() => {
                        taskprocess2();
                    })));
                }
                DateTime dt1 = DateTime.Now;
                resetEvent.Set();
                Task.WaitAll(tsks.ToArray());
    
    
                double takeseconds = (DateTime.Now - dt1).TotalSeconds;
                double speed = ids.Distinct().Count() / takeseconds;
                txtMsg.Text += "
    ";
                txtMsg.Text += string.Format("lock方式总共生成{0}个号码,耗时{1}秒,速度{2}条/秒", ids.Distinct().Count(), takeseconds, speed.ToString("F2"));
            }
    
            private void taskprocess2()
            {
                resetEvent.WaitOne();
                for (int i = 0; i < 100000; i++)
                {
                    ids.Add(IDCreator.getSeqID());
                }
            }
        }
    }

    其中lock方法生成的代码:

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    
    namespace snowflake
    {
        public class IDCreator
        {
            static long m_curid = 1;
            static object mlock = new object();
            public static long getSeqID()
            {
                lock (mlock)
                {
                    if (m_curid > long.MaxValue)
                    {
                        m_curid = 1;
                    }
                    m_curid++;
                    return m_curid;    
                }
    
            }
        }
    }

    测试结论:雪花算法每秒生成100w条左右,而Lock方式直接生成id速度可达到777w条,只是雪花算法可以分布式,带了机器码和时间戳。如果程序基于单机运行,直接lock速度更快。

  • 相关阅读:
    CF91 B. Queue
    CF18 C. Stripe
    CF767 A. Snacktower
    CF349 B. Color the Fence
    CF519 B. A and B and Compilation Errors
    NLog Helpper日志帮助类配置和使用
    一步一步搭建 .net core 应用
    使用webform、websevice来进行ajax请求操作
    各种奇技淫巧-持续更新
    防止表单提交时刷新页面-阻止form表单的默认提交行为
  • 原文地址:https://www.cnblogs.com/tuyile006/p/13445413.html
Copyright © 2020-2023  润新知