分布式唯一id的7种方案

背景

为什么需要使用分布式唯一id?
如果我们的系统是单体的,数据库是单库,那无所谓,怎么搞都行。
但是如果系统是多系统,如果id是和业务相关,由各个系统生成的情况下,那每个主机生成的主键id就是不可控的,多个主机就有可能会造成主键冲突的问题。

方案

1、数据库自增

1024表,不是依赖每一张表的自增主键,不同的表都从1开始累加id

专门搞一个库,搞一个表,专门用于生成全局唯一id,insert into插入一条数据,他会返回给你一个全局唯一id,然后你把这个id设置给数据,插入分表后的1024张表里去,全局唯一的

优点:超简单,落实起来非常方便,公司有一个统一的库和表,专门用于生成id;或者你自己的系统的库里你专门弄一张表,用来生成id

缺点:单库单表,并发抗不住,一旦达到每秒几千的高并发;不停的在表里插入数据获取id,表数据会越来越多,还得定期清理,很麻烦

适用场景:分库分表是因为数据量大,但是低并发低负载,而且数据库单机有高可用问题,必须上高可用方案,另外是单表数据一直增长也是个问题,一般不会直接投入生产,投入生产环境的时候会用下面说的flickr的数据库唯一id生成方案

2、UUID

优点:本地生成,没有所谓的并发压力
缺点:太长了!作为主键绝对是不靠谱的!数据库频繁页分裂问题!

适用场景:除数据库主键之外的其他唯一键场景,都适合,这个方案一般不考虑在分布式唯一ID生成里,在我们的主题里,其实可以忽略

3、Twitter开源的Snowflake方案

核心思想:64个bit位,41位放时间(最多使用69年),10位放机器标识(最多把snowflake程序部署在1024台机器上),12位放序号(每毫秒,每台机器,可以顺序生成4096个ID),最高位1个bit是0

snowflake程序分布式部署在多台机器上,每台机器生成的每个ID,都是这一毫秒、机器id、序号,每台机器每毫秒最多4096个ID,绝对够用了,分布式方案可以抗高并发,大不了加机器,最多1024台机器,纯基于内存生成,性能很高

优点:高性能,高并发,分布式,可伸缩,最多扩展1024台机器,ID绝对够用

缺点:光是开源算法还不用,还得考虑时钟回拨等一系列问题,如果要解决那堆问题,需要开发很多机制,开发完了还得独立部署,有独立部署和维护的成本

适用场景:中大型公司,有高并发生成唯一ID场景,基于snowflake算法自研,加入时钟回拨解决方案,多机房方案,等等,各种生产方案,有人力去维护,有少数大厂采用了这个方案,可以作为生产级方案,但是需要解决很多问题

4、Redis自增机制

核心思想:Redis单线程,绝对有序自增,incrby;集群部署,比如5台机器,那么每台机器的初始值依次为1、2、3、4、5,每台机器的自增步长是5,第1台机器就是1、6、11、16、21,第2台机器就是2、7、12、17、22,以此类推,直到第5台机器就是5、10、15、20、25

优点:不用额外开发,一般公司都提供redis集群,直接用就行

缺点:客户端需要自己封装,基于Jedis去封装,客户端里需要写死Redis机器数量,每次获取1个ID,都是找到一台机器,然后按步长去incrby,接着返回给系统;而且扩容麻烦,如果5台机器抗不住并发了怎么办?扩容的时候加机器,客户端需要修改代码,或者基于动态感知,这其实也有开发成本,另外扩容的时候,步长就会改变,那之前的ID怎么办?都得重新洗掉,全部从头开始计算,极为麻烦

适用场景:鉴于他的缺点,一般不用redis集群玩自增主键生成;分库分表了,然后每秒在万左右的高并发,但是可预见的不会达到几万以及十万级的并发,那么此时可以用Redis单机去生成自增主键,避免redis集群扩容的步长改变问题;但是还得部署Redis主从同步+哨兵高可用,可是主从同步是异步的,有id重复问题,所以最终生产一般不用

5、基于时间+业务id的组合

核心思想:比如打车软件,可以用时间戳+起点编号+车牌号作为一个id,业务组合上是不会有重复的;比如电商订单,可以用时间戳+用户id,一个用户在1毫秒内一般最多就下一个订单,一般不会重复,除非用户基于程序刷单,否则手点的情况下,这个组合id一般没问题,还可以加个下单渠道、第一个商品id等其他业务id组合起来

