zoukankan      html  css  js  c++  java
  • snowflake算法(java版)

     转自:http://www.cnblogs.com/haoxinyue/p/5208136.html

    1. 数据库自增长序列或字段

    最常见的方式。利用数据库,全数据库唯一。

    优点:

    1)简单,代码方便,性能可以接受。

    2)数字ID天然排序,对分页或者需要排序的结果很有帮助。

    缺点:

    1)不同数据库语法和实现不同,数据库迁移的时候或多数据库版本支持的时候需要处理。

    2)在单个数据库或读写分离或一主多从的情况下,只有一个主库可以生成。有单点故障的风险。

    3)在性能达不到要求的情况下,比较难于扩展。

    4)如果遇见多个系统需要合并或者涉及到数据迁移会相当痛苦。

    5)分表分库的时候会有麻烦。

    优化方案:

    1)针对主库单点,如果有多个Master库,则每个Master库设置的起始数字不一样,步长一样,可以是Master的个数。比如:Master1 生成的是 1,4,7,10,Master2生成的是2,5,8,11 Master3生成的是 3,6,9,12。这样就可以有效生成集群中的唯一ID,也可以大大降低ID生成数据库操作的负载。

    2. UUID

    常见的方式。可以利用数据库也可以利用程序生成,一般来说全球唯一。

    优点:

    1)简单,代码方便。

    2)生成ID性能非常好,基本不会有性能问题。

    3)全球唯一,在遇见数据迁移,系统数据合并,或者数据库变更等情况下,可以从容应对。

    缺点:

    1)没有排序,无法保证趋势递增。

    2)UUID往往是使用字符串存储,查询的效率比较低。

    3)存储空间比较大,如果是海量数据库,就需要考虑存储量的问题。

    4)传输数据量大

    5)不可读。

    3. UUID的变种

    1)为了解决UUID不可读,可以使用UUID to Int64的方法。及

    /// <summary>
    /// 根据GUID获取唯一数字序列
    /// </summary>
    public static long GuidToInt64()
    {
        byte[] bytes = Guid.NewGuid().ToByteArray();
        return BitConverter.ToInt64(bytes, 0);
    }

    2)为了解决UUID无序的问题,NHibernate在其主键生成方式中提供了Comb算法(combined guid/timestamp)。保留GUID的10个字节,用另6个字节表示GUID生成的时间(DateTime)。

    /// <summary> 
    /// Generate a new <see cref="Guid"/> using the comb algorithm. 
    /// </summary> 
    private Guid GenerateComb()
    {
        byte[] guidArray = Guid.NewGuid().ToByteArray();
     
        DateTime baseDate = new DateTime(1900, 1, 1);
        DateTime now = DateTime.Now;
     
        // Get the days and milliseconds which will be used to build    
        //the byte string    
        TimeSpan days = new TimeSpan(now.Ticks - baseDate.Ticks);
        TimeSpan msecs = now.TimeOfDay;
     
        // Convert to a byte array        
        // Note that SQL Server is accurate to 1/300th of a    
        // millisecond so we divide by 3.333333    
        byte[] daysArray = BitConverter.GetBytes(days.Days);
        byte[] msecsArray = BitConverter.GetBytes((long)
          (msecs.TotalMilliseconds / 3.333333));
     
        // Reverse the bytes to match SQL Servers ordering    
        Array.Reverse(daysArray);
        Array.Reverse(msecsArray);
     
        // Copy the bytes into the guid    
        Array.Copy(daysArray, daysArray.Length - 2, guidArray,
          guidArray.Length - 6, 2);
        Array.Copy(msecsArray, msecsArray.Length - 4, guidArray,
          guidArray.Length - 4, 4);
     
        return new Guid(guidArray);
    }

    用上面的算法测试一下,得到如下的结果:作为比较,前面3个是使用COMB算法得出的结果,最后12个字符串是时间序(统一毫秒生成的3个UUID),过段时间如果再次生成,则12个字符串会比图示的要大。后面3个是直接生成的GUID。

    Twitter的snowflake算法

    snowflake是Twitter开源的分布式ID生成算法,结果是一个long型的ID。其核心思想是:使用41bit作为毫秒数,10bit作为机器的ID(5个bit是数据中心,5个bit的机器ID),12bit作为毫秒内的流水号(意味着每个节点在每毫秒可以产生 4096 个 ID),最后还有一个符号位,永远是0。具体实现的代码可以参看https://github.com/twitter/snowflake

    C#代码如下:

    复制代码
    /// <summary>
        /// From: https://github.com/twitter/snowflake
        /// An object that generates IDs.
        /// This is broken into a separate class in case
        /// we ever want to support multiple worker threads
        /// per process
        /// </summary>
        public class IdWorker
        {
            private long workerId;
            private long datacenterId;
            private long sequence = 0L;
    
            private static long twepoch = 1288834974657L;
    
            private static long workerIdBits = 5L;
            private static long datacenterIdBits = 5L;
            private static long maxWorkerId = -1L ^ (-1L << (int)workerIdBits);
            private static long maxDatacenterId = -1L ^ (-1L << (int)datacenterIdBits);
            private static long sequenceBits = 12L;
    
            private long workerIdShift = sequenceBits;
            private long datacenterIdShift = sequenceBits + workerIdBits;
            private long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;
            private long sequenceMask = -1L ^ (-1L << (int)sequenceBits);
    
            private long lastTimestamp = -1L;
            private static object syncRoot = new object();
    
            public IdWorker(long workerId, long datacenterId)
            {
    
                // sanity check for workerId
                if (workerId > maxWorkerId || workerId < 0)
                {
                    throw new ArgumentException(string.Format("worker Id can't be greater than %d or less than 0", maxWorkerId));
                }
                if (datacenterId > maxDatacenterId || datacenterId < 0)
                {
                    throw new ArgumentException(string.Format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));
                }
                this.workerId = workerId;
                this.datacenterId = datacenterId;
            }
    
            public long nextId()
            {
                lock (syncRoot)
                {
                    long timestamp = timeGen();
    
                    if (timestamp < lastTimestamp)
                    {
                        throw new ApplicationException(string.Format("Clock moved backwards.  Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
                    }
    
                    if (lastTimestamp == timestamp)
                    {
                        sequence = (sequence + 1) & sequenceMask;
                        if (sequence == 0)
                        {
                            timestamp = tilNextMillis(lastTimestamp);
                        }
                    }
                    else
                    {
                        sequence = 0L;
                    }
    
                    lastTimestamp = timestamp;
    
                    return ((timestamp - twepoch) << (int)timestampLeftShift) | (datacenterId << (int)datacenterIdShift) | (workerId << (int)workerIdShift) | sequence;
                }
            }
    
            protected long tilNextMillis(long lastTimestamp)
            {
                long timestamp = timeGen();
                while (timestamp <= lastTimestamp)
                {
                    timestamp = timeGen();
                }
                return timestamp;
            }
    
            protected long timeGen()
            {
                return (long)(DateTime.UtcNow - new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc)).TotalMilliseconds;
            }
        }
    复制代码

    测试代码如下:

    复制代码
    private static void TestIdWorker()
            {
                HashSet<long> set = new HashSet<long>();
                IdWorker idWorker1 = new IdWorker(0, 0);
                IdWorker idWorker2 = new IdWorker(1, 0);
                Thread t1 = new Thread(() => DoTestIdWoker(idWorker1, set));
                Thread t2 = new Thread(() => DoTestIdWoker(idWorker2, set));
                t1.IsBackground = true;
                t2.IsBackground = true;
    
                t1.Start();
                t2.Start();
                try
                {
                    Thread.Sleep(30000);
                    t1.Abort();
                    t2.Abort();
                }
                catch (Exception e)
                {
                }
    
                Console.WriteLine("done");
            }
    
            private static void DoTestIdWoker(IdWorker idWorker, HashSet<long> set)
            {
                while (true)
                {
                    long id = idWorker.nextId();
                    if (!set.Add(id))
                    {
                        Console.WriteLine("duplicate:" + id);
                    }
    
                    Thread.Sleep(1);
                }
            }
    复制代码

    snowflake算法可以根据自身项目的需要进行一定的修改。比如估算未来的数据中心个数,每个数据中心的机器数以及统一毫秒可以能的并发数来调整在算法中所需要的bit数。

    优点:

    1)不依赖于数据库,灵活方便,且性能优于数据库。

    2)ID按照时间在单机上是递增的。

    缺点:

    1)在单机上是递增的,但是由于涉及到分布式环境,每台机器上的时钟不可能完全同步,也许有时候也会出现不是全局递增的情况。

  • 相关阅读:
    GSM Arena 魅族mx四核评测个人翻译
    Oracle Exists用法|转|
    NC公有协同的实现原理|同13的QQ||更新总部往来协同|
    NC客商bd_custbank不可修改账号、名称但可修改默认银行并更新分子公司trigger
    试玩了plsql中test窗口declare声明变量|lpad函数||plsql sql command test window区别|
    使用windows live writer测试
    用友写insert on bd_custbank 触发器和自动更新单位名称2in1
    oracle触发器select into和cursor用法的区别
    |转|oracle中prior的用法,connect by prior,树形目录
    客商增加自动增加银行账户|搞定!||更新使用游标course写法|
  • 原文地址:https://www.cnblogs.com/fangyuan303687320/p/5743702.html
Copyright © 2011-2022 走看看