1. ID号的要求有哪些呢?
- 全局唯一性:不重复、唯一标识。
- 趋势递增:有序方便主键索引,插入查询性能。
- 单调递增:保证下一个ID一定大于上一个ID,例如事务版本号、IM增量消息、排序等特殊需求。
- 信息安全:可能暴露信息,例如:订单数量、用户数量
2. 分布式ID都有哪些生成方式?
- UUID 随机数
- 数据库自增ID;数据库特性
- Redis 生成 ID
- 雪花算法(SnowFlake)等算法
生成算法 | 优点 | 缺点 |
---|---|---|
UUID | ID 是无序、无业务含义、太长、查询慢,不适合建立索引 | ID 是无序的,查询慢,不适合建立索引 |
数据库自增 | 代码简单,数据递增 | DB 单点故障、并发瓶颈等,需 DBA 专业维护 |
snowflake雪花算法 | 低位趋势递增,不占带宽,性能高 | 依赖于服务器时间,时钟回拨问题 |
Redis自增 incr | 无单点故障(集群),性能高,递增 | 占用带宽,redis 集群维护 |
3. ID生成
mybatis plus
工具类
public class Snowflake {
public static final int NODE_SHIFT = 10;
public static final int SEQ_SHIFT = 12;
public static final short MAX_NODE = 1024;
public static final short MAX_SEQUENCE = 4096;
private short sequence;
private long referenceTime;
private int node;
public Snowflake(int node) {
if (node >= 0 && node <= MAX_NODE) {
this.node = node;
} else {
throw new IllegalArgumentException(String.format("node must be between %s and %s", 0, MAX_NODE));
}
}
public synchronized long next() {
long currentTime = System.currentTimeMillis();
long counter;
if (currentTime < this.referenceTime) {
throw new RuntimeException(String.format("Last referenceTime %s is after reference time %s", this.referenceTime, currentTime));
}
if (currentTime > this.referenceTime) {
this.sequence = 0;
} else {
if (this.sequence >= MAX_SEQUENCE) {
throw new RuntimeException("Sequence exhausted at " + this.sequence);
}
++this.sequence;
}
counter = this.sequence;
this.referenceTime = currentTime;
return currentTime << NODE_SHIFT << SEQ_SHIFT | (long) (this.node << SEQ_SHIFT) | counter;
}
}
public class IdUtils {
private final static Logger logger = LoggerFactory.getLogger(IdUtils.class);
final static Snowflake snowflake = new Snowflake(getNode());
/**
* 获取ID
*/
public static Long getId() {
return snowflake.next();
}
/**
* 获取节点
*/
private static int getNode() {
//获取本地主机
InetAddress address;
try {
address = InetAddress.getLocalHost();
} catch (UnknownHostException e) {
logger.error("获取本地主机出现异常", e);
return new Random().nextInt(1024) + 1;
}
//获取ip地址
String ip = address.getHostAddress();
if (ValidateUtils.isEmpty(ip) || ip.equals("127.0.0.1") || ip.equals("0.0.0.0")) {
try {
ip = getIP();
} catch (SocketException e) {
logger.error("获取IP地址出现异常", e);
try {
return getNodeByMacAddress();
} catch (IOException e1) {
logger.error("获取IP地址出现异常", e);
}
//随机数得到节点
return new Random().nextInt(1024) + 1;
}
}
if (ip == null) {
return -1;
}
//得到ip地址的每一位
String[] bs = ip.split("\\.");
//ip地址第3位
int b3 = Integer.parseInt(bs[2]);
//ip地址第4位
int b4 = Integer.parseInt(bs[3]);
//计算节点号
int node = b3 % 4 * 256 + b4 + 1;
return node;
}
/**
* Unix下获取本地IP
*/
private static String getIP() throws SocketException {
Enumeration allNetInterfaces = NetworkInterface.getNetworkInterfaces();
while (allNetInterfaces.hasMoreElements()) {
NetworkInterface netInterface = (NetworkInterface) allNetInterfaces.nextElement();
Enumeration addresses = netInterface.getInetAddresses();
while (addresses.hasMoreElements()) {
InetAddress ip = (InetAddress) addresses.nextElement();
if (ip != null && ip instanceof Inet4Address) {
if (!ip.isLoopbackAddress() && ip.isSiteLocalAddress()) {
return ip.getHostAddress();
}
}
}
}
return null;
}
/**
* 通过Mac地址计算节点
*/
private static int getNodeByMacAddress() throws UnknownHostException, SocketException {
InetAddress ip = InetAddress.getLocalHost();
NetworkInterface network = NetworkInterface.getByInetAddress(ip);
byte[] mac = network.getHardwareAddress();
if (ValidateUtils.isEmpty(mac)) {
mac = NetworkInterface.getByName("eth0").getHardwareAddress();
}
int sum = 0;
for (int i = 0; i < mac.length; i++) {
sum += mac[i] & 0xff;
}
int node = sum % 1024 + 1;
return node;
}
}
参考资料:https://tech.meituan.com/2017/04/21/mt-leaf.html
https://mp.weixin.qq.com/s/rgy3FmF-r-GEyBfT-w47vQ