redis分散式ID解決方法有哪些
常用的分散式ID解決方案
在分散式系統中,產生全域唯一ID是非常重要的,因為在分散式系統中,多個節點同時產生ID可能會導致ID衝突。
以下介紹幾種常用的分散式ID解決方案。
UUID
UUID(通用唯一標識符)是由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十分簡單易用,無需額外的配置和管理,其優點就在這裡。由於其過長(128位元),UUID並不適合作為資料庫表的主鍵,並且難以排序和索引。
Snowflake
Snowflake是Twitter開源的分散式ID產生演算法,它可以產生64位元的唯一ID,其中包含了時間戳記、資料中心ID和機器ID等資訊。 Snowflake演算法的Java程式碼如下所示:
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位元),可以作為資料庫表的主鍵,且方便排序和索引。但是要注意,如果叢集中的節點數超過了機器ID所佔的位數,或是叢集規模很大,時間戳位數不夠用,那麼就需要考慮其他的分散式ID產生演算法。
Leaf
Leaf是美團點評開源的分散式ID產生演算法,它可以產生全域唯一的64位元ID。 Leaf演算法的Java程式碼如下:
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演算法產生ID的速度略慢於Snowflake演算法,但它可以支援更多的工作節點。 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作為全域唯一ID,可以很好的保證ID的唯一性,並且實作簡單,但是並發量較高時可能會導致效能瓶頸。因此,在高並發場景下不建議使用。
基於UUID產生:使用UUID作為全域唯一ID,可以很好地保證ID的唯一性,但是ID長度較長(128位元),不便於儲存和傳輸,並且存在重複ID的機率非常小但不為0。建議在使用分散式系統時,需要考慮ID長度以及儲存和傳輸所需的成本。
基於Redis產生:使用Redis的原子性操作,可以保證ID的唯一性,且產生ID的速度非常快,可以適用於高並發場景。需要注意的是,若Redis崩潰或效能不佳,有可能會影響ID產生效率和可用性。
基於ZooKeeper產生:使用ZooKeeper的序號產生器,可以保證ID的唯一性,並且實作較為簡單,但是需要引入額外的依賴和資源,並且可能會存在效能瓶頸。
選擇適合自己業務場景的分散式ID產生方案,需要綜合考慮ID的唯一性、產生速度、長度、儲存成本、可擴充性、可用性等多個因素。執行不同方案需要考慮實際情況下的權衡和選擇,因為它們的執行細節和性能表現亦不相同。
下面給出每個方案的詳細程式碼demo:
基於資料庫自增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; } }
基於UUID產生
javaCopy code import java.util.UUID; public class IdGenerator { public String generateId() { return UUID.randomUUID().toString().replace("-", ""); } }
基於Redis產生
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; } }
基於ZooKeeper產生
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中文網其他相關文章!

熱AI工具

Undresser.AI Undress
人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover
用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool
免費脫衣圖片

Clothoff.io
AI脫衣器

AI Hentai Generator
免費產生 AI 無盡。

熱門文章

熱工具

記事本++7.3.1
好用且免費的程式碼編輯器

SublimeText3漢化版
中文版,非常好用

禪工作室 13.0.1
強大的PHP整合開發環境

Dreamweaver CS6
視覺化網頁開發工具

SublimeText3 Mac版
神級程式碼編輯軟體(SublimeText3)

熱門話題

Redis集群模式通過分片將Redis實例部署到多個服務器,提高可擴展性和可用性。搭建步驟如下:創建奇數個Redis實例,端口不同;創建3個sentinel實例,監控Redis實例並進行故障轉移;配置sentinel配置文件,添加監控Redis實例信息和故障轉移設置;配置Redis實例配置文件,啟用集群模式並指定集群信息文件路徑;創建nodes.conf文件,包含各Redis實例的信息;啟動集群,執行create命令創建集群並指定副本數量;登錄集群執行CLUSTER INFO命令驗證集群狀態;使

如何清空 Redis 數據:使用 FLUSHALL 命令清除所有鍵值。使用 FLUSHDB 命令清除當前選定數據庫的鍵值。使用 SELECT 切換數據庫,再使用 FLUSHDB 清除多個數據庫。使用 DEL 命令刪除特定鍵。使用 redis-cli 工具清空數據。

使用 Redis 指令需要以下步驟:打開 Redis 客戶端。輸入指令(動詞 鍵 值)。提供所需參數(因指令而異)。按 Enter 執行指令。 Redis 返迴響應,指示操作結果(通常為 OK 或 -ERR)。

Redis 使用單線程架構,以提供高性能、簡單性和一致性。它利用 I/O 多路復用、事件循環、非阻塞 I/O 和共享內存來提高並發性,但同時存在並發性受限、單點故障和不適合寫密集型工作負載的局限性。

理解 Redis 源碼的最佳方法是逐步進行:熟悉 Redis 基礎知識。選擇一個特定的模塊或功能作為起點。從模塊或功能的入口點開始,逐行查看代碼。通過函數調用鏈查看代碼。熟悉 Redis 使用的底層數據結構。識別 Redis 使用的算法。

要從 Redis 讀取隊列,需要獲取隊列名稱、使用 LPOP 命令讀取元素,並處理空隊列。具體步驟如下:獲取隊列名稱:以 "queue:" 前綴命名,如 "queue:my-queue"。使用 LPOP 命令:從隊列頭部彈出元素並返回其值,如 LPOP queue:my-queue。處理空隊列:如果隊列為空,LPOP 返回 nil,可先檢查隊列是否存在再讀取元素。

Redis 使用哈希表存儲數據,支持字符串、列表、哈希表、集合和有序集合等數據結構。 Redis 通過快照 (RDB) 和追加只寫 (AOF) 機制持久化數據。 Redis 使用主從復制來提高數據可用性。 Redis 使用單線程事件循環處理連接和命令,保證數據原子性和一致性。 Redis 為鍵設置過期時間,並使用 lazy 刪除機制刪除過期鍵。

使用Redis進行鎖操作需要通過SETNX命令獲取鎖,然後使用EXPIRE命令設置過期時間。具體步驟為:(1) 使用SETNX命令嘗試設置一個鍵值對;(2) 使用EXPIRE命令為鎖設置過期時間;(3) 當不再需要鎖時,使用DEL命令刪除該鎖。
