“叮咚,叮咚……”,微信提示音一声接一声,声音是那么的频繁,有妖气,待俺去看一看。

这天刚吃完午饭,打开微信,发现我们的技术讨论组里有 100 多条未读消息,心想,是不是系统出问题了?怎么消息那么频繁?

于是迅速的爬楼,历时 1 秒 23,爬到楼顶,虚惊一场。了解消息的来龙去脉,大体意思:下午两点,研发一组在第二会议室开会,会议主题是:开发一个适合多个业务场景的分布式 ID 生成器。

到了两点,我们都来到第二会议室,开始了激烈讨论。

可能你们都知道,ID 是某个体系中唯一的编码,用来标识事务,比如:身份标识号、账号、唯一编码、专属号码。

ID 生成器又是什么呢?说白了就是生成 ID 工具,而在这里我们说的 ID 生成器,是一个服务(下同)。

为什么要开发分布式 ID 生成器?

原因大体有两点:

1. 许多业务系统需要对大量的订单、消息进行进行唯一标识,如:金融、电商、支付等。

2. 每个部门都开发一套 ID 生成器,总体上增加了工作量,增加公司的成本,不利于维护、管理。

分布式 ID 生成器有哪些要求?

1. 全局唯一性:不能出现重复的 ID 号,既然是唯一标识,这是最基本的要求。

2. 递增:比较低要求的条件为趋势递增,即保证下一个 ID 一定大于上一个 ID,而比较苛刻的要求是连续递增,如1001,1002,1003 等等。根据我们自己的业务,我们选择的是趋势递增(连续递增,会暴露出系统实际订单量)。

3. 高可用:ID 生成事关重大,一旦挂掉会导致整个系统崩溃,给公司带来巨大的损失,需要保证 ID 的正常、稳定生成。

4. 高性能:必须要在压测下表现良好,如果达不到要求则在高并发环境下依然会导致系统瘫痪。

5. 灵活多变:每个业务场景对 ID 的要求也各不相同,ID 生成要做到灵活多变可配置,尽可能多的满足需求。

针对以上那么多要求,我们到底要怎么做?看看前辈们都是怎么做的吧,目前业内有几种常见的解决方案。

一、UUID(用的最多)方案

UUID:通用唯一识别码(Universally Unique Identifier)的标准型式包含 32 个 16 进制数字,以连字号分为五段,形式为 8-4-4-4-12 的 36 个字符,示例:550e8400-e29b-41d4-a716-446655440000。

优点:本地生成,全局唯一,没有网络消耗。

缺点:UUID 太长,通常以 36 长度的字符串表示,对 MySQL 索引不利,如果作为数据库主键,在 InnoDB 引擎下,UUID 的无序性可能会引起数据位置频繁变动,严重影响性能;UUID 不能标识业务含义,可读性差;不满足递增要求;不够灵活。

二、Twitter 的雪花算法 SnowFlake 方案

这种方案大致来说是一种以划分命名空间(UUID 也算,由于比较常见,所以单独分析)来生成 ID 的一种算法,这种方案把 64-bit 分别划分成多段,分开来标示机器、时间等,比如在 snowflake 中的 64-bit 分别表示如下图所示:

第一位为未使用,接下来的 41 位为毫秒级时间(41 位的长度可以使用 69 年),然后是 5 位 datacenterId 和 5 位 workerId (10 位的长度最多支持部署 1024 个节点) ,最后 12 位是毫秒内的计数(12 位的计数顺序号支持每个节点每毫秒产生 4096 个 ID 序号)一共加起来刚好 64 位,为一个 Long 型 (转换成字符串长度为 18) 。示例:323893460451070160,323893460455264256。

优点:整体上按照时间自增排序;全局唯一;效率高。

缺点:有序,但不连续;不能同时满足多个系统对ID的需求,不够灵活。

三、数据库自增序列生成方案

以 MySQL 举例,利用给字段设置 auto_increment_increment和 auto_increment_offset 来保证 ID 自增,每次业务使用下列 SQL 读写 MySQL 得到 ID 号。

优点:简单,利用现有数据库系统的功能实现;成本小,有 DBA 专业维护;ID 号自增,可以实现一些对 ID 有特殊要求的业务。

