Loading... # UUID生成策略 UUID最好生成12位及以上,按照自定义策略来说,一般前3位是当做申请时间点的标识,后面全都是字母和数字的随机匹配。当公司项目到达一个中等级别的时候,uuid随机位如果只有11-3=7位的话,往往随机碰撞概率就会很高。 <!--more--> ## 一、自定义策略 依据日期生成一个 `12`位的uuid,前3位以字母和数字的组合来表示年月日,剩下 `9`位则以**数字和小写字母**随机生成字符串,如果是更大的项目可以改成**数字+小写字母+大写字母**。 判断生成的key是否在Redis中,不存在则保存到Redis中并且设置过期时间为 `1`天(因为前三位就是按照日期来设定的,所以只需要保证当天不重复即可)。 另外重试机制为 `9`次,如果第9次还失败就停止并抛error,否则抛个warn。 <div class="hideContent">该部分仅登录用户可见</div> ## 二、雪花算法 SnowFlake 算法,是 Twitter 开源的分布式 id 生成算法。满足了高并发下ID不重复,且保证了基本有序递增。  算法产生的是一个long型 64 比特位的值: * 第一个部分, 1 个 bit:0,这个是无意义的。 * 第二个部分, 41 个 bit:表示的是时间戳。 大约可用 `2^41 / (1000*60*60*24*365) ≈ 69`年 * 第三个部分,10 个 bit:表示数据机器位,可以部署在1024个节点。一般拆解成(机房+机器)。 * 第四个部分, 12 个 bit:表示某个机房某台机器上这一毫秒内同时生成的 id 的序号,支持每毫秒4096个序列号。 ### Java实现版分析: ```java public class SnowflakeIdWorker { /** 开始时间截 (这个用自己业务系统上线的时间) */ private final long twepoch = 1575365018000L; /** 机器id所占的位数 */ private final long workerIdBits = 10L; /** 支持的最大机器id,结果是31 (这个移位算法可以很快的计算出几位二进制数所能表示的最大十进制数) */ private final long maxWorkerId = -1L ^ (-1L << workerIdBits); /** 序列在id中占的位数 */ private final long sequenceBits = 12L; /** 机器ID向左移12位 */ private final long workerIdShift = sequenceBits; /** 时间截向左移22位(10+12) */ private final long timestampLeftShift = sequenceBits + workerIdBits; /** 生成序列的掩码,这里为4095 (0b111111111111=0xfff=4095) */ private final long sequenceMask = -1L ^ (-1L << sequenceBits); /** 工作机器ID(0~1024) */ private long workerId; /** 毫秒内序列(0~4095) */ private long sequence = 0L; /** 上次生成ID的时间截 */ private long lastTimestamp = -1L; //==============================Constructors===================================== /** * 构造函数 * @param workerId 工作ID (0~1024) */ public SnowflakeIdWorker(long workerId) { if (workerId > maxWorkerId || workerId < 0) { throw new IllegalArgumentException(String.format("workerId can't be greater than %d or less than 0", maxWorkerId)); } this.workerId = workerId; } // ==============================Methods========================================== /** * 获得下一个ID (该方法是线程安全的) * @return SnowflakeId */ public synchronized long nextId() { long timestamp = timeGen(); //如果当前时间小于上一次ID生成的时间戳,说明系统时钟回退过这个时候应当抛出异常 if (timestamp < lastTimestamp) { throw new RuntimeException( String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestamp - timestamp)); } //如果是同一时间生成的,则进行毫秒内序列 if (lastTimestamp == timestamp) { sequence = (sequence + 1) & sequenceMask; //毫秒内序列溢出 if (sequence == 0) { //阻塞到下一个毫秒,获得新的时间戳 timestamp = tilNextMillis(lastTimestamp); } } //时间戳改变,毫秒内序列重置 else { sequence = 0L; } //上次生成ID的时间截 lastTimestamp = timestamp; //移位并通过或运算拼到一起组成64位的ID return ((timestamp - twepoch) << timestampLeftShift) // | (workerId << workerIdShift) // | sequence; } /** * 阻塞到下一个毫秒,直到获得新的时间戳 * @param lastTimestamp 上次生成ID的时间截 * @return 当前时间戳 */ protected long tilNextMillis(long lastTimestamp) { long timestamp = timeGen(); while (timestamp <= lastTimestamp) { timestamp = timeGen(); } return timestamp; } /** * 返回以毫秒为单位的当前时间 * @return 当前时间(毫秒) */ protected long timeGen() { return System.currentTimeMillis(); } } ``` ### 缺点与不足 ❌ 生成的ID太长。 ❌ 瞬时并发量不够。 ❌ 不能解决时间回拨问题。 ❌ 不支持后补生成前序ID。 ❌ 可能依赖外部存储系统。 ### 优化与改进 很多大厂都会根据雪花进行二次改进和适配,比如有的业务年限短,但是节点要求多,就可以分割点时间戳的位数来当填补节点位,或者当成整个系统下的不同业务线。 当然更普遍的就是处理时间回拨问题。 这个是Star较高的一个优化: [雪花ID全家桶(SnowFlake IdGenerator)](https://gitee.com/yitter/idgenerator) Last modification:August 22, 2022 © Allow specification reprint Like 0 喵ฅฅ