在分布式系统中,有一些场景需要全局唯一的ID。在这种情况下,可以使用36位UUID来防止ID冲突。但是,UUID有一些缺点。首先,它们相对较长。此外,UUID通常是无序的。有时我们想使用更简单的ID,希望ID能够及时顺序生成。什么是雪花算法?Snowflake中文意思是雪花,所以常被称为雪花算法。算法TwitterSnowflake算法生成后是一个64位的long值,组成部分引入了时间戳,基本保持了自增SnowFlake算法的优点:高性能高可用:不依赖生成时的数据库,完全在内存中生成高位数据。吞吐量:每秒可生成百万个自增ID自增:存储在数据库中,索引效率高可能会导致ID冲突或重复的雪花算法形成如下图所示的雪花结构:包含四个未使用的组件:1bit,最高位为符号位,0表示正数,1表示负数,固定为0Timestamp:41bit,timeinmillisecondsStamp(41-bitlength可以用69年)identificationbit:5bitdatacenterID,5bitworkingmachineID,两个identificationbit的组合最多可以支持部署1024个节点Serialnumber:12bit增量序列号,表示节点在毫秒内重复生成,序列号是唯一的,12bit每毫秒可以生成4096个ID,序列号可以生成4096个唯一IDs在1毫秒内,因此4096*1000=409w个ID可以在1秒内生成。默认的雪花算法是64位,具体长度可以自己配置。如果想运行更长时间,增加时间戳的位数;如果需要支持更多的节点部署,增加标识位的长度;如果并发高,增加序号的位数场景定制雪花算法适用于场景因为雪花算法是有序自增的,保证了B+Tree索引结构插入的高性能MySQL。因此,在日常业务使用中,雪花算法更多的应用于数据库的主键ID和业务关联的主键雪花算法生成ID重复问题假设:一个订单微服务通过雪花算法生成ID,部署共三个节点,标识位一致。此时有200个并发,三个节点均匀分布。三个节点在相同的毫秒数和相同的序号下生成ID,会产生重复的ID。通过上面的假设场景,我们可以知道雪花算法产生ID冲突是有一定前提条件的。服务部署在集群中,部分机器标识位是一致的。业务有一定的并发量。没有并发量,就无法触发复制。问题ID生成时机:如何定义相同毫秒下序列号的一致标识位如果可以保证标识位不重复,那么SnowflakeID就不会重复通过上面的案例,我们知道了必要的条件用于ID重复。如果要避免服务中出现重复的ID,需要从标识位移开。我们先看看如何使用开源框架中的雪花算法来定义标识位。Mybatis-Plusv3.4.2雪花算法实现类Sequence提供了两种构造方法:不带参构造,自动生成dataCenterId和workerId;带参数构造,在创建Sequence时明确指定标识位Hutoolv5.7.9参考了Mybatis-PlusdataCenterId和workerId的生成方案,并提供了默认实现。无参数构造,如何生成dataCenterId和workerIdpublicstaticlonggetDataCenterId(longmaxDatacenterId){longid=1L;finalbyte[]mac=NetUtil.getLocalHardwareAddress();if(null!=mac){id=((0x000000FF&(long)mac[mac.length-2])|(0x0000FF00&(((long)mac[mac.length-1])<<8)))>>6;id=id%(maxDatacenterId+1);}returnid;}复制代码输入参数maxDatacenterId是一个固定值,代表数据中心ID的最大值,默认值为31。为什么最大值是31?因为5bit的二进制最大值为11111,所以获取十进制值31对应的dataCenterId有两种情况,一种是网络接口为空,默认值为1L;另一个不为空。通过Mac地址可以知道dataCenterId值与Mac地址有关。接下来看workerIdpublicstaticlonggetWorkerId(longdatacenterId,longmaxWorkerId){finalStringBuildermpid=newStringBuilder();mpid.append(datacenterId);try{mpid.append(RuntimeUtil.getPid());}catch(UtilExceptionigonre){//ignore}return(mpid.toString().hashCode()&0xffff)%(maxWorkerId+1);}复制代码入口参数maxWorkderId也是一个固定值,代表工作机器ID的最大值,默认值为31;datacenterId是从上面的getDatacenterId方法中取的name变量值是PID@IP,所以name需要根据@进行分割,下标0得到PID,通过MAC+PID的hashcode得到低16位,执行计算,最终得到workerId分配标志。Mybatis-Plusflag的获取依赖于Mac地址和进程PID。虽然可以尽量做到不重复,但还是有小概率的。flag怎么定义能不能不重复?有两种解决方案:预分配和动态分配。预分配申请上线前,统计当前服务的节点数,手动申请标识位。该方案无代码开发量,服务节点固定或项目少时可以使用,但无法解决。服务节点的动态可扩展性问题。动态分配将标识位存储在Redis、Zookeeper、MySQL等中间件中,在服务启动时请求标识位。在请求之后,标识位被更新为下一个可用的。通过存储标识位,扩展一个问题:雪花算法的ID是服务内唯一还是全局唯一?以Redis为例,如果想在服务内唯一,可以使用自己项目中存储标识位的Redis节点;如果是全局唯一的,所有使用雪花算法的应用,都使用同一个Redis节点,两者的区别在于不同服务是否共享Redis。如果没有全局唯一的要求,最好让ID在服务内唯一,这样可以避免单点问题。服务节点数超过1024个,则需要额外扩展;可以扩展10位标识位,或者选择开源的分布式ID框架动态分配实现方案。Redis存储了一个Hash结构Key,其中包含两个键值对:dataCenterId和workerId。当应用程序启动时,Redis通过Lua脚本获取标识位。dataCenterId和workerId的获取和自增是在Lua脚本中完成的。调用返回后,可用标志可用。Lua脚本的具体逻辑如下:当获取第一个服务节点时,Redis可能没有snowflake_work_id_key的Hash,首先判断Hash是否存在,初始化Hash,dataCenterId和workerId初始化为0.如果Hash已经存在,则判断dataCenterId和workerId是否等于最大值31,如果满足则将dataCenterId和workerId初始化为0,返回dataCenterId和workerId共1024种排列组合。赋值时,先赋值workerId,判断是否workerId!=31,满足则workerId自增返回;如果workerId=31,自动增加dataCenterId并将workerId设置为0。ring。通过Lua脚本的原子性,保证1024个节点下雪花算法的生成不会重复。如果标识位等于1024,则从头继续循环。开源的分布式ID框架Leaf和Uid都实现了雪花算法。Leaf另外提供数段方式生成美团LeafID:https://github.com/Meituan-Dianping/Leaf百度Uid:https://github.com/baidu/uid-generator雪花算法可以满足大部分场景。如非必要,不建议引入开源方案增加系统的复杂度。最后,如果你觉得这篇文章对你有点帮助,点个赞吧。或者可以加入我的开发交流群:1025263163互相学习,我们会有专业的技术解答。如果您觉得这篇文章对您有用,请给我们的开源项目一个小星星:http://github。crmeb.net/u/defu非常感谢!
