分布式id生成方案

传统的雪花算法

请添加图片描述

  雪花算法就是使用64位long类型的数据存储id,最高位一位是0,41位毫秒级时间戳,10位workerId,12位序列号。这样最大2的10次方的机器,也就是1024台机器,最多每毫秒每台机器产生2的12次方也就是4096个id(0-4095),如果在1ms里面出现超过了4096个,会阻塞到下一个毫秒序。
传统雪花的缺点:
	1.雪花算法严重依赖时间,所以当发生服务器时钟回拨的问题是会导致可能产生重复的id,所以在我们的业务方法里面如果发生了时间回拨,直接抛出异常
	2.有瓶颈,如果在并发足够的情况下,也许会出现分布式id不足的情况
    (下面有代码实现)
package com.daoism.common.utils;

import org.apache.commons.lang.time.DateFormatUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

/**
 * DaoismOrderNoGenerator 分布订单编号生成器 参考Twitter_Snowflake算法<br>
 * SnowFlake的结构如下(每部分用-分开):<br>
 * 0 - 0000000000 0000000000 0000000000 0000000000 0 - 00000 - 00000 -
 * 000000000000 <br>
 * 1位标识,由于long基本类型在Java中是带符号的,最高位是符号位,正数是0,负数是1,所以id一般是正数,最高位是0<br>
 * 41位时间截(毫秒级),注意,41位时间截不是存储当前时间的时间截,而是存储时间截的差值(当前时间截 -
 * 开始时间截)得到的值,这里的的开始时间截,一般是我们的id生成器开始使用的时间
 * ,由我们程序来指定的(如下下面程序DaoismIdGenerator类的twepoch属性)。41位的时间截,可以使用69年,年T = (1L <<
 * 41) / (1000L * 60 * 60 * 24 * 365) = 69<br>
 * 10位的数据机器位,可以部署在1024个节点,包括5位datacenterId和5位workerId<br>
 * 12位序列,毫秒内的计数,12位的计数顺序号支持每个节点每毫秒(同一机器,同一时间截)产生4096个ID序号<br>
 * 加起来刚好64位,为一个Long型。<br>
 * SnowFlake的优点是,整体上按照时间自增排序,并且整个分布式系统内不会产生ID碰撞(由数据中心ID和机器ID作区分),并且效率较高,经测试,
 * SnowFlake每秒能够产生26万ID左右。
 *
 * @author daoism
 * @version 1.0
 */
@Component
public class DaoismOrderUtils {

    private Logger logger = LoggerFactory.getLogger(getClass());

    // ==============================Fields===========================================
    /**
     * 开始时间截 (2015-01-01)
     */
    @SuppressWarnings("unused")
    private final long twepoch = 1420041600000L;

    /**
     * 机器id所占的位数
     */
    private final long workerIdBits = 5L;

    /**
     * 数据标识id所占的位数
     */
    private final long datacenterIdBits = 5L;

    /**
     * 支持的最大机器id,结果是31 (这个移位算法可以很快的计算出几位二进制数所能表示的最大十进制数)
     */
    private final long maxWorkerId = -1L ^ (-1L << workerIdBits);

    /**
     * 支持的最大数据标识id,结果是31
     */
    private final long maxDatacenterId = -1L ^ (-1L << datacenterIdBits);

    /**
     * 序列在id中占的位数
     */
    private final long sequenceBits = 12L;

    /**
     * 机器ID向左移12位
     */
    private final long workerIdShift = sequenceBits;

    /**
     * 数据标识id向左移17位(12+5)
     */
    private final long datacenterIdShift = sequenceBits + workerIdBits;

    /**
     * 时间截向左移22位(5+5+12)
     */
    @SuppressWarnings("unused")
    private final long timestampLeftShift = sequenceBits + workerIdBits
            + datacenterIdBits;

    /**
     * 生成序列的掩码,这里为4095 (0b111111111111=0xfff=4095)
     */
    private final long sequenceMask = -1L ^ (-1L << sequenceBits);

    /**
     * 订单编号前缀,如NO
     **/
    private final String orderPrefix;

    /**
     * 工作机器ID(0~31)
     */
    @Value("${daoism.orderWorkerId: 1}")
    private long workerId;

