架构设计——ID生成器「建议收藏」

架构设计——ID生成器「建议收藏」一、分布式ID发号器要求很明确:不同机器同一时间生成不同ip;同一机器不同时间生成不同IP;所以根据需求,可选变量有:机器(网卡、IP)+时间,随机数二、WhynotUUID?UUID的实现:算法的核心思想是结合机器的网卡、当地时间、一个随机数来生成UUID。优势:保证唯一性;本地调用,不需要rpcUUID的缺陷:1.UUID较长,占用内存空间;往往用字符串表示…

大家好,又见面了,我是你们的朋友全栈君。

一、分布式ID发号器 

要求很明确:不同机器同一时间生成不同ip;同一机器不同时间生成不同IP;

所以根据需求,可选变量有: 机器(网卡、IP)+时间,随机数

二、Why not UUID?

UUID的实现:算法的核心思想是结合机器的网卡、当地时间、一个随机数来生成UUID。

优势:保证唯一性;本地调用,不需要rpc

UUID的缺陷:

1.UUID较长,占用内存空间;往往用字符串表示,作为主键建立索引查询效率低,常见优化方案为“转化为两个uint64整数存储”或者“折半存储”(折半后不能保证唯一性)

2.不具有有序性,无法保证趋势递增

三、依赖DB自增可行么?

实现:自增+设定步长;8个服务节点8个实例(类似取模);

缺陷:

1.不利于服务节点扩充;服务节点固定,步长固定,难以进行水平服务节点的扩展。

2.依赖DB,对数据库造成额外压力

四.全局唯一ID生成器如何设计?考量因素有哪些

1.全局唯一;有序性(不一定连续,但趋势递增);可反解信息;

2.业务性能要求:高性能、高可用

3.接入方式:嵌入式、中心服务发布、rest发布模式

设计一 :ID数据结构

 

架构设计——ID生成器「建议收藏」

64位

1.机器ID 10位;2的10次方最多可支持1k+台机器;如果每台机器TPS为1W/s,10台服务器则可以有10W/s的TPS,基本满足大部分业务要求。

2.序列号 10位或20位;同理,计算2的20次方,每秒平均可产生百万个ID

3.时间戳 30或40位(分别对应秒级、毫秒级)-时间保证趋势递增

其他可根据业务特点,加上定制的不同生产方式(pom、rest)或版本信息

设计二 并发

为支持大量并发的网络请求,ID生成服务一般采用多线程支持,对于竞争时间和序列(time sequence)采用juc中ReentrantLock或synchronized或atomic变量支持(推荐)

实现

1.定义id实体结构(机器+时间+种子(生成随机数))

//时间戳
private Long time;
//序列号
private Integer sequence;
//生成随机数的种子
private Integer seed;
//机器ip
private Integer clusterIp;

2.根据结构获取对应请求数据,组装实体数据(获取时间戳(毫秒或秒级)os获取本机ip、mac等数据),生成一个不重复的id

//synch保证线程安全
public synchronized long getId()

计算(左移拼装,)【时间戳-初始时间】左移两位集群ip-sequence

3.解析ip,获取对应机器ip、时间戳等信息,则右移取指定位即可

五、Twitter的snowflake算法

Twitter 利用 zookeeper 实现了一个全局ID生成的服务 Snowflake:https://github.com/twitter-archive/snowflake

ID组成:

架构设计——ID生成器「建议收藏」

 

  • 1位符号位:

由于 long 类型在 java 中带符号的,最高位为符号位,正数为 0,负数为 1,且实际系统中所使用的ID一般都是正数,所以最高位为 0。

  • 41位时间戳(毫秒级):

需要注意的是此处的 41 位时间戳并非存储当前时间的时间戳,而是存储时间戳的差值(当前时间戳 – 起始时间戳),这里的起始时间戳一般是ID生成器开始使用的时间戳,由程序来指定,所以41位毫秒时间戳最多可以使用 (1 << 41) / (1000x60x60x24x365) = 69年

  • 10位数据机器位:

包括5位数据标识位和5位机器标识位,这10位决定了分布式系统中最多可以部署 1 << 10 = 1024 s个节点。超过这个数量,生成的ID就有可能会冲突。

  • 12位毫秒内的序列:

这 12 位计数支持每个节点每毫秒(同一台机器,同一时刻)最多生成 1 << 12 = 4096个ID

加起来刚好64位,为一个Long型。

  • 优点:高性能,低延迟,按时间有序,一般不会造成ID碰撞
  • 缺点:需要独立的开发和部署,依赖于机器的时钟

实现:

public class IdWorker {
    /**
     * 起始时间戳 2017-04-01
     */
    private final long epoch = 1491004800000L;
    /**
     * 机器ID所占的位数
     */
    private final long workerIdBits = 5L;
    /**
     * 数据标识ID所占的位数
     */
    private final long dataCenterIdBits = 5L;
    /**
     * 支持的最大机器ID,结果是31
     */
    private final long maxWorkerId = ~(-1L << workerIdBits);
    /**
     * 支持的最大数据标识ID,结果是31
     */
    private final long maxDataCenterId = ~(-1 << dataCenterIdBits);
    /**
     * 毫秒内序列在id中所占的位数
     */
    private final long sequenceBits = 12L;
    /**
     * 机器ID向左移12位
     */
    private final long workerIdShift = sequenceBits;
    /**
     * 数据标识ID向左移17(12+5)位
     */
    private final long dataCenterIdShift = sequenceBits + workerIdBits;
    /**
     * 时间戳向左移22(12+5+5)位
     */
    private final long timestampShift = sequenceBits + workerIdBits + dataCenterIdBits;
    /**
     * 生成序列的掩码,这里为4095 (0b111111111111=0xfff=4095)
     */
    private final long sequenceMask = ~(-1L << sequenceBits);
    /**
     * 数据标识ID(0~31)
     */
    private long dataCenterId;
    /**
     * 机器ID(0~31)
     */
    private long workerId;
    /**
     * 毫秒内序列(0~4095)
     */
    private long sequence;
    /**
     * 上次生成ID的时间戳
     */
    private long lastTimestamp = -1L;

