前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >使用雪花算法生成流水号!

使用雪花算法生成流水号!

作者头像
程序员小航
发布2020-11-23 11:33:49
1.5K0
发布2020-11-23 11:33:49
举报
文章被收录于专栏:程序员小航

前言

" 在分布式系统中常见的问题就是如何生成流水号,一般情况下会有专门的流水号系统,不过在开发过程中或者开发早期不一定会有专门流水号系统,在这里介绍下我所使用的流水号生成器——雪花算法"

1

概述

雪花算法生成的ID结果是一个64bit大小的整数,并且保证在分布式系统中不会重复。

结构

使用64位long型数字作为全局唯一id

1位 无意义 0

41位 时间戳

5位 机房id

5位 机器id

12位自增序号 表示同一时间同一机房同一机器生成的序列号

1. 1位为什么没有意义?

二进制中 第一位代表符号位, 默认 0 表示生成的序列号为正数

2. 41位时间戳

41位最大能表示 2^41-1 的数字,毫秒值 69.7年。

(2^41-1)/1000/60/60/24

当时间大于69.7即时间戳差值大于 2199023255551,会开始出现负值流水号

3. 机房id+机器id

机房id+机器id 2^10 1024台机器。

代码语言:javascript
复制
// 但是使用中不可能每部署一台机器都改下编号, 所以我做出以下改动
// 8位机器号(最大256) 2位机房号
// 机器号使用IP地址后三位 机房id 默认1
// 只需要确保机器的ip后三位不同即可
private static final long MACHINE_BIT = 8;
private static final long DATA_CENTER_BIT = 2;

private static final long DATA_CENTER_ID = 1;
private static long address;
static {
    InetAddress localIp = IpUtils.getLocalIp();
    address = localIp.getAddress()[3] & 0xff;
    log.info("当前系统的 address 为: {}", address);
}

4. 12位序列号

表示同一毫秒内生成的id 2^12-1 个正整数。

总结

SnowFlake每秒能够产生26万ID左右

优点:

生成ID时不依赖于DB,完全在内存生成,高性能高可用。ID呈趋势递增,后续插入索引树的时候性能较好。

缺点:

依赖于系统时钟的一致性。如果某台机器的系统时钟回拨,有可能造成ID冲突,或者ID乱序。

2

代码

SerialNumber

代码语言:javascript
复制
public class SerialNumber  {

    /**
     * 起始的时间戳 2018-01-01 00:00:00
     */
    private static final long START_STAMP = 1514736000000L;

    /**
     * 每一部分占用的位数
     * 序列号 占用位数 12 位 (同一毫秒内生成的id 2^12-1 个正整数)
     * 机器标识  占用位数 8 位 (一般是使用5位)
     * 数据中心 占用位数 2 位 (一般是使用5位)
     *
     */
    private static final long SEQUENCE_BIT = 12;
    private static final long MACHINE_BIT = 8;
    private static final long DATA_CENTER_BIT = 2;

    /**
     * 每一部分的最大值
     */
    private static final long MAX_DATA_CENTER_NUM = ~(-1L << DATA_CENTER_BIT);
    private static final long MAX_MACHINE_NUM = ~(-1L << MACHINE_BIT);
    private static final long MAX_SEQUENCE = ~(-1L << SEQUENCE_BIT);

    /**
     * 每一部分向左的位移
     * 机器Id左移12位 (SEQUENCE_BIT = 12)
     * 数据中心左移20位 (SEQUENCE_BIT + MACHINE_BIT = 12 + 8)
     * 时间戳左移22位 (DATA_CENTER_LEFT + DATA_CENTER_BIT = 12 + 8 + 2)
     *
     */
    private static final long MACHINE_LEFT = SEQUENCE_BIT;
    private static final long DATA_CENTER_LEFT = SEQUENCE_BIT + MACHINE_BIT;
    private static final long TIME_STAMP_LEFT = DATA_CENTER_LEFT + DATA_CENTER_BIT;
    /**
     * 数据中心 机器标识 序列号 上一次时间戳
     * 数据中心标识和机器标识一般是外部传入
     */
    private static final long DATA_CENTER_ID = 1;
    private static long address;
    private long sequence = 0L;
    private long lastStamp = -1L;

    private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyMMdd");

    static {
      InetAddress localIp = IpUtils.getLocalIp();
      address = localIp.getAddress()[3] & 0xff;
      log.info("当前系统的 address 为: {}", address);
    }

    /**
     * 产生下一个ID
     *
     * @return
     */
    private synchronized long nextId() {
        long currStamp = getNewStamp();
        if (currStamp < lastStamp) {
            throw new RuntimeException("Clock moved backwards.  Refusing to generate id");
        }

        if (currStamp == lastStamp) {
            // 相同毫秒内,序列号自增 (sequence + 1) & (~(-1L << SEQUENCE_BIT))
            sequence = (sequence + 1) & MAX_SEQUENCE;
            // 同一毫秒的序列数已经达到最大
            if (sequence == 0L) {
                currStamp = getNextMill();
            }
        } else {
            // 不同毫秒内,序列号置为0
            sequence = 0L;
        }

        lastStamp = currStamp;
        // 时间戳部分 数据中心部分 机器标识部分 序列号部分
        return (currStamp - START_STAMP) << TIME_STAMP_LEFT | DATA_CENTER_ID << DATA_CENTER_LEFT
            | address << MACHINE_LEFT | sequence;
    }

    private long getNextMill() {
        long mill = getNewStamp();
        while (mill <= lastStamp) {
            mill = getNewStamp();
        }
        return mill;
    }

    private long getNewStamp() {
        return System.currentTimeMillis();
    }
}

IpUtils

代码语言:javascript
复制
import java.net.*;
import java.util.Enumeration;

/**
 * @author liuzhihang
 * @date 2019/12/19 16:03
 */
public class IpUtils {

    public static InetAddress getLocalIp() {
        try {
            for (Enumeration<NetworkInterface> e = NetworkInterface.getNetworkInterfaces(); e.hasMoreElements(); ) {
                NetworkInterface item = e.nextElement();
                for (InterfaceAddress address : item.getInterfaceAddresses()) {
                    if (item.isLoopback() || !item.isUp()) {
                        continue;
                    }
                    if (address.getAddress() instanceof Inet4Address) {
                        return address.getAddress();
                    }
                }
            }
            return InetAddress.getLocalHost();
        } catch (SocketException | UnknownHostException e) {
            throw new RuntimeException(e);
        }
    }

}

- <End /> -

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-05-31,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 程序员小航 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档