分布式 id 生成系统 滴滴 Tinyid 快速入门

目录

分布式 id 生成系统 Tinyid 概述

分布式 id 生成系统 Tinyid 快速入门

tinyid-client  获取 分布式 id

tinyid 多 db 数据源配置

tinyid 由 Mysql 切换成 Oracle 数据库

tinyid Long 类型 id 如何防止被扫库


本文演示环境:Java JDK 1.8 + Maven 3.6 + Tinyid 2020年12月28下载版本 + Mysql 5.6(单db配置) + Oracle 11g

分布式 id 生成系统 Tinyid 概述

1、Tinyid 是滴滴用 Java 开发的一款分布式 id 生成系统,基于数据库号段算法实现,Tinyid 扩展了 leaf-segment 算法,支持了多db(master),同时提供了 java-client(sdk) 使 id 生成本地化,获得了更好的性能与可用性。Tinyid 在滴滴客服部门使用,均通过 tinyid-client 方式接入,每天生成亿级别的 id。

2、Tinyid 特性如下:1)全局唯一的 long 型 id、2)趋势递增的id(不保证下一个id一定比上一个大)、3)非连续性、4)提供 http 和 java client 方式接入、5)支持批量获取id、6)支持生成1,3,5,7,9...序列的id、7)支持多个db的配置。

3、http 方式访问时,性能取决于 http server 的能力,网络传输速度。

4、java-client 方式访问时,id为本地生成,号段长度(step)越长,qps越大,如果将号段设置足够大,则qps可达1000w+。只要server有一台存活,则理论上可用,server全挂,因为client有缓存,也可以继续使用一段时间。

5、Tinyid 依赖db,当db不可用时,因为server有缓存,所以还可以使用一段时间,如果配置了多个db,则只要有1个db存活,则服务可用。

6、Tinyid 不适用场景:类似订单 id 的业务(因为生成的id大部分是连续的,容易被扫库、或者测算出订单量)

tinyid 原理架构

1)tinyid 是基于数据库发号算法实现的,简单来说是数据库中保存了可用的id号段,tinyid会将可用号段加载到内存中,之后生成id会直接内存中产生。

2)可用号段在第一次获取id时加载,如当前号段使用达到一定量时,会异步加载下一可用号段,保证内存中始终有可用号段。

3)如可用号段1~1000被加载到内存,则获取id时,会从1开始递增获取,当使用到一定百分比时,如20%(默认),即200时,会异步加载下一可用号段到内存,假设新加载的号段是1001~2000,则此时内存中可用号段为200~1000,1001~2000),当id递增到1000时,当前号段使用完毕,下一号段会替换为当前号段。依次类推。

1)Tinyid-server 从数据库中加载可用的号段缓存在服务端的内存中,不同的 Tinyid-server 加载不同的号段,所以 http 请求或者 Tinyid-client 请求时,id 不会重复

2)Tinyid-client 请求 Tinyid-server  时同样加载可用的号段缓存在业务系统的内存中,同一个业务系统不同节点加载的是不同的号段,所以不会重复。

3)如果 Tinyid-server 或者 Tinyid-client 重启了,则内存中预加载的号段就作废了,下次请求时会获得新的号段,从而会浪费一部分 id;这就是为什么不保证 id 一定连续,不是单调递增的,而是趋势递增。

推荐使用方式

1)tinyid-server 推荐部署到多个机房的多台机器,多机房部署可用性更高,http 方式访问需使用方考虑延迟问题

2)推荐使用 tinyid-client 来获取id,好处如下:

  • id 为本地生成(调用 AtomicLong.addAndGet方法),性能大大增加
  • client 对 server 访问变的低频,减轻了 server 的压力
  • 因为低频,即便 client 使用方和 server 不在一个机房,也无须担心延迟
  • 即便所有 server 挂掉,因为client预加载了号段,依然可以继续使用一段时间 注:使用tinyid-client方式,如果client机器较多频繁重启,可能会浪费较多的id,这时可以考虑使用http方式

3)推荐db配置两个或更多:只要有1个db存活,则服务可用,如配置了两个db,则每次新增业务需在两个db中都写入相关数据。

其他说明

1)tinyid 并不是滴滴官方产品,只是滴滴拥有的代码。

2)其他id生成项目推荐

分布式 id 生成系统 Tinyid 快速入门

)git 克隆 TinyId 项目源码https://github.com/didi/tinyid.git,或者直接下载源码压缩包:https://github.com/didi/tinyid/archive/master.zip。(可以将源码导入到 IDE 中进行学习参考,不导入也没关系)