    /**
     * 数据中心ID(0~31)
     */
    private long datacenterId;

    /**
     * 毫秒内序列(0~4095)
     */
    private long sequence = 0L;

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

    // ==============================Constructors=====================================
    public DaoismOrderUtils() {
        // this.workerId =
        // Long.parseLong(PropertiesUtil.getPropertity("orderWorkerId"));
        this.datacenterId = 0;
        this.orderPrefix = "";
    }

    /**
     * 构造函数
     *
     * @param workerId     工作ID (0~31)
     * @param datacenterId 数据中心ID (0~31)
     */
    public DaoismOrderUtils(long workerId, long datacenterId) {
        this("", workerId, datacenterId);
    }

    /**
     * 构造函数
     *
     * @param orderPrefix  订单编号前缀
     * @param workerId     工作ID (0~31)
     * @param datacenterId 数据中心ID (0~31)
     */
    public DaoismOrderUtils(String orderPrefix, long workerId,
                            long datacenterId) {
        if (workerId > maxWorkerId || workerId < 0) {
            throw new IllegalArgumentException(String.format(
                    "worker Id can't be greater than %d or less than 0",
                    maxWorkerId));
        }
        if (datacenterId > maxDatacenterId || datacenterId < 0) {
            throw new IllegalArgumentException(String.format(
                    "datacenter Id can't be greater than %d or less than 0",
                    maxDatacenterId));
        }
        this.workerId = workerId;
        this.datacenterId = datacenterId;
        if (orderPrefix == null) {
            this.orderPrefix = "";
        } else {
            this.orderPrefix = orderPrefix;
        }
    }

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

    /**
     * 获得下一个ID (该方法是线程安全的)
     *
     * @return SnowflakeId
     */
    public synchronized String nextId(String orderPrefix) {
        logger.debug("workerId - " + workerId);
        long timestamp = timeGen();
        // 如果当前时间小于上一次ID生成的时间戳,说明系统时钟回退过这个时候应当抛出异常
        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;
        }
        // 上次生成ID的时间截
        lastTimestamp = timestamp;
        /* 移位并通过或运算拼到一起组成64位的ID return ((timestamp - twepoch) <<
         * timestampLeftShift) // | (datacenterId << datacenterIdShift) // |
         * (workerId << workerIdShift) // | sequence; */
        long suffix = (datacenterId << datacenterIdShift)
                | (workerId << workerIdShift) | sequence;
        // System.out.println(suffix);
        String datePrefix = DateFormatUtils.format(timestamp,
                "yyyyMMddHHmmssSSS");
        // return orderPrefix + datePrefix + suffix;
        return orderPrefix + datePrefix.substring(0, 8) + suffix + datePrefix.substring(8);
    }

    public synchronized String nextId() {
        return nextId(this.orderPrefix);
    }

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

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


}

seata雪花算法IdWorker

seata 的分布式 ID 生成器,不再依赖于时间。由于序列号只有 12 位,它的取值范围就是 [0,4095]。如果我们序列号就是生成到了 4096 导致溢出了,怎么办呢?很简单,序列号重新归 0,溢出的这一位加到时间戳上,让时间戳 +1。那你再进一步想想,如果让时间戳 +1 了,那么岂不是会导致一种“超前消费”的情况出现,导致时间戳和系统时间不一致了,不一致就不一致呗,反正我们现在也不依赖于事实的系统时间了,就第一次初始化的时间

请添加图片描述

public class IdWorker {

    /**
     * Start time cut (2020-05-03)
     */
    private final long twepoch = 1588435200000L;

    /**
     * The number of bits occupied by workerId
     */
    private final int workerIdBits = 10;

    /**
     * The number of bits occupied by timestamp
     */
    private final int timestampBits = 41;

    /**
     * The number of bits occupied by sequence
     */
    private final int sequenceBits = 12;

    /**
     * Maximum supported machine id, the result is 1023
     */
    private final int maxWorkerId = ~(-1 << workerIdBits);

    /**
     * business meaning: machine ID (0 ~ 1023)
     * actual layout in memory:
     * highest 1 bit: 0
     * middle 10 bit: workerId
     * lowest 53 bit: all 0
     */
    private long workerId;

