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

举报
福州司马懿 发表于 2023/01/19 16:56:57 2023/01/19
【摘要】 背景近几日,被主键ID生成折磨的不太行,于是就在寻找一种合适的主键生成策略,选择一种合适的主键生成策略,可以大大降低主键ID的维护成本。 主键ID生成方法最常用的4种主键ID生成方法UUID:全局唯一性,但是生成的ID是无序的且长度过长,单纯的就无序这一点,数据库中就不建议使用,因为数据库会为主键创建唯一索引,主键无序的话索引维护代价太大。数据库自增ID:自增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序号。

图片.png

完整代码

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的一种很好的解决方案,也是目前市面上使用较多的一种方案,具体怎么使用还是得看自己的需求,总之,适合自己系统的才是最好的

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。