Seata中IDworker类源码分享
背景
在之前的文章中,我们介绍了几种Uid的生成策略,其中涉及到了Seata的IdWorker类,但由于篇幅原因,并未展开讲,Seata的这个实现具有很强的代表性,在本文中,笔者就带大家详细的了解一下这个实现。
源码解读
这个类大体上可分为三个部分
- 位数分配
- 获取workerID
- 生成UId
/**
* Start time cut (2020-05-03)
*/
private final long twepoch = 1588435200000L;
/**
* The number of bits occupied by sequence
*/
private final int sequenceBits = 12;
/**
* timestamp and sequence mix in one Long
* highest 11 bit: not used
* middle 41 bit: timestamp
* lowest 12 bit: sequence
*/
private AtomicLong timestampAndSequence;
public IdWorker(Long workerId) {
initTimestampAndSequence();
initWorkerId(workerId);
}
/**
* init first timestamp and sequence immediately
*/
private void initTimestampAndSequence() {
long timestamp = getNewestTimestamp();
long timestampWithSequence = timestamp << sequenceBits;
this.timestampAndSequence = new AtomicLong(timestampWithSequence);
}
private long getNewestTimestamp() {
return System.currentTimeMillis() - twepoch;
}
这个构造器主要用来初始化时间戳及占位 twepoch 是初始时间 getNewestTimestamp()减去初始时间可以延长整个Uid的使用寿命,不了解的小伙伴可以翻翻之前的文章。initTimestampAndSequence 主要是将时间位左移12位,大家可以看到timestampAndSequence的定义 高11位是标志位和workerid位 ,中间41位是时间戳,低12位是序列
下面我们再看下initWorkerId 做了什么
/**
* Maximum supported machine id, the result is 1023
*/
private final int maxWorkerId = ~(-1 << workerIdBits);
/**
* The number of bits occupied by timestamp
*/
private final int timestampBits = 41;
/**
* The number of bits occupied by sequence
*/
private final int sequenceBits = 12;
private void initWorkerId(Long workerId) {
if (workerId == null) {
workerId = generateWorkerId();
}
if (workerId > maxWorkerId || workerId < 0) {
String message = String.format("worker Id can't be greater than %d or less than 0", maxWorkerId);
throw new IllegalArgumentException(message);
}
this.workerId = workerId << (timestampBits + sequenceBits);
}
private long generateWorkerId() {
try {
return generateWorkerIdBaseOnMac();
} catch (Exception e) {
return generateRandomWorkerId();
}
}
private long generateWorkerIdBaseOnMac() throws Exception {
Enumeration<NetworkInterface> all = NetworkInterface.getNetworkInterfaces();
while (all.hasMoreElements()) {
NetworkInterface networkInterface = all.nextElement();
boolean isLoopback = networkInterface.isLoopback();
boolean isVirtual = networkInterface.isVirtual();
if (isLoopback || isVirtual) {
continue;
}
byte[] mac = networkInterface.getHardwareAddress();
return ((mac[4] & 0B11) << 8) | (mac[5] & 0xFF);
}
throw new RuntimeException("no available mac found");
}
private long generateRandomWorkerId() {
return new Random().nextInt(maxWorkerId + 1);
}
生成workerid时 如果未指定 则先采用网卡地址生成,注意在生成时左移8位 主要是避免k8环境下前面相同的情况,如果网卡生成时错误则随机生成,在生成后将其左移,使其放在高位,此时,UId的整体结构已经确定。下面我们看一下具体生成Uid的方法
public long nextId() {
waitIfNecessary();
long next = timestampAndSequence.incrementAndGet();
long timestampWithSequence = next & timestampAndSequenceMask;
return workerId | timestampWithSequence;
}
private void waitIfNecessary() {
long currentWithSequence = timestampAndSequence.get();
long current = currentWithSequence >>> sequenceBits;
long newest = getNewestTimestamp();
if (current >= newest) {
try {
Thread.sleep(5);
} catch (InterruptedException ignore) {
// don't care
}
}
}
nextId() 方法返回的就是UId ,每次获取id时都会判断当前持有的时间戳与系统时间戳,如果持有的时间戳大于等于系统时间 则睡眠5ms,在时间校验通过后就对时间戳及序列加一。这也是为什么Seata会调整原雪花id结构的原因,这样编程起来十分方便。之前的文章我们提到过,针对时钟回拨的情况,大部分采用的都是启动时获取一次时间,后续采用累加的方式,但这种方式有个缺点,就是时间是不能无限超前使用的,如果超前使用了很久,那么在下次重启后获取的时间戳是一定重复的(前提是在一个workerid内),所以,大家能更好的理解为什么百度Uid的workerid是每次都生成新的了吧,另外百度Uid的workerid的位数也做了相应的调整,而Seata的workerid是固定的,如果不限制超前就很容易出现之前所说的问题,此外,也可以采用延迟启动的方式,在获取时间戳后,延迟一段时间,用来应对超前的消费。
尾声
本篇文章是对之前UId文章的补充,通过这次源码的分享,希望大家能触类旁通,举一反三,能够对各种UId的结构,实现有更深层的了解。其实各种实现方式都是为了实现业务,每种实现方式也各有优缺点。希望各位在使用中能根据实现的场景设计出合适的结构
- 点赞
- 收藏
- 关注作者
评论(0)