通常雪花算法的机器id是根据网络ip地址计算得到的一个long类型值。从阿里的nacos扒下来的雪花算法实现:
static {
InetAddress address = NetUtil.getLocalAddress0();
if (address == null) {
throw new IllegalStateException("Cannot get LocalHost InetAddress, please check your network!");
}
byte[] ipAddressByteArray = address.getAddress();
hostIp = address.getHostAddress();
workerId = (((ipAddressByteArray[ipAddressByteArray.length - 2] & 0B11) << Byte.SIZE) + (
ipAddressByteArray[ipAddressByteArray.length - 1] & 0xFF));
log.info("snowflower workerId: {}", workerId);
}
getLocalAddress0方法的实现 NetworkInterface的getInetAddress对象,返回的是Inet6Address
public static InetAddress getLocalAddress0() {
InetAddress localAddress = null;
// @since 2.7.6, choose the {@link NetworkInterface} first
try {
NetworkInterface networkInterface = findNetworkInterface();
Enumeration<InetAddress> addresses = networkInterface.getInetAddresses();
while (addresses.hasMoreElements()) {
Optional<InetAddress> addressOp = toValidAddress(addresses.nextElement());
if (addressOp.isPresent()) {
try {
if (addressOp.get().isReachable(100)) {
return addressOp.get();
}
} catch (IOException e) {
// ignore
}
}
}
} catch (Throwable e) {
log.warn("{}", e.getMessage(), e);
}
try {
localAddress = InetAddress.getLocalHost();
Optional<InetAddress> addressOp = toValidAddress(localAddress);
if (addressOp.isPresent()) {
return addressOp.get();
}
} catch (Throwable e) {
log.warn("{}", e.getMessage(), e);
}
return localAddress;
}
getValidNetworkInterfaces方法的实现:
返回53个主机上的网络接口,名字是lo eth1 eth2 … net0 net1 wlan0等等。这些ip地址都是ipv6的。NetworkInterface类。
private static List<NetworkInterface> getValidNetworkInterfaces() throws SocketException {
List<NetworkInterface> validNetworkInterfaces = new LinkedList<>();
Enumeration<NetworkInterface> interfaces = NetworkInterface.getNetworkInterfaces();
while (interfaces.hasMoreElements()) {
NetworkInterface networkInterface = interfaces.nextElement();
validNetworkInterfaces.add(networkInterface);
}
return validNetworkInterfaces;
}
findNetworkInterface方法的实现:
public static NetworkInterface findNetworkInterface() {
List<NetworkInterface> validNetworkInterfaces = Collections.emptyList();
try {
validNetworkInterfaces = getValidNetworkInterfaces();
} catch (Throwable e) {
log.warn("{}", e.getMessage(), e);
}
for (NetworkInterface networkInterface : validNetworkInterfaces) {
Enumeration<InetAddress> addresses = networkInterface.getInetAddresses();
while (addresses.hasMoreElements()) {
Optional<InetAddress> addressOp = toValidAddress(addresses.nextElement());
if (addressOp.isPresent()) {
try {
if (addressOp.get().isReachable(100)) {
return networkInterface;
}
} catch (IOException e) {
// ignore
}
}
}
}
return validNetworkInterfaces.get(0);
}
上面的代码含义是获取主机的所有网络接口。然后从第一个网络接口开始,如果可访问(isReachable(100)),就将这个IP地址返回。相同的ip地址必然产生相同的机器id(workerId)
今天的坑是因为服务器不同,ipv4地址不同,但是ipv6地址相同。使用了ipv6地址生成的workerId,就必然相同了。影响了雪花算法。以及其它用workerid作分布式的业务。
因为只有测试环境的ipv6地址都相同;线上的不会相同,所以为了测试,在IDEA edit configuration里加上的-Djava.net.preferIPv4Stack=true,这时看到所有的接口用的ipv4。
例如lo的地址,不加这个option,是16个字节的0::1;加了就是4个字节的127.0.0.1
后期改进,可以先从文件读取workerId, 如果没有再生成,并保存到文件中。避免主机ip地址变更导致的workerid变更问题。