snowFlake OC 雪花算法 实现

0. UUID

UUID(Universally Unique Identifier,通用唯一识别码是按照开放软件基金会(OSF)制定的标准计算,用到了以太网卡地址、纳秒级时间、芯片 ID 码和许多可能的数字。

UUID 是由一组 32 位数的 16 进制数字所构成,是故 UUID 理论上的总数为1632=2128,约等于 3.4 x 10123。

也就是说若每纳秒产生 1 百万个 UUID,要花 100 亿年才会将所有 UUID 用完。

UUID 通常以连字号分隔的五组来显示,形式为 8-4-4-4-12,总共有 36 个字符(即 32 个英数字母和 4 个连字号)。例如: 123e4567-e89b-12d3-a456-426655440000

JDK 从 1.5 开始在 java.util 包下提供了一个 UUID 类用来生成 UUID:

UUID uuid = UUID.randomUUID();
String uuidStr1 = uuid.toString();
String uuidStr2 = uuidStr1.replaceAll("-","");

1. UUID 的缺点和一个『好』ID 的标准

为了得到一个全局唯一 ID,很自然地就会想到 UUID 算法。但是,UUID 算法有明显的缺点:

  1. UUID 太长了。16 个字节(128 位),通常以 36 长度的字符串表示,很多场景不适用。

  2. 非纯数字。UUID 中会出现 ABCDEF 这些十六进制的字母,因此,在数据库和代码中,自然就不能存储在整型字段或变量中。因此,在数据库中以它作为主键,建立索引的代价比较大,性能有影响。

  3. 不安全。UUID 中会包含网卡的 MAC 地址。

从 UUID 的缺点我们也能推导出一个『好』ID 的标准应该有哪些:

  1. 最好是由纯数字组成。

  2. 越短越好,最好能存进整型变量和数据库的整型字段中。

  3. 信息安全。另外,『ID 连续』并非好事情。

  4. 在不连续的情况下,最好是递增的。即便不是严格递增,至少也应该是趋势递增。

2. SnowFlake 的原理

Snowflake 是 Twitter 开源的分布式 ID 生成算法。最初 Twitter 把存储系统从 MySQL 迁移到 Cassandra,因为Cassandra 没有顺序 ID 生成机制,所以 Twitter 开发了这样一套全局唯一 ID 生成服务。

SnowFlake 的优点是,整体上按照时间自增排序,并且整个分布式系统内不会产生 ID 碰撞(由数据中心 ID 和机器 ID 作区分),并且效率较高。

经测试,SnowFlake 每秒能够产生 26 万 ID 左右。

Snowflake 会生成一个 long 类型的 id 值,Snowflake 对于 long 的各个位都有固定的规范:

SnowFlake 所生成的 ID 的结构如下(为便于理解,这里额外加上了 - 和空格作分隔符):

unused                                                  datacenter_id         sequence_id
  │                                                           │                    │
  │                                                           │                    │
  │ │                                                     │   │                    │
  v │<──────────────────    41 bits   ───────────────────>│   v                    v
┌───┼─────────────────────────────────────────────────────┼───────┬───────┬────────────────┐
│ 0 │ 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0 │ 00000 │ 00000 │ 0000 0000 0000 │
└───┴─────────────────────────────────────────────────────┴───────┴───────┴────────────────┘
                             ^                                        ^
                             │                                        │
                             │                                        │
                             │                                        │
                   time in milliseconds                          worker_id
  • 最高位标识(1 位)

由于 long 基本类型在 Java 中是带符号的,最高位是符号位,正数是 0,负数是 1,所以 id 一般是正数,最高位是 0 。

  • 毫秒级时间截(41 位)

注意,41 位时间截不是存储当前时间的时间截,而是存储时间截的差值(当前时间截 - 开始时间截) 得到的值,这里的的开始时间截,一般是我们的 id 生成器开始使用的时间,由我们程序来指定的(如下面程序 IdGenerator 类的 startTime 属性)

41 位的时间截,可以使用 69 年。

(1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69

  • 数据机器位(10 位)

这 10 位的机器位实际上是由 5 位的 data-center-id 和 5 位的 worker-id 。

在 Twitter 的设计中,最多支持 32 个数据中心(地方),每个中心(地方)最多由 32 台电脑各自计算 ID 。即,总共允许存在 1024 台电脑各自计算 ID 。

每台电脑都由 data-center-id 和 worker-id 标识,逻辑上类似于联合主键的意思。

  • 顺序号(12 位)

毫秒内的计数,12 位的计数顺序号支持每个节点每毫秒(同一机器,同一时间截)产生 4096 个 ID 序号。

3. Snowfake 实现源码(OC版本)

//
//  NSSnowFlake.h
//
//  Created by carbonzhao on 2023/4/8.
//
/*
unused                                                  datacenter_id         sequence_id
  │                                                           │                    │
  │                                                           │                    │
  │ │                                                     │   │                    │
  v │<──────────────────    41 bits   ───────────────────>│   v                    v
┌───┼─────────────────────────────────────────────────────┼───────┬───────┬────────────────┐
│ 0 │ 0000 0000 0000 0000 0000 0000 0000 0000 0000 0000 0 │ 00000 │ 00000 │ 0000 0000 0000 │
└───┴─────────────────────────────────────────────────────┴───────┴───────┴────────────────┘
                             ^                                        ^
                             │                                        │
                             │                                        │
                             │                                        │
                   time in milliseconds                          worker_id
*/

#import 

NS_ASSUME_NONNULL_BEGIN

@interface NSSnowFlake : NSObject
/**工作ID (0~31),default is 0**/
@property (nonatomic,assign) NSInteger workerId;
/**数据中心 ID (0~31),default is 0**/
@property (nonatomic,assign) NSInteger dataCenterId;

/**开始时间截,default is 1420070400000L*/
@property (nonatomic,assign) NSInteger twepoch;

+ (instancetype)sharedInstance;

- (NSInteger)snowFlakeID;
@end

NS_ASSUME_NONNULL_END
//
//  NSSnowFlake.m
//
//  Created by carbonzhao on 2023/4/8.
//

#import "NSSnowFlake.h"
#include 

@interface NSSnowFlake ()
{
    NSInteger sequenceBits;
    NSInteger sequenceShift;
    NSInteger sequenceMask;

    NSInteger workerIdBits;
    NSInteger workerIdShift;
    NSInteger workerIdMask;

    NSInteger dataCenterIdBits;
    NSInteger dataCenterIdShift;
    NSInteger dataCenterIdMask;

    NSInteger timestampBits;
    NSInteger timestampLeftShift;
    NSInteger timestampMask;

    NSInteger sequence;

    /**
     * 上次生成 ID 的时间截
     */
    NSInteger lastTimestamp;
}
@end

static NSSnowFlake *aInstance = nil;
@implementation NSSnowFlake
@synthesize workerId,dataCenterId;

- (instancetype)init
{
    if (self = [super init])
    {
        sequenceBits = 12L;
        sequenceShift = 0L;
        sequenceMask = ~(-1L << sequenceBits);

        workerIdBits = 5L;
        workerIdShift = sequenceBits;
        workerIdMask = ~(-1L << workerIdBits);

        dataCenterIdBits = 5L;
        dataCenterIdShift = sequenceBits + workerIdBits;
        dataCenterIdMask = ~(-1L << dataCenterIdBits);

        timestampBits = 41L;
        timestampLeftShift = sequenceBits + workerIdBits + dataCenterIdBits;
        timestampMask = ~(-1L << timestampBits);

        /**
         * 开始时间截 (2015-01-01)
         */
        self.twepoch = 1420070400000L;
        sequence = 0L;
        /**
         * 上次生成 ID 的时间截
         */
        lastTimestamp = -1L;
    }
    return self;
}


+ (instancetype)sharedInstance
{
    if (!aInstance)
    {
        aInstance = [[NSSnowFlake alloc] init];
    }
    return aInstance;
}


- (void)setWorkerId:(NSInteger)wId
{
    if (wId > workerIdMask || wId < 0)
    {
        NSAssert(NO, @"workerId can't be greater than %d or less than 0");
    }
    workerId = wId;
}


- (void)setDataCenterId:(NSInteger)dId
{
    if (dId > dataCenterIdMask || dId < 0)
    {
        NSAssert(NO, @"dataCenterId can't be greater than %d or less than 0");
    }
    dataCenterId = dId;
}


- (NSInteger)nextId
{
    @synchronized(self) {
        NSInteger timestamp = [self currTimeIntervalms];
        
        // 如果当前时间小于上一次 ID 生成的时间戳,说明系统时钟回退过,这个时候应当抛出异常。
        // 出现这种原因是因为系统的时间被回拨,或出现闰秒现象。
        // 你也可以不抛出异常,而是调用 tilNextMillis 进行等待
        if (timestamp < lastTimestamp)
        {
            NSAssert(NO, @"Clock moved backwards. Refusing to generate id for %d milliseconds");
        }
        
        // 如果是同一时间生成的,则进行毫秒内序列
        if (lastTimestamp == timestamp)
        {
            // 相同毫秒内,序列号自增
            sequence = (sequence + 1) & sequenceMask;
            // 毫秒内序列溢出,即,同一毫秒的序列数已经达到最大
            if (sequence == 0)
            {
                // 阻塞到下一个毫秒,获得新的时间戳
                timestamp = [self tilNextMillis:lastTimestamp];
            }
        }
        // 时间戳改变,毫秒内序列重置
        else
        {
            sequence = 0L;
        }
        
        // 将当前生成的时间戳记录为『上次时间戳』。『下次』生成时间戳时要用到。
        lastTimestamp = timestamp;
        
        // 移位并通过或运算拼到一起组成 64 位的 ID
        return ((timestamp - self.twepoch) << timestampLeftShift) // 时间戳部分
        | (dataCenterId << dataCenterIdShift) // 数据中心部分
        | (workerId << workerIdShift) // 机器标识部分
        | sequence; // 序列号部分
    }
}


- (NSInteger)tilNextMillis:(NSInteger)lastTimestamp
{
    long timestamp = [self currTimeIntervalms];
    while (timestamp <= lastTimestamp) {
        timestamp = [self currTimeIntervalms];
    }
    return timestamp;
}


- (NSTimeInterval)currTimeIntervalms
{
    struct timeval time_now;
    gettimeofday(&time_now,NULL);
    uint64_t ms_time =time_now.tv_sec*1000+time_now.tv_usec/1000;
    return ms_time;
}

- (NSInteger )snowFlakeID
{
    return [[NSSnowFlake sharedInstance] nextId];
}
@end

使用方式:设定参考体系数据

[[NSSnowFlake sharedInstance] setDataCenterId:3];
[[NSSnowFlake sharedInstance] setTwepoch:1288834974657];

 获取生成的ID:

NSInteger uniqueInt = [[NSSnowFlake sharedInstance] snowFlakeID];

 

4.Snowfake 实现源码(JAVA版本)

public class SnowflakeIdGenerator {

    // ==============================Fields===========================================

    // 所占位数、位移、掩码/极大值
    private static final long sequenceBits = 12L;
    private static final long sequenceShift = 0L;
    private static final long sequenceMask = ~(-1L << sequenceBits);

    private static final long workerIdBits = 5L;
    private static final long workerIdShift = sequenceBits;
    private static final long workerIdMask = ~(-1L << workerIdBits);

    private static final long dataCenterIdBits = 5L;
    private static final long dataCenterIdShift = sequenceBits + workerIdBits;
    private static final long dataCenterIdMask = ~(-1L << dataCenterIdBits);

    private static final long timestampBits = 41L;
    private static final long timestampLeftShift = sequenceBits + workerIdBits + dataCenterIdBits;
    private static final long timestampMask = ~(-1L << timestampBits);

    /**
     * 开始时间截 (2015-01-01)
     */
    private static final long twepoch = 1420070400000L;
     /*
     * Instant instant = Instant.parse("2015-01-01T00:00:00Z");
     * System.out.println(instant.getEpochSecond());
     * System.out.println(instant.toEpochMilli());
     */


    private long sequence = 0L;
    private long workerId;
    private long dataCenterId;

    /**
     * 上次生成 ID 的时间截
     */
    private long lastTimestamp = -1L;

    //==============================Constructors=====================================

    public SnowflakeIdGenerator() {
        this(0, 0);
    }

    /**
     * 构造函数
     *
     * @param workerId     工作ID (0~31)
     * @param dataCenterId 数据中心 ID (0~31)
     */
    public SnowflakeIdGenerator(long workerId, long dataCenterId) {
        if (workerId > workerIdMask || workerId < 0) {
            throw new IllegalArgumentException(String.format("workerId can't be greater than %d or less than 0", workerIdMask));
        }

        if (dataCenterId > dataCenterIdMask || dataCenterId < 0) {
            throw new IllegalArgumentException(String.format("dataCenterId can't be greater than %d or less than 0", dataCenterIdMask));
        }

        this.workerId = workerId;
        this.dataCenterId = dataCenterId;
    }

    // ============================== Methods ==========================================

    /**
     * 获得下一个 ID (该方法是线程安全的,synchronized)
     */
    public synchronized long nextId() {
        long timestamp = timeGen();

        // 如果当前时间小于上一次 ID 生成的时间戳,说明系统时钟回退过,这个时候应当抛出异常。
        // 出现这种原因是因为系统的时间被回拨,或出现闰秒现象。
        // 你也可以不抛出异常,而是调用 tilNextMillis 进行等待
        if (timestamp < lastTimestamp) {
            throw new RuntimeException(
                    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;

        // 移位并通过或运算拼到一起组成 64 位的 ID
        return ((timestamp - twepoch) << timestampLeftShift) // 时间戳部分
                | (dataCenterId << dataCenterIdShift) // 数据中心部分
                | (workerId << workerIdShift) // 机器标识部分
                | sequence; // 序列号部分
    }

    /**
     * 阻塞到下一个毫秒,直到获得新的时间戳
     *
     * @param lastTimestamp 上次生成ID的时间截
     * @return 当前时间戳
     */
    protected long tilNextMillis(long lastTimestamp) {
        long timestamp = timeGen();
        while (timestamp <= lastTimestamp) {
            timestamp = timeGen();
        }
        return timestamp;
    }

    /**
     * 阻塞到下一个毫秒,直到获得新的时间戳
     *
     * @param timestamp 当前时间错
     * @param lastTimestamp 上次生成ID的时间截
     * @return 当前时间戳
     */
    protected long tilNextMillis(long timestamp, long lastTimestamp) {
        while (timestamp <= lastTimestamp) {
            timestamp = timeGen();
        }
        return timestamp;
    }

    /**
     * 返回以毫秒为单位的当前时间
     *
     * @return 当前时间(毫秒)
     */
    protected long timeGen() {
        return System.currentTimeMillis();
    }

    //==============================Test=============================================

    /**
     * 测试
     */
    public static void main(String[] args) {
        System.out.println(System.currentTimeMillis());
        SnowflakeIdGenerator idWorker = new SnowflakeIdGenerator(1, 1);
        long startTime = System.nanoTime();
        for (int i = 0; i < 50000; i++) {
            long id = idWorker.nextId();
            System.out.println(id);
        }
        System.out.println((System.nanoTime() - startTime) / 1000000 + "ms");
    }
}

5、解决时间回拨问题

原生的 Snowflake 算法是完全依赖于时间的,如果有时钟回拨的情况发生,会生成重复的 ID,市场上的解决方案也是不少。简单粗暴的办法有:

  • 最简单的方案,就是关闭生成唯一 ID 机器的时间同步。

  • 使用阿里云的的时间服务器进行同步,2017 年 1 月 1 日的闰秒调整,阿里云服务器 NTP 系统 24 小时“消化”闰秒,完美解决了问题。

  • 如果发现有时钟回拨,时间很短比如 5 毫秒,就等待,然后再生成。或者就直接报错,交给业务层去处理。也可以采用 SonyFlake 的方案,精确到 10 毫秒,以 10 毫秒为分配单元。

6、 Sonyflake 算法

sonyflake算法是索尼公司基于snowflake改进的一个分布式唯一ID生成算法。基本思路和snowflake一致,不过位分配上略有不同。

+-----------------------------------------------------------------------------+
| 1 Bit Unused | 39 Bit Timestamp |  8 Bit Sequence ID  |   16 Bit Machine ID |
+-----------------------------------------------------------------------------+

这里时间戳用 39 位精确到 10ms ,所以可以达到 174 年,比 snowflake 的长很久(其实,这么久也没有什么太大意义)

8bit 做为序列号,每 10 毫秒最大生成 256 个,1 秒最多生成 25600 个,比原生的 Snowflake 少好多。这意味着,Sonyflake 的使用场景并发量并没有 Snowfake 那么大。

你可能感兴趣的:(iOS开发,算法,java,数据库,java,开发语言)