来吧,自己动手撸一个分布式ID生成器组件(上)

简介: 来吧,自己动手撸一个分布式ID生成器组件(上)

在经过了众多轮的面试之后,小林终于进入到了一家互联网公司的基础架构组,小林目前在公司有使用到架构组研究到分布式id生成器,前一阵子大概看了下其内部的实现,发现还是存在一些架构设计不合理之处。但是又由于适用于当前的业务场景,所以并没有做过多的优化,这里记录一些相关的技术笔记。


研发背景


在分布式服务中,各种复杂的业务场景需要有一个用于做唯一标识的id,例如订单业务,支付流水,聊天通信等业务场景。尤其是在分库分表场景中,分布式id生成器的使用频率更高。因此分布式id组件的设计应该要能支持以下几个特性:


1.全局唯一特性


这个点比较好理解,这里就不做过多的解释。


2.组件递增特性


可以是每个id都具有递增的特性也可以是支持区间段内具备递增的特性。


3.安全性


有些重要的id如果无意中暴露在了外网环境中,如果没有做过安全防范其实是一件非常危险的事情。例如说订单的id如果只是更具日期加订单数目的格式生成,例如说:


2020111100001表示2020年11月11日的第一笔订单,那么如果竞对获取到了

2020111100999这个id,再根据订单的生成时间则大概可以推断出该公司某日生成的订单数目的大致量级。


4.高qps


分布式id生成组件在使用过程中主要是qps偏高,因此在设计起初应该要能支持较高的qps查询,同时对于网络的延迟特性也需要尽可能降低。


5.高可用


由于分布式id生成器是一个需要支持多个服务调用方共同使用的公共服务,一旦出现崩溃后果不堪设想,可能会导致大面积的业务线崩塌,所以在高可用方面需要考虑得尤其重要。


业界常见的分布式id生成方案比对


uuid


java程序中实现uuid的代码:


String result = UUID.randomUUID().toString();
System.out.println(result);


生成的格式如下所示:


b0b2197d-bc8c-4fab-ad73-2b54e11b0869


uuid的格式其实是会被 - 符号划分为五个模块,主要是分为了8-4-4-4-12个字符拼接而成的一段字符串。但是这种字符串的格式对于互联网公司中主推的MySQL数据库并不友好。


尤其是当使用生成的id作为索引的时候,uuid长度过长,大数据量的时候会导致b+树的叶子结点裂变频率加大,而且在进行索引比对的时候需要进行逐个字符比对,性能损耗也较高,应该抛弃该方案。小林查询了一些网上的资料发现uuid的主要组成由以下几个部位:


  • 当前日期和时间
  • 随机数字
  • 机器的MAC地址(能够保证全球范围内机器的唯一特性)


雪花算法


SnowFlake是Twitter公司采用的一种算法,目的是在分布式系统中产生全局唯一且趋势递增的ID。


image.png


稍微解释一些雪花算法的含义:


第一位通常是0,没有特殊使用含义,因为1通常表示为补码。


中间的41位是用于存储时间,41位的长度足够容纳69年左右的时长。


10bit用于标示机器自身的id,从而表示不通机器自身id的不同。


最后12位bit用于表示某一毫秒内的序列号,12位(bit)可以表示的最大正整数是4096-1=4095,所以也就是说一毫秒内可以同时生成4095个id。


时间戳位置和序列号位置还不能随意调整,应为要保证逐渐递增的特性。


好处


能够保证递增的特性,id具有明确的含义,易懂。


不足点


但是对于机器自身的系统时间有所依赖,一旦机器的系统时间发生了变化,在高并发环境下就有可能会有重复id生成的风险。


有些业务场景希望在id中加入特殊的业务规则名称前缀


例如短信的id:


sms_108678123


奖券的id:


coupon_12908123


需要基于这种算法进行改造,实现支持id注入“基因”的这一特性。


mongodb的主键id设计思路


