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

版权声明:本文为博主原创文章,未经博主允许不得转载,转载请注明出处. 博主博客地址是 http://blog.csdn.net/liubenlong007 https://blog.csdn.net/fgyibupi/article/details/74354713

分布式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;
    }
}

测试代码:

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);
                    }
                }
            });
        }

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

.

时间回调,时钟同步问题

snowflake算法时间回拨问题思考
由于存在时间回拨问题,但是他又是那么快和简单,我们思考下是否可以解决呢? 零度在网上找了一圈没有发现具体的解决方案,但是找到了一篇美团不错的文章:Leaf——美团点评分布式ID生成系统(https://tech.meituan.com/MT_Leaf.html)文章很不错,可惜并没有提到时间回拨如何具体解决。下面看看零度的一些思考:

分析时间回拨产生原因
第一:人物操作,在真实环境一般不会有那个傻逼干这种事情,所以基本可以排除。
第二:由于有些业务等需要,机器需要同步时间服务器(在这个过程中可能会存在时间回拨,查了下我们服务器一般在10ms以内(2小时同步一次))。

上面也说了,这个算法存在一个问题就是时钟同步的问题,尤其时钟回调。百度的uid-generator给出了一种解决办法,大家可以直接拿来使用: https://github.com/baidu/uid-generator

没有更多推荐了,返回首页