48 分布式id的生成策略

1.UUID
1.UUID (Universally Unique Identifier),通用唯一识别码。UUID是基于当前时间、计数器(counter)和硬件标识(通常为无线网卡的MAC地址)等数据计算生成的。

UUID由以下几部分的组合:

1.当前日期和时间,UUID的第一个部分与时间有关,如果你在生成一个UUID之后,过几秒又生成一个UUID,则第一个部分不同,其余相同。
2.时钟序列。
3.全局唯一的IEEE机器识别号,如果有网卡,从网卡MAC地址获得,没有网卡以其他方式获得。

UUID 是由一组32位数的16进制数字所构成,以连字号分隔的五组来显示,形式为 8-4-4-4-12,总共有 36个字符(即三十二个英数字母和四个连字号)。例如:

aefbbd3a-9cc5-4655-8363-a2a43e6e6c80 
xxxxxxxx-xxxx-Mxxx-Nxxx-xxxxxxxxxxxx

在这里插入图片描述
如果需求是只保证唯一性,那么UUID也是可以使用的,但是按照上面的分布式id的要求, UUID其实是不能做成分布式id的,原因如下:

1.首先分布式id一般都会作为主键,但是安装mysql官方推荐主键要尽量越短越好,UUID每一个都很长,所以不是很推荐。
2.既然分布式id是主键,然后主键是包含索引的,然后mysql的索引是通过b+树来实现的,每一次新的UUID数据的插入,为了查询的优化,都会对索引底层的b+树进行修改,因为UUID数据是无序的,所以每一次UUID数据的插入都会对主键生成的b+树进行很大的修改,这一点很不好。
3.信息不安全:基于MAC地址生成UUID的算法可能会造成MAC地址泄露,这个漏洞曾被用于寻找梅丽莎病毒的制作者位置。
2.自增ID

这种方式在单个数据库的场景中是可以这样做的,但如果是在分库分表的环境下。直接利用单个数据库的自增肯定会出现问题。因为ID要唯一,但是分表分库后只能保证一个表中的ID的唯一,而不能保证整体的ID唯一。
在这里插入图片描述
上面的情况我们可以通过单独创建主键维护表来处理。
在这里插入图片描述

3.数据库多主模式

单点数据库方式存在明显的性能问题,可以对数据库进行优化,担心一个主节点挂掉没法使用,可以选择做双主模式集群,也就是两个MySQL实例都能单独生产自增的ID。

可以设置主键自增的步长从2开始:
在这里插入图片描述
这种在并发量比较高的情况下,如何保证扩展性其实会是一个问题。在高并发情况下无能为力。

4.号段模式

例如 (1,1000] 代表1000个ID,具体的业务服务将本号段,生成1~1000的自增ID并加载到内存。表结构如下:

CREATE TABLE id_generator ( 
id int(10) NOT NULL, 
max_id bigint(20) NOT NULL COMMENT '当前最大id', 
step int(20) NOT NULL COMMENT '号段的布长', 
biz_type int(20) NOT NULL COMMENT '业务类型', 
version int(20) NOT NULL COMMENT '版本号', 
PRIMARY KEY (`id`) )

字段说明:

biz_type :代表不同业务类型
max_id :当前最大的可用id
step :代表号段的长度
version :是一个乐观锁,每次都更新version,保证并发时数据的正确性

等这批号段ID用完,再次向数据库申请新号段,对maxid字段做一次update操作,update maxid= max_id + step,update成功则说明新号段获取成功,新的号段范围是(maxid ,maxid +step]
在这里插入图片描述
由于多业务端可能同时操作,所以采用版本号version乐观锁方式更新,这种分布式ID生成方式不强依赖于数据库,不会频繁的访问数据库,对数据库的压力小很多。但同样也会存在一些缺点比如:服务器重启,单点故障会造成ID不连续。

5.Redis

基于全局唯一ID的特性,通过Redis的INCR命令来生成全局唯一ID。
在这里插入图片描述
同样使用Redis也有对应的缺点:

1.ID 生成的持久化问题,如果Redis宕机了怎么进行恢复
2.节点宕机问题