    public IdWorker(long dataCenterId, long workerId) {
        if (dataCenterId > maxDataCenterId || dataCenterId < 0) {
            throw new IllegalArgumentException(String.format("dataCenterId can't be greater than %d or less than 0", maxDataCenterId));
        }
        if (workerId > maxWorkerId || workerId < 0) {
            throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
        }
        this.dataCenterId = dataCenterId;
        this.workerId = workerId;
    }

    /**
     * 获得下一个ID (该方法是线程安全的)
     * @return snowflakeId
     */
    public synchronized long nextId() {
        long timestamp = timeGen();
        //如果当前时间小于上一次ID生成的时间戳,说明系统时钟回退过,这个时候应当抛出异常
        if (timestamp < lastTimestamp) {
            throw new RuntimeException(String.format("Clock moved backwards.  Refusing to generate id for %d milliseconds", lastTimestamp - timestamp));
        }
        //如果是同一时间生成的,则进行毫秒内序列
        if (timestamp == lastTimestamp) {
            sequence = (sequence + 1) & sequenceMask;
            //毫秒内序列溢出
            if (sequence == 0) {
                //阻塞到下一个毫秒,获得新的时间戳
                timestamp = nextMillis(lastTimestamp);
            }
        } else {//时间戳改变,毫秒内序列重置
            sequence = 0L;
        }
        lastTimestamp = timestamp;
        //移位并通过按位或运算拼到一起组成64位的ID
        return ((timestamp - epoch) << timestampShift) |
                (dataCenterId << dataCenterIdShift) |
                (workerId << workerIdShift) |
                sequence;
    }

    /**
     * 返回以毫秒为单位的当前时间
     * @return 当前时间(毫秒)
     */
    protected long timeGen() {
        return System.currentTimeMillis();
    }

    /**
     * 阻塞到下一个毫秒,直到获得新的时间戳
     * @param lastTimestamp 上次生成ID的时间截
     * @return 当前时间戳
     */
    protected long nextMillis(long lastTimestamp) {
        long timestamp = timeGen();
        while (timestamp <= lastTimestamp) {
            timestamp = lastTimestamp;
        }
        return timestamp;
    } 
}

实现注意点:

1.由于存在对时进行修改,snowflake算法应对时钟回拨的做法是直接抛出异常。

2.其次是12位序列号溢出的问题,即1ms内请求生成的ID个数超过了2的12次方=4096个id。snowflake算法对此的做法是,等到下一ms再生成ID。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/148260.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)
blank

相关推荐

  • 提高系统可用性

    提高系统可用性计算机网络发展史首先前解释一下什么是计算机网络,计算机网络是指将地理位置不同的具有独立功能的多台计算机及其外部设备,通过通信线路连接起来,在网络操作系统,网络管理软件及网络通信协议的管理和协调下,实现资源共享和信息传递的计算机系统。计算机网络也称计算机通信网。关于计算机网络的最简单定义是:一些相互…

  • nginx设置301永久重定向

    nginx设置301永久重定向

    2021年10月27日
  • origin绘图基础1

    origin绘图基础11.绘制带有置信区间的拟合曲线分析-拟合-拟合曲线图-勾选之信贷(默认95%);图片来源:https://www.originlab.com/index.aspx?go=Products/Origin/DataAnalysis/CurveFitting置信区间估计(confidenceintervalestimate):利用估计的回归方程,对于自变量x的一个给定值x0,求出因变量y的平均值的估计区间。预测区间估计(predictionintervalestimate):利用估计

  • 如何为pycharm配置Python解释器_pycharm选择python解释器

    如何为pycharm配置Python解释器_pycharm选择python解释器@本文来源于公众号:csdn2299,喜欢可以关注公众号程序员学府文章目录1.python解释器安装2.配置环境变量3.pycharm安装4.pycharm设置及使用4.1pycharm设置4.2pycharm使用1.python解释器安装下载地址:https://www.python.org/打开官网,点击downloads,选择操作系统,以windows为例:选择python2与python3解释器版本(以python3.6.6及python2.7.16为例):以python3.6.6为例

  • SSM-Mybatis(1)[通俗易懂]

    SSM-Mybatis(1)[通俗易懂]Mybatis什么是MybatisMyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。第一个Mybatis程序搭建环境数据库CREATE DATABASE mybatis;uesr mybatis

  • Tomcat频繁宕机的原因分析「建议收藏」

    Tomcat频繁宕机的原因分析「建议收藏」资源!没有完全释放,用完后要父NULL值;数据库连接顺序关闭;优化JAVA虚拟机加入相应的内存参数;TOMCAT在LINUX下不是很稳定;String类型使用,不符合规范;不要在数据库中获取大段文本;JAVA不推荐用String获取大量信息,造成内存溢出就是它;

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号