优点:实现简单,没额外成本,没并发之类的扩容问题

缺点:有的业务场景(比如订单之类的),还可以用这种方案,但是有的业务场景可能根本没法通过业务来组合,而且始终担心有重复问题

适用场景:很多大厂都用这个方案,做订单编号这些,但是分库分表不光是订单,还有什么用户、账号以及各种其他的业务场景,所以部分适用于生产

6、flickr(雅虎旗下的图片分享平台)公司的方案

CREATE TABLE uid_sequence (
id bigint(20) unsigned NOT NULL auto_increment,
stub char(1) NOT NULL default ‘’,
PRIMARY KEY (id),
UNIQUE KEY stub (stub)
) ENGINE=MyISAM;

REPLACE INTO uid_sequence (stub) VALUES (‘test’);
SELECT LAST_INSERT_ID();

replace into语法替代insert into,避免表行数过大,一张表就一行数据,然后再select获取这个表的最新id,last_insert_id()函数是connection级别的,就你这个连接的最近insert生成的id,多个客户端之间没影响

当然,其实也可以优化成这样,就是每次你一台机器要申请一个唯一id,你就REPLACE INTO uid_sequence (stub) VALUES (‘192.168.31.226’),用你自己机器的ip地址去replace into,那么就你自己机器会有id不停自增,完了用select id from table where stub=机器地址,就可以了

最多如果你要考虑到多线程并发问题,那么就在机器地址后加入线程编号,这样一台机器的不同线程,都是对自己的id在自增

这个方案本质跟第一个方案没区别,唯一优化就是用replace into替代了insert into,避免表数据量过大,缺点也在于数据库并发能力不高,所以适用场景,就是分库分表的时候,低并发,用这个方案生成唯一id,低并发场景下可以用于生产

而且一般会部署数据库高可用方案,两个库设置不同的起始位置和步长,分别是1、3、5,以及2、4、6

7、基于flickr方案的高并发优化

有一种变种方案,是基于flickr方案的高并发优化,他核心问题在于每一次生成id都得找数据库,所以这就是并发瓶颈,所以这里可以把数据库优化为号段,而不是id号,什么意思呢?一起来看看

每台机器都引入一个自己封装的客户端,只要一旦服务启动,就直接采用flickr方案获取一个id,但是他仅仅代表的是一个号段,什么意思呢?比如说,一个服务启动,通过flickr方案的replace into拿到一个id,假设是1吧

此时你的号段可以配置为一个号段是10000个id号,那么此时你这个号段的起始id就是1 * 10000,然后可以把起始id设置到AtomicLong里去,还可以保存一下号段的最大id,也就是(n + 1)* 10000,就是2 * 10000,20000

