浅谈雪花算法

1 概述

雪花算法,SnowFlake,是Twitter公司开源的由64位整数组成的分布式ID算法,目的是在分布式系统中产生全局唯一且趋势递增的ID,以解决分布式系统中ID全局唯一、递增、确保在任何时候都能生成正确ID、高并发生成ID的需求。

2 组成部分

(1) 第一位,占用1bit,其值始终是0,表示是正整数;

(2) 2-42位,占用41bit,表示时间戳,时间是毫秒,总共可以容纳69年的时间;

(3) 43-52位,占用10bit,记录工作机器的ID,10bit里面可以按需要拆分数位作为机房ID,其他作为服务器ID,例如5bit为机房ID,可以部署到2^5个机房,另外5bit为服务器ID,每个机房可以部署2^5台服务器;

(4) 53-64位,占用12bit,用来记录同一个毫秒内产生的不同ID,12bit可以代表的最大正整数是2^12-1=4096个,也就是说同一毫秒内同一台机器所生成的最大ID数量为4096个;

3 雪花算法的实现

package com.allawn.loadbalance.algorithm;

import com.allawn.AllawnException;
import com.allawn.DateUtils;
import lombok.extern.log4j.Log4j2;

import java.util.Optional;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;

/**
 * Snow Flake Algorithm
 *
 * @author Blake
 * @date 2022-02-08 10:58:11
 **/
@Log4j2
public final class SnowFlakeWorker {

    /**
     * bits for sequence
     **/
    private static final long SEQUENCE_BITS = 12L;

    /**
     * mask for sequence
     **/
    private final long SEQUENCE_MASK = ~(-1L << SEQUENCE_BITS);

    /**
     * data center id
     **/
    private final long dataCenterId;

    /**
     * bits for data center id
     **/
    private final long dataCenterIdBits;

    /**
     * offset for data center id
     **/
    private final long dataCenterIdShift;

    /**
     * offset for timestamp
     **/
    private final long timestampShift;

    /**
     * worker id
     **/
    private final long workerId;

    /**
     * bits for worker id
     **/
    private final long workerIdBits;

    /**
     * last timestamp
     **/
    private long lastTimestamp = -1L;

    /**
     * sequence in milliseconds
     **/
    private long sequence;

    /**
     * start time stamp
     **/
    private long startTimestamp;

    private SnowFlakeWorker(Builder builder) {
        this.workerIdBits = builder.workerIdBits;
        this.dataCenterIdBits = builder.dataCenterIdBits;
        try {
            startTimestamp = DateUtils.parseLong("2000-01-01 00:00:00");
        } catch (AllawnException e) {
            log.error("generate start timestamp error", e);
        }
        this.workerId = builder.workerId;
        this.dataCenterId = builder.dataCenterId;
        this.dataCenterIdShift = SEQUENCE_BITS + workerIdBits;
        this.timestampShift = SEQUENCE_BITS + workerIdBits + dataCenterIdBits;
    }

    /**
     * generate next id
     *
     * @throws AllawnException when lastTimestamp less than timestamp
     * @author Blake
     * @date 2022-02-08 12:38:18
     **/
    public synchronized long nextId() throws AllawnException {
        AtomicLong timestamp = new AtomicLong(System.currentTimeMillis());
        AtomicBoolean throwException = new AtomicBoolean(false);
        Optional.of(timestamp.get()).filter(ts -> ts == lastTimestamp).ifPresentOrElse(ts -> {
            // if timestamp equals to lastTimestamp
            sequence = (sequence + 1) & SEQUENCE_MASK;
            // if sequence equals 0
            Optional.of(sequence).filter(s -> s == 0).ifPresent(s -> timestamp.set(tilNextMillis(lastTimestamp)));
        }, () -> Optional.of(timestamp.get()).filter(ts -> ts > lastTimestamp).ifPresentOrElse(
                // if timestamp greater than lastTimestamp
                ts -> sequence = 0L,
                // if timestamp less than lastTimestamp
                () -> throwException.set(true)));

        Optional.of(throwException).filter(te -> !te.get()).orElseThrow(() -> new AllawnException(
                "Clock moved backwards, refusing to generate id for " + (lastTimestamp - timestamp.get()) +
                        " milliseconds"));

        lastTimestamp = timestamp.get();

        return ((timestamp.get() - startTimestamp) << timestampShift) | (dataCenterId << dataCenterIdShift) |
                (workerId << SEQUENCE_BITS) | sequence;
    }

    /**
     * generate next timestamp after lastTimestamp
     *
     * @param lastTimestamp lastTimestamp
     * @return timestamp
     * @author Blake
     * @date 2022-02-08 12:37:31
     **/
    private long tilNextMillis(long lastTimestamp) {
        long timestamp = System.currentTimeMillis();
        while (timestamp <= lastTimestamp) {
            timestamp = System.currentTimeMillis();
        }
        return timestamp;
    }

