本文共 4440 字,大约阅读时间需要 14 分钟。
在我们工作中,很多场景下都需要生成唯一id,比如订单号、优惠券码等,本篇文章就给大家带来如何用java实现生成唯一id。
首先还是按惯例贴出github地址,可直接从github下载源码运行:https://github.com/whiteBX/IDGenerator
唯一ID的核心点
唯一ID的设计要点
本例子中采用一个long类型来存储我们的id,也就是64个bit,最终根据需要转换为十进制或32进制使用,可根据自己的业务需要来选择。下面为id的构成讲解:版本号2位 + 秒级时间30位 + 序列号20位 + 机器id5位 = 57位二进制
详细解释:
版本号2位:表示用两个bit来存储版本号,留作用于大的业务变动时使用,便于直观区分是哪个版本的业务。 秒级时间30位:采用当前时间戳减去一个系统上线的固定时间来存储,30位算下来可以使用三十多年。如果不够可以扩充。 序列号20位:每秒可以支持生成2^20-1个,即百万级个不重复的序列号,不够用的话可以扩充。 机器id5位:即可以支持2^5-1 = 31台机器部署,不够可以扩充。 由于我们存储是用long来存储的,那么这里还剩下7位可以自由扩充,比如增加序列号占用的位数或者机器id占用的位数。具体实现
本段代码核心点其实就两点:代码如下:
通过锁来控制序列号不重复:
public class LockSequenceGenerator extends SequenceGenerator { private ReentrantLock lock = new ReentrantLock(); /** * 通过锁来实现 * @param generateInfo * @param generateProperties */ public void generate(IdGenerateInfo generateInfo, GenerateProperties generateProperties) { lock.lock(); try { long time = TimeGenerator.generateTime(generateProperties.getBeginEpoch()); if (time == timestamp) { sequence++; }else { sequence = 0; timestamp = time; } generateInfo.setSequence(sequence); generateInfo.setTime(time); }finally { lock.unlock(); } }}
通过cas来实现序列号不重复:
public class CasGenerator extends SequenceGenerator { private AtomicReferencesequenceInfo = new AtomicReference (new SequenceInfo()); public void generate(IdGenerateInfo generateInfo, GenerateProperties generateProperties) { while (true) { long time = TimeGenerator.generateTime(generateProperties.getBeginEpoch()); SequenceInfo oldSequenceInfo = sequenceInfo.get(); SequenceInfo newSequenceInfo = new SequenceInfo(); if (time == oldSequenceInfo.getTimestamp()) { newSequenceInfo.setTimestamp(time); newSequenceInfo.setSequence(oldSequenceInfo.getSequence() + 1); } else { newSequenceInfo.setTimestamp(time); } if (sequenceInfo.compareAndSet(oldSequenceInfo, newSequenceInfo)) { generateInfo.setTime(newSequenceInfo.getTimestamp()); generateInfo.setSequence(newSequenceInfo.getSequence()); break; } } } class SequenceInfo { private int sequence; private long timestamp; public int getSequence() { return sequence; } public void setSequence(int sequence) { this.sequence = sequence; } public long getTimestamp() { return timestamp; } public void setTimestamp(long timestamp) { this.timestamp = timestamp; } }}
解决掉序列号不重复问题后,剩下的其实就是获取配置的版本号和机器id来组装出我们的id即可:
// 通过spring的PropertySource来读取配置文件,使其都可自由配置。其中都赋上默认值。@Component@PropertySource("classpath:generate.properties")public class GenerateProperties { /** * 版本号,默认占2位,即二进制的00,重大版本变更时配置,需要扩容的话要同步改动versionLength */ private int version = 0; private int versionLength = 2; /** * 序列号占用字节,默认占20位 即每秒2^20个序列,按需配置 */ private Integer sequenceLength = 20; /** * 机器ID,默认占5位,即二进制的00000,可以支持32台服务器,需要扩容的话要同步改动machineIdLength */ private int machineId = 0; private int machineIdLength = 5; /** * 开始时间 */ private Long beginEpoch = 1533686888L;}
根据配置组装id:
/** * 生成id 1 | 0 = 1,或出所有1 */ public static long genearte(IdGenerateInfo info, GenerateProperties generateProperties) { long id = 0; id |= info.getMachineId(); id |= info.getSequence() << generateProperties.getMachineIdLength(); id |= info.getTime() << (generateProperties.getMachineIdLength() + generateProperties.getSequenceLength()); id |= info.getVersion() << (generateProperties.getMachineIdLength() + generateProperties.getSequenceLength() + 30); return id; }
反解这里就不写代码示例了,各位可以自己实现即可,即根据id的二进制码对应右移指定位数即可解出来在哪台机器,哪个时间等信息。
性能测试
运行我本机开了十个线程,测量的结果为每个线程生成一千万个id耗时都为10秒左右,单个id低于1毫秒,可以看到效率是足够使用的。当然例子中有一些地方还没有完全完善,读者可以自行完善,比如一秒内生成的id数量超过最大上限时提示错误或者等待下个点再生成等。
转载地址:http://ehsni.baihongyu.com/