其实在mongodb里面也有使用到往主键id中注入一些“基因”要素点的这类思路:

mongodb里面没有自增的id。


image.png


在对这几种方案进行了调研之后,于是小林便开始萌生了开发一款专用于自己公司平台的id生成器。


自研主要设计思路


MySQL配置id生成规则,拉取到本地缓存中形成一段本地id,从而降低对于db的访问。

支持集群配置id生成器,能够支持高qps访问和较好的扩容性。


image.png


建表sql:


CREATE TABLE `t_id_builder_config` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `des` varchar(255) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '描述',
  `init_num` bigint(13) DEFAULT NULL COMMENT 'id起步值',
  `current_threshold` bigint(16) DEFAULT NULL COMMENT '当前id所在阶段的阈值',
  `step` int(8) DEFAULT NULL COMMENT 'id递增区间',
  `biz_code` varchar(60) COLLATE utf8mb4_unicode_ci DEFAULT NULL COMMENT '业务前缀码,如果没有则返回时不携带',
  `version` int(11) NOT NULL DEFAULT '0' COMMENT '乐观锁版本号',
  `is_seq` smallint(2) NOT NULL DEFAULT '0' COMMENT 'id连续递增,0 是  1 不是',
  `create_time` datetime DEFAULT CURRENT_TIMESTAMP,
  `update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;


几个核心设计点:


当同时有多个请求访问mysql获取id配置的时候该如何防止并发问题?


这里我采用了for update的方式加行锁进行读取,同时对于行信息进行更新的时候加入了version版本号信息字段防止更新重复的情况。


假设说更新失败,也会有cas的方式进行重试,重试超过一定次数之后直接中断。


为何不引入redis作为分布式锁来防止并发修改数据库操作?


不希望将该组件变得过于繁杂,减少系统对于第三方的依赖性


假设本地id还没使用完,结果当前服务器宕机了,该如何预防?


每次服务启动都需要更新表的配置,拉去最新的一批id集合到本地,这样就不会出现和之前id冲突的问题了。


本地id集合中如何判断id是否已经使用过?


如果是连续递增型的id,这一步可以忽略,因为本地id每次获取的时候都会比上一个id要大。但是如果是拉取了一段区间的id到本地之后进行随机返回就需要加入bitset作为过滤器了。对于已经使用过的id,则对应bit置为1。如果随机返回的区间id多次都已经被占用了,则超过一定频率之后需要重新拉取id到本地。


image.png


不通机器的状态表示码该如何设置?


可以通过启动脚本中配置相关参数:


-DidBuilder.index=1001


进行配置,然后通过System.getProperty("idBuilder.index")的方式来获取.


核心代码思路:


接口设计:


package com.qiyu.tech.id.builder.service;
/**
 * @Author idea
 * @Date created in 11:16 下午 2020/12/17
 */
public interface IdBuilderService {
    /**
     * 根据本地步长度来生成唯一id(区间性递增)
     *
     * @return
     */
    Long unionId(int code);
    /**
     * 对于unionId的算法进行优化(连续性递增)
     *
     * @param code
     * @return
     */
    Long unionSeqId(int code);
    /**
     * 生成包含业务前缀的自增id(区间性递增)
     *
     * @param code
     * @return
     */
    String unionIdStr(int code);
    /**
     * 生成包含业务前缀的自增id(连续性递增)
     *
     * @param code
     * @return
     */
    String unionSeqIdStr(int code);
}
相关文章
|
1月前
|
消息中间件 存储 监控
消息队列:分布式系统中的重要组件
消息队列:分布式系统中的重要组件
|
3月前
|
负载均衡 Java 开发者
【分布式】Spring Cloud 组件综述
【1月更文挑战第25天】【分布式】Spring Cloud 组件综述
|
21天前
|
设计模式 安全 Java
【分布式技术专题】「Tomcat技术专题」 探索Tomcat技术架构设计模式的奥秘(Server和Service组件原理分析)
【分布式技术专题】「Tomcat技术专题」 探索Tomcat技术架构设计模式的奥秘(Server和Service组件原理分析)
23 0
|
3月前
|
缓存 算法 NoSQL
【分布式详解】一致性算法、全局唯一ID、分布式锁、分布式事务、 分布式缓存、分布式任务、分布式会话
分布式系统通过副本控制协议,使得从系统外部读取系统内部各个副本的数据在一定的约束条件下相同,称之为副本一致性(consistency)。副本一致性是针对分布式系统而言的,不是针对某一个副本而言。强一致性(strong consistency):任何时刻任何用户或节点都可以读到最近一次成功更新的副本数据。强一致性是程度最高的一致性要求,也是实践中最难以实现的一致性。单调一致性(monotonic consistency):任何时刻,任何用户一旦读到某个数据在某次更新后的值,这个用户不会再读到比这个值更旧的值。
380 0
|
21天前
|
NoSQL Java Redis
【分布式技术专题】「分布式技术架构」手把手教你如何开发一个属于自己的分布式锁的功能组件(二)
【分布式技术专题】「分布式技术架构」手把手教你如何开发一个属于自己的分布式锁的功能组件
14 0
|
1天前
|
存储 SQL 算法
搞定了 6 种分布式ID,分库分表哪个适合做主键?
在《ShardingSphere5.x分库分表原理与实战》系列的第七篇文章中,作者探讨了分布式ID在分库分表中的重要性,以及如何利用`ShardingSphere-jdbc`的多种主键生成策略。文章介绍了`UUID`、`NanoID`、自定义雪花算法和`CosId`等策略的优缺点,并警告不要在SQL中手动拼接主键字段。此外,文章还展示了如何配置这些策略,并提醒读者`CosId`在5.2.0版本可能不可用。最后,文章讨论了如何自定义分布式主键生成算法,并强调选择策略时要考虑全局唯一性、性能和易用性。
|
21天前
|
缓存 算法 关系型数据库
深度思考:雪花算法snowflake分布式id生成原理详解
雪花算法snowflake是一种优秀的分布式ID生成方案,其优点突出:它能生成全局唯一且递增的ID,确保了数据的一致性和准确性;同时,该算法灵活性强,可自定义各部分bit位,满足不同业务场景的需求;此外,雪花算法生成ID的速度快,效率高,能有效应对高并发场景,是分布式系统中不可或缺的组件。
深度思考:雪花算法snowflake分布式id生成原理详解
|
1月前
|
SpringCloudAlibaba Java 持续交付
【Springcloud Alibaba微服务分布式架构 | Spring Cloud】之学习笔记(一)基础知识+各个组件介绍+聚合父工程创建
【Springcloud Alibaba微服务分布式架构 | Spring Cloud】之学习笔记(一)基础知识+各个组件介绍+聚合父工程创建
79 1
|
1月前
|
算法 Java 数据中心
分布式ID生成系统之雪花算法详解
在当今的云计算和微服务架构盛行的时代,分布式系统已成为软件开发的重要组成部分。随着系统规模的扩大和业务的复杂化,对数据一致性和唯一性的要求也越来越高,尤其是在全局唯一标识符(ID)的生成上。因此,分布式ID生成系统应运而生,成为保证数据唯一性和提高系统可扩展性的关键技术之一。雪花算法(Snowflake)是Twitter开源的一种算法,用于生成64位的全局唯一ID,非常适用于分布式系统中生成唯一标识符。下面我们将深入探讨雪花算法的原理、结构和实现方式。
93 2
 分布式ID生成系统之雪花算法详解
|
1月前
|
NoSQL 算法 Java
【Redis】4、全局唯一 ID生成、单机(非分布式)情况下的秒杀和一人一单
【Redis】4、全局唯一 ID生成、单机(非分布式)情况下的秒杀和一人一单
62 0

热门文章

最新文章