二)执行 tinyid-server 服务下的 db.sql 文件脚本。官方只为 Mysql 数据库提供了脚本,如果是其它的关系型数据库,比如 Oracle,则需要自己改写。(tiny_id_info 表与 tiny_id_token 表以及字段都有完整的注释,看一下就一目了然。无需累述)

三)修改 tinyid-server  数据库连接配置(单 DB 配置如下):(tinyid-server.iml 中默认配置的是让 resources/offline 生效,所以默认修改离休配置即可

server.port=9999
server.context-path=/tinyid
batch.size.max=100000

datasource.tinyid.names=primary
datasource.tinyid.type=org.apache.tomcat.jdbc.pool.DataSource

datasource.tinyid.primary.driver-class-name=com.mysql.jdbc.Driver
datasource.tinyid.primary.url=jdbc:mysql://localhost:3306/wangmx?autoReconnect=true&useUnicode=true&characterEncoding=UTF-8
datasource.tinyid.primary.username=root
datasource.tinyid.primary.password=root

四)启动 tinyid-server   服务端:可以直接在 IDE 中启动,也可以打成 .jar 包,复制到任意地方运行,官方打包命令如下(自己使用 IDE 打包也是可以的,下一节有演示):

cd tinyid-server      #进入 tinyid-server 服务源码目录下
build.sh offline        #使用 buiild.sh 脚本自动打包,选择里面 offline 下的配置文件
java -jar output/tinyid-server-xxx.jar       #执行打包好的 .jar 包。

五)http 请求 tinyid-server   服务获取 id:IdContronller  提供了如下所示的 get 请求方式:

http://localhost:9999/tinyid/id/nextId?bizType=test&token=0f673adf80504e2eaa552f5d791b644c&batchSize=3

1)bizType:表示业务类型,必填项,对应 tiny_id_info 表的 biz_type 字段(唯一约束)。

2)token:表示口令/令牌,必填项,对应 tiny_id_token 表的 token 字段,一个 bizType 可以有多个 token 关联。

3)bizType 与 token 在 数据库中需要提取配置好,否则请求返回错误,比如:{"data":null,"code":5,"message":"token is error"}

4)batchSize:表示批量获取的个数,可选项,默认为 1,不能超过配置文件中的 batch.size.max 属性值。

4)返回格式:{"data":[207,208,209],"code":200,"message":""},请求成功时  message 会为空,否则会有错误信息,比如当 bizType 或者 token 不存在时

http://localhost:9999/tinyid/id/nextIdSimple?bizType=test_odd&token=0f673adf80504e2eaa552f5d791b644c&batchSize=10

1)参数含义同上,区别在于 nextId 返回的是 json 对象,而这里直接返回的 id 字符串

2)比如:183,185,187 或者 188

tinyid-client  获取 分布式 id

上面"快速入门”中演示的是 http 请求 tinyid-server  服务获取 id,而官方推荐使用 tinyid-client 客户端调用,它相当于一个 SdK,自己的系统引入依赖后,调用客户端封装好的 API 获取即可,查看源码可知 tinyid-client 客户端底层使用HttpURLConnection。

一)对 tinyid-client  项目打包:maven 仓库中心是没有 tinyid-client 依赖的,至少我没有找到,如同需要自己手动对 tinyid-server 打包一样,也需要手动对 tinyid-client 打包,打包后,本地 Maven 仓库就有依赖,本地业务系统中就可以引用然后进行开发。(打包时必须对整个 tinyid 父项目进行打包,下面动图有演示)

<dependency>
    <groupId>com.xiaoju.uemc.tinyid</groupId>
    <artifactId>tinyid-client</artifactId>
    <version>${tinyid.version}</version>
</dependency>

二)类路径下添加 tinyid_client.properties 配置文件:查看 com.xiaoju.uemc.tinyid.client.factory.impl.IdGeneratorFactoryClient 的源码就会发现它默认读取的就是此文件,

# tinyid client 配置文件,com.xiaoju.uemc.tinyid.client.factory.impl.IdGeneratorFactoryClient 中会调用

# tinyid server 服务器地址,集群部署时,使用逗号隔开,必填项
tinyid.server=localhost:9999
# 业务系统口令,对应 tiny_id_token 表中的 token 字段,必填项
tinyid.token=0f673adf80504e2eaa552f5d791b644c

# 连接超时与读取超时时间,单位毫秒,默认都是 50000
tinyid.readTimeout=50000
tinyid.connectTimeout=50000

三)直接调用 TinyId 工具类:tinyid-client 源码本身就不是很大,所以使用非常简单,,其中就两个方法:

Long id = TinyId.nextId("test");
List<Long> ids = TinyId.nextId("test", 10);

