雪花算法原理:一个 64 bit 的 long 型的数字作为全局唯一 id。这 64 个 bit 中,其中 1 个 bit 是不用的,然后用其中的 41 bit 作为毫秒数,用 10 bit 作为工作机器 id,12 bit 作为序列号。

  1. 1bit,不用,因为二进制中最高位是符号位,1表示负数,0表示正数。生成的id一般都是用整数,所以最高位固定为0。
  2. 41bit-时间戳,用来记录时间戳,毫秒级。
  3. 10bit-工作机器id,用来记录工作机器id。(注意这俩字段,MyBatisPlus源码会用到)
  4. 12bit-序列号,序列号,用来记录同毫秒内产生的不同id。即可以用0、1、2、3、…4094这4095个数字,来表示同一机器同一时间截(毫秒)内产生的4095个ID序号(注意这俩字段,MyBatisPlus源码会用到)

3.2.0版本——MybatisPlus中使用雪花算法源码:

(1)@TableId注解切入的字段为主键字段;

@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.FIELD)
public @interface TableId {
    /**
     * 字段值(驼峰命名方式,该值可无)
     */
    String value() default "";
    /**
     * 主键ID
     * {@link IdType}
     */
    IdType type() default IdType.NONE;
}

(2)环境配置了全局唯一ID (idWorker),type为3

public enum IdType {
    /**
     * 数据库ID自增
     */
    AUTO(0),
    /**
     * 该类型为未设置主键类型(将跟随全局)
     */
    NONE(1),
    /**
     * 用户输入ID
     * <p>该类型可以通过自己注册自动填充插件进行填充</p>
     */
    INPUT(2),

    /* 以下3种类型、只有当插入对象ID 为空,才自动填充。 */
    /**
     * 全局唯一ID (idWorker)
     */
    ID_WORKER(3),
    /**
     * 全局唯一ID (UUID)
     */
    UUID(4),
    /**
     * 字符串全局唯一ID (idWorker 的字符串表示)
     */
    ID_WORKER_STR(5);
}

(3)接着分析ID_WORKER源码

自定义 ParameterHandler 重装构造函数,填充插入方法主键 ID

mysql自己生成18位雪花算法id_构造器

(4)com.baomidou.mybatisplus.core.toolkit.IdWorker

/**
 * 无参数构造器:主机和进程的机器码
 */
private static Sequence WORKER = new Sequence();
/**
 * 有参构造器
 *
 * @param workerId     工作机器 ID(雪花算法上边介绍的)
 * @param datacenterId 序列号(雪花算法上边介绍的)
 */
public static void initSequence(long workerId, long datacenterId) {
    WORKER = new Sequence(workerId, datacenterId);
}

/**
 * 获取ID
 */
public static long getId() {
    return WORKER.nextId();
}

(5)Sequence(核心)源码分析

//在没有设置机器号(也就是无参构造器)的情况下,会通过当前物理网卡地址和jvm的进程ID自动生成。在一个集群中,MAC+JVM进程PID
一样的几率非常小

/**
 * 无参构造器
 *
 */
public Sequence() {
    // 通过当前物理网卡地址获取datacenterId序列号(雪花算法用到)
    this.datacenterId = getDatacenterId(maxDatacenterId);
    // 物理网卡地址+jvm进程pi获取workerId(工作机器ID,雪花算法用到)
    this.workerId = getMaxWorkerId(datacenterId, maxWorkerId);
}

// 有机器号情况下
/**
 * 有参构造器
 *
 * @param workerId     工作机器 ID(雪花算法的)
 * @param datacenterId 序列号(雪花算法的)
 */
public Sequence(long workerId, long datacenterId) {
    Assert.isFalse(workerId > maxWorkerId || workerId < 0,
        String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
    Assert.isFalse(datacenterId > maxDatacenterId || datacenterId < 0,
        String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));
    this.workerId = workerId;
    this.datacenterId = datacenterId;
}

