分散システムでは、グローバルに一意の ID を生成することが非常に重要です。分散システムでは複数のノードが存在するためです。同時に ID を生成すると、ID の競合が発生する可能性があります。
次に、一般的に使用される分散 ID ソリューションをいくつか紹介します。
UUID (Universally Unique Identifier) は 128 桁で構成される識別子で、その生成アルゴリズムはタイムスタンプ、ノード ID などの要素に基づいているため、グローバルな一意性を保証できます。 UUID は、以下に示すように、Java 独自の UUID クラスを使用して生成できます:
javaCopy code import java.util.UUID; public class UuidGenerator { public static void main(String[] args) { UUID uuid = UUID.randomUUID(); System.out.println(uuid.toString()); } }
Java 独自の UUID クラスによって生成された UUID は非常にシンプルで使いやすく、追加の構成や管理を必要としません。 。 UUID はその長さ (128 ビット) のため、データベース テーブルの主キーとしては適しておらず、並べ替えやインデックス付けが困難です。
Snowflake は、Twitter がオープンソース化した分散 ID 生成アルゴリズムで、タイムスタンプ、データセンター ID、マシン ID などの情報を含む 64 ビットの一意の ID を生成できます。 Snowflake アルゴリズムの Java コードは次のとおりです。
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 bits) をデータベース テーブルの主キーとして使用すると、並べ替えとインデックス付けが容易になります。ただし、クラスター内のノードの数がマシン ID が占める桁数を超える場合、またはクラスターが非常に大きくタイムスタンプの桁数が十分でない場合は、他の分散 ID 生成アルゴリズムを使用する必要があることに注意してください。考慮されます。
Leaf は、Meituan Dianping によってオープンソース化された分散 ID 生成アルゴリズムであり、グローバルに一意の 64 ビット ID を生成できます。 Leaf アルゴリズムの Java コードは次のとおりです。
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 アルゴリズムは、Snowflake アルゴリズムよりも ID の生成がわずかに遅くなりますが、より多くのワーカーをサポートできます。ノード 。 Leaf アルゴリズムで生成される ID は、タイムスタンプ、ワーカー ID、シーケンス番号の 3 つの部分で構成され、タイムスタンプが 42 ビット、ワーカー ID が 10 ビット、シーケンス番号が 12 ビットの合計 64 ビットを占めます。
上記は一般的な分散 ID 生成アルゴリズムですが、もちろん、MongoDB ID、UUID、Twitter Snowflake など、他のソリューションもあります。ビジネスシーンに応じて適したソリューションが異なり、具体的な実装内容やパフォーマンスも異なるため、実際の状況に応じて適切なソリューションを選択する必要があります。
上記で紹介した分散 ID 生成アルゴリズムに加えて、Flicker の分散 ID 生成アルゴリズムなど、新しい分散 ID 生成ソリューションもいくつか登場しています。これは、Snowflake と同様のアイデアを使用しますが、異なるビット割り当て方法を採用しています。 Snowflake よりも柔軟性があり、各部分が占めるビット数は必要に応じて動的に調整できます。さらに、Facebook は ID 生成サービス (IGS) ソリューションも開始しました。これは ID の生成と保存を分離し、より柔軟でスケーラブルなソリューションを提供しますが、より複雑なアーキテクチャの設計と実装が必要になります。
さまざまなビジネス ニーズに応じて、複数セットの分散 ID 生成ソリューションを設計できます。私の個人的な提案をいくつか紹介します。
データベース自動インクリメント ID に基づいて生成する: データベース自動インクリメント ID をグローバルに一意な ID として使用すると、ID の一意性が確保され、簡単になります。ただし、同時実行性が高いとパフォーマンスのボトルネックが発生する可能性があります。したがって、同時実行性の高いシナリオで使用することはお勧めできません。
UUID に基づいて生成: グローバルに一意な ID として UUID を使用すると、ID の一意性を確保できますが、ID の長さが長く (128 ビット)、保管や保存に不便です。 ID が重複する可能性は非常に低いですが、0 ではありません。分散システムを使用する場合は、ID の長さと、保存および送信のコストを考慮する必要があることが推奨されます。
Redis に基づく生成: Redis のアトミック操作を使用すると、ID の一意性が保証され、ID 生成速度が非常に高速になるため、同時実行性の高いシナリオに適用できます。 Redis がクラッシュしたり、パフォーマンスが低下したりすると、ID 生成の効率と可用性に影響が出る可能性があることに注意してください。
ZooKeeper に基づく生成: ZooKeeper のシリアル番号ジェネレーターを使用すると、ID の一意性を確保でき、実装は比較的簡単ですが、追加の依存関係とリソースの導入が必要です。パフォーマンスの問題がボトルネックになっている可能性があります。
ビジネス シナリオに合った分散 ID 生成ソリューションを選択するには、ID の一意性、生成速度、長さ、ストレージ コスト、拡張性、可用性などの複数の要素を総合的に考慮する必要があります。異なるソリューションを実装するには、実装の詳細やパフォーマンスも異なるため、実際の状況でのトレードオフと選択を考慮する必要があります。
各ソリューションの詳細なコード デモを以下に示します。
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; } }
javaCopy code import java.util.UUID; public class IdGenerator { public String generateId() { return UUID.randomUUID().toString().replace("-", ""); } }
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; } }
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 が固有であることを確認します。
以上がRedis 分散 ID のソリューションは何ですか?の詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。