文章目录

  • 分布式ID
  • 数据库
    • 自增ID
    • 多主模式
  • 号段模式
  • 雪花算法
  • Redis
  • 总结

分布式ID

ID是数据的唯一标识,传统的做法是使用数据库的自增ID,但是随着业务规模的不断发展,数据量将越来越大,于是需要进行分库分表,而分表后,每个表中的ID都会按自己的节奏进行自增,很有可能出现ID冲突的情况。这时就需要一个单独的机制来负责生成一个全局唯一的ID。这个ID也可以叫做分布式ID

对于一个合格的分布式ID,它应该满足以下几项条件

  • 全局唯一
  • 高可用
  • 高性能
  • 趋势递增
  • 方便接入

下面就介绍几种常见的分布式ID生成方案

数据库

自增ID

我们可以利用数据库的auto_increment自增ID来生成分布式ID,只需要一个数据库实例即可完成。

建表如下

CREATE DATABASE `SEQID`;CREATE TABLE SEQID.SEQUENCE_ID (id bigint(20) unsigned NOT NULL auto_increment, stub char(10) NOT NULL default '',PRIMARY KEY (id),UNIQUE KEY stub (stub)
) ENGINE=MyISAM;

我们通过往该表中插入数据,并获取到自增的主键id。为了考虑到并发的安全 ,我们可以通过事务来完成这个操作。

begin;
insert into SEQUENCE_ID(value) VALUES ('values');
select last_insert_id();
commit;

我们可以看出,这种依赖单点的方法虽然实现起来简单,但是这种依赖单点的方法并不可靠,因为Mysql并不能很好的支持高并发,当请求ID量特别大的时候就会因为单点的宕机而影响到整个业务系统。

多主模式

既然单点不可靠,那么我们可以考虑使用集群的方式来解决这个问题。考虑到单个主节点可能会因为宕机而没有将数据同步到从节点,可能会导致ID重复的情况,因此我们可以考虑使用多主集群

为了防止多个主节点生成重复的ID,我们通常会通过控制初始值步长来避免这个问题。

例如在有两个主节点的情况下,我们就可以通过这种方式来错开ID。

-- 节点1
set @@auto_increment_offset = 1;     -- 起始值
set @@auto_increment_increment = 2;  -- 步长-- 节点2
set @@auto_increment_offset = 2;     -- 起始值
set @@auto_increment_increment = 2;  -- 步长

但是,这种方法的可拓展性不强,倘若我们要往集群中增加新的主节点时,我们就需要重新去设置步长,并且还需要根据前两个节点的自增ID的大小来考虑我们新节点的起始值,而这些操作只能人工来进行。如果在修改步长的时候出现了重复的ID,此时就还需要进行停机修改。

号段模式

前面两种方法的局限性在于其每次获取ID时都需要直接访问数据库,效率较低,如果能够一次获取大量的ID,并将其缓存在本地,那样就可以大大的提升ID获取的效率,这也是号段模式的核心思想。