缺点:强依赖 DB,当 DB 异常时整个系统不可用,属于致命问题;配置主从复制可以尽可能的增加可用性,但是数据一致性在特殊情况下难以保证;主从切换时的不一致可能会导致重复发号。

ID 发号性能瓶颈限制在单台 MySQL 的读写性能。

四、基于 ZooKeeper 和本地缓存的方案

使用 ZooKeeper 作为分段节点协调工具,每台服务器首先从 Zookeeper 缓存一段,如 1 – 2000 的 ID,此时 Zookeeper 上保存最大值 2000,每次获取的时候都会进行判断,如果 ID <= 2000,则更新本地的当前值,如果为 2001,则会将 Zookeeper 上的最大值更新至 4000,本地缓存段更新为 2001 - 4000,更新的时候使用分布式锁来实现。

优点:全局唯一,效率高。

缺点:维护成本较高,不能同时满足多个系统对 ID 的需求,不够灵活。

五、我们的方案

看了许多业内解决方案,有些方案已经基本上可以满足我们的需求,但是不够灵活。我们需要一种灵活、多变、可配置的方案。

经过一番讨论,我们选择了自己造轮子,核心思想:

使用数据库双 buffer 优化方案,每次从数据库拿取一个号段,当该号段下发 50% 时,如果下一个号段未更新,则启动另一个线程去提前更新新号段,当该号段已全部下发完成,且下一个号段准备完成,则切换到下一个号段,就这样一次一次的循环。

举个栗子:第一次取一个号段 100,000 - 110,000(1),当该号段下发到 105,000 时,去检查 120,000 号段是否更新,如果未更新,启动一个线程去更新号段120,000 - 130,000(2),当(1)号段已经下发完成,切换到(2)号段。

这样做的好处是不用频繁的访问数据库,保证了效率,在数据库宕机的一段时间内,服务仍然可生成 ID 。

这样就灵活了吗?

NO,这提高性能、效率的方式,要想做到灵活,我们需要设计一张数据库表,表中要区别不同的业务类型,可以设置ID 的前缀规则、后缀规则、长度、步长、最大 ID,保证 ID 灵活、多变、可配置。

数据库表结构设计:

key_name:区分业务

key_length:ID 长度

key_cache:缓存数量

key_prefix:前缀规则

key_subffix:后缀规则

key_digit:key 生成规则,支持 10 进制、36 进制或者 62 进制

数据库表测试数据:

测试服务器配置:Linux 2 核 4G 内存 X 2

步长设置为 1000,缓冲池设置为 1000,每秒大约可生成 16,675,231 次。

分布式 ID 生成器的方案还有很多,各有各的优点,需要你们根据自己的业务场景去选择,“不选贵的,只选对的”。

希望这篇文章对你们有帮助,欢迎关注我的公众号。

ID生成器之——别人家的方案and自家的方案的更多相关文章

  1. 一种基于Orleans的分布式Id生成方案

    基于Orleans的分布式Id生成方案,因Orleans的单实例.单线程模型,让这种实现变的简单,贴出一种实现,欢迎大家提出意见 public interface ISequenceNoGenerat ...

  2. 【php】mysql全局ID生成方案

    生产系统随着业务增长总会经历一个业务量由小变大的过程,可扩展性是考量数据库系统高可用性的一个重要指标;在单表/数据库数据量过大,更新量不断飙涨时,MySQL DBA往往会对业务系统提出sharding ...

  3. 分布式系统唯一ID生成方案汇总

    系统唯一ID是我们在设计一个系统的时候常常会遇见的问题,也常常为这个问题而纠结.生成ID的方法有很多,适应不同的场景.需求以及性能要求.所以有些比较复杂的系统会有多个ID生成的策略.下面就介绍一些常见 ...

  4. MySQL分库分表环境下全局ID生成方案 转

    在大型互联网应用中,随着用户数的增加,为了提高应用的性能,我们经常需要对数据库进行分库分表操作.在单表时代,我们可以完全依赖于数据库的自增ID来唯一标识一个用户或数据对象.但是当我们对数据库进行了分库 ...

  5. MySQL分库分表环境下全局ID生成方案

    在大型互联网应用中,随着用户数的增加,为了提高应用的性能,我们经常需要对数据库进行分库分表操作.在单表时代,我们可以完全依赖于数据库的自增ID来唯一标识一个用户或数据对象.但是当我们对数据库进行了分库 ...

  6. 【转】MySQL分库分表环境下全局ID生成方案

    转载一篇博客,里面有很多的知识和思想值得我们去思考. —————————————————————————————————————————————————————————————————————— 在大 ...

  7. 分布式唯一ID生成方案是什么样的?(转)

    一.前言 分布式系统中我们会对一些数据量大的业务进行分拆,如:用户表,订单表.因为数据量巨大一张表无法承接,就会对其进行分库分表. 但一旦涉及到分库分表,就会引申出分布式系统中唯一主键ID的生成问题, ...

  8. [转]分布式系统唯一ID生成方案汇总

    系统唯一ID是我们在设计一个系统的时候常常会遇见的问题,也常常为这个问题而纠结.生成ID的方法有很多,适应不同的场景.需求以及性能要求.所以有些比较复杂的系统会有多个ID生成的策略.下面就介绍一些常见 ...

  9. 分布式系统唯一ID生成方案汇总 转

    系统唯一ID是我们在设计一个系统的时候常常会遇见的问题,也常常为这个问题而纠结.生成ID的方法有很多,适应不同的场景.需求以及性能要求.所以有些比较复杂的系统会有多个ID生成的策略.下面就介绍一些常见 ...

