时间:2023-03-09来源:系统城装机大师作者:佚名
在分布式系统中,生成全局唯一ID是非常重要的,因为在分布式系统中,多个节点同时生成ID可能会导致ID冲突。
下面介绍几种常用的分布式ID解决方案。
UUID(通用唯一标识符)是由128位数字组成的标识符,它可以保证在全球范围内的唯一性,因为其生成算法基于时间戳、节点ID等因素。UUID可以使用Java自带的UUID类来生成,如下所示:
1 2 3 4 5 6 7 8 |
javaCopy code import java.util.UUID; public class UuidGenerator { public static void main(String[] args) { UUID uuid = UUID.randomUUID(); System.out.println(uuid.toString()); } } |
UUID的优点是简单易用,无需额外的配置和管理,可以直接使用Java自带的UUID类生成。但是UUID长度较长(128位),不太适合作为数据库表的主键,且不易于排序和索引。
Snowflake是Twitter开源的一种分布式ID生成算法,它可以生成64位的唯一ID,其中包含了时间戳、数据中心ID和机器ID等信息。Snowflake算法的Java代码如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 |
javaCopy code public class SnowflakeGenerator { private final static long START_STMP = 1480166465631L; private final static long SEQUENCE_BIT = 12 ; private final static long MACHINE_BIT = 5 ; private final static long DATACENTER_BIT = 5 ; private final static long MAX_DATACENTER_NUM = -1L ^ (-1L << DATACENTER_BIT); private final static long MAX_MACHINE_NUM = -1L ^ (-1L << MACHINE_BIT); private final static long MAX_SEQUENCE = -1L ^ (-1L << SEQUENCE_BIT); private final static long MACHINE_LEFT = SEQUENCE_BIT; private final static long DATACENTER_LEFT = SEQUENCE_BIT + MACHINE_BIT; private final static long TIMESTMP_LEFT = DATACENTER_LEFT + DATACENTER_BIT; private long datacenterId; private long machineId; private long sequence = 0L; private long lastStmp = -1L; public SnowflakeGenerator( long datacenterId, long machineId) { if (datacenterId > MAX_DATACENTER_NUM || datacenterId < 0 ) { throw new IllegalArgumentException( "datacenterId can't be greater than MAX_DATACENTER_NUM or less than 0" ); } if (machineId > MAX_MACHINE_NUM || machineId < 0 ) { throw new IllegalArgumentException( "machineId can't be greater than MAX_MACHINE_NUM or less than 0" ); } this .datacenterId = datacenterId; this .machineId = machineId; } public synchronized long nextId() { long currStmp = getNewstmp(); if (currStmp < lastStmp) { throw new RuntimeException( "Clock moved backwards. Refusing to generate id" ); } if (currStmp == lastStmp) { sequence = (sequence + 1 ) & MAX_SEQUENCE; if (sequence == 0L) { currStmp = getNextMill(); } } else { sequence = 0L; } lastStmp = currStmp; return (currStmp - START_STMP) << TIMESTMP_LEFT | datacenterId << DATACENTER_LEFT | machineId << MACHINE_LEFT | sequence; } private long getNextMill() { long mill = getNewstmp(); while (mill <= lastStmp) { mill = getNewstmp(); } return mill; } private long getNewstmp() { return System.currentTimeMillis(); } } |
Snowflake算法的优点是生成ID的性能高,且ID长度较短(64位),可以作为数据库表的主键,且便于排序和索引。但是需要注意,如果集群中的节点数超过了机器ID所占的位数,或者集群规模很大,时间戳位数不够用,那么就需要考虑其他的分布式ID生成算法。
Leaf是美团点评开源的一种分布式ID生成算法,它可以生成全局唯一的64位ID。Leaf算法的Java代码如下所示:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
javaCopy code public class LeafGenerator { private static final Logger logger = LoggerFactory.getLogger(LeafGenerator. class ); private static final String WORKER_ID_KEY = "leaf.worker.id" ; private static final String PORT_KEY = "leaf.port" ; private static final int DEFAULT_PORT = 8080 ; private static final int DEFAULT_WORKER_ID = 0 ; private static final int WORKER_ID_BITS = 10 ; private static final int SEQUENCE_BITS = 12 ; private static final int MAX_WORKER_ID = ( 1 << WORKER_ID_BITS) - 1 ; private static final int MAX_SEQUENCE = ( 1 << SEQUENCE_BITS) - 1 ; private static final long EPOCH = 1514736000000L; private final SnowflakeIdWorker idWorker; public LeafGenerator() { int workerId = SystemPropertyUtil.getInt(WORKER_ID_KEY, DEFAULT_WORKER_ID); int port = SystemPropertyUtil.getInt(PORT_KEY, DEFAULT_PORT); this .idWorker = new SnowflakeIdWorker(workerId, port); logger.info( "Initialized LeafGenerator with workerId={}, port={}" , workerId, port); } public long nextId() { return idWorker.nextId(); } private static class SnowflakeIdWorker { private final long workerId; private final long port; private long sequence = 0L; private long lastTimestamp = -1L; SnowflakeIdWorker( long workerId, long port) { if (workerId < 0 || workerId > MAX_WORKER_ID) { throw new IllegalArgumentException(String.format( "workerId must be between %d and %d" , 0 , MAX_WORKER_ID)); } this .workerId = workerId; this .port = port; } synchronized long nextId() { long timestamp = System.currentTimeMillis(); if (timestamp < lastTimestamp) { throw new RuntimeException( "Clock moved backwards. Refusing to generate id" ); } if (timestamp == lastTimestamp) { sequence = (sequence + 1 ) & MAX_SEQUENCE; if (sequence == 0L) { timestamp = tilNextMillis(lastTimestamp); } } else { sequence = 0L; } lastTimestamp = timestamp; return ((timestamp - EPOCH) << (WORKER_ID_BITS + SEQUENCE_BITS)) | (workerId << SEQUENCE_BITS) | sequence; } private long tilNextMillis( long lastTimestamp) { long timestamp = System.currentTimeMillis(); while (timestamp <= lastTimestamp) { timestamp = System.currentTimeMillis(); } return timestamp; } } } |
Leaf算法的特点是生成ID的速度比Snowflake算法略慢,但是可以支持更多的Worker节点。Leaf算法生成的ID由三部分组成,分别是时间戳、Worker ID和序列号,其中时间戳占用42位、Worker ID占用10位、序列号占用12位,总共64位。
以上是常见的分布式ID生成算法,当然还有其他的一些方案,如:MongoDB ID、UUID、Twitter Snowflake等。不同的方案适用于不同的业务场景,具体实现细节和性能表现也有所不同,需要根据实际情况选择合适的方案。
除了上述介绍的分布式ID生成算法,还有一些新的分布式ID生成方案不断涌现,例如Flicker的分布式ID生成算法,它使用了类似于Snowflake的思想,但是采用了不同的位数分配方式,相比Snowflake更加灵活,并且可以根据需要动态调整每个部分占用的位数。此外,Facebook还推出了ID Generation Service (IGS)方案,该方案将ID的生成和存储分离,提供了更加灵活和可扩展的方案,但是需要进行更加复杂的架构设计和实现。
针对不同的业务需求,可以设计多套分布式ID生成方案。下面是我个人的一些建议:
选择适合自己业务场景的分布式ID生成方案,需要综合考虑ID的唯一性、生成速度、长度、存储成本、可扩展性、可用性等多个因素。同时需要注意,不同方案的实现细节和性能表现也有所不同,需要根据实际情况进行权衡和选择。
下面给出每种方案的详细代码demo:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
javaCopy code public class IdGenerator { private static final String JDBC_URL = "jdbc:mysql://localhost:3306/test" ; private static final String JDBC_USER = "root" ; private static final String JDBC_PASSWORD = "password" ; public long generateId() { Connection conn = null ; PreparedStatement pstmt = null ; ResultSet rs = null ; try { Class.forName( "com.mysql.jdbc.Driver" ); conn = DriverManager.getConnection(JDBC_URL, JDBC_USER, JDBC_PASSWORD); pstmt = conn.prepareStatement( "INSERT INTO id_generator (stub) VALUES (null)" , Statement.RETURN_GENERATED_KEYS); pstmt.executeUpdate(); rs = pstmt.getGeneratedKeys(); if (rs.next()) { return rs.getLong( 1 ); } } catch (Exception e) { e.printStackTrace(); } finally { try { if (rs != null ) { rs.close(); } if (pstmt != null ) { pstmt.close(); } if (conn != null ) { conn.close(); } } catch (Exception e) { e.printStackTrace(); } } return 0L; } } |
1 2 3 4 5 6 7 |
javaCopy code import java.util.UUID; public class IdGenerator { public String generateId() { return UUID.randomUUID().toString().replace( "-" , "" ); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 |
javaCopy code import redis.clients.jedis.Jedis; public class IdGenerator { private static final String REDIS_HOST = "localhost" ; private static final int REDIS_PORT = 6379 ; private static final String REDIS_PASSWORD = "password" ; private static final int ID_GENERATOR_EXPIRE_SECONDS = 3600 ; private static final String ID_GENERATOR_KEY = "id_generator" ; public long generateId() { Jedis jedis = null ; try { jedis = new Jedis(REDIS_HOST, REDIS_PORT); jedis.auth(REDIS_PASSWORD); long id = jedis.incr(ID_GENERATOR_KEY); jedis.expire(ID_GENERATOR_KEY, ID_GENERATOR_EXPIRE_SECONDS); return id; } catch (Exception e) { e.printStackTrace(); } finally { if (jedis != null ) { jedis.close(); } } return 0L; } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 |
javaCopy code import java.util.concurrent.CountDownLatch; import org.apache.zookeeper.CreateMode; import org.apache.zookeeper.WatchedEvent; import org.apache.zookeeper.Watcher; import org.apache.zookeeper.ZooDefs.Ids; import org.apache.zookeeper.ZooKeeper; public class IdGenerator implements Watcher { private static final String ZK_HOST = "localhost" ; private static final int ZK_PORT = 2181 ; private static final int SESSION_TIMEOUT = 5000 ; private static final String ID_GENERATOR_NODE = "/id_generator" ; private static final int ID_GENERATOR_EXPIRE_SECONDS = 3600 ; private long workerId = 0 ; public IdGenerator() { try { ZooKeeper zk = new ZooKeeper(ZK_HOST + ":" + ZK_PORT, SESSION_TIMEOUT, this ); CountDownLatch latch = new CountDownLatch( 1 ); latch.await(); if (zk.exists(ID_GENERATOR_NODE, false ) == null ) { zk.create(ID_GENERATOR_NODE, null , Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); } workerId = zk.getChildren(ID_GENERATOR_NODE, false ).size(); zk.create(ID_GENERATOR_NODE + "/worker_" + workerId, null , Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL); } catch (Exception e) { e.printStackTrace(); } } public long generateId() { ZooKeeper zk = null ; try { zk = new ZooKeeper(ZK_HOST + ":" + ZK_PORT, SESSION_TIMEOUT, null ); CountDownLatch latch = new CountDownLatch( 1 ); latch.await(); zk.create(ID_GENERATOR_NODE + "/id_" , null , Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL, (rc, path, ctx, name) -> {}, null ); byte [] data = zk.getData(ID_GENERATOR_NODE + "/worker_" + workerId, false , null ); long id = Long.parseLong( new String(data)) * 10000 + zk.getChildren(ID_GENERATOR_NODE, false ).size(); return id; } catch (Exception e) { e.printStackTrace(); } finally { if (zk != null ) { try { zk.close(); } catch (Exception e) { e.printStackTrace(); } } } return 0L; } @Override public void process(WatchedEvent event) { if (event.getState() == Event.KeeperState.SyncConnected) { System.out.println( "Connected to ZooKeeper" ); CountDownLatch latch = new CountDownLatch( 1 ); latch.countDown(); } } } |
注意,这里使用了ZooKeeper的临时节点来协调各个工作节点,如果一个工作节点挂掉了,它的临时节点也会被删除,这样可以保证每个工作节点获得的ID是唯一的。
以上就是各种分布式ID生成方案的详细代码demo,实际上,每种方案都有其优缺点,应根据具体业务场景和系统架构选择合适的方案。
2023-11-01
React中immutable的使用2023-11-01
命令行清除Redis缓存的实现2023-11-01
Redis缓存空间优化实践详解引言大厂很多项目都是部署到多台服务器上,这些服务器在各个地区都存在,当我们访问服务时虽然执行的是同一个服务,但是可能是不同服务器运行的;在我学习项目时遇到这样一个登录情...
2023-11-01
1.多次修改一个redis的String过期键,如何保证他仍然能保留第一次设置时的删除时间 2.修改hash、set、Zset、list的值,会使过期时间重置吗?...
2023-11-01