开发者社区> YouHaveMe> 正文
阿里云
为了无法计算的价值
打开APP
阿里云APP内打开

雪花算法

简介: 常见的ID生成方式有好几种,其中雪花算法算是分布式系统中应用比较广泛的一种了,并且生成方法也相对简单,可以简单学习一下。
+关注继续查看
近几日,被主键ID生成折磨的不太行,于是就在寻找一种合适的主键生成策略,选择一种合适的主键生成策略,可以大大降低主键ID的维护成本。

那么,常用的主键ID都是如何生成的呢,下面就是主键ID最常用的几种生成方式,接下来就简单的介绍一下。

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

image-20211029233551745

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

  • 从左到右,第一位为符号位,0表示正,1表示负。
  • 时间戳(毫秒转化为年):2^41/(3652460601000)=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序号。

说了那么多,那么雪花算法到底是怎么使用以及生成的呢,迫不及待了吧,那就开始吧。

Snowflake

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

hutool

<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-core</artifactId>
    <version>5.1.2</version>
</dependency>

// 传入机器id和数据中心id,数据范围为:0~31
Snowflake snowflake = IdUtil.createSnowflake(1L, 1L);
System.out.println(snowflake.nextId());  

简单的介绍这两个吧,雪花算法是目前解决分布式唯一ID的一种很好的解决方案,也是目前市面上使用较多的一种方案,具体怎么使用还是得看自己的需求,总之,适合自己系统的才是最好的,如有不妥之处,欢迎交流指导!

版权声明:本文内容由阿里云实名注册用户自发贡献,版权归原作者所有,阿里云开发者社区不拥有其著作权,亦不承担相应法律责任。具体规则请查看《阿里云开发者社区用户服务协议》和《阿里云开发者社区知识产权保护指引》。如果您发现本社区中有涉嫌抄袭的内容,填写侵权投诉表单进行举报,一经查实,本社区将立刻删除涉嫌侵权内容。

相关文章
rust实现雪花算法
rust实现雪花算法
0 0
唯一ID生成原理与PHP实现-雪花算法
唯一ID生成原理与PHP实现-雪花算法
0 0
手写分布式雪花(SnowFlake)算法生成ID
snowflake是Twitter开源的分布式ID生成算法,结果是一个long型的ID。
0 0
mybatis-plus雪花算法增强:idworker
mybatis-plus雪花算法增强:idworker
0 0
mybatis-plus雪花算法生成Id使用详解
mybatis-plus雪花算法生成Id使用详解
0 0
全栈开发之后端脚手架:SpringBoot集成MybatisPlus代码生成,分页,雪花算法,统一响应,异常拦截,Swagger3接口文档
全栈开发之后端脚手架:SpringBoot集成MybatisPlus代码生成,分页,雪花算法,统一响应,异常拦截,Swagger3接口文档
0 0
JAVA 雪花算法 唯一ID生成工具类
JAVA 雪花算法 唯一ID生成工具类
0 0
从零搭建基于SpringBoot的秒杀系统(四):雪花算法生成订单号以及抢购功能实现
抢购功能是整个系统的核心,接下来的很多优化都是在优化抢购功能,在写抢购功能模块之前,先封装几个公共的类。
0 0
将数组a中数据元素实现就地逆置的算法
给出将整型数组a中数据元素实现就地逆置的算法。所谓就地逆置,就是利用数组a原有空间来存放数组a中逆序排放后的各个数据元素。
0 0
+关注
文章
问答
文章排行榜
最热
最新
相关电子书
更多
网易云音乐音视频算法处理的 Serverless 探索之路
立即下载
阿里技术参考图册-算法篇
立即下载
阿里千亿特征深度学习算法XNN实践
立即下载