正文

redis分布式ID解决方案示例详解(redisson分布式锁问题)没想到

文章摘要

这篇文章介绍了几种常见的分布式ID生成方案,包括UUID、Snowflake、Leaf、Redis、Zookeeper等,分析了它们的特点、优缺点及适用场景。文章指出,根据业务需求可以选择适合的生成方案,例如基于数据库自增ID适合低并发场景,基于UUID适合中等规模应用,Redis适合高并发需求,而Zookeeper则在节点故障恢复方面表现良好。文章还提到了其他一些方案,如MongoDB ID、Flicker和Facebook的IGS方案,建议根据具体需求选择合适的算法。总结而言,文章强调了分布式ID生成方案在分布式系统中的重要性,以及不同方案在性能、存储成本、扩展性和可用性方面的权衡。


目录常用的分布式ID解决方案UUIDSnowflakeSnowflake算法的Java代码:LeafLeaf算法的Java代码:基于数据库自增ID生成基于UUID生成基于Redis生成基于ZooKeeper生成

在分布式系统中,生成全局唯一ID是非常重要的,因为在分布式系统中,多个节点同时生成ID可能会导致ID冲突。

下面介绍几种常用的分布式ID解决方案。

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()); } }

UUID的优点是简单易用,无需额外的配置和管理,可以直接使用Java自带的UUID类生成。但是UUID长度较长(128位),不太适合作为数据库表的主键,且不易于排序和索引。

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是美团点评开源的一种分布式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算法略慢,但是可以支持更多的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作为全局唯一ID,可以很好的保证ID的唯一性,并且实现简单,但是并发量较高时可能会导致性能瓶颈。因此,在高并发场景下不建议使用。基于UUID生成:使用UUID作为全局唯一ID,可以很好地保证ID的唯一性,但是ID长度较长(128位),不便于存储和传输,并且存在重复ID的概率非常小但不为0。因此,建议在分布式系统中使用时要考虑ID的长度和存储传输的成本。基于Redis生成:使用Redis的原子性操作,可以保证ID的唯一性,并且生成ID的速度非常快,可以适用于高并发场景。但是需要注意,如果Redis宕机或者性能不足,可能会影响ID的生成效率和可用性。基于ZooKeeper生成:使用ZooKeeper的序列号生成器,可以保证ID的唯一性,并且实现较为简单,但是需要引入额外的依赖和资源,并且可能会存在性能瓶颈。

选择适合自己业务场景的分布式ID生成方案,需要综合考虑ID的唯一性、生成速度、长度、存储成本、可扩展性、可用性等多个因素。同时需要注意,不同方案的实现细节和性能表现也有所不同,需要根据实际情况进行权衡和选择。

下面给出每种方案的详细代码demo:

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是唯一的。

以上就是各种分布式ID生成方案的详细代码demo,实际上,每种方案都有其优缺点,应根据具体业务场景和系统架构选择合适的方案。

以上就是redis分布式ID解决方案示例详解的详细内容,更多关于redis分布式ID的资料请关注脚本之家其它相关文章!

您可能感兴趣的文章:基于Redis实现分布式单号及分布式ID(自定义规则生成)Redisson分布式限流的实现原理解析redisson分布式限流RRateLimiter源码解析Spring?Boot?集成Redisson实现分布式锁详细案例Java实现redis分布式锁的三种方式Redis分布式锁解决秒杀超卖问题