当前位置: 首页 > 科技观察

滴滴的分布式ID生成器(Tinyid)对一群不懂分布式ID生成器的

时间:2023-03-18 00:08:33 科技观察

同学很有用。回顾一下之前的《9种分布式ID生成方式》Tinyid是滴滴开发的分布式ID系统。,tinyid基于美团(Leaf)的leaf-segment算法进行升级。不仅支持数据库的多主节点模式,还提供了tinyid-client客户端的访问方式,使用更方便。但与美团(Leaf)不同的是,微信只支持一种号段模式,不支持雪花模式。Tinyid的特点全球唯一长型ID趋势增加的id提供通过http和java-client访问支持批量获取ID支持生成1,3,5,7,9...序列ID支持配置多个dbs适用场景:只关心ID个数和递增趋势的系统可以容忍ID不连续和ID浪费不适用:对于订单ID这样的业务,生成的ID大多是连续的,容易被扫描,或者计算订单数量和其他信息。Tinyid的原理是基于数段模式。简单说明一下数段方式的原理:就是批量从数据库中获取自增ID,每次从数据库中取出一个范围的数段,例如(1,1000]代表1000个ID.业务服务会在本地生成1~1000个自增ID并加载到内存中.tinyid会将可用的数字段加载到内存中并在内存中生成ID.可用的数字段在获取到ID时加载第一次使用,如果当前号码段使用到一定比例,系统会异步加载下一个可用号码段,保证内存中一直有可用号码段,以便号码后发布服务宕机一段时间时间内还有可用的ID,示意图大致如下:Tinyid示意图Tinyid实现Tinyid的GitHub地址:https://github.com/didi/tinyid.gitTinyid提供了两种调用方式,一种是基于Tinyid-server的http方式,另一种是Tinyid-client客户端方式。无论使用哪种方法调用,都必须提前创建tiny_id_info和tiny_id_token表来构建Tinyid。CREATETABLE`tiny_id_info`(`id`bigint(20)unsignedNOTNULLAUTO_INCREMENTCOMMENT'自增主键',`biz_type`varchar(63)NOTNULLDEFAULT''COMMENT'业务类型,唯一',`begin_id`bigint(20)NOTNULLDEFAULT'0'COMMENT'Startid,只记录初始值,无其他含义。初始化时begin_id和max_id要一致',`max_id`bigint(20)NOTNULLDEFAULT'0'COMMENT'当前最大id',`step`int(11)DEFAULT'0'COMMENT'步长',`delta`int(11)NOTNULLDEFAULT'1'COMMENT'everyidincrement',`remainder`int(11)NOTNULLDEFAULT'0'COMMENT'remainder',`create_time`timestampNOTNULLDEFAULT'2010-01-0100:00:00'COMMENT'creationtime',`update_time`timestampNOTNULLDEFAULT'2010-01-0100:00:00'COMMENT'更新时间',`version`bigint(20)NOTNULLDEFAULT'0'COMMENT'版本号',PRIMARYKEY(`id`),UNIQUEKEY`uniq_biz_type`(`biz_type`))ENGINE=InnoDBAUTO_INCREMENT=1DEFAULTCHARSET=utf8COMMENT'id信息表';CREATETABLE`tiny_id_token`(`id`int(11)unsignedNOTNULLAUTO_INCREMENTCOMMENT'自增id',`token`varchar(255)NOTNULLDEFAULT''COMMENT'token',`biz_type`varchar(63)NOTNULLDEFAULT''COMMENT'业务此令牌可以访问的类型标识符',`remark`varchar(255)NOTNULLDEFAULT''COMMENT'remark',`create_time`timestampNOTNULLDEFAULT'2010-01-0100:00:00'COMMENT'创建时间',`update_time`timestampNOTNULLDEFAULT'2010-01-0100:00:00'COMMENT'更新时间',PRIMARYKEY(`id`))ENGINE=InnoDBAUTO_INCREMENT=1DEFAULTCHARSET=utf8COMMENT'代币信息表';INSERTINTO`tiny_id_info`(`id`,`biz_type`,`begin_id`,`max_id`,`step`,`delta`,`remainder`,`create_time`,`update_time`,`version`)VALUES(1,'test',1,1,100000,1,0,'2018-07-2123:52:58','2018-07-2223:19:27',1);INSERTINTO`tiny_id_info`(`id`,`biz_type`,`begin_id`,`max_id`,`step`,`delta`,`remainder`,`create_time`,`update_time`,`version`)VALUES(2,'test_odd',1,1,100000,2,1,'2018-07-2123:52:58','2018-07-2300:39:24',3);插入`tiny_id_token`(`id`,`token`,`biz_type`,`remark`,`create_time`,`update_time`)VALUES(1,'0f673adf80504e2eaa552f5d791b644c','test','1','2017-12-1416:36:46','2017-12-1416:36:48');INSERTINTO`tiny_id_token`(`id`,`token`,`biz_type`,`remark`,`create_time`,`update_time`)VALUES(2,'0f673adf80504e2eaa552f5d791b644c','test_odd','1','2017-12-1416:36:46','2017-12-1416:36:48');tiny_id_info表为具体业务方号码段的信息数据表max_id:号码段的最大值step:步长,为号码段的长度biz_type:获取业务类型号码段并进行更新操作对max_id字段进行,updatemax_id=max_id+step,如果更新成功,则表示新号码段获取成功,新号码段范围为(max_id,max_id+step]tiny_id_token为权限表,表示当前token可以操作哪些服务的段数信息。修改tinyid-server中的\offline\application.properties文件配置数据库。由于tinyid支持数据库多主模式,可以配置多个数据库信息。启动TinyIdServerApplication来测试它。datasource.tinyid.primary.driver-class-name=com.mysql.jdbc.Driverdatasource.tinyid.primary.url=jdbc:mysql://127.0.0.1:3306/xin-master?autoReconnect=true&useUnicode=true&characterEncoding=UTF-8datasource.tinyid.primary.username=junkangdatasource.tinyid.primary.password=junkangdatasource.tinyid.primary.testOnBorrow=falsedatasource.tinyid.primary.maxActive=10datasource.tinyid.secondary.driver-class-name=com.mysql.jdbc。Driverdatasource.tinyid.secondary.url=jdbc:mysql://localhost:3306/db2?autoReconnect=true&useUnicode=true&characterEncoding=UTF-8datasource.tinyid.secondary.username=rootdatasource.tinyid.secondary.password=123456datasource.tinyid.secondary。testOnBorrow=falsedatasource.tinyid.secondary.maxActive=101,Http方式tinyid一共提供了4个http接口获取ID和号码段。packagecom.xiaoju.uemc.tinyid.server.controller;/***@authordu_imba*/@RestController@RequestMapping("/id/")publicclassIdContronller{privatestaticfinalLoggerlogger=LoggerFactory.getLogger(IdContronller.class);@AutowiredprivateIdGeneratorFactoryServeridGeneratorFactoryServer;@AutowiredprivateSegmentIdServicesegmentIdService;@AutowiredprivateTinyIdTokenServicetinyIdTokenService;@Value("${batch.size.max}")privateIntegerbatchSizeMax;@RequestMapping("nextId")publicResponse>nextId(StringbizType,IntegerbatchSize,Stringtoken){Response>响应=newResponse<>();try{IdGeneratoridGenerator=idGeneratorFactoryServer.getIdGenerator(bizType);Listids=idGenerator.nextId(newBatchSize);response.setData(ids);}catch(Exceptione){response.setCode(ErrorCode.SYS_ERR.getCode());response.setMessage(e.getMessage());logger.error("nextIderror",e);}returnresponse;}@RequestMapping("nextIdSimple")publicStringnextIdSimple(StringbizType,IntegerbatchSize,Stringtoken){Stringresponse="";try{IdGeneratoridGenerator=idGeneratorFactoryServer.getIdGenerator(bizType);if(newBatchSize==1){Longid=idGenerator.nextId();response=id+"";}else{ListidList=idGenerator.nextId(newBatchSize);StringBuildersb=newStringBuilder();for(Longid:idList){sb.append(id).append(",");}response=sb.deleteCharAt(sb.length()-1).toString();}}catch(Exceptione){logger.error("nextIdSimpleerror",e);}returnresponse;}@RequestMapping("nextSegmentId")publicResponsenextSegmentId(StringbizType,Stringtoken){try{SegmentIdsegmentId=segmentIdService.getNextSegmentId(bizType);response.setData(segmentId);}catch(Exceptione){response.setCode(ErrorCode.SYS_ERR.getCode());response.setMessage(e.getMessage());logger.error("nextSegmentIderror",e);}returnresponse;}@RequestMapping("nextSegmentIdSimple")publicStringnextSegmentIdSimple(StringbizType,Stringtoken){Stringresponse="";尝试{SegmentIdsegmentId=segmentIdService.getNextSegmentId(bizType);response=segmentId.getCurrentId()+","+segmentId.getLoadingId()+","+segmentId.getMaxId()+","+segmentId.getDelta()+","+segmentId.getRemainder();}catch(Exceptione){logger.error("nextSegmentIdSimpleerror",e);}returnresponse;}}nextId,nextIdSimple是获取下一个ID,nextSegmentIdSimple,getNextSegmentId是获取下一个可用的号码段,区别在于接口是否有返回状态nextId:'http://localhost:9999/tinyid/id/nextId?bizType=test&token=0f673adf80504e2eaa552f5d791b644c'response:{"data":[2],"code":200,"message":""}nextIdSimple:'http://localhost:9999/tinyid/id/nextIdSimple?bizType=test&token=0f673adf80504e2eaa552bf64c79'respond79:32、tinyid-client客户端如果不想使用http,tinyid-client客户端也是不错的选择。参考tinyid-server包com.xiaoju.uemc.tinyidtinyid-client${tinyid.version}启动tinyid-server项目打包得到tinyid-server-0.1.0-SNAPSHOT.jar,版本${tinyid.version}设置为0.1.0-SNAPSHOT。在我们的项目application.properties中配置tinyid-server服务的请求地址和用户身份tokentinyid.server=127.0.0.1:9999tinyid.token=0f673adf80504e2eaa552f5d791b644c```在Java代码中调用TinyId也很简单,只需一行代码是必需的。//根据业务类型获取单个IDLongid=TinyId.nextId("test");//根据业务类型批量获取10个IDListids=TinyId.nextId("test",10);整个Tinyid项目的源码实现也比较简单,比如和数据库交互更直接使用jdbcTemplate实现@OverridepublicTinyIdInfoqueryByBizType(StringbizType){Stringsql="selectid,biz_type,begin_id,max_id,"+"step,delta,remainder,create_time,update_time,version"+"fromtiny_id_infowherebiz_type=?";Listlist=jdbcTemplate.query(sql,newObject[]{bizType},newTinyIdInfoRowMapper());if(list==null||list.isEmpty()){returnull;}returnlist.get(0);}综上所述,两种方法都推荐使用Tinyid-client。在此方法中,ID是在本地生成的。步长(step)越长,支持的qps越大。如果step设置的足够大,qps可以达到1000w+。而且tinyid-client访问tinyid-server的频率变低了,减轻了server端的压力。