概述
在单体架构中,我们通常使用数据库字段自带的自增auto_increment属性来自动为每一条记录生成唯一的ID,但是当我们采用分布式系统后,特别是分库分表后,就无法再依靠数据库的auto_increment属性来唯一标识一条记录,这就需要采用其他方法来生成全局唯一ID。
在zookeeper中,它提供了一种以创建临时有序节点来获取到全局唯一ID,它能保证在整个分布式系统中的全局唯一性。
项目demo地址
案例实现
实现步骤:
- 1、客户端连接到zookeeper服务器端;
- 2、客户端在指定路径下生成临时有序节点;
- 3、取出序列号,这就是分布式全局唯一ID;
zookeeper实现
1、引入依赖:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| <dependency> <groupId>org.apache.zookeeper</groupId> <artifactId>zookeeper</artifactId> <version>3.5.7</version> <exclusions> <exclusion> <groupId>log4j</groupId> <artifactId>log4j</artifactId> </exclusion> <exclusion> <groupId>org.slf4j</groupId> <artifactId>slf4j-log4j12</artifactId> </exclusion> </exclusions> </dependency>
|
2、代码实现
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 62 63 64 65 66 67 68 69 70 71 72 73 74 75
| @Slf4j public class ZookeeperUniqueID implements Watcher {
public static CountDownLatch countDownLatch = new CountDownLatch(1);
public static ZooKeeper zooKeeper;
private final String IP = "127.0.0.1:2181";
private final String DEFAULT_PATH = "/uniqueId"; private final String ROOT_PATH = "/uniqueId";
public ZookeeperUniqueID() { try { zooKeeper = new ZooKeeper(IP, 6000, this); countDownLatch.await(); Stat stat = zooKeeper.exists(ROOT_PATH, false); if (stat == null) { zooKeeper.create(ROOT_PATH, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.PERSISTENT); } } catch (Exception e) { e.printStackTrace(); } }
@Override public void process(WatchedEvent watchedEvent) { try { if (watchedEvent.getType() == Event.EventType.None) { if (watchedEvent.getState() == Event.KeeperState.SyncConnected) { log.info("连接成功"); countDownLatch.countDown(); } else if (watchedEvent.getState() == Event.KeeperState.Disconnected) { log.info("断开连接"); } else if (watchedEvent.getState() == Event.KeeperState.Expired) { log.info("会话超时"); zooKeeper = new ZooKeeper(IP, 6000, this); } else if (watchedEvent.getState() == Event.KeeperState.AuthFailed) { log.info("认证失败"); } } } catch (Exception e) { e.printStackTrace(); } }
public String getUniqueId() { String path = ""; try { path = zooKeeper.create(ROOT_PATH + DEFAULT_PATH, new byte[0], ZooDefs.Ids.OPEN_ACL_UNSAFE, CreateMode.EPHEMERAL_SEQUENTIAL); log.info("zk创建临时有序节点:{}", path); } catch (KeeperException | InterruptedException e) { e.printStackTrace(); } return path.substring(ROOT_PATH.length() + DEFAULT_PATH.length()); }
}
|
3、测试
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
| public class ZookeeperUniqueIDTest {
@Test public void getUniqueId() { ZookeeperUniqueID zookeeperUniqueID = new ZookeeperUniqueID(); for (int i = 0; i < 10; i++) { System.out.println("分布式唯一ID:" + zookeeperUniqueID.getUniqueId()); } }
@Test public void getUniqueIdForMore() { ZookeeperUniqueID zookeeperUniqueID = new ZookeeperUniqueID();
new Thread(() -> { for (int i = 0; i < 10; i++) { System.out.println(Thread.currentThread().getName() + "分布式唯一ID:" + zookeeperUniqueID.getUniqueId()); } }, "thread-0").start();
new Thread(() -> { for (int i = 0; i < 10; i++) { System.out.println(Thread.currentThread().getName() + "分布式唯一ID:" + zookeeperUniqueID.getUniqueId()); } }, "thread-1").start();
new Thread(() -> { for (int i = 0; i < 10; i++) { System.out.println(Thread.currentThread().getName() + "分布式唯一ID:" + zookeeperUniqueID.getUniqueId()); } }, "thread-2").start();
try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); }
}
}
|
Zookeeper Client 查看节点数据:
分析:
原生ZooKeeper的API实现的一些问题
会话连接是异步的,需要自己去处理。比如使用 CountDownLatch
Watch 需要重复注册,不然就不能生效
开发的复杂性还是比较高的
不支持多节点删除和创建,需要自己去递归。
因此可以使用ZooKeeper的框架——Curator。
Curator实现分布式ID
Curator是Netflix公司开源的一套ZooKeeper客户端框架,提供了一套易用性和可读性更强的Fluent风格的客户端API框架。为ZooKeeper客户端框架提供了一些比较普遍的、开箱即用的、分布式开发用的解决方案,例如Recipe、共享锁服务、Master选举机制和分布式计算器等,帮助开发者避免了“重复造轮子”的无效开发工作。
Guava is to Java that Curator to ZooKeeper
更多Curator介绍参考:《Zookeeper开源客户端框架Curator简介》https://www.iteye.com/blog/macrochen-1366136
1、代码实现
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
| @Slf4j public class CuratorUniqueID {
private static CuratorFramework curatorFrameworkClient;
private static RetryPolicy retryPolicy;
private static final String IP = "127.0.0.1:2181";
private static String ROOT = "/uniqueId-curator";
private static String NODE_NAME = "/uniqueId";
static { retryPolicy = new ExponentialBackoffRetry(1000, 3); curatorFrameworkClient = CuratorFrameworkFactory .builder() .connectString(IP) .sessionTimeoutMs(5000) .connectionTimeoutMs(5000) .retryPolicy(retryPolicy) .build(); curatorFrameworkClient.start(); try { Stat stat = curatorFrameworkClient.checkExists().forPath(ROOT); if (stat == null) { curatorFrameworkClient.create().withMode(CreateMode.PERSISTENT).forPath(ROOT, null); } } catch (Exception e) { e.printStackTrace(); } }
public static String generateId() { String backPath = ""; String fullPath = ROOT.concat(NODE_NAME); try { backPath = curatorFrameworkClient.create().withMode(CreateMode.EPHEMERAL_SEQUENTIAL).forPath(fullPath, null); log.info("zk创建临时有序节点:{}", backPath); } catch (Exception e) { e.printStackTrace(); } return backPath.substring(ROOT.length() + NODE_NAME.length()); }
}
|
2、测试
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 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107
| @SpringBootTest public class CuratorUniqueIDTest {
@Test public void generateId() { String id = CuratorUniqueID.generateId(); System.out.println(id); }
@Test public void getUniqueIdForThread() { new Thread(() -> { for (int i = 0; i < 10; i++) { System.out.println(Thread.currentThread().getName() + "分布式唯一ID:" + CuratorUniqueID.generateId()); } }, "thread-0").start();
new Thread(() -> { for (int i = 0; i < 10; i++) { System.out.println(Thread.currentThread().getName() + "分布式唯一ID:" + CuratorUniqueID.generateId()); } }, "thread-1").start();
new Thread(() -> { for (int i = 0; i < 10; i++) { System.out.println(Thread.currentThread().getName() + "分布式唯一ID:" + CuratorUniqueID.generateId()); } }, "thread-2").start();
try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } }
@Test public void generateUniqueIdForMore() { LocalDateTime startTime = LocalDateTime.now(); for (int i = 0; i < 100000; i++) { String id = CuratorUniqueID.generateId(); System.out.println(id); } LocalDateTime endTime = LocalDateTime.now(); long minutes = Duration.between(startTime, endTime).toMillis(); System.out.println("生成10万个分布式id所用的时间:" + minutes + " ms"); }
@Test public void generateUniqueIdForThreadPoolExecutor() { ThreadPoolExecutor threadPoolExecutor = null;
threadPoolExecutor = new ThreadPoolExecutor(10, 20, 10, TimeUnit.SECONDS, new LinkedBlockingQueue<>(20), new ThreadPoolExecutor.CallerRunsPolicy()); LocalDateTime startTime = LocalDateTime.now(); for (int i = 0; i < 100000; i++) { FutureTask<String> futureTask = new FutureTask<>(new ThreadPoolTask()); threadPoolExecutor.execute(futureTask); try { String id = futureTask.get(); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); } } threadPoolExecutor.shutdown(); LocalDateTime endTime = LocalDateTime.now(); long minutes = Duration.between(startTime, endTime).toMillis(); System.out.println("线程池 生成10万个分布式id所用的时间:" + minutes + " ms");
}
static class ThreadPoolTask implements Callable<String> {
@Override public String call() { String id = CuratorUniqueID.generateId(); System.out.println(Thread.currentThread().getName() + "---" + id); return id; }
}
|
Zookeeper Client 查看节点数据:
单线程生成10万个分布式ID测速
ZooKeeper单机环境下,使用单线程,生成10万分布式ID测速。
代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
|
@Test public void generateUniqueIdForMore() { LocalDateTime startTime = LocalDateTime.now(); for (int i = 0; i < 100000; i++) { String id = CuratorUniqueID.generateId(); System.out.println(id); } LocalDateTime endTime = LocalDateTime.now(); long minutes = Duration.between(startTime, endTime).toMillis(); System.out.println("生成10万个分布式id所用的时间:" + minutes + " ms"); }
|
结果:
1 2 3 4 5 6 7 8 9 10
| 2021-08-21 10:29:28,568 INFO [cn.duktig.learn.id.CuratorUniqueID] - zk创建临时有序节点:/uniqueId-curator/uniqueId0000100087 0000100087 2021-08-21 10:29:28,603 INFO [cn.duktig.learn.id.CuratorUniqueID] - zk创建临时有序节点:/uniqueId-curator/uniqueId0000100088 0000100088 2021-08-21 10:29:28,645 INFO [cn.duktig.learn.id.CuratorUniqueID] - zk创建临时有序节点:/uniqueId-curator/uniqueId0000100089 0000100089 2021-08-21 10:29:28,681 INFO [cn.duktig.learn.id.CuratorUniqueID] - zk创建临时有序节点:/uniqueId-curator/uniqueId0000100090 0000100090 生成10万个分布式id所用的时间:3060085 ms
|
线程池10个线程生成10万个分布式ID测速
ZooKeeper单机环境下,使用线程池开10个线程,生成10万分布式ID测速。
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
|
@Test public void generateUniqueIdForThreadPoolExecutor() { ThreadPoolExecutor threadPoolExecutor = null;
threadPoolExecutor = new ThreadPoolExecutor(10, 20, 10, TimeUnit.SECONDS, new LinkedBlockingQueue<>(20), new ThreadPoolExecutor.CallerRunsPolicy()); LocalDateTime startTime = LocalDateTime.now(); for (int i = 0; i < 100000; i++) { FutureTask<String> futureTask = new FutureTask<>(new ThreadPoolTask()); threadPoolExecutor.execute(futureTask); try { String id = futureTask.get(); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); } } threadPoolExecutor.shutdown(); LocalDateTime endTime = LocalDateTime.now(); long minutes = Duration.between(startTime, endTime).toMillis(); System.out.println("线程池 生成10万个分布式id所用的时间:" + minutes + " ms");
}
static class ThreadPoolTask implements Callable<String> {
@Override public String call() { String id = CuratorUniqueID.generateId(); System.out.println(Thread.currentThread().getName() + "---" + id); return id; }
}
|
结果:
1 2 3 4 5 6 7 8 9
| …… 2021-08-21 11:24:31,674 INFO [cn.duktig.learn.id.CuratorUniqueID] - zk创建临时有序节点:/uniqueId-curator/uniqueId0000200088 pool-1-thread-4---0000200088 2021-08-21 11:24:31,694 INFO [cn.duktig.learn.id.CuratorUniqueID] - zk创建临时有序节点:/uniqueId-curator/uniqueId0000200089 pool-1-thread-2---0000200089 2021-08-21 11:24:31,718 INFO [cn.duktig.learn.id.CuratorUniqueID] - zk创建临时有序节点:/uniqueId-curator/uniqueId0000200090 pool-1-thread-3---0000200090
线程池 生成10万个分布式id所用的时间:3073690 ms
|
Redis与ZooKeeper实现分布式ID对比
环境:单机的Redis和单机的ZooKeeper进行测试
|
Redis |
ZooKeeper |
单线程10万分布式ID |
110353 ms |
3060085 ms 大约为 51min |
线程池开10个线程生成10万分布式ID |
106959 ms |
3073690 ms 基本和单线程环境一致 |
This is copyright.