tinyid介绍 及架构分析

(转载请务必显著注明出处, 尊重南波兔的劳动成果)
tinyid源码解读 server部分
tinyid源码解读 client部分

tinyid概览

  • 其是滴滴开源的一个分布式id生成器(中间件)
  • 其参考了美团开源项目leaf
  • 语言是java,目前也仅支持java项目

下面是有关分布式id的相关特性

  • 高可用: id生成器项目可以部署在多个服务器上,
  • 高并发: 可以连接多个数据库,同时因为采用号段模式, 故对数据库的压力并不大
  • 全局id唯一: 因为采用了数据库自增,所以能够保证id唯一
  • 简单易用

  • 算法上,采用号段模式,将一部分的号段缓存在客户端和服务端,从而不用频繁访问数据库
  • 此处的号段模式本身又基于数据库的自增主键来实现id的增长

tinyid原理

通过数据库的自增主键来生成id, 但区别于访问一次db只拿一个id, tinyid会一次获取一个号段(多个id), 放入自己的内存中,之后的客户端获取id, 则直接在server的内存中获取

tinyid架构及分析

在这里插入图片描述
上图是官方提供的架构图
根据改图来介绍tinyid的架构


首先tinyid分为两个部分: clientserver
二者通过http协议来进行交互,所以server是个web服务器
(当然你也可以不要server,直接每次都http来获取id or 号段,自己来进行维护)

在server中,有这么几个类

  • IdGeneratorFactory:用于获取id生成器
  • IdGenerator: 用于生成id, 是个接口, 这里用的工厂模式实际
  • CacheIdGenerator: 整个项目的核心类, 上面接口的实现类, 用于生成id
  • server的内存中, 会维护两个变量, CurrentSegmentId 和 NextSegment, 分别用来存储当前号段和下一个号段

server对外提供两个方法 nextId() 以及nextSegment()

  • 前者是获取一定数量的id
  • 后者是获取一整个号段(数据库中会规定号段的大小)

下面来说一下nextId()方法的流程

	client通过http调用server的nextId()方法
	server调用CacheIdGenerator.nextId()方法
	server查看自己内存中缓存的号段CurrentSegmentId,看看是否有余量
	如果有余量,则返回一个号段
	如果没有余量,则调用getNextSegmentId()方法,获取一整个号段
	getNextSegmentId()方法是通过查询更改数据库来获取号段

而通过架构图, 可以看到, 如果直接调用nextSegmentId方法, 是会绕过缓存的号段, 直接向数据库来申请新的号段分配给客户端

server与db交互,拿到号段,而client与server交互,拿到server已经获取的号段


这里需要说的是, 架构图上没有展示client的结构
实际上可以将client的结构理解成和server的一模一样
client 也有那些类(只是实现类不太一样), 也有自己缓存的CurrentSegmentId 和 NextSegment, 当自己缓存的号段不够了, 会去获取新的可用号段, 唯一不同是, server是与db交互来获取, 而client是通过http来与server交互获取


由于采用client-server的模式,所以可以有多个client
又由于可以有多个db, 所以不同server之间是可以独立的, 所以可以搭建server集群

对于多个sever, client这里的负载均衡逻辑是, 从本地存储的可用server列表中随机选择一个server, 由于每次的会话在业务逻辑上都是无状态的, 所以该负载均衡方法可行

而对于多个db, 项目中采用的方式是,采用动态数据源的方式, 来进行db的切换, 从而降低db的压力

tinyid 的 db号段算法

该项目通过db自增的号段算法来实现分布式id的唯一生成

