Java分布式全局唯一Id:id生成要求、为什么不用UUID、生成分布式雪花Id

文章目录

  • 为什么需要分布式全局唯一Id
  • Id生成规则部分硬性要求
  • Id生成系统的可用性要求
  • 为什么不用UUID
  • 生成分布式雪花Id
    • POM
    • 代码示例
    • API
    • 生成18位雪花Id
    • 生成13位雪花Id

为什么需要分布式全局唯一Id

在复杂分布式系统中,往往需要对大量的数据和消息进行唯一标识。

如在美团点评的金融、支付、餐饮、酒店;猫眼电影等产品的系统中数据日渐增长,对数据分库分表后需要有一个唯一Id来标识一条数据或消息;特别一点的如订单、骑手、优惠券也都需要有唯一Id坐标时。

此时一个能够生成全局唯一Id的系统是非常必要的。

Id生成规则部分硬性要求

  • 全局唯一
  • 趋势递增:在Mysql的InnoDB引擎中使用的是聚集索引,由于多数RDBMS使用Btree的数据结构来存储索引数据,在主键的选择上-面我们应该尽量使用有序的主键来保证写入性能;
  • 单调递增:尽量保证下一个Id一定大于上一个Id,例如事务版本号、IM增量消息、排序等特殊需求;
  • 信息安全:如果Id是连续的,恶意用户的扒取工作就非常容易做了,直接按照顺序下载指定URL即可;如果是订单号就更危险了,竞对可以直接知道我们一天的单量.所以在一些应用场景下,需要Id无规则不规则,让竞争对手不好猜;
  • 含时间戳:这样就能够在开发中快速了解这个分布式Id的生成时间。

Id生成系统的可用性要求

  • 高可用:发一个获取分布式Id的请求,服务器就要保证99.999%的情况下给我创建一个唯一分布式Id;
  • 低延迟:发一个获取分布式Id的请求,服务器要快,极速;
  • 高QPS:例如并发一口气10万个创建分布式Id请求同时杀过来,服务器要顶得住且一下子成功创建10万个分布式Id。

为什么不用UUID

在《阿里巴巴 Java 开发手册》第五章 MySQL 规定第九条中,强制规定了单表的主键 id 必须为无符号的 bigint 类型,且是自增的。MySQL 中索引的数据结构是 B+Tree,这种数据结构的特点是索引树上的节点的数据是有序的,而如果使用 UUID 作为主键,那么每次插入数据时,因为无法保证每次产生的 UUID 有序,所以就会出现新的 UUID 需要插入到索引树的中间去,这样可能会频繁地导致页分裂,使性能下降。

太占用内存。每个 UUID 由 36 个字符组成,在字符串进行比较时,需要从前往后比较,字符串越长,性能越差。另外字符串越长,占用的内存越大,由于页的大小是固定的,这样一个页上能存放的关键字数量就会越少,这样最终就会导致索引树的高度越大,在索引搜索的时候,发生的磁盘 IO 次数越多,性能越差。

生成分布式雪花Id

POM

   		<dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.8.6</version></dependency>

代码示例

   @Testpublic void test(){System.out.println(IdUtil.getSnowflakeNextId());}

API

