网站编程设计心得体会,文件管理系统,wordpress博客迁移,成都制作开发小程序文章目录 1. 分布式ID2. 数据库主键自增3. 数据库号段模式4. Redis自增5. UUID6. Snowflake (雪花算法)7. Leaf (美团分布式ID生成系统)7.1 Leaf-segment 号段方案7.1.2 双buffer优化 7.2 Leaf-snowflake方案7.3 Leaf-snowflake Demo 1. 分布式ID
在分布式系统中#xff0c;通… 文章目录 1. 分布式ID2. 数据库主键自增3. 数据库号段模式4. Redis自增5. UUID6. Snowflake (雪花算法)7. Leaf (美团分布式ID生成系统)7.1 Leaf-segment 号段方案7.1.2 双buffer优化 7.2 Leaf-snowflake方案7.3 Leaf-snowflake Demo 1. 分布式ID
在分布式系统中通常都需要对大量数据和消息进行唯一标识这个表示通常被称为分布式ID。
分布式ID是用于识别不同实体或数据对象的这就要求分布式ID必须具有全局唯一性不能出现重复的ID。
并且由于在复杂的分布式系统下分布式ID使用的场景很多这就要求分布式ID的生成速度应该足够快并且对本地资源消耗小。除此之外生成分布式ID必须是高可用的因为分布式ID关联着众多系统必须要求分布式ID的生成的服务可用性无限趋向于100%。 在业务方面ID通常有以下两个需求但是这两个需求是互斥的 单调递增在某些业务场景ID必须是单调递增的也就是上一个ID要比下一个ID要小。例如MySQL事务的版本号。无规则上述的这种ID生成策略如果遇到恶意的人就能从ID号得到信息。比如存储图片进MySQL的时候图片的重名是ID自增的那么别人就可以恶意猜测到下一张图片的URL是啥再比如我们所熟悉的订单号如果订单号是递增的那么别人就可以知道一天的单量。 现如今分布式ID的生成方案有很多
数据库主键自增Redis自增UUIDSnowflake (雪花算法)Leaf (美团分布式ID生成系统)uid-generator (百度分布式ID生成系统)Tinyid (滴滴分布式ID生成系统)
本文重点介绍前五种后两种以后有机会再做介绍~ 2. 数据库主键自增
在MySQL数据库我们可以通过创建一个具有主键ID自增字段的表。
每次向数据库中插入一条数据那么数据库插入的记录的ID就会自增ID。
接着将这个操作抽象成一个服务那么这就是一个简单的分布式ID生成服务了。
这样的方式实现的分布式ID系统显而易见的简单优点也是简单方便。
但是这样的实现方式会存在以下缺点
并发性能并不好受限于MySQL的性能当数据量上来的时候需要进行分库分表操作起来复杂 3. 数据库号段模式
在前面这种通过数据库自增的方式生成ID每次都需要访问一次数据库很容易受到数据库上限导致并发低。
因此可以改造为批量获取然后存进内存里需要用到的时候直接在内存拿即可。
这就是数据库的号段模式每次请求分配一个号段号段模式相比主键自增性能有一定提升。 4. Redis自增
Redis可以通过自增命令INCR将某一个Key进行自增。
并且这一过程是线程安全的利用这些特性我们就可以实现一个分布式ID生成系统也可以在分布式系统中使用。
这样的分布式ID生成系统可用性依赖于Redis。
虽然Redis有AOF与RDB持久化但是依然会存在数据丢失问题。一旦数据发送丢失那么就可以出现ID重复问题但是系统出现异常。 5. UUID
UUID是通用唯一标识符Universally Unique Identifier的缩写。它是由数字和字母组成的32位字符串用于在计算机系统中唯一地标识实体或资源。UUID的生成算法能够保证在正常情况下几乎不会生成重复的标识符。
UUID中包含了网卡MAC地址、时间戳、名字空间Namespace、随机或伪随机数、时序等元素其就是利用这些元素生成UUID的。
UUID的优点在于UUID的生成性能非常高因为UUID是本地生成的没有任何网络消耗。
不过UUID也存在以下缺点
不易存储UUID包含了32个16进制的数字形成8-4-4-4-12的36个字符生成的ID太长在很多场景不适用信息不安全UUID看似无规则实际其是基于MAC地址生成的因此有可能泄漏MAC地址MAC地址是可以被用到定位位置的不适用于MySQL主键在MySQL官方文档中提到主键的长度应该越短越好 6. Snowflake (雪花算法)
Snowflake也称雪花算法是一种用于生成分布式系统中唯一ID的算法。它是Twitter公司开发的目的是为了解决分布式系统中高并发场景下生成ID的问题。
Snowflake算法生成的ID是一个64位整数其中包含41位的时间戳、10位的机器ID和12位的序列号。 41-bit的时间可以表示1L41/(1000L360024*365)69年的时间10-bit机器可以分别表示1024台机器。如果我们对IDC划分有需求还可以将10-bit分5-bit给IDC分5-bit给工作机器。这样就可以表示32个IDC每个IDC下可以有32台机器可以根据自身需求定义。12个自增序列号可以表示2^12个ID理论上snowflake方案的QPS约为409.6w/s这种分配方式可以保证在任何一个IDC的任何一台机器在任意毫秒内生成的ID都是不同的。
由于前41为是时间戳也就是毫秒数因此雪花算法生成的ID是趋势自增的。
雪花算法不依赖与第三方系统以服务的方式部署稳定性更高生成ID的性能也是非常高的。
另外可以根据业务特性分配bit位非常灵活。
当然雪花算法也存在缺点那就是雪花算法强依赖机器时钟如果机器上时钟回拨会导致发号重复或者服务会处于不可用状态。
这是一个雪花算法生成的工具类是基于hutool工具类生成的
Component
public class SnowFlake{private Snowflake snowflake;PostConstructpublic void init() {// 0 ~ 31 位可以采用配置的方式使用long workerId;try {workerId NetUtil.ipv4ToLong(NetUtil.getLocalhostStr());} catch (Exception e) {workerId NetUtil.getLocalhostStr().hashCode();}workerId workerId 16 31;long dataCenterId 1L;snowflake IdUtil.createSnowflake(workerId, dataCenterId);}public synchronized long nextId() {return snowflake.nextId();}}7. Leaf (美团分布式ID生成系统) Leaf 是美团开源的一个分布式 ID 解决方案。提供了号段模式 和 Snowflake这两种模式来生成分布式 ID。
Leaf 具有高可靠、低延迟、全局唯一的特点。 7.1 Leaf-segment 号段方案
Leaf-segment 号段方案是基于数据库自增方案做的改进。
在数据库自增方案中每次获取新的ID都需要请求一次数据库会造成数据库压力很大。
而在Leaf-segment 号段方案中改为利用proxy server批量获取每次获取一个segment(step决定大小)号段的值。用完之后再去数据库获取新的号段可以大大的减轻数据库的压力。
在使用 Leaf-segment 号段方案需要建立DB表
CREATE DATABASE leaf
CREATE TABLE leaf_alloc (biz_tag varchar(128) NOT NULL DEFAULT ,max_id bigint(20) NOT NULL DEFAULT 1,step int(11) NOT NULL,description varchar(256) DEFAULT NULL,update_time timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,PRIMARY KEY (biz_tag)
) ENGINEInnoDB;insert into leaf_alloc(biz_tag, max_id, step, description) values(leaf-segment-test, 1, 2000, Test leaf Segment Mode Get Id)biz_tag是用于区分业务的 max_id表示该biz_tag目前所被分配的ID号段的最大值 step表示每次分配的号段长度 原来获取ID每次都需要写数据库现在只需要把step设置得足够大比如1000。那么只有当1000个号被消耗完了之后才会去重新读写一次数据库。读写数据库的频率从1减小到了1/step test_tag在第一台Leaf机器上是11000的号段当这个号段用完时会去加载另一个长度为step1000的号段假设另外两台号段都没有更新这个时候第一台机器新加载的号段就应该是30014000。同时数据库对应的biz_tag这条数据的max_id会从3000被更新成4000更新号段的SQL语句如下 Begin
UPDATE table SET max_idmax_idstep WHERE biz_tagxxx
SELECT tag, max_id, step FROM table WHERE biz_tagxxx
Commit并配置leaf.jdbc.url, leaf.jdbc.username, leaf.jdbc.password
如果不想使用该模式配置leaf.segment.enablefalse即可。 Leaf-segment 号段方案有以下优缺点
优点
Leaf服务可以很方便的线性扩展性能完全能够支撑大多数业务场景。ID号码是趋势递增的8byte的64位数字满足上述数据库存储的主键要求。容灾性高Leaf服务内部有号段缓存即使DB宕机短时间内Leaf仍能正常对外提供服务。可以自定义max_id的大小非常方便业务从原有的ID方式上迁移过来。
缺点
ID号码不够随机能够泄露发号数量的信息不太安全。TP999数据波动大当号段使用完之后还是会hang在更新数据库的I/O上tg999数据会出现偶尔的尖刺。DB宕机会造成整个系统不可用。 7.1.2 双buffer优化
针对第二个缺点TP999数据波动大当号段使用完之后还是会hang在更新数据库的I/O上tg999数据会出现偶尔的尖刺。
这其中的耗时主要体现在Leaf取号时机是在号段消耗完进行的也就意味着号段临界点的ID下发时间取决于下一次从DB取回号段的时间也就是这段时间内会导致线程阻塞倘若请求DB的网络和性能不稳定会导致整体响应时间变慢。
优化思路也很简单就是希望DB取号段的过程做到无阻塞也就是在号段消费到某个程度的时候就异步将下一个号段加载进内存中而不是等待号段消耗完成才去更新号段。 什么是TP999 TP90就是满足百分之九十的网络请求所需要的最低耗时。 TP99就是满足百分之九十九的网络请求所需要的最低耗时。 同理TP999就是满足千分之九百九十九的网络请求所需要的最低耗时。 美团Leaf对此的优化是采用双Buffer的方式Leaf服务内部有两个号段缓存区segment。当前号段已下发10%时如果下一个号段未更新则另启一个更新线程去更新下一个号段。当前号段全部下发完后如果下个号段准备好了则切换到下个号段为当前segment接着下发循环往复。
每个biz-tag都有消费速度监控通常推荐segment长度设置为服务高峰期发号QPS的600倍10分钟这样即使DB宕机Leaf仍能持续发号10-20分钟不受影响。每次请求来临时都会判断下个号段的状态从而更新此号段所以偶尔的网络抖动不会影响下个号段的更新。 7.2 Leaf-snowflake方案
Leaf-segment 号段方案生成的ID是递增的并不适用与订单场景。
于是Leaf还提供了Leaf-snowflake方案。 Leaf-snowflake方案沿用了Snowflake 的设计思想也就1411012的方式装订ID。 对于workerID的分配当服务集群数量较小的情况下完全可以手动配置。Leaf服务规模较大动手配置成本太高。所以使用Zookeeper持久顺序节点的特性自动对snowflake节点配置wokerID。Leaf-snowflake是按照下面几个步骤启动的
启动Leaf-snowflake服务连接Zookeeper在leaf_forever父节点下检查自己是否已经注册过是否有该顺序子节点。如果有注册过直接取回自己的workerIDzk顺序节点生成的int类型ID号启动服务。如果没有注册过就在该父节点下面创建一个持久顺序节点创建成功后取回顺序号当做自己的workerID号启动服务。 该方式除了每次会去ZK拿数据以外也会在本机文件系统上缓存一个workerID文件。这样的好处在于ZK出现问题的时候能确保服务能够正常启动这样使得Leaf-snowflake弱依赖于第三方组件。 那么Leaf-snowflake是如何解决时钟问题呢 snowflake算法当时钟回拨的时候有可能出现重复ID的情况在Leaf-snowflake是这也解决时钟问题的。 服务会先检查自己是否写过ZooKeeper leaf_forever节点如果写过则用自身系统时间与leaf_forever/${self}节点记录时间做比较如果小于则认为机器时间发生回拨服务启动失败并报警如果没写过证明是新服务节点直接创建持久节点leaf_forever/${self}并写入自身系统时间接下来综合对比其余Leaf节点的系统时间来判断自身系统时间是否准确具体做法是取leaf_temporary下的所有临时节点(所有运行中的Leaf-snowflake节点)的服务IPPort然后通过RPC请求得到所有节点的系统时间计算sum(time)/nodeSize。若abs( 系统时间-sum(time)/nodeSize ) 阈值认为当前系统时间准确正常启动服务同时写临时节点leaf_temporary/${self} 维持租约。否则认为本机系统时间发生大步长偏移启动失败并报警。每隔一段时间(3s)上报自身系统时间写入leaf_forever/${self}。 由于强依赖时钟对时间的要求比较敏感在机器工作时NTP同步也会造成秒级别的回退建议可以直接关闭NTP同步。要么在时钟回拨的时候直接不提供服务直接返回ERROR_CODE等时钟追上即可。或者做一层重试然后上报报警系统更或者是发现有时钟回拨之后自动摘除本身节点并报警如下 //发生了回拨此刻时间小于上次发号时间if (timestamp lastTimestamp) {long offset lastTimestamp - timestamp;if (offset 5) {try {//时间偏差大小小于5ms则等待两倍时间wait(offset 1);//waittimestamp timeGen();if (timestamp lastTimestamp) {//还是小于抛异常并上报throwClockBackwardsEx(timestamp);} } catch (InterruptedException e) { throw e;}} else {//throwthrowClockBackwardsEx(timestamp);}}//分配ID 7.3 Leaf-snowflake Demo
想要使用Leaf-snowflake方案首先需要上GitHub将项目clone下来
美团Leaf下载
然后安装并启动Zookeeper这里可以参考这个博客Zookeeper 安装(Windows)_zookeeper windows安装_coder i的博客-CSDN博客 完成后配置leaf.properties因为这里使用的snowflake 方案所以leaf.segment.enable设置为falseleaf.snowflake.enable设置为true并配置Zookeeper的地址和端口
leaf.namecom.sankuai.leaf.opensource.test
leaf.segment.enablefalse
#leaf.jdbc.url
#leaf.jdbc.username
#leaf.jdbc.passwordleaf.snowflake.enabletrue
leaf.snowflake.zk.address127.0.0.1
leaf.snowflake.port2181启动leaf-server服务默认端口为8080。但是由于这里我冲突了所以我将端口设置为8081。 浏览器访问(http://127.0.0.1:8081/api/snowflake/get/leaf-test)就会得到一个ID 对应的调用就是LeafController的getSnowflakeId方法这里需要传入一个Key这个Key可以是用到这个分布式ID生成的服务表示这样可以做到不同模块之间的分布式ID隔离。 Leaf的简单使用就到这里了在项目中可以将该项目单独为一个服务使用OpenFeign或是Dubbo等RPC框架远程调用获取接口。 参考 Leaf——美团点评分布式ID生成系统 - 美团技术团队 (meituan.com)面试总被问分布式ID 美团Leaf了解一下 - 掘金 (juejin.cn)不能错过的分布式ID生成器Leaf 好用的一批 - 掘金 (juejin.cn)分布式ID生成服务的技术原理和项目实战 - 掘金 (juejin.cn)【分布式系统】10种分布式唯一ID生成方案总结 - 掘金 (juejin.cn)常见分布式ID解决方案总结数据库、算法、开源组件 - 掘金 (juejin.cn)