首頁  >  文章  >  資料庫  >  redis分散式ID解決方法有哪些

redis分散式ID解決方法有哪些

王林
王林轉載
2023-06-03 10:14:271537瀏覽

    常用的分散式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&#39;t be greater than MAX_DATACENTER_NUM or less than 0");
            }
            if (machineId > MAX_MACHINE_NUM || machineId < 0) {
                throw new IllegalArgumentException("machineId can&#39;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中文網其他相關文章!

    陳述:
    本文轉載於:yisu.com。如有侵權,請聯絡admin@php.cn刪除