号段模式每次从数据库中批量的获取一段自增ID,即取出一个范围的ID交给号段服务维护。例如(1,2000]即代表着2000个ID。我们的业务系统只需要到号段服务中申请ID,不需要每次都去请求数据库,直到所有的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`)
)

号段服务不再强依赖数据库,即使数据库不可用,号段服务也可以继续工作直到申请的ID全部使用完。但是如果此时号段服务重启,就会导致剩余的ID丢失。

为了保证号段服务的高可用,我们同样需要建立一个集群,在请求方从号段服务获取ID时,就会随机的选取一个节点来获取,而这种并发场景下我们同样需要考虑到并发安全的问题,因此我们上面的表中也提供了一个版本号的字段version,我们可以使用乐观锁来进行并发的控制。

update id_generator set current_max_id=#{newMaxId}, version=version+1 where version = #{version}

我们可以使用上面的SQL来获取新号段,当update更新成功就说明号段获取成功了。

雪花算法

雪花算法(Snowflake) 是twitter开源的一个分布式ID的生成算法,它的核心思想是:生成一个long类型的ID,一个long大小8字节,一个字节8个比特位,因此它使用64个比特位来确定一个分布式ID。其中41bit代表时间戳,10bit标识一台机器,剩下12bit则用来标识每个id

snowflake结构图

  • 第1个bit位为符号位,因为生成的id通常为正数所以固定为0
  • 2~42位为时间戳部分,其精确至毫秒。同时为了更加合理的利用,其并不会存储当前的时间,而是使用时间戳的差值(当前时间 - 固定的开始时间),这样就可以保证ID从更小的起点开始生成。
  • 43~52位为工作机器的id,这里的计算会更加灵活,可以根据机房数量、机器数量来自行均衡,保证能够利用到更多的机器。
  • 53~64位为序列编号,在同一毫秒中的同一台机器上我们可以生成4096个ID

考虑到不同的业务场景以及各个公司的特性,大多数公司并不会去直接使用snowflake,而是会对其进行改造,让其更贴合自身的使用场景。如百度的uid-generator、美团的Leaf、滴滴的TinyId等。

下面是github上开源的一个java实现的snowflake

/*** twitter的snowflake算法 -- java实现* github链接:https://github.com/beyondfengyu/SnowFlake* @author beyond* @date 2016/11/26*/
public class SnowFlake {/*** 起始的时间戳*/private final static long START_STMP = 1480166465631L;/*** 每一部分占用的位数*/private final static long SEQUENCE_BIT = 12; //序列号占用的位数private final static long MACHINE_BIT = 5;   //机器标识占用的位数private final static long DATACENTER_BIT = 5;//数据中心占用的位数/*** 每一部分的最大值*/private final static long MAX_DATACENTER_NUM = -1L ^ (-1L << DATACENTER_BIT);private final static long MAX_MACHINE_NUM = -1L ^ (-1L << MACHINE_BIT);private final static long MAX_SEQUENCE = -1L ^ (-1L << SEQUENCE_BIT);/*** 每一部分向左的位移*/private final static long MACHINE_LEFT = SEQUENCE_BIT;private final static long DATACENTER_LEFT = SEQUENCE_BIT + MACHINE_BIT;private final static long TIMESTMP_LEFT = DATACENTER_LEFT + DATACENTER_BIT;private long datacenterId;  //数据中心private long machineId;     //机器标识private long sequence = 0L; //序列号private long lastStmp = -1L;//上一次时间戳public SnowFlake(long datacenterId, long machineId) {if (datacenterId > MAX_DATACENTER_NUM || datacenterId < 0) {throw new IllegalArgumentException("datacenterId can't be greater than MAX_DATACENTER_NUM or less than 0");}if (machineId > MAX_MACHINE_NUM || machineId < 0) {throw new IllegalArgumentException("machineId can't be greater than MAX_MACHINE_NUM or less than 0");}this.datacenterId = datacenterId;this.machineId = machineId;}/*** 产生下一个ID** @return*/public synchronized long nextId() {long currStmp = getNewstmp();if (currStmp < lastStmp) {throw new RuntimeException("Clock moved backwards.  Refusing to generate id");}if (currStmp == lastStmp) {//相同毫秒内,序列号自增sequence = (sequence + 1) & MAX_SEQUENCE;//同一毫秒的序列数已经达到最大if (sequence == 0L) {currStmp = getNextMill();}} else {//不同毫秒内,序列号置为0sequence = 0L;}lastStmp = currStmp;return (currStmp - START_STMP) << TIMESTMP_LEFT //时间戳部分| datacenterId << DATACENTER_LEFT       //数据中心部分| machineId << MACHINE_LEFT             //机器标识部分| sequence;                             //序列号部分}private long getNextMill() {long mill = getNewstmp();while (mill <= lastStmp) {mill = getNewstmp();}return mill;}private long getNewstmp() {return System.currentTimeMillis();}public static void main(String[] args) {SnowFlake snowFlake = new SnowFlake(2, 3);for (int i = 0; i < (1 << 12); i++) {System.out.println(snowFlake.nextId());}}
}

Redis

我们可以利用Redis中的incr命令来原子的获取自增ID

127.0.0.1:6379> set seq_id 1     // 初始化自增ID
OK
127.0.0.1:6379> incr seq_id      // 自增并返回结果
(integer) 2

使用Redis实现起来特别简单且高效,但是我们还需要考虑到持久化时带来的一些问题

  • RDB持久化:由于其是定期保存一次数据库的快照,为了保证效率他也存在着一定的时间间隔。倘若在我们刚保存一次快照后,连续获取了几次ID,而此时还没来得及做下一次持久化就宕机了。当我们通过持久化重启Redis后,这段时间生成的ID就会被重复使用。
  • AOF持久化:AOF相当于是逻辑日志,其会通过保存我们执行过的命令来进行持久化。它并不像RDB出现一段时间的数据丢失而导致的ID重复的情况,但是在它恢复的过程中需要重新执行保存的命令,因此随着ID数量的增多,它重启恢复数据的时间也会越来越慢。

总结

优点 缺点
数据库自增ID 实现简单,ID单调自增,数值类型查询效率高 单点问题,在高并发时可能会有宕机的风险
数据库多主集群 解决了单点问题,一定程度上提高了稳定性 可拓展性不强,随着业务规模的不断扩大, 集群也会随之增加,但是新主节点的加入较为麻烦,需要人工操作
号段模式 不强依赖数据库,提高了效率 当为了保证高可用而使用多主集群时,仍然需要去修改起始值和步长
雪花算法 1.可以根据业务特性自由分配比特位,较为灵活。2.不依赖第三方系统,独立部署 强依赖机器时钟,如果出现时钟回拨则会导致系统不可用
Redis 实现起来简单且高效 持久化恢复存在问题,如RDB重复ID,AOF速度慢

分布式系统概念 | 分布式ID:数据库、号段模式、雪花算法(Snowflake)、Redis实现方案相关推荐

  1. 分布式id生成器:彻底解决雪花算法时间回拨问题

    Butterfly 简介 雪花算法是twitter提出的分布式id生成器方案,但是有三个问题,其中前两个问题在业内很常见: 时间回拨问题 机器id的分配和回收问题 机器id的上限问题 Butterfl ...

  2. 发号器:雪花算法(Snowflake)

    雪花算法(Snowflake)是twitter公司内部分布式项目采用的ID生成算法,开源后广受国内大厂的好评,在该算法影响下各大公司相继开发出各具特色的分布式生成器. 第一个bit位(1bit):Ja ...

  3. 分布式ID生成解决方案——推特雪花算法

    对于某些应用,MySQL里的某个表可能会占用很大的存储空间,甚至让服务器硬盘满了,这时候就会涉及到数据库的分片,把一个数据库进行拆分,通过数据库中间件(MyCat)进行连接. 假设现在我们有三台服务器 ...

  4. ID生成方案之号段模式

    号段模式ID生成器组件地址 https://github.com/15928587230/os-component-idworker 开箱即用, github上面有简洁的使用说明. 一 Leaf号段模 ...

  5. 美团Leaf源码——号段模式源码解析

    前言 分布式ID生成策略基本要求就是全局不重复,最好还能递增,长度较短,性能高,可用性强.关于相关的实现方案有很多,本文着重使用美团开源的分布式ID生成解决方案--Leaf. 关于Leaf,美团官方的 ...

  6. 美团开源分布式ID生成系统——Leaf源码阅读笔记(Leaf的号段模式)

    Leaf 最早期需求是各个业务线的订单ID生成需求.在美团早期,有的业务直接通过DB自增的方式生成ID,有的业务通过redis缓存来生成ID,也有的业务直接用UUID这种方式来生成ID.以上的方式各自 ...

  7. 深度解析leaf分布式id生成服务源码(号段模式)

    原创不易,转载请注明出处 文章目录 前言 1.实现原理推演 1.1 基于mysql最简单分布式ID实现 1.2 flickr分布式id解决方案 1.3 号段+mysql 2.源码剖析 2.1初始化 2 ...

  8. 基于号段模式、百度UID实现的分布式ID生成器kylin-id

    1.简介 1.1.开源项目 kylin-id:麒麟分布式id生成器,支持号段模式.雪花算法 并未发布jar到中央仓库,需要自己本地构建 1.2.介绍 参考滴滴[tinyid] 整合百度[UID] 麒麟 ...

  9. 分布式ID解决方案(一)数据库号段方式

    一.前言 在一些简单系统中,我们可以直接使用数据库ID自增方式来标识和保存数据,但是随着系统的逐渐复杂,数据量的日益增多,我们可能需要对数据表.数据库实现分库分表.单纯的使用数据库的ID自增无法满足业 ...

最新文章

  1. window下git的使用
  2. 陕西师范大学第七届程序设计竞赛 C题 iko和她的糖
  3. 你缺啥,你缺一个得力的办公软件
  4. matlab语言和python_matlab语言转译成python
  5. excel vba移位运算
  6. PPS网络电视清爽去广告版
  7. ImportError:cannot import name 'distribute_covar_matrix_to_match_covariance_type'
  8. 计算机与英语的关系论文摘要,中国计算机专业研究生英文学术论文摘要中元话语的使用与特征研究...
  9. ASP.NET Core免费(视频)教程汇总
  10. 【Oracle】并行等待之PX Deq: Table Q Normal
  11. 金融知识国民读本(一)
  12. ps基础学习:钢笔工具抠图
  13. RPC 框架 Kitex 初体验 (虚拟机环境)
  14. 【CloudCompare】高程显示
  15. 气象绘图(二)——散点图
  16. 在2B和2C之间,还有一个2H(下)
  17. 排序-----直接插入排序------带监视哨和不带监视哨
  18. aps软件中的运营管理至关重要
  19. pageHelp分页插件
  20. CSDN:写博客时怎么添加文章目录,然后点击目录跳转到对应的内容目录?

热门文章

  1. aop实现原理-动态代理CGLib代理
  2. 手写spring编程事务
  3. spring-DAO
  4. python pymongo+networkx 实现mongo数据血缘关系可视化
  5. 问题:linux系统经常出现断网的情况,重启之后系统恢复正常
  6. 第 25 章 OpenManage
  7. 《Python高效开发实战》实战演练——开发Django站点1
  8. 解决:the selection is not within a valid module
  9. Android开发之Intent.Action
  10. 数据库内容导出为excel并下载