package cn.hutool.core.util;import cn.hutool.core.exceptions.UtilException;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.lang.ObjectId;
import cn.hutool.core.lang.Singleton;
import cn.hutool.core.lang.Snowflake;
import cn.hutool.core.lang.UUID;
import cn.hutool.core.lang.id.NanoId;
import cn.hutool.core.net.NetUtil;public class IdUtil {public IdUtil() {}public static String randomUUID() {return UUID.randomUUID().toString();}public static String simpleUUID() {return UUID.randomUUID().toString(true);}public static String fastUUID() {return UUID.fastUUID().toString();}public static String fastSimpleUUID() {return UUID.fastUUID().toString(true);}public static String objectId() {return ObjectId.next();}/** @deprecated */@Deprecatedpublic static Snowflake createSnowflake(long workerId, long datacenterId) {return new Snowflake(workerId, datacenterId);}public static Snowflake getSnowflake(long workerId, long datacenterId) {return (Snowflake)Singleton.get(Snowflake.class, new Object[]{workerId, datacenterId});}public static Snowflake getSnowflake(long workerId) {return (Snowflake)Singleton.get(Snowflake.class, new Object[]{workerId});}public static Snowflake getSnowflake() {return (Snowflake)Singleton.get(Snowflake.class, new Object[0]);}public static long getDataCenterId(long maxDatacenterId) {Assert.isTrue(maxDatacenterId > 0L, "maxDatacenterId must be > 0", new Object[0]);if (maxDatacenterId == Long.MAX_VALUE) {--maxDatacenterId;}long id = 1L;byte[] mac = null;try {mac = NetUtil.getLocalHardwareAddress();} catch (UtilException var6) {}if (null != mac) {id = (255L & (long)mac[mac.length - 2] | 65280L & (long)mac[mac.length - 1] << 8) >> 6;id %= maxDatacenterId + 1L;}return id;}public static long getWorkerId(long datacenterId, long maxWorkerId) {StringBuilder mpid = new StringBuilder();mpid.append(datacenterId);try {mpid.append(RuntimeUtil.getPid());} catch (UtilException var6) {}return (long)(mpid.toString().hashCode() & '\uffff') % (maxWorkerId + 1L);}public static String nanoId() {return NanoId.randomNanoId();}public static String nanoId(int size) {return NanoId.randomNanoId(size);}public static long getSnowflakeNextId() {return getSnowflake().nextId();}public static String getSnowflakeNextIdStr() {return getSnowflake().nextIdStr();}
}

生成18位雪花Id

public class SnowFlake {// 起始的时间戳,这个时间戳可以是你的系统初始时间,一般取当前时间戳private final static long START_TIMESTAMP = 1672502400000L; // 2023-01-01 00:00:00// 每一部分占用的位数,可以根据自己的需求进行调整,这里是按照默认的占位数进行分配private final static long SEQUENCE_BIT = 12; // 序列号占用的位数private final static long MACHINE_BIT = 5; // 机器标识占用的位数private final static long DATA_CENTER_BIT = 5; // 数据中心占用的位数// 每一部分的最大值,可以根据占用的位数进行计算得到private final static long MAX_SEQUENCE = ~(-1L << SEQUENCE_BIT);private final static long MAX_MACHINE_NUM = ~(-1L << MACHINE_BIT);private final static long MAX_DATA_CENTER_NUM = ~(-1L << DATA_CENTER_BIT);// 每一部分向左的位移,计算出来的值是为了后面生成 ID 做准备private final static long MACHINE_LEFT = SEQUENCE_BIT;private final static long DATA_CENTER_LEFT = SEQUENCE_BIT + MACHINE_BIT;private final static long TIMESTAMP_LEFT = DATA_CENTER_LEFT + DATA_CENTER_BIT;private long dataCenterId; // 数据中心 IDprivate long machineId; // 机器 IDprivate long sequence = 0L; // 序列号private long lastTimeStamp = -1L; // 上一次时间戳/*** <h2>构造方法</h2>** @param dataCenterId 数据中心 ID* @param machineId    机器 ID*/public SnowFlake(long dataCenterId, long machineId) {if (dataCenterId > MAX_DATA_CENTER_NUM || dataCenterId < 0) {throw new IllegalArgumentException("数据中心标识不能大于等于 " + MAX_DATA_CENTER_NUM + " 或小于 0");}if (machineId > MAX_MACHINE_NUM || machineId < 0) {throw new IllegalArgumentException("机器标识不能大于等于 " + MAX_MACHINE_NUM + " 或小于 0");}this.dataCenterId = dataCenterId;this.machineId = machineId;}/*** <h2>雪花算法核心方法</h2>* 通过调用 nextId() 方法,让当前这台机器上的 snowflake 算法程序生成一个全局唯一的 id*/public synchronized long nextId() {// 获取系统当前时间戳long currentTimeStamp = getSystemCurrentTimeMillis();if (currentTimeStamp < lastTimeStamp) {throw new RuntimeException("时钟向后移动,拒绝生成雪花算法ID");}if (currentTimeStamp == lastTimeStamp) {// 当前毫秒内,序列号自增sequence = (sequence + 1) & MAX_SEQUENCE;// 序列号超出范围,需要等待下一毫秒if (sequence == 0L) {// 获取下一毫秒currentTimeStamp = getNextMill(lastTimeStamp);}} else {// 不同毫秒内,序列号置为 0sequence = 0L;}lastTimeStamp = currentTimeStamp;// 使用位运算生成最终的 IDreturn (currentTimeStamp - START_TIMESTAMP) << TIMESTAMP_LEFT| dataCenterId << DATA_CENTER_LEFT| machineId << MACHINE_LEFT| sequence;}/*** <h2>获取系统当前时间戳</h2>** @return 当前时间(毫秒)*/private long getSystemCurrentTimeMillis() {return System.currentTimeMillis();}/*** <h2>获取下一毫秒</h2>* 当某一毫秒的时间,产生的 id 数 超过4095,系统会进入等待,直到下一毫秒,系统继续产生 ID** @param lastTimestamp 上次生成 ID 的时间截* @return 当前时间戳*/private long getNextMill(long lastTimestamp) {long timeMillis = getSystemCurrentTimeMillis();while (timeMillis <= lastTimestamp) {timeMillis = getSystemCurrentTimeMillis();}return timeMillis;}public static void main(String[] args) {SnowFlake worker1 = new SnowFlake(1, 1);System.out.println(worker1.nextId());}
}

