Twitter的分布式自增ID算法snowflake(有改动Java版)

分布式ID生成器 
全局唯一ID生成 
分布式纯数字ID

其实这也不是Twitter独有的,mongodb也采用类似的方法生产自增ID。对于全局唯一ID的说明请参考我另一篇文章 : 高并发分布式环境中获取全局唯一ID[分布式数据库全局唯一主键生成]

该算法最大的好处就是:纯数字基本有序递增

为了简单起见,我这里对snowflake算法进行了一点点修改,修改后的格式为:41位时间戳 |10位进程号 |12位计数器。共计63位(为什么不是64位:第一位是符号位

加锁实现

具体逻辑情况先忙代码中的注释:

import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Set;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.ConcurrentSkipListSet;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * 64位生成规则【首位是符号位,代表正副,所以实际有效是63位】:
 * <p>
 * 41位时间戳 |10位进程号 |12位计数器
 * <p>
 * 41位时间戳:
 * 注意:这里没有直接使用时间戳,因为直接使用的话只能用到2039-09-07 23:47:35
 * 这里采用(当前时间戳-初始时间戳)。最多这样可以使用69年【(1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69】
 * 10位机器:最多1023台机器
 * 12位计数器:每毫秒最多生成4095条记录
 * <p>
 * 这里可以根据项目中实际情况,调整每个位置的长度,比如分布式集群机器数量比较少,可以缩减机器的位数,增加计数器位数
 * <p>
 * <p>
 * 使用注意事项:1、必须关闭时钟同步;2、currentTimeMillis一经定义,不可修改;3、并发量太高的时候,超过了4095,未做处理;4、最大不超过64位
 *<p>
 * 自测性能:一秒能有三四十万的数据产生。
 *<p>
 * 无锁没写出来......
 *<p>
 * Created by hzliubenlong on 2017/6/28.
 */
public class IDGeneratorUtil {


    /**
     * 初始时间戳:2017-1-1 0:0:0
     * 一经定义,不可修改
     */
    private static final long initTimeMillis = 1483200000929L;
    /**
     * 进程。
     * 这里也可以手动指定每台实例的ID号;或者通过ZK的临时递增节点自动获取。
     * 固定值
     */
    private static final int pid = 3;
    /**
     * 计数器
     * 需要保证线程安全
     */
    private static volatile long counter;
    private static volatile long currentTimeMillis = System.currentTimeMillis() - initTimeMillis;
    private static volatile long lastTimeMillis = currentTimeMillis;


    /**
     * 无锁模式暂时没有写出来
     *
     * @return
     */
    public static synchronized long nextId() {
        long series = counter++;
        if (series >= (1 << 12) - 1) {//单位毫秒内计时器满了,需要重新计时
            while (lastTimeMillis == currentTimeMillis) {//等待到下一秒
                currentTimeMillis = System.currentTimeMillis() - initTimeMillis;
            }

            lastTimeMillis = currentTimeMillis;
            counter = 0;
            series = counter++;
        }
        return (currentTimeMillis << 22) | (pid << 12) | series;
    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74

测试代码:

static Set<Long> set = new ConcurrentSkipListSet<>();


ExecutorService fixedThreadPool = Executors.newFixedThreadPool(10);
        for (int i = 0; i < 10; i++) {
            fixedThreadPool.execute(() -> {
                while (true) {
                    long l = nextId();
                    boolean add = set.add(l);
                    if (!add) {
                        System.out.println(System.currentTimeMillis() + "  " + l + " : " + Long.toBinaryString(l));
                        System.exit(0);
                    }
                }
            });
        }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

这种加锁的实现,自测,每秒钟可以产生30W条记录。足够了

无锁实现

没写出来,下面的代码在高并发的情况下会产生重复的数据 ,不要使用。哪位大侠可以帮忙更正一下不胜感激。

public class IDGeneratorUtilNoLock {


    /**
     * 初始时间戳:2017-1-1 0:0:0
     * 一经定义,不可修改
     */
    private static final long initTimeMillis = 1483200000929L;
    /**
     * 进程。
     * 这里也可以手动指定每台实例的ID号;或者通过ZK的临时递增节点自动获取。
     * 固定值
     */
    private static final int pid = 3;
    /**
     * 计数器
     * 需要保证线程安全
     */
    private static volatile AtomicLong counter = new AtomicLong(0);
    private static volatile long currentTimeMillis = System.currentTimeMillis() - initTimeMillis;
    private static volatile long lastTimeMillis = currentTimeMillis;
    /**
     * 无锁模式
     * @return
     */
    public static long nextId() {
        long series;
        while (true) {
            if (lastTimeMillis == currentTimeMillis) {
                series = counter.incrementAndGet();
                if (series >= (1 << 12) - 1) {//同一毫秒内可能达到series最大值
                    while (lastTimeMillis == currentTimeMillis) {//等待到下一毫秒
                        currentTimeMillis = System.currentTimeMillis() - initTimeMillis;
                    }
                    counter.compareAndSet(series, 0);
                    continue;
                }
                break;
            }
            lastTimeMillis = currentTimeMillis;
        }
        return (currentTimeMillis << 22) | (pid << 12) | series;
    }
}

猜你喜欢

转载自blog.csdn.net/qq_39781497/article/details/80152952