所以这个号段的id就是[10000, 20000,20000是不包含在内的

接着服务里如果要获取唯一id,直接找你封装的客户端,每次拿一个id,就是AtomicLong.incrementAndGet(),直接原子递增,这样你大部分的id获取,都是在内存里通过号段内递增实现的

高并发问题,解决了!!数据库仅仅用于维护号段罢了

如果拿到了号段里最大id,此时对获取id的请求得阻塞住,只要拿到的id大于等于了最大id,请求全部自己陷入阻塞,比如大家都去while循环阻塞,过一会儿再次获取id,跟最大id比较

发号器客户端的线程,定时轮询,一旦发现这个问题,此时就重新利用flickr方案获取一个号段,再次设置AtomicLong里的初始id以及更新最大id,在这个过程中别的任何一个线程来获取id都会发现AtomicLong自增值比最大id是大的

即使是发号器客户端线程,刚刚设置了AtomicLong的值,然后还没设置volatile的最大id值,此时别的线程在while循环过程中获取了id,AotmicLong自增值一定大于之前的最大id值,也会继续陷入阻塞的

只有当发号器客户端线程更新了volatile最大id值之后,其他线程才会在while循环之后,发现AtomicLong自增值是小于最大id值的,此时就可以继续工作了,这种情况通常是很少的,所以大部分情况下,各个服务都是基于本地的号段在内存里获取id,而且全局上还是唯一的,没有高并发问题,数据库的并发也是很低的

这个方案的唯一缺点就是,每次重启服务,就会浪费一个号段里还没自增到的大量id,重启后又是新的号段了,但是如果要优化,可以在spring销毁事件里,发号器内部设置一个volatile标识,不允许获取id了,接着把AtomicLong的值持久化到本地磁盘,下次服务重启后直接从本地磁盘里读取,就不会浪费了

其实这个优化以后的方案,就可以投入生产了,确实也有个别大厂是这么做的,也运行的很好。如果一定要说这个方案有什么弊端,那就是,归根结底,还是有一个数据库这么个外部依赖,其实如果方案真做好了,你还得考虑数据库的高可用方案这些东西,就是牵扯到了外部依赖,就容易做的很重

另外一个问题,就是对于这个方案,你还得去做步长的配置,那么到底允许多长的步长呢?是否允许用户自己配置呢?如果不允许,你固定一个步长,那个步长会不会在一些特殊高并发场景下,比如你1000作为步长,1000个号瞬间被秒光,一个服务每秒都得请求一次数据库获取新的号段,此时你有上千个服务实例,数据库不还是抗不住?

所以,这个方案适合一些没有特殊超高并发的场景,而且扩展性和灵活性不是很强,总是让人担心他的号段步长会出一些问题,但是在一些普通场景下,其实一般可能也没什么问题,所以有普通高并发场景的生产环境,还是可用的

基于数据库的方案就是flickr方案以及flickr高并发优化方案,但是没有snowflake生产级方案那么具备普适性,snowflake方案不涉及什么号段问题,也不会额外依赖数据库,不需要考虑数据库高可用之类的,他自己就是peer-to-peer的一个集群架构,随时可以扩容

时间戳+业务id,相当好用,推荐第一选择是他,能用时间戳+业务id的,就别搞分布式id生成,如果不行的,再考虑flickr方案或者snowflake方案

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

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

相关文章

Linux网络 -- TCP FIN包发送超时时间设置

使用 SO_LINGER 选项控制FIN包发送的超时时间 技术背景 在TCP连接中,当应用程序调用 close 或 shutdown 函数关闭一个socket时,操作系统内核会发送一个FIN包以通知对端连接即将关闭。默认情况下,内核会等待所有未发送的数据发送完毕后再发送…

使用崖山YMP 迁移 Oracle/MySQL 至YashanDB 23.2 验证测试

前言 首届YashanDB「迁移体验官」开放后,陆续收到「体验官」们的投稿,小崖在此把优秀的投稿文章分享给大家~今天分享的用户文章是《使用崖山YMP 迁移 Oracle/MySQL 至YashanDB 23.2 验证测试》(作者:尚雷)&#xff0c…

PHP宠物店萌宠小程序系统源码

🐾萌宠生活新方式🐾 🏡【一键直达萌宠世界】 你是否也梦想着拥有一家随时能“云撸猫”、“云吸狗”的神奇小店?现在,“宠物店萌宠小程序”就是你的秘密花园!🌟只需轻轻一点,就能瞬…

什么是股指期货交割?股指期货交割的例子

股指期货交割是指在股指期货合约到期时,投资者需要按照合约规定完成的结算过程。与一般的商品期货、国债期货或外汇期货不同,股指期货采用的是现金交割方式。 股指期货交割的方式 【现金交割】股指期货的交割不需要实际交割一篮子股票指数成分股。相反…

(社恐福音)用python写一个定时弹窗功能

背景 背景是换了一个工作,需要点外卖了 写代码太认真的时候又经常忘记 这时候就需要一个闹钟 手机闹钟声音太大 会影响他人 所以用python 写一个弹窗功能,只影响自己 效果图 原理 管理列表和定时功能通过windows自带的计划完成 python程序不用占用后台…

js中! 、!!、?.、??、??=的用法及使用场景

js中! 、 !. 、!、?.、.?、??、??的用法及使用场景 !!!?.??????、?? 区别 !. (ts)注意 ! (非空断言符号) 用于取反一个布尔值或将一个值转换为布尔类型并取反 const a true; const b false; const value !a; // false const value !…

https和http区别

1、安全性 HTTP信息是明文传输,而HTTPS则通过SSL/TLS协议进行加密传输,确保数据传输的安全性。HTTPS可以验证服务器身份,防止中间人攻击,保护数据的完整性和保密性。 2、端口号 HTTP默认使用80端口,而HTTPS默认使用…

最佳实践:设计思维:“6+1”迭代过程的创新实践丨IDCF

谢志萌 研发效能(DevOps)工程师(中级)认证学员 一、前言 “有效的Devops关键在于专注于人员和文化,而不仅仅是工具和技术。” -John Willis 随着数字化转型的加速,组织越来越依赖于快速、高效的软件交…

7月18日学习打卡,数据结构堆

hello大家好呀,本博客目的在于记录暑假学习打卡,后续会整理成一个专栏,主要打算在暑假学习完数据结构,因此会发一些相关的数据结构实现的博客和一些刷的题,个人学习使用,也希望大家多多支持,有不…

ARM架构(二)—— arm v7/v8/v9寄存器介绍

1、ARM v7寄存器 1.1 通用寄存器 V7 V8开始 FIQ个IRQ优先级一样, 通用寄存器:31个 1.2 程序状态寄存器 CPSR是程序状态毒存器,保存条件标志位,中断禁止位,当前处理器模式等控制和状态位。每种异常模式下还存在SPSR&…

《系统架构设计师教程(第2版)》第12章-信息系统架构设计理论与实践-02-信息系统架构

文章目录 1. 概述1.1 信息系统架构(ISA)1.2 架构风格 2. 信息系统架构分类2.1 信息系统物理结构2.1.1 集中式结构2.1.2 分布式结构 2.2 信息系统的逻辑结构1)横向综合2)纵向综合3)纵横综合 3. 信息系统架构的一般原理4…

