该书来源:https://mp.weixin.qq.com/s/GmfDeMKTeRJ07N3MhgQsIA
译者:开发者Rosenbrock事
一、为什么要用网络通讯ID?
在说网络通讯ID的概要与此同时与此同时实现之前,她们来纯粹预估哈哈为什么用网络通讯ID?网络通讯ID应该满足什么样特征?
1、什么是网络通讯ID?拿MySQL数据库举个栗子:
在她们分销统计数据量并不大的时候,单库茹基夫完全能支撑旧有分销,统计数字再大不要紧搞个MySQLcharacterization博戈达EEPROM并立也能对付。
但随着统计数字不断增长,characterization博戈达也扛不住了,就只须对数据库进行科艾麻户主,但隐脉户主后只须有三个惟一ID来记号一条统计数字,数据库的自增ID或许不能满足需求;特别不要紧的如供货、优惠券也都只须有惟一ID做记号。这时三个能够裂解由上而下惟一ID的系统是非常必要性的。因此这个由上而下惟一ID就叫网络通讯ID。
2、因此网络通讯ID只须满足那些条件?由上而下惟一:必须保证ID是由上而下性惟一的,基本特别强调高性能:高需以低延时,ID裂解鼎力支持要块,要不然反倒会成为分销困境高需以:100%的可用性是没错的,但也要无限吻合于100%的可用性好网络相连:要更加适于用作蔗茅的设计准则,在系统设计和与此同时与此同时实现上要尽可能的纯粹势头递增:最合适势头递增,这个明确要求就得看概要分销情境了,一般不把关二、 网络通讯ID都有什么样裂解方式?
今天主要预估哈哈以下9种,网络通讯ID裂解器方式和好坏:
UUID数据库自增ID数据库多主商业模式Mandsaur商业模式Redis彩虹算法(SnowFlake)Lyft出品(TinyID)百度 (Uidgenerator)携程网(Leaf)因此它们都是如何与此同时与此同时实现?和梅塞县有什么好坏?她们往下看
相片源于网络
1、如前所述UUID
在Java的世界里,想得到三个具有惟一性的ID,首先被想到可能就是UUID,即便它有著全球惟一的优点。因此UUID能做网络通讯ID吗?标准答案是能的,但并不推荐!
publicstaticvoidmain(String[] args){ String uuid = UUID.randomUUID().toString().replaceAll("-",""); System.out.println(uuid); }UUID的裂解纯粹到只有一行代码,输出结果c2b8c2b9e46c47e3b30dca3b0d447718,但UUID却并不适用于实际的分销需求。像用作供货号UUID这样的字符串没有丝毫的意义,看不出和供货相关的有用信息;而对于数据库来说用作分销主键ID,它不仅是太长还是字符串,存储性能差查询也很耗时,因此不推荐用作分布式系统ID。
优点:
裂解足够纯粹,本地裂解无网络消耗,具有惟一性缺点:
无序的字符串,不具备势头自增优点没有概要的分销含义长度过长16 字节128位,36位长度的字符串,存储和查询对MySQL的性能消耗较大,MySQL官方明确建议主键要尽可能越短越好,作为数据库主键 UUID 的无序性会导致统计数字位置频繁变动,严重影响性能。2、如前所述数据库自增ID如前所述数据库的auto_increment自增ID完全能充当网络通讯ID,概要与此同时与此同时实现:只须三个单独的MySQL实例用来裂解ID,建表结构如下:
CREATEDATABASE`SEQ_ID`;CREATETABLESEQID.SEQUENCE_ID (idbigint(20)unsignedNOTNULLauto_increment,valuechar(10)NOTNULLdefault, PRIMARYKEY(id), )ENGINE=MyISAM;insertintoSEQUENCE_ID(value)VALUES(values);当她们只须三个ID的时候,向表中插入一条记录返回主键ID,但这种方式有三个比较致命的缺点,访问量激增时MySQL本身就是系统的困境,用它来与此同时与此同时实现网络通讯服务风险比较大,不推荐!
优点:
与此同时与此同时实现纯粹,ID单调自增,数值类型查询速度快缺点:
DB单点存在宕机风险,无法扛住高并发情境3、如前所述数据库集群商业模式前边说了单点数据库方式不可取,那对上边的方式做一些高需以优化,换成characterization商业模式集群。害怕三个主节点挂掉没法用,那就做双主商业模式集群,也就是三个Mysql实例都能单独的生产自增ID。
那这样还会有个问题,三个MySQL实例的自增ID都从1开始,会裂解重复的ID怎么办?
解决方案:设置起始值和自增步长
MySQL_1 配置:
set@@auto_increment_offset =1;-- 起始值set@@auto_increment_increment =2;-- 步长MySQL_2 配置:
set@@auto_increment_offset =2;-- 起始值set@@auto_increment_increment =2;-- 步长这样三个MySQL实例的自增ID分别就是:
1、3、5、7、92、4、6、8、10
那如果集群后的性能还是扛不住高并发咋办?就要进行MySQL扩容增加节点,这是三个比较麻烦的事。
从上图能看出,水平扩展的数据库集群,有利于解决数据库单点压力的问题,与此同时为了ID裂解优点,将自增步长按照机器数量来设置。
增加第三台MySQL实例只须人工修改一、二两台MySQL实例的起始值和步长,把第三台机器的ID起始裂解位置设定在比旧有最大自增ID的位置远一些,但必须在一、二两台MySQL实例ID还没有增长到第三台MySQL实例的起始ID值的时候,要不然自增ID就要出现重复了,必要性时可能还只须停机修改。
优点:
解决DB单点问题缺点:
不利于后续扩容,而且实际上单个数据库自身压力还是大,依旧无法满足高并发情境。4、如前所述资料库的Mandsaur商业模式Mandsaur商业模式是当下网络通讯ID裂解器的主流与此同时与此同时实现方式之一,Mandsaur商业模式能理解为从数据库批量的获取自增ID,每次从数据库取出三个Mandsaur范围,例如 (1,1000] 代表1000个ID,具体内容的分销服务将本Mandsaur,裂解1~1000的自增ID并加载到内存。表结构如下:
CREATETABLEid_generator (idint(10)NOTNULL, max_idbigint(20)NOTNULLCOMMENT当前最大id, stepint(20)NOTNULLCOMMENTMandsaur的布长, biz_typeint(20)NOTNULLCOMMENT分销类型,versionint(20)NOTNULLCOMMENT版本号, PRIMARYKEY(`id`) )biz_type :代表不同销售业务类型
max_id :当前最大的需以id
step :代表Mandsaur的长度
version :是三个乐观锁,每次都更新version,保证并发时统计数字的正确性
等这批MandsaurID用完,再次向数据库申请新Mandsaur,对max_id字段做一次update操作,update max_id= max_id + step,update成功则说明新Mandsaur获取成功,新的Mandsaur范围是(max_id ,max_id +step]。
updateid_generatorsetmax_id ={max_id+step}, version = version + 1 where version = {version} and biz_type = XXX由于多分销端可能与此同时操作,因此采用版本号version乐观锁方式更新,这种网络通讯ID裂解方式不强依赖于数据库,不会频繁的访问数据库,对数据库的压力小很多。
5、如前所述Redis商业模式Redis也同样能与此同时与此同时实现,原理就是利用redis的 incr命令与此同时与此同时实现ID的原子性自增。
127.0.0.1:6379>setseq_id1//初始化自增ID为1OK127.0.0.1:6379>incrseq_id//增加1,并返回递增后的数值(integer)2用redis与此同时与此同时实现只须注意不要紧,要考虑到redis持久化的问题。redis有两种持久化方式RDB和AOF
RDB会定时打三个快照进行持久化,假如连续自增但redis没及时持久化,而这会Redis挂掉了,重启Redis后会出现ID重复的情况。AOF会对每条写命令进行持久化,即使Redis挂掉了也不会出现ID重复的情况,但由于incr命令的特殊性,会导致Redis重启恢复的统计数字时间过长。6、如前所述彩虹算法(Snowflake)商业模式彩虹算法(Snowflake)是twitter公司内部网络通讯项目采用的ID聚合算法,开源后广受国内大厂的好评,在该算法影响下各大公司相继开发出各具特色的网络通讯裂解器。
以上相片源于网络,如有侵权联系删除
Snowflake裂解的是Long类型的ID,三个Long类型占8个字节,每个字节占8比特,也就是说三个Long类型占64个比特。
Snowflake ID组成结构:正数位(占1比特)+ 时间戳(占41比特)+ 机器ID(占5比特)+ 统计数字中心(占5比特)+ 自增值(占12比特),总共64比特组成的三个Long类型。
第三个bit位(1bit):Java中long的最高位是符号位代表正负,正数是0,负数是1,一般裂解ID都为正数,因此默认为0。时间戳部分(41bit):毫秒级的时间,不建议存当前时间戳,而是用(当前时间戳 - 固定开始时间戳)的差值,能使产生的ID从更小的值开始;41位的时间戳能使用69年,(1L << 41) / (1000L * 60 * 60 * 24 * 365) = 69年工作机器id(10bit):也被叫做workId,这个能灵活配置,机房或者机器号组合都能。序列号部分(12bit),自增值支持同一毫秒内同三个节点能裂解4096个ID根据这个算法的逻辑,只只须将这个算法用Java语言与此同时与此同时实现出来,封装为三个工具方法,因此各个分销应用能直接使用该工具方法来获取网络通讯ID,只需保证每个分销应用有自己的工作机器id即可,而不只须单独去搭建三个获取网络通讯ID的应用。
Java版本的Snowflake算法与此同时与此同时实现:
/** * Twitter的SnowFlake算法,使用SnowFlake算法裂解三个整数,然后转化为62进制变成三个短地址URL * * https://github.com/beyondfengyu/SnowFlake */publicclassSnowFlakeShortUrl{/** * 起始的时间戳 */privatefinalstaticlongSTART_TIMESTAMP =1480166465631L;/** * 每一部分占用的位数 */privatefinalstaticlongSEQUENCE_BIT =12;//序列号占用的位数privatefinalstaticlongMACHINE_BIT =5;//机器记号占用的位数privatefinalstaticlongDATA_CENTER_BIT =5;//统计数字中心占用的位数/** * 每一部分的最大值 */privatefinalstaticlongMAX_SEQUENCE =-1L^ (-1L<< SEQUENCE_BIT);privatefinalstaticlongMAX_MACHINE_NUM =-1L^ (-1L<< MACHINE_BIT);privatefinalstaticlongMAX_DATA_CENTER_NUM =-1L^ (-1L<< DATA_CENTER_BIT);/** * 每一部分向左的位移 */privatefinalstaticlongMACHINE_LEFT = SEQUENCE_BIT;privatefinalstaticlongDATA_CENTER_LEFT = SEQUENCE_BIT + MACHINE_BIT;privatefinalstaticlongTIMESTAMP_LEFT = DATA_CENTER_LEFT + DATA_CENTER_BIT;privatelongdataCenterId;//统计数字中心privatelongmachineId;//机器记号privatelongsequence =0L;//序列号privatelonglastTimeStamp =-1L;//上一次时间戳privatelonggetNextMill(){longmill = getNewTimeStamp();while(mill <= lastTimeStamp) { mill = getNewTimeStamp(); }returnmill; }privatelonggetNewTimeStamp(){returnSystem.currentTimeMillis(); }/** * 根据指定的统计数字中心ID和机器标志ID裂解指定的序列号 * * @param dataCenterId 统计数字中心ID * @param machineId 机器标志ID */publicSnowFlakeShortUrl(longdataCenterId,longmachineId){if(dataCenterId > MAX_DATA_CENTER_NUM || dataCenterId <0) {thrownewIllegalArgumentException("DtaCenterId cant be greater than MAX_DATA_CENTER_NUM or less than 0!"); }if(machineId > MAX_MACHINE_NUM || machineId <0) {thrownewIllegalArgumentException("MachineId cant be greater than MAX_MACHINE_NUM or less than 0!"); }this.dataCenterId = dataCenterId;this.machineId = machineId; }/** * 产生下三个ID * * @return */publicsynchronizedlongnextId(){longcurrTimeStamp = getNewTimeStamp();if(currTimeStamp < lastTimeStamp) {thrownewRuntimeException("Clock moved backwards. Refusing to generate id"); }if(currTimeStamp == lastTimeStamp) {//相同毫秒内,序列号自增sequence = (sequence +1) & MAX_SEQUENCE;//同一毫秒的序列数已经达到最大if(sequence ==0L) { currTimeStamp = getNextMill(); } }else{//不同毫秒内,序列号置为0sequence =0L; } lastTimeStamp = currTimeStamp;return(currTimeStamp - START_TIMESTAMP) << TIMESTAMP_LEFT//时间戳部分| dataCenterId << DATA_CENTER_LEFT//统计数字中心部分| machineId << MACHINE_LEFT//机器记号部分| sequence;//序列号部分}publicstaticvoidmain(String[] args){ SnowFlakeShortUrl snowFlake =newSnowFlakeShortUrl(2,3);for(inti =0; i < (1<<4); i++) {//10进制System.out.println(snowFlake.nextId()); } } }7、百度(uid-generator)
uid-generator是由百度技术部开发,项目GitHub地址https://github.com/baidu/uid-generator
uid-generator是如前所述Snowflake算法与此同时与此同时实现的,与原始的snowflake算法不同在于,uid-generator支持自定义时间戳、工作机器ID和 序列号 等各部分的位数,而且uid-generator中采用用户自定义workId的裂解策略。
uid-generator只须与数据库配合使用,只须新增三个WORKER_NODE表。当应用启动时会向数据库表中去插入一条统计数字,插入成功后返回的自增ID就是该机器的workId统计数字由host,port组成。
对于uid-generator ID组成结构:
workId,占用了22个bit位,时间占用了28个bit位,序列化占用了13个bit位,只须注意的是,和原始的snowflake不太一样,时间的单位是秒,而不是毫秒,workId也不一样,而且同一应用每次重启就会消费三个workId。
参考文献https://github.com/baidu/uid-generator/blob/master/README.zh_cn.md
8、携程网(Leaf)Leaf由携程网开发,github地址:https://github.com/Meituan-Dianping/Leaf
Leaf与此同时支持Mandsaur商业模式和snowflake算法商业模式,能切换使用。
Mandsaur商业模式先导入源码https://github.com/Meituan-Dianping/Leaf ,在建一张表leaf_alloc
DROPTABLEIFEXISTS`leaf_alloc`;CREATETABLE`leaf_alloc`(`biz_tag`varchar(128)NOTNULLDEFAULTCOMMENT分销key,`max_id`bigint(20)NOTNULLDEFAULT1COMMENT当前已经分配了的最大id,`step`int(11)NOTNULLCOMMENT初始步长,也是动态调整的最小步长,`description`varchar(256)DEFAULTNULLCOMMENT分销key的描述,`update_time`timestampNOTNULLDEFAULTCURRENT_TIMESTAMPONUPDATECURRENT_TIMESTAMPCOMMENT数据库维护的更新时间, PRIMARYKEY(`biz_tag`) )ENGINE=InnoDB;然后在项目中开启Mandsaur商业模式,配置对应的数据库信息,并关闭snowflake商业模式
leaf.name=com.sankuai.leaf.opensource.testleaf.segment.enable=trueleaf.jdbc.url=jdbc:mysql://localhost:3306/leaf_test?useUnicode=true&characterEncoding=utf8&characterSetResults=utf8leaf.jdbc.username=rootleaf.jdbc.password=rootleaf.snowflake.enable=falseleaf.snowflake.zk.address=leaf.snowflake.port=启动leaf-server 模块的 LeafServerApplication项目就跑起来了
Mandsaur商业模式获取网络通讯自增ID的测试url :http://localhost:8080/api/segment/get/leaf-segment-test
监控Mandsaur商业模式:http://localhost:8080/cache
snowflake商业模式Leaf的snowflake商业模式依赖于ZooKeeper,不同于原始snowflake算法也主要是在workId的裂解上,Leaf中workId是如前所述ZooKeeper的顺序Id来裂解的,每个应用在使用Leaf-snowflake时,启动时都会都在Zookeeper中裂解三个顺序Id,相当于一台机器对应三个顺序节点,也就是三个workId。
leaf.snowflake.enable=trueleaf.snowflake.zk.address=127.0.0.1leaf.snowflake.port=2181snowflake商业模式获取网络通讯自增ID的测试url:http://localhost:8080/api/snowflake/get/test
9、Lyft(Tinyid)Tinyid由Lyft开发,Github地址:https://github.com/didi/tinyid。
Tinyid是如前所述Mandsaur商业模式原理与此同时与此同时实现的与Leaf如出一辙,每个服务获取三个Mandsaur(1000,2000]、(2000,3000]、(3000,4000]
Tinyid提供http和tinyid-client两种方式网络相连
Http方式网络相连(1)导入Tinyid源码:
git clone https://github.com/didi/tinyid.git
(2)创建统计数字表:
CREATETABLE`tiny_id_info`(`id`bigint(20)unsignedNOTNULLAUTO_INCREMENTCOMMENT自增主键,`biz_type`varchar(63)NOTNULLDEFAULTCOMMENT分销类型,惟一,`begin_id`bigint(20)NOTNULLDEFAULT0COMMENT开始id,仅记录初始值,无其他含义。初始化时begin_id和max_id应相同,`max_id`bigint(20)NOTNULLDEFAULT0COMMENT当前最大id,`step`int(11)DEFAULT0COMMENT步长,`delta`int(11)NOTNULLDEFAULT1COMMENT每次id增量,`remainder`int(11)NOTNULLDEFAULT0COMMENT余数,`create_time`timestampNOTNULLDEFAULT2010-01-01 00:00:00COMMENT创建时间,`update_time`timestampNOTNULLDEFAULT2010-01-01 00:00:00COMMENT更新时间,`version`bigint(20)NOTNULLDEFAULT0COMMENT版本号, PRIMARYKEY(`id`),UNIQUEKEY`uniq_biz_type`(`biz_type`) )ENGINE=InnoDBAUTO_INCREMENT=1DEFAULTCHARSET=utf8COMMENTid信息表;CREATETABLE`tiny_id_token`(`id`int(11)unsignedNOTNULLAUTO_INCREMENTCOMMENT自增id,`token`varchar(255)NOTNULLDEFAULTCOMMENTtoken,`biz_type`varchar(63)NOTNULLDEFAULTCOMMENT此token可访问的分销类型记号,`remark`varchar(255)NOTNULLDEFAULTCOMMENT备注,`create_time`timestampNOTNULLDEFAULT2010-01-01 00:00:00COMMENT创建时间,`update_time`timestampNOTNULLDEFAULT2010-01-01 00:00:00COMMENT更新时间, PRIMARYKEY(`id`) )ENGINE=InnoDBAUTO_INCREMENT=1DEFAULTCHARSET=utf8COMMENTtoken信息表;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-21 23:52:58,2018-07-22 23: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-21 23:52:58,2018-07-23 00:39:24,3);INSERTINTO`tiny_id_token`(`id`,`token`,`biz_type`,`remark`,`create_time`,`update_time`)VALUES(1,0f673adf80504e2eaa552f5d791b644c,test,1,2017-12-14 16:36:46,2017-12-14 16:36:48);INSERTINTO`tiny_id_token`(`id`,`token`,`biz_type`,`remark`,`create_time`,`update_time`)VALUES(2,0f673adf80504e2eaa552f5d791b644c,test_odd,1,2017-12-14 16:36:46,2017-12-14 16:36:48);(3)配置数据库:
datasource.tinyid.names=primarydatasource.tinyid.primary.driver-class-name=com.mysql.jdbc.Driverdatasource.tinyid.primary.url=jdbc:mysql://ip:port/databaseName?autoReconnect=true&useUnicode=true&characterEncoding=UTF-8datasource.tinyid.primary.username=rootdatasource.tinyid.primary.password=123456(4)启动tinyid-server后测试
获取网络通讯自增ID:http://localhost:9999/tinyid/id/nextIdSimple?bizType=test&token=0f673adf80504e2eaa552f5d791b644c返回结果:3批量获取网络通讯自增ID:http://localhost:9999/tinyid/id/nextIdSimple?bizType=test&token=0f673adf80504e2eaa552f5d791b644c&batchSize=10返回结果:4,5,6,7,8,9,10,11,12,13Java客户端方式网络相连
重复Http方式的(2)(3)操作
引入依赖
<dependency><groupId>com.xiaoju.uemc.tinyid