(6)无机器号情况下核心源码分析

/**
 * 数据标识id部分,获取序列号
 */
protected static long getDatacenterId(long maxDatacenterId) {
    long id = 0L;
    try {
        // 物理网卡地址获取
        InetAddress ip = InetAddress.getLocalHost();
        NetworkInterface network = NetworkInterface.getByInetAddress(ip);
        if (network == null) {
            id = 1L;
        } else {
            byte[] mac = network.getHardwareAddress();
            if (null != mac) {
                id = ((0x000000FF & (long) mac[mac.length - 1]) | (0x0000FF00 & (((long) mac[mac.length - 2]) << 8))) >> 6;
                id = id % (maxDatacenterId + 1);
            }
        }
    } catch (Exception e) {
        logger.warn(" getDatacenterId: " + e.getMessage());
    }
    return id;
}

/**
 * 获取 maxWorkerId工作机器ID
 */
protected static long getMaxWorkerId(long datacenterId, long maxWorkerId) {
    StringBuilder mpid = new StringBuilder();
    mpid.append(datacenterId);
    String name = ManagementFactory.getRuntimeMXBean().getName();
    if (StringUtils.isNotEmpty(name)) {
        /*
         * GET jvmPid
         */
        mpid.append(name.split(StringPool.AT)[0]);
    }
    /*
     * MAC + PID 的 hashcode 获取16个低位
     */
    return (mpid.toString().hashCode() & 0xffff) % (maxWorkerId + 1);
}

3.3.0版本——MybatisPlus中使用雪花算法源码:

(1)采用Type为3的方式,即ASSIGN_ID
可见原来的ID_WORKER(3)已经被标注过期了,这一点和上一版本还是有很大区别

public enum IdType {
    /**
     * 数据库ID自增
     */
    AUTO(0),
    /**
     * 该类型为未设置主键类型(注解里等于跟随全局,全局里约等于 INPUT)
     */
    NONE(1),
    /**
     * 用户输入ID
     * <p>该类型可以通过自己注册自动填充插件进行填充</p>
     */
    INPUT(2),

    /* 以下3种类型、只有当插入对象ID 为空,才自动填充。 */
    /**
     * 分配ID (主键类型为number或string)
     *
     * @since 3.3.0
     */
    ASSIGN_ID(3),
    /**
     * 分配UUID (主键类型为 string)
     */
    ASSIGN_UUID(4),
    /**
     * @deprecated 3.3.0 please use {@link #ASSIGN_ID}
     */
    @Deprecated
    ID_WORKER(3),
    /**
     * @deprecated 3.3.0 please use {@link #ASSIGN_ID}
     */
    @Deprecated
    ID_WORKER_STR(3),
    /**
     * @deprecated 3.3.0 please use {@link #ASSIGN_UUID}
     */
    @Deprecated
    UUID(4);
}

(2)对ASSIGN_ID进行分析

/**
 * Id生成器接口
 */
public interface IdentifierGenerator {
    /**
     * 生成Id(整型ID,推荐)
     *
     * @param entity 实体
     * @return id
     */
    Number nextId(Object entity);
// 底下相对于上一版本增加了个UUID,不推荐
.......
}


该源码可见依然是定义了Sequence有参构造器和无参构造器,Sequence源码(核心)经过在线代码比较器比较,和上一版本并无区别

/**
 * 默认生成器
 *
 */
public class DefaultIdentifierGenerator implements IdentifierGenerator {

    private final Sequence sequence;

    public DefaultIdentifierGenerator() {
        this.sequence = new Sequence();
    }

    public DefaultIdentifierGenerator(long workerId, long dataCenterId) {
        this.sequence = new Sequence(workerId, dataCenterId);
    }

    public DefaultIdentifierGenerator(Sequence sequence) {
        this.sequence = sequence;
    }

    @Override
    public Long nextId(Object entity) {
        return sequence.nextId();
    }
}