针对故障问题我们可以通过Redis集群来处理,比如我们有三个Redis的Master节点。可以初始化每台Redis的值分别是1,2,3,然后分别把分布式ID的KEY用Hash Tags固定每一个master节点,步长就是master节点的个数。各个Redis生成的ID为:

节点A:1,4,7
节点B:2,5,8
节点C:3,6,9

优点:

1.不依赖于数据库,灵活方便,且性能优于数据库
2.数字ID有序,对分页处理和排序都很友好
3.防止了Redis的单机故障

缺点:

1.如果没有Redis数据库,需要安装配置,增加复杂度
2.集群节点确定是3个后,后面调整不是很友好
public class RedisDistributedId {@Autowiredprivate StringRedisTemplate redisTemplate;private static final long BEGIN_TIMESTAMP = 1659312000l;/*** 生成分布式ID* 符号位    时间戳[31位]  自增序号【32位】* @param item* @return*/public long nextId(String item){// 1.生成时间戳LocalDateTime now = LocalDateTime.now();// 格林威治时间差long nowSecond = now.toEpochSecond(ZoneOffset.UTC);// 我们需要获取的 时间戳 信息long timestamp = nowSecond - BEGIN_TIMESTAMP;// 2.生成序号 --》 从Redis中获取// 当前当前的日期String date = now.format(DateTimeFormatter.ofPattern("yyyy:MM:dd"));// 获取对应的自增的序号Long increment = redisTemplate.opsForValue().increment("id:" + item + ":" + date);return timestamp << 32 | increment;}
}
6.雪花算法

Snowflake,雪花算法是有Twitter开源的分布式ID生成算法,以划分命名空间的方式将64bit位分割成了多个部分,每个部分都有具体的不同含义,在Java中64Bit位的整数是Long类型,所以在Java中Snowflake算法生成的ID就是long来存储的。具体如下:
在这里插入图片描述
第一部分:占用1bit,第一位为符号位,不适用
第二部分:41位的时间戳,41bit位可以表示241个数,每个数代表的是毫秒,那么雪花算法的时间年限是(241)/(1000×60×60×24×365)=69年
第三部分:10bit表示是机器数,即 2^ 10 = 1024台机器,通常不会部署这么多机器
第四部分:12bit位是自增序列,可以表示2^12=4096个数,一秒内可以生成4096个ID

public class SnowflakeIdWorker {/*** 开始时间截 (2020-11-03,一旦确定不可更改,否则时间被回调,或者改变,可能会造成id重复或冲突)*/private final long twepoch = 1604374294980L;/*** 机器id所占的位数*/private final long workerIdBits = 5L;/*** 数据标识id所占的位数*/private final long datacenterIdBits = 5L;/*** 支持的最大机器id,结果是31 (这个移位算法可以很快的计算出几位二进制数所能表示的最大十进制数)*/private final long maxWorkerId = -1L ^ (-1L << workerIdBits);/*** 支持的最大数据标识id,结果是31*/private final long maxDatacenterId = -1L ^ (-1L << 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位(5+5+12)*/private final long timestampLeftShift = sequenceBits + workerIdBits + datacenterIdBits;/*** 生成序列的掩码,这里为4095 (0b111111111111=0xfff=4095)*/private final long sequenceMask = -1L ^ (-1L << sequenceBits);/*** 工作机器ID(0~31)*/private long workerId;/*** 数据中心ID(0~31)*/private long datacenterId;/*** 毫秒内序列(0~4095)*/private long sequence = 0L;/*** 上次生成ID的时间截*/private long lastTimestamp = -1L;//==============================Constructors=====================================/*** 构造函数**/public SnowflakeIdWorker() {this.workerId = 0L;this.datacenterId = 0L;}/*** 构造函数** @param workerId     工作ID (0~31)* @param datacenterId 数据中心ID (0~31)*/public SnowflakeIdWorker(long workerId, long datacenterId) {if (workerId > maxWorkerId || workerId < 0) {throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));}if (datacenterId > maxDatacenterId || datacenterId < 0) {throw new IllegalArgumentException(String.format("datacenter Id can't be greater than %d or less than 0", maxDatacenterId));}this.workerId = workerId;this.datacenterId = datacenterId;}// ==============================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 - twepoch) << timestampLeftShift) //| (datacenterId << datacenterIdShift) //| (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();}/*** 随机id生成,使用雪花算法** @return*/public static String getSnowId() {SnowflakeIdWorker sf = new SnowflakeIdWorker();String id = String.valueOf(sf.nextId());return id;}//=========================================Test=========================================/*** 测试*/public static void main(String[] args) {SnowflakeIdWorker idWorker = new SnowflakeIdWorker(0, 0);for (int i = 0; i < 1000; i++) {long id = idWorker.nextId();System.out.println(id);}}
}

实际生产环境中应该怎么来应用雪花算法来实现分布式ID。
在这里插入图片描述

7.百度(Uidgenerator)
1.源码地址:https://github.com/baidu/uid-generator
2.中文文档地址:https://github.com/baidu/uid-generator/blob/master/README.zh_cn.md

UidGenerator是百度开源的Java语言实现,基于Snowflake算法的唯一ID生成器。它是分布式的,并克服了雪花算法的并发限制。单个实例的QPS能超过6000000。需要的环境:JDK8+,MySQL(用于分配WorkerId)。

百度的Uidgenerator对结构做了部分的调整,具体如下:
在这里插入图片描述

8.美团(Leaf)

由美团开发,开源项目链接:https://github.com/Meituan-Dianping/Leaf

Leaf同时支持号段模式和snowflake算法模式,可以切换使用。ID号码是趋势递增的8byte的64位数字,满足上述数据库存储的主键要求。

Leaf的snowflake模式依赖于ZooKeeper,不同于原始snowflake算法也主要是在workId的生成上,Leaf中workId是基于ZooKeeper的顺序Id来生成的,每个应用在使用Leaf-snowflake时,启动时都会都在Zookeeper中生成一个顺序Id,相当于一台机器对应一个顺序节点,也就是一个workId。

Leaf的号段模式是对直接用数据库自增ID充当分布式ID的一种优化,减少对数据库的频率操作。相当于从数据库批量的获取自增ID,每次从数据库取出一个号段范围,例如 (1,1000] 代表1000个ID,业务服务将号段在本地生成1~1000的自增ID并加载到内存.。

特性:

1.全局唯一,绝对不会出现重复的ID,且ID整体趋势递增。
2.高可用,服务完全基于分布式架构,即使MySQL宕机,也能容忍一段时间的数据库不可用。
3.高并发低延时,在CentOS 4C8G的虚拟机上,远程调用QPS可达5W+TP991ms内。
4.接入简单,直接通过公司RPC服务或者HTTP调用即可接入。

Leaf采用双buffer的方式,它的服务内部有两个号段缓存区segment。当前号段已消耗10%时,还没能拿到下一个号段,则会另启一个更新线程去更新下一个号段。

简而言之就是Leaf保证了总是会多缓存两个号段,即便哪一时刻数据库挂了,也会保证发号服务可以正常工作一段时间。

<dependency><groupId>com.sankuai.inf.leaf</groupId><artifactId>leaf-boot-starter</artifactId><version>1.0.1-RELEASE</version></dependency><dependency><groupId>org.apache.curator</groupId><artifactId>curator-recipes</artifactId><version>2.6.0</version><exclusions><exclusion><groupId>log4j</groupId><artifactId>log4j</artifactId></exclusion></exclusions></dependency>

属性文件配置:

 leaf.name=com.boge.vipleaf.segment.enable=falseleaf.segment.url=leaf.segment.username=leaf.segment.password=leaf.snowflake.enable=trueleaf.snowflake.address=leaf.snowflake.port=leaf.snowflake.address=192.168.56.100leaf.snowflake.port=2181

放开注解:
在这里插入图片描述
注意你还需要启动一个Zookeeper服务。
测试代码:

 @SpringBootTestclass DistributedIdsVipApplicationTests {@AutowiredRedisDistributedId redisDistributedId;@Autowiredprivate SnowflakeService snowflakeService;@Test
void contextLoads() {Result abc = snowflakeService.getId("abc");System.out.println(abc.getId());System.out.println(Long.toBinaryString(abc.getId()));
}}

在这里插入图片描述

9.滴滴(TinyID)

由滴滴开发,开源项目链接:https://github.com/didi/tinyid

Tinyid是在美团(Leaf)的leaf-segment算法基础上升级而来,不仅支持了数据库多主节点模式,还提供了tinyid-client客户端的接入方式,使用起来更加方便。但和美团(Leaf)不同的是,Tinyid只支持号段一种模式不支持雪花模式。Tinyid提供了两种调用方式,一种基于Tinyid-server提供的http方式,另一种Tinyid-client客户端方式。每个服务获取一个号段(1000,2000]、(2000,3000]、(3000,4000]

特性:

1.全局唯一的longID
2.趋势递增的id
3.提供 http 和 java-client 方式接入
4.支持批量获取ID
5.支持生成1,3,5,7,9...序列的ID
6.支持多个db的配置

适用场景:只关心ID是数字,趋势递增的系统,可以容忍ID不连续,可以容忍ID的浪费
不适用场景:像类似于订单ID的业务,因生成的ID大部分是连续的,容易被扫库、或者推算出订单量等信息

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

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

相关文章

RabbitMQ脑裂处理

脑裂现象&#xff1a; Network partition detected Mnesia reports that this RabbitMQ cluster has experienced a network partition. There is a risk of losing data. Please read RabbitMQ documentation about network partitions and the possible solutions. 转载请在文…

缓存击穿,缓存穿透,缓存雪崩的原因和解决方案?

缓存穿透 缓存穿透是指缓存和数据库中都没有的数据&#xff0c;导致所有的请求都落到数据库上&#xff0c;造成数据库短时间内承 受大量请求而崩掉。 解决方案&#xff1a; 接口层增加校验&#xff0c;如用户鉴权校验&#xff0c;id做基础校验&#xff0c;id<0的直接拦截…

zotero使用gpt

zotero使用gpt 下载 zotero下载&#xff1a;https://www.zotero.org/download/ 插件下载&#xff1a;https://github.com/MuiseDestiny/zotero-gpt?tabreadme-ov-file 插件安装 zotero中选择 工具->添加组件 选择右上角的齿轮&#xff0c;选择Install add-on from fil…

NeRF算法原理总结概述

简介 本文旨在对NeRF算法进行总结。论文翻译见博客&#xff1a;《NeRF算法论文解析与翻译》 参考链接&#xff1a; 神经网络辐射场NeRF、实时NeRF Baking、有向距离场SDF、占用网络Occupancy、NeRF 自动驾驶 NeRF详解 NeRF入门之体渲染 (Volume Rendering) NeRF中的位置编码 …

C++力扣题目501--二叉搜索树中的众数

给你一个含重复值的二叉搜索树&#xff08;BST&#xff09;的根节点 root &#xff0c;找出并返回 BST 中的所有 众数&#xff08;即&#xff0c;出现频率最高的元素&#xff09;。 如果树中有不止一个众数&#xff0c;可以按 任意顺序 返回。 假定 BST 满足如下定义&#xf…

中国X射线管行业研究与投资预测报告(2024版)

内容介绍&#xff1a; X射线管俗称球管&#xff0c;是X射线机的核心部件。X射线管在医学上可用于诊断和治疗&#xff0c;在工业技术方面可用于材料的无损检测、结构分析、光谱分析和底片曝光等。 目前&#xff0c;X射线管的壳体材料主要采用玻璃、陶瓷、金属材料这三种材质。玻…

项目中Ant Design Pro业务问题解决方案

ProTable实现多选反显筛选项多级关联选择 import {forwardRef,useImperativeHandle,useEffect,useRef,useReducer, } from "react"; import { Drawer, Space, Button, message } from "antd"; import * as PC from "ant-design/pro-components";…

Berstagram ——模拟

Polycarp最近注册了一个新的社交网络Berstagram。他立即在那里发表了 n 篇帖子。他给所有帖子分配了从 1 到 n 的数字&#xff0c;并逐一发布。所以&#xff0c;就在发布Polycarp的新闻推送后&#xff0c;包含了从 1 到 n 的帖子——最高的帖子编号为 1&#xff0c;下一个帖子编…

Snakemake:初探

我已经安装了mamba&#xff08;没有的话可以这样试试&#xff09; curl -L https://github.com/conda-forge/miniforge/releases/latest/download/Mambaforge-Linux-x86_64.sh -o Mambaforge-Linux-x86_64.sh bash Mambaforge-Linux-x86_64.sh 安装snakemake Setup - Snakem…

PHP项目添加分布式锁,这里是ThinkPHP8框架实现分布式锁

背景&#xff1a;公司旧项目&#xff0c;最初访问量不多&#xff0c;单机部署的。后来&#xff0c;访问量上来了&#xff0c;有阵子很卡&#xff0c;公司决定横向扩展&#xff0c;后端代码部署了三台服务器。部署调整后&#xff0c;有用户反馈&#xff0c;一个订单支付了三次。…

【Java高级应用:深入探索Java编程的强大功能,JVM 类加载机制, JVM 内存模型,垃圾回收机制,JVM 字节码执行,异常处理机制】

本人详解 作者:王文峰,参加过 CSDN 2020年度博客之星,《Java王大师王天师》 公众号:JAVA开发王大师,专注于天道酬勤的 Java 开发问题中国国学、传统文化和代码爱好者的程序人生,期待你的关注和支持!本人外号:神秘小峯 山峯 转载说明:务必注明来源(注明:作者:王文峰…

网络原理--http

目录 一、 DNS&#xff08;应用层协议&#xff09; 1、域名概念 2、维护ip地址和域名之间的映射&#xff08;域名解析系统&#xff09; 3、DNS系统&#xff08;服务器&#xff09; 4、如何解决DNS服务器高并发问题 二、HTTP&#xff08;应用层协议&#xff09; 1、htt…

开源云真机平台-Sonic平台-python自定义脚本-config.json方式实现全局配置参数的读写操作

【主要功能】 config.json方式实现全局配置参数的读写操作 使用python实现以下功能&#xff1a; 1、使用将接口获取的变量值&#xff0c;写入到当前目录下的config文件中&#xff0c;如delayTime10&#xff1b; 2、读取当前目录下的config文件中&#xff0c;特定变量的值&…

修改默认负载均衡策略(Ribbon)

修改方式 配置类配置文件 配置类 增加配置类&#xff1a;配置类必须拥有 Configuration 且不能在 ComponentScan 包下 package com.learning.springcloud.config; import com.alibaba.cloud.nacos.ribbon.NacosRule; import com.netflix.loadbalancer.IRule; import org.sp…

vue3深入组件:组件事件

触发与监听事件 在组件的模板表达式中&#xff0c;可以直接使用 $emit 方法触发自定义事件&#xff1a; <button click"$emit(someEvent)">click me</button>父组件可以通过 v-on (缩写为 ) 来监听事件&#xff1a; <MyComponent some-event"…

fs 模块

1. fs写文件 1.1 普通写入 const fs require(fs) // 异步写入 fs.writeFile(./hello.txt,刚刚我错过的大雨,err>{console.log(err); }) // 同步写入 fs.writeFileSync(./text.txt,hello boy!)1.2 文件的追加写入 const fs require(fs) fs.appendFile(./text.txt,\r\r\r…

【华为OD机试真题2023CD卷 JAVAJS】高效货运

华为OD2023(C&D卷)机试题库全覆盖,刷题指南点这里 高效货运 知识点递归循环 时间限制:1s 空间限制:32MB 限定语言:不限 题目描述: 1.老李是货运公司承运人,老李的货车额定载货重量为wt 2.现有两种货物,货物A单件重量为wa,单件运费利润为pa,货物B单件重量为wb,单…

0115qt聊天室客户端+数据库

#ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QSqlDatabase>//数据库管理类 #include <QSqlQuery>//执行sql语句类 #include <QSqlRecord>//数据库记录类 #include <QSqlError>//数据库错误类 #include <QMessageBox>/…

win10安装ssh服务

前置条件&#xff1a; 远程虚拟机&#xff0c;防火墙关闭&#xff0c;本地主机与虚拟机互相可以ping通 虚拟机是win10专业版本 操作步骤&#xff1a; 1、搜索框搜索“Windows PowerShell”并以管理员身份运行 2、输入如下代码&#xff0c;检查本地是否有ssh服务&#xff0c…