    @Override
    public String toString() {
        return "startTimestamp is " + startTimestamp + ", workerIdBits is " + workerIdBits + ", workerId is " +
                workerId + ", dataCenterIdBits is " + dataCenterIdBits + ", dataCenterId is " + dataCenterId;
    }

    /**
     * SnowFlakeWorker Builder
     *
     * @author Blake
     * @date 2022-02-08 12:38:01
     **/
    public static final class Builder {

        /**
         * 数据中心ID
         **/
        private long dataCenterId;

        /**
         * 数据标识ID所占的位数
         **/
        private long dataCenterIdBits = 5L;

        /**
         * 工作机器ID
         **/
        private long workerId;

        /**
         * 机器ID所占的位数
         **/
        private long workerIdBits = 5L;

        /**
         * build a SnowFlakeWorker Object
         *
         * @return SnowFlakeWorker Object
         * @throws AllawnException when dataCenterIdBits plus workerIdBits is not equals 10
         * @author Blake
         * @date 2022-02-08 12:00:36
         **/
        public SnowFlakeWorker build() throws AllawnException {
            Optional.of(this).filter(builder -> builder.dataCenterIdBits + builder.workerIdBits == 10)
                    .orElseThrow(() -> new AllawnException("dataCenterIdBits plus workerIdBits must equals 10"));
            return new SnowFlakeWorker(this);
        }

        /**
         * set dataCenterId
         *
         * @param dataCenterId dataCenterId
         * @return Builder Object
         * @throws AllawnException when data center id greater than maxDataCenterId or less than 0
         * @author Blake
         * @date 2022-02-08 12:01:39
         **/
        public Builder dataCenterId(long dataCenterId) throws AllawnException {
            this.dataCenterId = dataCenterId;
            long maxDataCenterId = ~(-1L << dataCenterIdBits);
            Optional.of(dataCenterId).filter(id -> id <= maxDataCenterId && id > 0).orElseThrow(
                    () -> new AllawnException(
                            "data center id can't be greater than " + maxDataCenterId + " or less than 0"));
            return this;
        }

        /**
         * set dataCenterIdBits
         *
         * @param dataCenterIdBits dataCenterIdBits
         * @return Builder Object
         * @author Blake
         * @date 2022-02-08 12:02:21
         **/
        public Builder dataCenterIdBits(long dataCenterIdBits) {
            this.dataCenterIdBits = dataCenterIdBits;
            return this;
        }

        /**
         * set workerId
         *
         * @param workerId workerId
         * @return Builder Object
         * @throws AllawnException when worker id greater than maxWorkerId or less than 0
         * @author Blake
         * @date 2022-02-08 12:02:46
         **/
        public Builder workerId(long workerId) throws AllawnException {
            this.workerId = workerId;
            long maxWorkerId = ~(-1L << workerIdBits);
            Optional.of(workerId).filter(id -> id <= maxWorkerId && id > 0).orElseThrow(
                    () -> new AllawnException("worker id can't be greater than " + maxWorkerId + " or less than 0"));
            return this;
        }

        /**
         * set workerIdBits
         *
         * @param workerIdBits workerIdBits
         * @return Builder Object
         * @author Blake
         * @date 2022-02-08 12:03:16
         **/
        public Builder workerIdBits(long workerIdBits) {
            this.workerIdBits = workerIdBits;
            return this;
        }

    }

}

(1) 已知,第53-64位为同一个毫秒内同一台机器(同一个机房dataCenter,同一个服务器worker)的序列号,那么按同一毫秒timestamp、同一机房dataCenter、同一服务器worker不断递增sequence,再与111111111111(12个1,即序号占12位)进行位与运算&即可得到第53-64位的bit值;

(2) 已知,第43-52位为工作机器的ID,那么:

1) 先指定机房ID占用的bit数dataCenterIdBits,再指定服务器ID占用的bit数workerIdBits;

2) 计算服务器ID的bit值,把给定的服务器ID,即workerId向左位移12位(因其右边是12位sequence)即可;

3) 计算机房ID的bit值,把给定的机房ID,即dataCenterId向左位移workerIdBits+12即可,因其右边是workerIdBits位的workerId和12位的sequenct;

(3) 已知,第2-42位是时间戳的bit值,把timestamp向左位移dataCenterIdBits+workerIdBits+12即可,因其右边是dataCenterIdBits位的dataCenterId、workerIdBits位的workerId和12位的sequence;

(4) 已知,第1位是0表示整数,无需计算,如需要表示成bit值,则应是64个0;

(5) 把上述计算的预留数bit值、时间戳bit值、机房bit值、服务器bit值、序列号bit值进行位或运算,得到需要的ID值;

注:如果把雪花算法算出来的ID转化成bit值,可能会出现总长度只有62位的情况,这是因为第1位0表示这是一个正整数,第2位也是0,表示时间戳算出来的结果也是正整数,转化成bit值后前面的两个0被省略了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

有马大树

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

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

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

打赏作者

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

抵扣说明:

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

余额充值