Oracle Id生成算法 —— 雪花算法

Posted 福州司马懿

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Oracle Id生成算法 —— 雪花算法相关的知识,希望对你有一定的参考价值。

背景

近几日,被主键ID生成折磨的不太行,于是就在寻找一种合适的主键生成策略,选择一种合适的主键生成策略,可以大大降低主键ID的维护成本。

主键ID生成方法

最常用的4种主键ID生成方法

  1. UUID:全局唯一性,但是生成的ID是无序的且长度过长,单纯的就无序这一点,数据库中就不建议使用,因为数据库会为主键创建唯一索引,主键无序的话索引维护代价太大。
  2. 数据库自增ID:自增ID单机环境其实还好,但是分布式环境下如果并发量过高,就需要集群部署,那么这个时候,为了避免主键冲突,就需要设置步长来自增ID,这样的话成本也是很高的。
  3. Redis的INCR:这种方式主要是因为redis单线程,天生满足原子性,可以用自带的INCR去生成唯一ID,但是,这样也存在问题,并发量太大的时候,需要集群部署,也就需要设置不同的步长,并且它的key还有过期策略,维护这样的一个ID生成策略成本也是很高的。
  4. 雪花算法:SnowFlake算法是Twitter公司推出的专门针对分布式ID的解决方案,着重介绍一下。

雪花算法介绍

雪花算法结构:符号位+时间戳+工作进程位+序列号位,一个64bit的整数,8字节,正好为一个long类型数据。

  • 从左到右,第一位为符号位,0表示正,1表示负。
  • 时间戳(毫秒转化为年):2^41/(365 * 24 * 60 * 60 * 1000)=69.73年。说明雪花算法可表示的范围为69年(从1970年开始),说明雪花算法能用到2039年。
  • 10bit工作位是5位数据中心ID和5位工作ID,其中5位的数据中心ID和5位工作ID的范围是:0到2^5 -1 = 31,分布式环境中一般都是通过设置不同的数据中心ID和工作ID来确保生成的ID不会重复。
  • 12bit-序列号表示每个机房的每个机器每毫秒可以产生2^12-1(4095)个不同的ID序号。

完整代码

package cn.org.ppdxzz.tool;

import java.io.Serializable;
import java.time.LocalDateTime;
import java.time.ZoneOffset;

/**
 * @author: PeiChen
 * @version: 1.0
 */