Pandas教程:近万字讲解在Pandas中如何操作Excel

目录 1. 安装与配置 2. 读取Excel文件 2.1 基本用法 2.2 指定工作表 2.3 指定单元格范围 3. 数据检查与预处理 3.1 查看数据的基本信息 3.2 数据类型检查与转换 3.3 检查缺失值 3.4 处理缺失值 4. 数据清洗与转换 4.1 重命名列 4.2 删除重复数据 4.3 数据替换 4…

Android使用ANativeWindow更新surfaceView内容最简Demo

SurfaceView简介 SurfaceView对比View的区别 安卓的普通VIew,都依赖于当前Activity的Window的surface,这个surface用于承载view树从底到顶绘制出来的所有内容,因此任何一个view需要更新时,都需要把所有view中底到顶进行更新,即使使…

解决:Linux上SVN 1.12版本以上无法直接存储明文密码

问题:今天在Linux机器上安装了SVN,作为客户端使用,首次执行SVN相关操作,输入账号密码信息后,后面再执行SVN相关操作(比如"svn update")还是每次都需要输入密码。 回想以前在首次输入…

Python进阶(4)--正则表达式

正则表达式 在Python中,正则表达式(Regular Expression,简称Regex)是一种强大的文本处理工具,它允许你使用一种特殊的语法来匹配、查找、替换字符串中的文本。 在这之前,还记得之前我们是通过什么方法分割…

实习问题总结

为什么使用GRPC作为通信方式,对比如REST好处在哪里? 使用protobuf作为序列化格式,以二进制进行传输,减小了数据包的大小,效率高 proto强类型定义、支持复杂数据类型,编译运行可以进行严格的数据验证&#…

[论文笔记] pai-megatron-patch Qwen2-CT 长文本rope改yarn

更改: # Copyright (c) 2024 Alibaba PAI and Nvidia Megatron-LM Team. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. # You may obtain a copy of the License a…

【系统架构设计】数据库系统(二)

数据库系统(二) 数据库模式与范式数据库设计数据库设计的方法数据库设计的基本步骤 事务管理并发控制故障和恢复 备份与恢复分布式数据库系统数据仓库数据挖掘NoSQL大数据 数据库模式与范式 数据库设计 数据库设计的方法 目前已有的数据库设计方法可分…

element UI :el-table横向列内容超出宽度,滚动条不显示问题

是否能解决你问题的前提 **看到这篇文章的解决问题的方案之前,请先回忆你是否在项目中的全局样式或者私有组件中去单独设置过滚动条样式。如果有 请继续往下看:**单独设置过滚动条样式代码实例: ::-webkit-scrollbar {/*滚动条整体样式*/wi…

layui 让table里的下拉框不被遮挡

记录:layui 让table里的下拉框不被遮挡 /* 这个是让table里的下拉框不被遮挡 */ .goods_table .layui-select-title,.goods_table .layui-select-title input{line-height: 28px;height: 28px; }.goods_table .layui-table-cell {overflow: visible !important; }.…