Java中雪花算法的实现

文章介绍了雪花算法的背景,它在高并发环境下Twitter的需求下诞生,通过机房号、机器号和时间戳的组合生成全局唯一的ID,避免了集群中ID重复的问题。作者详细展示了算法的实现过程和注意事项。
摘要由CSDN通过智能技术生成

背景

在id生成器中,我们自己手写一个自增的id生成器很简单,也很好用。但这只是单机中的id生成器,当我们在集群中使用时,一个集群就会有一个id生成器实例,就意味着每一个集群的id都会从0开始,最后就会导致id出现重复的情况。这种情况下有没有更好的id生成器呢?

雪花算法(Snowflake)最初是在Twitter的内部产生的,其主要背景是Twitter在高并发环境下对唯一ID生成的需求。由于Twitter面临着大量的用户和数据交互,因此需要一种高效且能够生成全局唯一ID的算法来支持其分布式系统的运行。雪花算法就是在这样的背景下应运而生,并且由于其高效、可靠的特点,逐渐被广泛使用。(世界上没有一片雪花是相同的)

雪花算法

用一个接地气的说法来解释一下雪花算法。(此解释不一定是准确算法,但大致意思正确,为了更好地理解)

我们可以对每个机房进行id编号,在这基础上再对每个机房中的服务进行编号,这样生成的每一个id就不会出现重复的情况,就实现了全局id的唯一。

雪花算法的实现

机房号

机器号

时间戳

序列号(同一个机房同一个机器同一个时间时,想要并发产生大量id就使用到了序列号,就不会出现重复的id)。

我们可以设置机房号位5bit,机器号为5bit,时间戳是64bit以内,根据序列号使用12bit来规定,我们可以将时间戳设置为 42bit。

雪花算法的实现在宏观上来看就是将42位的时间戳左移22位让他占在id的前42位,然后让机房号占用第43到47位,以此类推,最后生成一个唯一的id。

我们先将这些号码位数规定好

//起始时间戳
    public static final long START_STAMP = DateUtils.get("2024-1-1").getTime();

    public static final long ROOM_BIT = 5L;
    public static final long MACHINE_BIT = 5L;
    public static final long SEQUENCE_BIT = 12L;

然后将他们的最大值设定好来防止他们超过最大值而产生重复值。

public static final long ROOM_MAX = ~(-1L << ROOM_BIT);
public static final long MACHINE_MAX = ~(-1L << MACHINE_BIT);
public static final long SEQUENCE_MAX = ~(-1L << SEQUENCE_BIT);

然后设定好他们要左移的位数。

public static final long TIME_STAMP_LEFT = ROOM_BIT + MACHINE_BIT + SEQUENCE_BIT;

public static final long ROOM_LEFT = MACHINE_BIT + SEQUENCE_BIT;

public static final long MACHINE_LEFT = SEQUENCE_BIT;

声明好各个id。

private long roomId;

private long machineId;

private AtomicLong sequenceId = new AtomicLong(0);

我们现在需要一个构造器来指定机房号和机器号。

public IdGenerator(long roomId, long machineId) {
        if (roomId > ROOM_MAX || machineId > MACHINE_MAX){
            throw new IllegalArgumentException("机房号id或者机器号id不合法");
        }
        this.roomId = roomId;
        this.machineId = machineId;
    }

然后是获取id的逻辑,大体思路是获取当前与开始时间戳的差值时间戳,比较一下是否比上一次时间戳小,如果小的话,就抛出时钟回拨异常,然后与上次记录的时间戳进行比较,如果相同就使用原子类不断累加序列号,最后将这些id左移后并进行返回。

public long getId(){
        long currentTime = System.currentTimeMillis();
        long timeStamp = currentTime - START_STAMP;

        if (timeStamp < lastTimeStamp){
            throw new RuntimeException("出现时钟回拨问题。");
        }

        if (timeStamp == lastTimeStamp){
            sequenceId.incrementAndGet();
            if (sequenceId.longValue() >= SEQUENCE_MAX){
                timeStamp = getNextTimeStamp();
                sequenceId.set(0);
            }
        } else {
            sequenceId.set(0);
        }

        lastTimeStamp = timeStamp;

        long sequence = sequenceId.longValue();

        return timeStamp << TIME_STAMP_LEFT | roomId << ROOM_LEFT | machineId << MACHINE_LEFT
                | sequence;
    }

在序列号累加之中可能会出现累加后大于最大值的情况,这时候我们可以再次获取一次时间戳,开始下一次的时间戳来生成id。

 private long getNextTimeStamp() {
        long timeStamp = System.currentTimeMillis() - START_STAMP;
        while (timeStamp == lastTimeStamp){
            timeStamp = System.currentTimeMillis() - START_STAMP;
        }
        return timeStamp;
    }

以上就是雪花算法的实现,下面附上全部代码。

package com.mhz;

import java.util.concurrent.atomic.AtomicLong;

/**
 * 请求id生成器
 */
public class IdGenerator {

    //起始时间戳
    public static final long START_STAMP = DateUtils.get("2024-1-1").getTime();

    public static final long ROOM_BIT = 5L;
    public static final long MACHINE_BIT = 5L;
    public static final long SEQUENCE_BIT = 12L;