public class Snowflake implements Serializable 

    private static final long serialVersionUID = 1L;

    /**
     * 起始时间戳
     */
    private final long START_TIMESTAMP;
    //数据标识占用位数
    private final long WORKERID_BIT = 5L;
    /**
     * 数据中心占用位数
     */
    private final long DATACENTER_BIT = 5L;
    /**
     * 序列号占用的位数
     */
    private final long SEQUENCE_BIT = 12L;
    /**
     * 最大支持机器节点数 0~31
     */
    private final long MAX_WORKERID = -1L ^ (-1L << WORKERID_BIT);
    /**
     * 最大支持数据中心节点数 0~31
     */
    private final long MAX_DATACENTER = -1L ^ (-1L << DATACENTER_BIT);
    /**
     * 最大支持序列号 12位 0~4095
     */
    private final long MAX_SEQUENCE = -1L ^ (-1L << SEQUENCE_BIT);

    private final long WORKERID_LEFT_SHIFT = SEQUENCE_BIT;
    private final long DATACENTER_LEFT_SHIFT = SEQUENCE_BIT + WORKERID_BIT;
    private final long TIMESTAMP_LEFT_SHIFT = SEQUENCE_BIT + WORKERID_BIT + DATACENTER_BIT;

    private final long workerId;
    private final long datacenterId;
    private long sequence = 0L;

    /**
     * 上一次的时间戳
     */
    private long lastTimestamp = -1L;

    public Snowflake(long workerId, long datacenterId) 
        this(null,workerId,datacenterId);
    

    public Snowflake(LocalDateTime localDateTime, long workerId, long datacenterId) 
        if (localDateTime == null) 
            //2021-10-23 22:41:08 北京时间
            this.START_TIMESTAMP = 1635000080L;
         else 
            this.START_TIMESTAMP = localDateTime.toEpochSecond(ZoneOffset.of("+8"));
        
        if (workerId > MAX_WORKERID || workerId < 0) 
            throw new IllegalArgumentException("workerId can't be greater than MAX_WORKERID or less than 0");
        
        if (datacenterId > MAX_DATACENTER || datacenterId < 0) 
            throw new IllegalArgumentException("datacenterId can't be greater than MAX_DATACENTER or less than 0");
        

        this.workerId = workerId;
        this.datacenterId = datacenterId;
    

    /**
     * 获取下一个ID
     * @return long:id
     */
    public synchronized long nextId() 
        long currentTimestamp = getCurrentTimestamp();
        if (currentTimestamp < lastTimestamp) 
            throw new IllegalArgumentException("Clock moved backwards. Refusing to generate id.");
        
        if (currentTimestamp == lastTimestamp) 
            sequence = (sequence + 1) & MAX_SEQUENCE;
            //毫秒内序列溢出就取新的时间戳
            if (sequence == 0L) 
                currentTimestamp = getNextTimestamp(lastTimestamp);
            
         else 
            //不同毫秒内,序列号置为0L
            sequence = 0L;
        

        //更新上次生成ID的时间截
        lastTimestamp = currentTimestamp;

        //移位并通过或运算拼到一起组成64位的ID
        return  //时间戳部分
                ((currentTimestamp - START_TIMESTAMP) << TIMESTAMP_LEFT_SHIFT)
                //数据中心部分
                | (datacenterId << DATACENTER_LEFT_SHIFT)
                //机器标识部分
                | (workerId << WORKERID_LEFT_SHIFT)
                //序列号部分
                | sequence;

    

    /**
     * 获取字符串类型的下一个ID
     * @return String:id
     */
    public String nextStringId() 
        return Long.toString(nextId());
    

    /**
     * 获取当前系统时间戳
     * @return 时间戳
     */
    private long getCurrentTimestamp() 
        return LocalDateTime.now().toEpochSecond(ZoneOffset.of("+8"));
    

    /**
     * 阻塞到下一个毫秒,直到获得新的时间戳
     * @param lastTimestamp 上一次生成ID的时间戳
     * @return 下一个时间戳
     */
    private long getNextTimestamp(long lastTimestamp) 
        long timestamp = getCurrentTimestamp();
        while (timestamp <= lastTimestamp) 
            timestamp = getCurrentTimestamp();
        
        return timestamp;
    

    /**
     * 根据ID获取工作机器ID
     * @param id 生成的雪花ID
     * @return 工作机器标识
     */
    public long getWorkerId(long id) 
        return id >> WORKERID_LEFT_SHIFT & ~(-1L << WORKERID_BIT);
    

    /**
     * 根据ID获取数据中心ID
     * @param id 生成的雪花ID
     * @return 数据中心ID
     */
    public long getDataCenterId(long id) 
        return id >> DATACENTER_LEFT_SHIFT & ~(-1L << DATACENTER_BIT);
    

    public static void main(String[] args) 
        Snowflake snowflake = new Snowflake(0,0);
        for (int i = 0; i < 20; i++) 
            System.out.println(snowflake.nextId());
        
    

总结

雪花算法是目前解决分布式唯一ID的一种很好的解决方案,也是目前市面上使用较多的一种方案,具体怎么使用还是得看自己的需求,总之,适合自己系统的才是最好的

以上是关于Oracle Id生成算法 —— 雪花算法的主要内容,如果未能解决你的问题,请参考以下文章

ID生成算法-雪花算法(SnowFlake)及代码实现

PHP实现生成唯一id的雪花算法

ID号生成 雪花算法

分布式唯一id生成器-snowflake雪花算法

spring boot中使用雪花算法生成雪花ID

php雪花算法SnowFlake生成唯一ID