下面通过动图进行演示:

 

tinyid 多 db 数据源配置

1、使用 Tinyid 前,必须先手动配置 tiny_id_info、tiny_id_token 表,默认脚本里面已经提供了注释以及插入了示例数据,一看就知道如何配置,下面将其中主要字段进行说明:

tiny_id_info

biz_type)业务类型,唯一约束。用于隔离不同业务系统的数据,业务系统请求时,需要携带这个值,通常每个业务系统使用一个唯一值。

begin_id)开始id,仅记录初始值无其他含义,用户请求时会从它的下一个值开始,初始化时 begin_id 和 max_id 应相同

max_id)当前最大的可用 id,当 Tinyid-server 的号段使用到一定长度后就会再次请求,同时就会修改这个值,手动修改此值,客户端获得的 id 就会变化。
step)号段的长度,即 Tinyid-server 每次请求的号段长度,值越大,服务端缓存就越多。

delta)每次 id 的增量

remainder)余数,通过设置 delta 和 remainder 来灵活控制生成的 id,比如 delta=1,remainder=0,返回 2,3,4,5,6...;比如 delta=2,remainder=1,返回 3,5,7,9,11...;比如 delta=2,remainder=0,返回 2,4,6,8,10...

version)版本号,是一个乐观锁,每次更新都加上version,能够保证并发更新的正确性。

tiny_id_tokentoken)口令/令牌,token 关联 biz_type,业务系统请求时带上这两个值就能唯一确定是哪一条 业务类型 数据。

2、step 配置的号段越大,意味着 Tinyid-server 与 Tinyid-client 本地缓存的可用号段值就越多,即使数据库宕机了,系统仍然能使用一段时间,但是 Tinyid-server 或者 Tinyid-client 重启了,则它们内存中缓存的号段也就丢失了,下次就会重新请求新的号段。

3、Tinyid-server 与 Tinyid-client 都需要自己下载源码然后手动打包(上面动图有演示)。无论是使用 http 请求还是使用 Tinyid-client SDK,都必须启动 Tinyid-server 。

以两个 db 配置为例,Tinyid-server 配置文件如下:

datasource.tinyid.names=primary,secondary

datasource.tinyid.primary.driver-class-name=com.mysql.jdbc.Driver
datasource.tinyid.primary.url=jdbc:mysql://localhost:3306/db1?autoReconnect=true&useUnicode=true&characterEncoding=UTF-8
datasource.tinyid.primary.username=root
datasource.tinyid.primary.password=123456
datasource.tinyid.primary.testOnBorrow=false
datasource.tinyid.primary.maxActive=10

datasource.tinyid.secondary.driver-class-name=com.mysql.jdbc.Driver
datasource.tinyid.secondary.url=jdbc:mysql://localhost:3306/db2?autoReconnect=true&useUnicode=true&characterEncoding=UTF-8
datasource.tinyid.secondary.username=root
datasource.tinyid.secondary.password=123456
datasource.tinyid.secondary.testOnBorrow=false
datasource.tinyid.secondary.maxActive=10

多个 DB 时,数据库 tiny_id_info 配置需要注意,生成的结果不能相同,否则会重复,需要让它们间隔生成,根据下面的示例依此类推:

两个 DB 时
数据库 Abiz_type=testdelta=2remainder=0生成2,4,6,8序列的数据...
数据库 Bbiz_type=testdelta=2remainder=1生成1,3,5,7序列的数据...

如果数据库 A 挂掉,则仍能正常服务,但只能产生1,3,5,7..的序列。不用介意 max_id 或者 begin_id 的值是多少,都能生成正确的序列

三个 DB 时
数据库 Abiz_type=testdelta=3remainder=0生成3,6,9,12,15序列的数据...
数据库 Bbiz_type=testdelta=3remainder=1生成1,4,7,10,13序列的数据...
数据库 Cbiz_type=testdelta=3remainder=2生成2,5,8,11,14序列的数据...

tinyid 由 Mysql 切换成 Oracle 数据库

1、tinyid-client 的源码不多,没有使用第三方的库,底层使用的是原生的 HttpURLConnection API 调用 tinyid-server。

2、Tinyid-server 源码也不多,两个实体类 TinyIdInfo、TinyIdToken,以及它们对应的业务层和 dao 层,底层使用  JdbcTemplate 进行修改与查询(queryByBizType、updateMaxId)。

3、因为平时并不一定是使用 Mysql 数据库,对于政府、银行、证券、保险等等项目通常还是 Oracle,所以以 Oracle为例进行切换说明,其它数据库同理。

