在分布式系统中,会有生成全局唯一ID的需求,已有的解决方案中,UUID不便于索引,全局ID生成器自身会有性能瓶颈,雪花算法则很好的解决了这两个问题
雪花算法生成的是一个64位的LONG值,主要由以下四个部分组成
1、1位的保留位
2、41位的时间戳(可以保证69年不重复)
3、10位的机器ID(可供1024台机器使用)
4、12位的自增序列号(从1-4096中循环使用)
基于雪花算法的设计原则(时间戳+机器号+自增号)可以很方便的设计出符合自己需要的ID生成器,需要注意的是,雪花算法依赖于机器自身的时间,如果机器自身的时间存在问题,则可能出现重复ID,这个问题可以在生成器初始化时,连接时间服务器来解决。
GITHUB上的雪花算法基于scala实现:
https://github.com/twitter/snowflake
基于Java的实现可参考,这个实现与twitter的实现略有不同,使用了保留位,可以生成139年不重复的ID:
https://www.callicoder.com/distributed-unique-id-sequence-number-generator/
import java.net.NetworkInterface; import java.security.SecureRandom; import java.time.Instant; import java.util.Enumeration; /** * Distributed Sequence Generator. * Inspired by Twitter snowflake: https://github.com/twitter/snowflake/tree/snowflake-2010 * * This class should be used as a Singleton. * Make sure that you create and reuse a Single instance of SequenceGenerator per node in your distributed system cluster. */ public class SequenceGenerator { private static final int TOTAL_BITS = 64; private static final int EPOCH_BITS = 42; private static final int NODE_ID_BITS = 10; private static final int SEQUENCE_BITS = 12; private static final int maxNodeId = (int)(Math.pow(2, NODE_ID_BITS) - 1); private static final int maxSequence = (int)(Math.pow(2, SEQUENCE_BITS) - 1); // Custom Epoch (January 1, 2015 Midnight UTC = 2015-01-01T00:00:00Z) private static final long CUSTOM_EPOCH = 1420070400000L; private final int nodeId; private volatile long lastTimestamp = -1L; private volatile long sequence = 0L; // Create SequenceGenerator with a nodeId public SequenceGenerator(int nodeId) { if(nodeId < 0 || nodeId > maxNodeId) { throw new IllegalArgumentException(String.format("NodeId must be between %d and %d", 0, maxNodeId)); } this.nodeId = nodeId; } // Let SequenceGenerator generate a nodeId public SequenceGenerator() { this.nodeId = createNodeId(); } public synchronized long nextId() { long currentTimestamp = timestamp(); if(currentTimestamp < lastTimestamp) { throw new IllegalStateException("Invalid System Clock!"); } if (currentTimestamp == lastTimestamp) { sequence = (sequence + 1) & maxSequence; if(sequence == 0) { // Sequence Exhausted, wait till next millisecond. currentTimestamp = waitNextMillis(currentTimestamp); } } else { // reset sequence to start with zero for the next millisecond sequence = 0; } lastTimestamp = currentTimestamp; long id = currentTimestamp << (TOTAL_BITS - EPOCH_BITS); id |= (nodeId << (TOTAL_BITS - EPOCH_BITS - NODE_ID_BITS)); id |= sequence; return id; } // Get current timestamp in milliseconds, adjust for the custom epoch. private static long timestamp() { return Instant.now().toEpochMilli() - CUSTOM_EPOCH; } // Block and wait till next millisecond private long waitNextMillis(long currentTimestamp) { while (currentTimestamp == lastTimestamp) { currentTimestamp = timestamp(); } return currentTimestamp; } private int createNodeId() { int nodeId; try { StringBuilder sb = new StringBuilder(); Enumeration<NetworkInterface> networkInterfaces = NetworkInterface.getNetworkInterfaces(); while (networkInterfaces.hasMoreElements()) { NetworkInterface networkInterface = networkInterfaces.nextElement(); byte[] mac = networkInterface.getHardwareAddress(); if (mac != null) { for(int i = 0; i < mac.length; i++) { sb.append(String.format("%02X", mac[i])); } } } nodeId = sb.toString().hashCode(); } catch (Exception ex) { nodeId = (new SecureRandom().nextInt()); } nodeId = nodeId & maxNodeId; return nodeId; } }
无边沉沦,苦海渡航