IdentifierGenerator
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
public interface IdentifierGenerator {
Number nextId(Object entity);
default String nextUUID(Object entity) {
return IdWorker.get32UUID();
}
}
自定义实现IdentifierGenerator
import com.baomidou.mybatisplus.core.incrementer.IdentifierGenerator;
import org.springframework.stereotype.Component;
@Component
public class CustomIdGenerator implements IdentifierGenerator {
@Override
public Long nextId(Object entity) {
return GlobalId.nextId();
}
}
定义全局id生成器
public class GlobalId {
private static SnowflakeId snowflakeId = null;
private GlobalId() {
}
public static void init(Long workerId) {
snowflakeId = new SnowflakeId(workerId);
}
public static long nextId() {
if (snowflakeId == null) {
throw new NullPointerException("GlobalId is not initialized!");
}
return snowflakeId.nextId();
}
}
Java实现雪花算法
import cn.hutool.core.util.RandomUtil;
import lombok.extern.slf4j.Slf4j;
import java.net.NetworkInterface;
import java.util.Enumeration;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;
@Slf4j
public final class SnowflakeId {
private final long twepoch = 1288834974657L;
private final int workerIdBits = 10;
private final int maxWorkerId = ~(-1 << workerIdBits);
private final int sequenceBits = 12;
private final int workerIdShift = sequenceBits;
private final int timestampLeftShift = sequenceBits + workerIdBits;
private final int sequenceMask = ~(-1 << sequenceBits);
private Long workerId;
private AtomicLong sequence;
private AtomicLong lastTimestamp;
public SnowflakeId(Long workerId) {
if (workerId == null) {
workerId = generateWorkerId();
}
if (workerId > maxWorkerId || workerId < 0) {
throw new IllegalArgumentException(String.format("worker Id can't be greater than %d or less than 0", maxWorkerId));
}
this.workerId = workerId;
this.sequence = new AtomicLong(0);
this.lastTimestamp = new AtomicLong(-1L);
}
public long nextId() {
long timestamp = timeGen();
long lastTimestampLocal = lastTimestamp.get();
if (timestamp < lastTimestampLocal) {
throw new RuntimeException(
String.format("Clock moved backwards. Refusing to generate id for %d milliseconds", lastTimestampLocal - timestamp));
}
if (lastTimestampLocal == timestamp) {
if (sequence.getAndIncrement() == sequenceMask){
timestamp = tilNextMillis(lastTimestampLocal);
sequence.set(0L);
}
}
else {
sequence.set(0L);
}
lastTimestamp.set(timestamp);
return ((timestamp - twepoch) << timestampLeftShift)
| (workerId << workerIdShift)
| sequence.get();
}
protected synchronized long tilNextMillis(long lastTimestamp) {
long timestamp = timeGen();
while (timestamp <= lastTimestamp) {
try {
TimeUnit.MILLISECONDS.sleep(1);
} catch (InterruptedException ignore) {
}
timestamp = timeGen();
}
return timestamp;
}
protected long timeGen() {
return System.currentTimeMillis();
}
private long generateWorkerId() {
try {
return generateWorkerIdBaseOnMac();
} catch (Exception e) {
return generateRandomWorkerId();
}
}
private long generateRandomWorkerId() {
return RandomUtil.randomInt(maxWorkerId + 1);
}
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();
if (mac == null) {
log.warn("WorkerId 获取mac地址失败,使用随机数代替");
return generateRandomWorkerId();
}
return ((mac[4] & 0B11) << 8) | (mac[5] & 0xFF);
}
throw new RuntimeException("no available mac found");
}
}
workId计算,解决服务存在多个实例导致ID重复
import io.lettuce.core.RedisException;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Configuration;
@Configuration
@RequiredArgsConstructor
public class GlobalIdAutoConfiguration implements InitializingBean {
@Autowired
private GlobalIdProperties globalIdProperties;
@Override
public void afterPropertiesSet() throws Exception {
Long workId = null;
if (GlobalIdConst.DEFAULT_WORKER_ID_ALGORITHM.equals(globalIdProperties.getWorkerIdAlgorithm())) {
try {
workId = RedisUtils.increment(GlobalIdConst.GLOBALID_WORKERID);
}
catch (RedisException ex) {
workId = RedisUtils.incrBy(GlobalIdConst.GLOBALID_WORKERID, 0);
if (workId != Long.MAX_VALUE) {
workId = RedisUtils.increment(GlobalIdConst.GLOBALID_WORKERID);
} else {
workId = (Long.MAX_VALUE % GlobalIdConst.MAX_WORKER_ID) + 1;
RedisUtils.set(GlobalIdConst.GLOBALID_WORKERID, workId);
}
}
workId = workId % GlobalIdConst.MAX_WORKER_ID;
}
GlobalId.init(workId);
}
}
常量类
public class GlobalIdConst {
public static final String DEFAULT_WORKER_ID_ALGORITHM = "redis";
public static final long MAX_WORKER_ID = ~(-1 << 10);
public static final String GLOBALID_WORKERID = "globalId:workerId";
}
workId生成算法配置类
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Data
@ConfigurationProperties(prefix = "global.id")
public class GlobalIdProperties {
private String workerIdAlgorithm;
}