随机推荐

  1. Java入门 - 高级教程 - 07.多线程

    原文地址:http://www.work100.net/training/java-multi-threading.html 更多教程:光束云 - 免费课程 多线程 序号 文内章节 视频 1 概述 2 ...

  2. 获取各类前几名数据的MYSQL写法

    前几天,某在培训的朋友问我一个问题:查询每门功课成绩最好的前两名该怎么写. 这个问题虽然听起来挺简单,但是很有意思,于是我就新建了一张如下的表: stuNo为学号,stuScore为分数,course ...

  3. BZOJ 3513 idiots

    题目传送门 分析: FFT一手统计两根棍子相加的方案 然后一个值2S可能会被同一根S自己乘自己得到 然后要减去 其次,A+B和B+A会被算成两种方案,所以还要除以2 然后不太好算合法的方案数,但是非法 ...

  4. 每天玩转3分钟 MyBatis-Plus - 5. 高级查询(三)(条件构造器)

    每天玩转3分钟 MyBatis-Plus - 1. 配置环境 每天玩转3分钟 MyBatis-Plus - 2. 普通查询 每天玩转3分钟 MyBatis-Plus - 3. 高级查询(一) 每天玩转 ...

  5. 深入理解Java虚拟机内存模型

    前言 本文中部分内容引用至<深入理解Java虚拟机:JVM高级特性与最佳实践(第2版)>第12章,如果有兴趣可自行深入阅读,文末放有书籍PDF版本连接. 一.物理机中的并发 物理机遇到的并 ...

  6. Vue methods,watch,computed的区别

    1. computed(计算属性) 计算属性的结果会被缓存,除非依赖的响应式属性变化才会重新计算.注意,如果某个依赖 (比如非响应式属性) 在该实例范畴之外,则计算属性是不会被更新的. eg: < ...

  7. ROS中3D机器人建模(五)

    一.创建一个差速驱动移动机器人模型 前面我们已经创建了一个7-DOF机械臂机器人模型,接下来我们将创建一个差速机器人模型,差速轮式机器人在机器人底盘的两端安装两个轮子, 整个底盘由一个或两个脚轮支撑. ...

  8. ROS中3D机器人建模(四)

    一.创建一个7-DOF机械臂机器人 创建一个名为seven_dof_arm.xacro的文件,写入相应的代码,其关节名称如下: bottom_joint shoulder_pan_joint shou ...

  9. c++中值传递,址传递,引用传递

    概念详解 1. 值传递: 形参是实参的拷贝,改变形参的值并不会影响外部实参的值. 从被调用函数的角度来说,值传递是单向的(实参->形参),参数的值只能传入,不能传出: 当函数内部需要修改参数,并 ...

  10. kvm命令

    查询:virsh -c     qemu:///system list    查看当前的虚拟系统 brctl show     列出当前所有的网桥接口virsh list   列出运行的虚拟机virs ...