    /**
     * timestamp and sequence mix in one Long
     * highest 11 bit: not used
     * middle  41 bit: timestamp
     * lowest  12 bit: sequence
     */
  	//这个就是我们的Atmoiclong
    private AtomicLong timestampAndSequence;

    /**
     * mask that help to extract timestamp and sequence from a long
     */
    private final long timestampAndSequenceMask = ~(-1L << (timestampBits + sequenceBits));

    /**
     * instantiate an IdWorker using given workerId
     * @param workerId if null, then will auto assign one
     */
    public IdWorker(Long workerId) {
      	//timestampWithSequence的初始化
        initTimestampAndSequence();
        initWorkerId(workerId);
    }

    /**
     * init first timestamp and sequence immediately
     */
    private void initTimestampAndSequence() {
      	//获得当前时间戳-(2020-05-03)的时间戳
        long timestamp = getNewestTimestamp();
      	//左移12位       41位时间戳 12位序列号
        long timestampWithSequence = timestamp << sequenceBits;
      	//初始化timestampWithSequence
        this.timestampAndSequence = new AtomicLong(timestampWithSequence);
    }

    /**
     * init workerId
     * @param workerId if null, then auto generate one
     */
    private void initWorkerId(Long workerId) {
        if (workerId == null) {
            workerId = generateWorkerId();
        }
        if (workerId > maxWorkerId || workerId < 0) {
            String message = String.format("worker Id can't be greater than %d or less than 0", maxWorkerId);
            throw new IllegalArgumentException(message);
        }
      	//workId 左移 (41 + 12)
        this.workerId = workerId << (timestampBits + sequenceBits);
    }

    /**
     * get next UUID(base on snowflake algorithm), which look like:
     * highest 1 bit: always 0
     * next   10 bit: workerId
     * next   41 bit: timestamp
     * lowest 12 bit: sequence
     * @return UUID
     */
    public long nextId() {
        waitIfNecessary();
      	//timestampAndSequence + 1 然后返回
        long next = timestampAndSequence.incrementAndGet();
        long timestampWithSequence = next & timestampAndSequenceMask;
      	//workerId或到timestampWithSequence的10位
        return workerId | timestampWithSequence;
    }

    /**
     * block current thread if the QPS of acquiring UUID is too high
     * that current sequence space is exhausted
     */
    private void waitIfNecessary() {
        long currentWithSequence = timestampAndSequence.get();
        long current = currentWithSequence >>> sequenceBits;
        long newest = getNewestTimestamp();
      	//如果当前的时间戳已经超过了newest的时间,sleep(5ms)
        if (current >= newest) {
            try {
                Thread.sleep(5);
            } catch (InterruptedException ignore) {
                // don't care
            }
        }
    }

    /**
     * get newest timestamp relative to twepoch
     */
    private long getNewestTimestamp() {
      	//获得当前时间戳-(2020-05-03)的时间戳
        return System.currentTimeMillis() - twepoch;
    }

    /**
     * auto generate workerId, try using mac first, if failed, then randomly generate one
     * @return workerId
     */
    private long generateWorkerId() {
        try {
            return generateWorkerIdBaseOnMac();
        } catch (Exception e) {
            return generateRandomWorkerId();
        }
    }

    /**
     * use lowest 10 bit of available MAC as workerId
     * @return workerId
     * @throws Exception when there is no available mac found
     */
    private long generateWorkerIdBaseOnMac() throws Exception {
        Enumeration<NetworkInterface> all = NetworkInterface.getNetworkInterfaces();
        while (all.hasMoreElements()) {
            NetworkInterface networkInterface = all.nextElement();
            boolean isLoopback = networkInterface.isLoopback();
            boolean isVirtual = networkInterface.isVirtual();
            if (isLoopback || isVirtual) {
                continue;
            }
            byte[] mac = networkInterface.getHardwareAddress();
            return ((mac[4] & 0B11) << 8) | (mac[5] & 0xFF);
        }
        throw new RuntimeException("no available mac found");
    }

    /**
     * randomly generate one as workerId
     * @return workerId
     */
    private long generateRandomWorkerId() {
        return new Random().nextInt(maxWorkerId + 1);
    }
}
  • 8
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值