CREATE TABLE `tiny_id_info` (
  `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '自增主键',
  `biz_type` varchar(63) NOT NULL DEFAULT '' COMMENT '业务类型,唯一',
  `begin_id` bigint(20) NOT NULL DEFAULT '0' COMMENT '开始id,仅记录初始值,无其他含义。初始化时begin_id和max_id应相同',
  `max_id` bigint(20) NOT NULL DEFAULT '0' COMMENT '当前最大id',
  `step` int(11) DEFAULT '0' COMMENT '步长',
  `delta` int(11) NOT NULL DEFAULT '1' COMMENT '每次id增量',
  `remainder` int(11) NOT NULL DEFAULT '0' COMMENT '余数',
  `create_time` timestamp NOT NULL DEFAULT '2010-01-01 00:00:00' COMMENT '创建时间',
  `update_time` timestamp NOT NULL DEFAULT '2010-01-01 00:00:00' COMMENT '更新时间',
  `version` bigint(20) NOT NULL DEFAULT '0' COMMENT '版本号',
  PRIMARY KEY (`id`),
  UNIQUE KEY `uniq_biz_type` (`biz_type`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8 COMMENT 'id信息表';

在db中, 会有这样的表来生成id
其中主要关注以下字段

  • biz_type 业务类型
  • max_id: 当前最大的id (下一个id是max_id+1) (下一个max_id是 max_id + step)
  • step: 一次可以获取号段范围
  • delta: client在进行获取id时, 每次自增的步长,用来区分db, 比如db1 delta为1, db2 delta为2,这样就不会冲突了
  • remainder: 余量, 用于计算初始值
  • version: 实现乐观锁,用于解决多个server同时访问一个db的并发安全问题

举例:

id	biz_type	max_id	step	version
1	1000		2000	1000		0
server访问db, 将max_id 设置为2000+1000, version设置为1,如果成功
则
id	biz_type	max_id	step	version
1	1000		3000	1000		1
此时server获取到了2001-3000的号段

tinyId 特点(优点)

  • 采用双号段缓存的方式
    由于采用的http连接, 如果号段才获取,就极有可能被http阻塞
    所以tinyid采用双号段缓存方式, 在当前号段使用到一定值时, 比如50% ? 20% ? 就异步的去获取下一个号段, 即内存中最多缓存两个号段

  • 本地获取id
    由于采用了 异步 双号段缓存的方式, 只要并发量不是那么巨大的时候, 基本可以实现效果: 除了第一次http请求略慢, 后面因为异步,所以基本感受不到client与server的交互时间, 所有的id都是从内存中获取
    减少了对网络的依赖

  • client 和 server都有缓存号段的实现
    于是,即使网络状况不好, 即使短时间db宕机, server宕机, id也能正常生成

  • 号段模式
    一次获取一个号段,只需修改db一次, 极大的减轻了db的压力

  • 多db支持
    如果只有一个master, 则不仅要考虑高并发,还要要考虑db高可用问题, 如果是主从, 还要考虑主从辅助的延迟
    tinyId支持多个master, 但要求master之间要进行区分, 不能id重复, 例如db A 生成奇数, db B生成偶数
    由于是动态数据源, 则db A和db B所支持的bizType应该完全一样,只是delta和remainder不一样

  • db的乐观锁
    通过乐观锁来解决不同server访问同一个数据库的并发问题
    实现是通过version字段和 事务开启下的 select+update
    这里有三次自旋的机会(当然我觉得三次有点少了似乎)

  • id 趋势增长
    总体id是趋势增长的,但是不是严格递增的
    因为号段模式,所以clientA拿到1-1000 和 clientB拿到1001-2000, 可能前者的id比后者的id要早分配
    同时,这样也就不一定连续了

  • 当client或者server挂掉
    此时重启之后,如果没有记录之前的数据, 则之前获取的号段未使用部分,相当于浪费掉了,因为已经存放到db中去了

  • 适用场景
    只关心id是数字,趋势递增的系统,可以容忍id不连续,有浪费的场景
    支持批量获取id

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

参考资料

https://github.com/didi/tinyid/wiki/tinyid%E5%8E%9F%E7%90%86%E4%BB%8B%E7%BB%8D
https://github.com/didi/tinyid/wiki
以及我自己的源码解读, 链接在本文顶部

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值