生成13位雪花Id

package com.ais.common.web.utils;public class IdUtil {/*** 开始时间截 (本次时间戳为:Thu Nov 04 2010 09:42:54 GMT+0800 (中国标准时间)----1288834974657L---1656543015264587776--19 )*/private final long startTime = 1683803335498L;/*** 机器id所占的位数*/private final long workerIdBits = 3L;/*** 支持的最大机器id,结果是31 (这个移位算法可以很快的计算出几位二进制数所能表示的最大十进制数)*/private final long maxWorkerId = -1L ^ (-1L << workerIdBits);/*** 序列在id中占的位数*/private final long sequenceBits = 5L;/*** 机器ID向左移12位*/private final long workerIdShift = sequenceBits;/*** 时间截向左移22位(10+12)*/private final long timestampLeftShift = sequenceBits + workerIdBits;/*** 生成序列的掩码,这里为4095 (0b111111111111=0xfff=4095)*/private final long sequenceMask = -1L ^ (-1L << sequenceBits);/*** 工作机器ID(0~1024)*/private long workerId;/*** 毫秒内序列(0~4095)*/private long sequence = 0L;/*** 上次生成ID的时间截*/private long lastTimestamp = -1L;private final static IdUtil idWorker = new IdUtil(1);//==============================Constructors=====================================/*** 构造函数** @param workerId 工作ID (0~1024)*/public IdUtil(long workerId) {if (workerId > maxWorkerId || workerId < 0) {throw new IllegalArgumentException(String.format("workerId can't be greater than %d or less than 0", maxWorkerId));}this.workerId = workerId;}// ==============================Methods==========================================/*** 获得下一个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 (lastTimestamp == timestamp) {sequence = (sequence + 1) & sequenceMask;//毫秒内序列溢出if (sequence == 0) {//阻塞到下一个毫秒,获得新的时间戳timestamp = tilNextMillis(lastTimestamp);}}//时间戳改变,毫秒内序列重置else {sequence = 0L;}//上次生成ID的时间截lastTimestamp = timestamp;//移位并通过或运算拼到一起组成64位的IDreturn ((timestamp - startTime) << timestampLeftShift) | (workerId << workerIdShift) | sequence;}/*** 阻塞到下一个毫秒,直到获得新的时间戳** @param lastTimestamp 上次生成ID的时间截* @return 当前时间戳*/protected long tilNextMillis(long lastTimestamp) {long timestamp = timeGen();while (timestamp <= lastTimestamp) {timestamp = timeGen();}return timestamp;}/*** 返回以毫秒为单位的当前时间** @return 当前时间(毫秒)*/protected long timeGen() {return System.currentTimeMillis();}public static long getSnowflakeNextId() {return idWorker.nextId();}/*** 测试*/public static void main(String[] args) {System.out.println(IdUtil.getSnowflakeNextId());}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/198829.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

23史上最全版---SQL注入详解

漏洞原因 一些概念&#xff1a; SQL&#xff1a;用于数据库中的标准数据查询语言。 web分为前端和后端&#xff0c;前端负责进行展示&#xff0c;后端负责处理来自前端的请求并提供前端展示的资源。 而数据库就是存储资源的地方。 而服务器获取数据的方法就是使用SQL语句进…

【开源】基于Vue+SpringBoot的数据可视化的智慧河南大屏

项目编号&#xff1a; S 059 &#xff0c;文末获取源码。 \color{red}{项目编号&#xff1a;S059&#xff0c;文末获取源码。} 项目编号&#xff1a;S059&#xff0c;文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块三、系统展示四、核心代码4.1 数据模块 …

Linux: Ftrace: function_graph 里面有irq处理的函数

Linux: ftrace: echo function_graph &#xff1e; current_tracer 在做这个函数调用图的时候&#xff0c;会发现这个函数调用非常的大&#xff0c;有些是irq的处理函数&#xff0c;也放在其中&#xff0c;前后有标记&#xff1a; Line 18702: 1) < | Line 22335: 1) …

AntV和AntD之间的区别与联系

前言&#xff1a;最近在调研前端的一些框架&#xff0c;技术栈主要是用react&#xff0c;所以找到了2个十分相似解决方案&#xff0c;拿来对比一下&#xff08;antd和antv都是基于react&#xff09; antd对比antv antd antv 解决方案企业级 UI 设计语言数据可视化解决方案提供…

springboot使用Validator参数校验

引用&#xff1a;https://www.cnblogs.com/yang-yz/p/17576507.html Validator校验框架遵循了 jsr-303验证规范(参数校验规范) JSR : java specification requests 为了解决开发人员在校验参数方面&#xff0c;少写代码 依赖&#xff1a; <dependency><groupId>o…

《数字中台建设总体方案》

《数字中台建设总体方案》 制定数字中台战略规划&#xff1a;制定符合企业实际情况的数字中台战略规划&#xff0c;明确建设目标、重点任务和时间表。确定数字中台架构&#xff1a;根据企业业务需求和特点&#xff0c;确定数字中台的架构&#xff0c;包括技术架构、应用架构和数…

20 动态规划解最长回文子序列

问题描述&#xff1a;给定一个字符串s&#xff0c;找到其中最长的回文子序列&#xff0c;并返回该序列的长度&#xff0c;可以假设s的最大长度为1000&#xff1b; 暴力解法&#xff1a;直接两个循环&#xff0c;遍历所有子串&#xff0c;并统计子串的最大长度&#xff1b; pu…

ftp的服务安装配置

安装 yum install -y vsftpd # 是否安装成功 rpm -qa | grep vsftpd # 是否开机启动 systemctl list-unit-files | grep vsftpd # 开机启动 systemctl enable vsftpd.service # ftp端口 netstat -antup | grep ftp # 状态 service vsftpd status service vsftpd start service…

java项目日常运维需要的文档资料

一、前言 java项目开发完成&#xff0c;部署上线&#xff0c;进入项目运维阶段&#xff0c;日常工作需要准备哪些资料和文档?当项目上线后&#xff0c;运行一段时间&#xff0c;或多或少会遇到一些运维上的问题&#xff0c;比如服务器磁盘饱满&#xff0c;服务器CPU&#xff0…

如何理解微服务体系结构中的 CQRS

本文翻译自 How To Understand CQRS In Microservices Architecture&#xff0c;原作者 OLEKSII。 问题描述 在典型的软件应用程序中&#xff0c;有一个负责写入和读取操作的数据存储。通常&#xff0c;应用程序实现一些 CRUD 操作&#xff0c;并且非常简单。你存储了一些东西并…

一文读懂中间件

前言&#xff1a;在程序猿的日常工作中&#xff0c; 经常会提到中间件&#xff0c;然而大家对中间件的理解并不一致&#xff0c;导致了一些不必要的分歧和误解。“中间件”一词被用来描述各种各样的软件产品&#xff0c;在不同文献中有着许多不同的中间件定义&#xff0c;包括操…

JS:绘制日历,结合vue3食用

思路解析&#xff1a;假设展示2023年12月的日历 ①通过new Date(new Date(2023,12).setDate(0)).getDate()获取2023.12月的天数lastDay ②通过new Date(2023,11,1).getDay()获取2023.12月第一天的起始位置startDay ③根据自己的需求在startDay和lastDay的前后补0&#xff0c…

Hadoop学习笔记(HDP)-Part.13 安装Ranger

目录 Part.01 关于HDP Part.02 核心组件原理 Part.03 资源规划 Part.04 基础环境配置 Part.05 Yum源配置 Part.06 安装OracleJDK Part.07 安装MySQL Part.08 部署Ambari集群 Part.09 安装OpenLDAP Part.10 创建集群 Part.11 安装Kerberos Part.12 安装HDFS Part.13 安装Ranger …

粒子群优化算法的实践

粒子群优化算法的实践 flyfish 粒子群优化算法&#xff08;Particle Swarm Optimization&#xff0c;PSO&#xff09;或者粒子群算法 红叉的地方是理想之地&#xff0c;这些粒子都想去&#xff0c;总结8个字是信息共享&#xff0c;个人决策。 上完图之后&#xff0c;上代码&a…

Vue JAVA开发常用模板

1.VsCode添加模板 左下角设置》用户代码片段 新建全局代码片段》将模板粘贴仅文件&#xff08;prefix用于指定触发关键字&#xff09; 添加成功过后输入配置的关键字即可使用 1.1 vue2模板 {// Example:"Print to console": {"prefix": "vue2",…

vue实现数字千分位格式化 如6,383,993,037,937.463

1.封装文件&#xff1a;numberToCurrency.js /**实现数字千分位格式化 如6,383,993,037,937.463 */ export function numberToCurrencyNo(value) {if (!value) return 0// 获取整数部分const intPart Math.trunc(value)// 整数部分处理&#xff0c;增加,const intPartFormat …

使用 Go Modules 管理依赖:简明教程

一、GoLang 中包的介绍和定义 包&#xff08;package&#xff09;是多个 Go 源码的集合&#xff0c;是一种高级的代码复用方案Go 语言为我们提供了很多内置包&#xff0c;如 fmt、strconv、strings、sort、errors、times、encoding/json、os、io 等Golang 中的包可以分为三种&…

C++ 实现的Ping类的封装

Ping 使用 Internet 控制消息协议&#xff08;ICMP&#xff09;来测试主机之间的连接。当用户发送一个 ping 请求时&#xff0c;则对应的发送一个 ICMP Echo 请求消息到目标主机&#xff0c;并等待目标主机回复一个 ICMP Echo 回应消息。如果目标主机接收到请求并且网络连接正常…

SpringCloud+Nacos项目集成Seata分布式事务

上一篇&#xff1a; 《 Seata-分布式事务介绍 》&#xff1a; 简单介绍了分布式事务的实现方式&#xff0c;以及详细讲述了Seata-AT模式的两阶段提交步骤流程。 完整示例项目代码地址&#xff1a; https://gitee.com/cnyunze/yz-seata.git Seata快速上手 安装教程Seata Server…

动手学深度学习笔记

1. 深度学习基础与MLP 1.1 框架&#xff1a; 线性回归&#xff1b; Softmax回归&#xff08;实际上用于分类问题&#xff09;&#xff1b; 感知机与多层感知机&#xff1b; 模型选择&#xff1b; 权重衰退&#xff08;weight decay&#xff09;&#xff1b; 丢弃法&…