一)初始化 Oracle 脚本:Tinyid 默认使用 Mysql 数据库,Tinyid-server 服务下的 db.sql 脚本也是为 Mysql 数据库准备的,所以需要手动改为 Oracle 数据库的语法,然后执行:分布式 id 生成系统 Tinyid 数据初始化(Oracle).sql

二)引入 Oracle 依赖:Tinyid-server 服务的 pom.xml 文件中引用 Oracle 的依赖。

        <!-- https://mvnrepository.com/artifact/com.github.noraui/ojdbc8 -->
        <!--这是网络上的'雷锋'提供的开源 ojdbc8 驱动,功能与官网的是一样的,专门用于替代 Oracle 官网 maven 下载失败-->
        <dependency>
            <groupId>com.github.noraui</groupId>
            <artifactId>ojdbc8</artifactId>
            <version>12.2.0.1</version>
        </dependency>

三)修改 DB 连接配置:将默认的 mysql 连接改为 Oracle 连接(多DB配置时也是同理)

server.port=9999
server.context-path=/tinyid

batch.size.max=100000

datasource.tinyid.names=primary
datasource.tinyid.type=org.apache.tomcat.jdbc.pool.DataSource

datasource.tinyid.primary.driver-class-name=oracle.jdbc.driver.OracleDriver
datasource.tinyid.primary.url=jdbc:oracle:thin:@127.0.0.1:1521:ORCL
datasource.tinyid.primary.username=scott
datasource.tinyid.primary.password=scott

四)修改 Tinyid-server 一行源码: Tinyid-server 服务中 com.xiaoju.uemc.tinyid.server.dao.impl.TinyIdInfoDAOImpl 中的第 38 行,update tiny_id_info set update_time=now() 改为 update_time=sysdate,因为当前时间在 Oracle 中无法用 now() 表示,而是 sysdate。

4、亲测到此就可以了,无论是 Http 请求还是 Tinyid-client 请求都没有问题, Tinyid-server 打包后就可以用于 oracle 数据库了,下面动图展示一下:

tinyid Long 类型 id 如何防止被扫库

1、官方 友情提示如下:

适用场景:只关心id是数字,趋势递增的系统,可以容忍id不连续,有浪费的场景
不适用场景:类似订单id的业务(因为生成的id大部分是连续的,容易被扫库、或者测算出订单量)

2、优缺点是相对的,tinyid  趋势递增的 Long 虽然容易被扫库,但是优点是长度小,而像雪花算法,UUID 这种虽然不容被扫库,但是长度太长,浪费存储空间

3、以下思路仅供参考:如果数据库主键是字符串类型,则可以在 tinyid Long 类型的基础上,给他随机插入一些特殊字符,这样相对来说就不会那么容易被人家直到规律。比如 tinyid 返回的是 "101,102,103",则随机插入字符后形如 “OJl101, p10uf2, 10f3Bd”。

4、下面是提供的一个工具类(完整在线源码),这样只要源 id 是不重复的,则插入随机字符后也是不会重复的。如果想要结果更加复杂,则可以增加随机插入的字符个数,或者增加插入的字符类型

    /**
     * 将 Long 类型的值转为 String 类型,同时往其中插入随机字母((a-z, A-Z))
     *
     * @param tinyid   :待转换的数据,如果为 null,则返回空
     * @param alphSize :随机插入字母的个数
     * @return
     */
    public static String randomTinyid(Long tinyid, int alphSize) {
        if (tinyid == null) {
            return "";
        }
        alphSize = alphSize < 0 ? 3 : alphSize;
        StringBuffer buffer = new StringBuffer(tinyid.toString());
        /**
         * 1、生成指定长度的随机字符串,字符从 (a-z, A-Z) 中选择
         * 2、被插入的字母是随机生成的,每次插入的单个字母的位置是随机的
         */
        String randomAlphabetic = RandomStringUtils.randomAlphabetic(alphSize);
        for (int i = 0; i < randomAlphabetic.length(); i++) {
            int length = buffer.length();
            //)nextInt(int bound):生成的是 [0,bound)之间的随机数,StringBuffer 插入时可以包含 上限(length)
            int nextInt = new Random().nextInt(length + 1);
            //)往目标字符串中的随机位置插入随机字符
            buffer.insert(nextInt, randomAlphabetic.charAt(i));
        }
        return buffer.toString();
    }

5、其中随机字符生成使用的是 Apache commons-lang3 的 RandomStringUtils#randomAlphabetic(int) 方法,它的依赖如下:

        <!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 -->
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.9</version>
        </dependency>

6、老规矩演示一下:

 

  • 6
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

蚩尤后裔-汪茂雄

芝兰生于深林,不以无人而不芳。

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值