    public static final long ROOM_MAX = ~(-1L << ROOM_BIT);
    public static final long MACHINE_MAX = ~(-1L << MACHINE_BIT);
    public static final long SEQUENCE_MAX = ~(-1L << SEQUENCE_BIT);

    public static final long TIME_STAMP_LEFT = ROOM_BIT + MACHINE_BIT + SEQUENCE_BIT;

    public static final long ROOM_LEFT = MACHINE_BIT + SEQUENCE_BIT;

    public static final long MACHINE_LEFT = SEQUENCE_BIT;

    private long roomId;

    private long machineId;

    private AtomicLong sequenceId = new AtomicLong(0);

    private long lastTimeStamp;

    public IdGenerator(long roomId, long machineId) {
        if (roomId > ROOM_MAX || machineId > MACHINE_MAX){
            throw new IllegalArgumentException("机房号id或者机器号id不合法");
        }
        this.roomId = roomId;
        this.machineId = machineId;
    }

    public long getId(){
        long currentTime = System.currentTimeMillis();
        long timeStamp = currentTime - START_STAMP;

        if (timeStamp < lastTimeStamp){
            throw new RuntimeException("出现时钟回拨问题。");
        }

        if (timeStamp == lastTimeStamp){
            sequenceId.incrementAndGet();
            if (sequenceId.longValue() >= SEQUENCE_MAX){
                timeStamp = getNextTimeStamp();
                sequenceId.set(0);
            }
        } else {
            sequenceId.set(0);
        }

        lastTimeStamp = timeStamp;

        long sequence = sequenceId.longValue();

        return timeStamp << TIME_STAMP_LEFT | roomId << ROOM_LEFT | machineId << MACHINE_LEFT
                | sequence;
    }

    private long getNextTimeStamp() {
        long timeStamp = System.currentTimeMillis() - START_STAMP;
        while (timeStamp == lastTimeStamp){
            timeStamp = System.currentTimeMillis() - START_STAMP;
        }
        return timeStamp;
    }


    public static void main(String[] args) {
        IdGenerator idGenerator = new IdGenerator(1,2);
        for (int i = 0; i < 1000; i++) {
            new Thread(() -> System.out.println(idGenerator.getId())).start();
        }
    }
}

 

 

以下是Java实现SnowFlake雪花算法代码和详细解释: ```java public class SnowFlake { // 起始的时间戳 private final static long START_STMP = 1480166465631L; // 每一部分占用的位数,序列号占用的位数 private final static long SEQUENCE_BIT = 12; // 机器标识占用的位数 private final static long MACHINE_BIT = 5; // 数据心占用的位数 private final static long DATACENTER_BIT = 5; // 每一部分的最大值 private final static long MAX_DATACENTER_NUM = -1L ^ (-1L << DATACENTER_BIT); private final static long MAX_MACHINE_NUM = -1L ^ (-1L << MACHINE_BIT); private final static long MAX_SEQUENCE = -1L ^ (-1L << SEQUENCE_BIT); // 每一部分向左的位移 private final static long MACHINE_LEFT = SEQUENCE_BIT; private final static long DATACENTER_LEFT = SEQUENCE_BIT + MACHINE_BIT; private final static long TIMESTMP_LEFT = DATACENTER_LEFT + DATACENTER_BIT; private long datacenterId; // 数据心 private long machineId; // 机器标识 private long sequence = 0L; // 序列号 private long lastStmp = -1L; // 上一次时间戳 public SnowFlake(long datacenterId, long machineId) { if (datacenterId > MAX_DATACENTER_NUM || datacenterId < 0) { throw new IllegalArgumentException("datacenterId can't be greater than MAX_DATACENTER_NUM or less than 0"); } if (machineId > MAX_MACHINE_NUM || machineId < 0) { throw new IllegalArgumentException("machineId can't be greater than MAX_MACHINE_NUM or less than 0"); } this.datacenterId = datacenterId; this.machineId = machineId; } public synchronized long nextId() { long currStmp = getNewstmp(); if (currStmp < lastStmp) { throw new RuntimeException("Clock moved backwards. Refusing to generate id"); } if (currStmp == lastStmp) { sequence = (sequence + 1) & MAX_SEQUENCE; if (sequence == 0L) { currStmp = getNextMill(); } } else { sequence = 0L; } lastStmp = currStmp; return (currStmp - START_STMP) << TIMESTMP_LEFT // 时间戳部分 | datacenterId << DATACENTER_LEFT // 数据心部分 | machineId << MACHINE_LEFT // 机器标识部分 | sequence; // 序列号部分 } private long getNextMill() { long mill = getNewstmp(); while (mill <= lastStmp) { mill = getNewstmp(); } return mill; } private long getNewstmp() { return System.currentTimeMillis(); } } ``` SnowFlake算法的核心思想是:使用一个64位的long型的数字作为全局唯一ID,其高位的42位是时间戳,接着是5位的数据心ID和5位的机器ID,最后是12位的序列号。在分布式系统,每个服务实例都需要有一个唯一的ID,而这个ID需要足够小,足够快速生成,足够全局唯一。SnowFlake算法就是为了解决这个问题而生的。 SnowFlake算法的优点是:生成ID的速度非常快,而且生成的ID是按照时间有序递增的,非常适合作为数据库表的主键。SnowFlake算法的缺点是:需要协调好数据心ID和机器ID,否则会产生ID冲突的问题。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

代码已经练习两年半

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值