Zookeeper案例-分布式全局唯一ID生成

Zookeeper实现分布式全局唯一ID生成

Posted by John Doe on 2021-07-05
Words 2.4k and Reading Time 11 Minutes
Viewed Times

概述

在单体架构中,我们通常使用数据库字段自带的自增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
<!--    排除zk依赖的log4j和slf4j,使用springboot提供的logback    -->
<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);
//等待zk正常连接后,往下走程序
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 {
//EventType = None时
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();
}
}

/**
* 根据 zk 临时有序节点,生成唯一ID
*
* @return 唯一ID
*/
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();
}
//截取defaultPath的长度
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 {

/**
* 测试 zk 生成唯一ID
*/
@Test
public void getUniqueId() {
ZookeeperUniqueID zookeeperUniqueID = new ZookeeperUniqueID();
for (int i = 0; i < 10; i++) {
System.out.println("分布式唯一ID:" + zookeeperUniqueID.getUniqueId());
}
}

/**
* 测试 zk 生成唯一ID(多线程下)
*/
@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 查看节点数据

分析

  • 所有ID唯一,无重复
  • 多线程环境下可保证ID自增

原生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 {
//请先判断父节点/root节点是否存在
Stat stat = curatorFrameworkClient.checkExists().forPath(ROOT);
if (stat == null) {
curatorFrameworkClient.create().withMode(CreateMode.PERSISTENT).forPath(ROOT, null);
}
} catch (Exception e) {
e.printStackTrace();
}
}

/**
* 生成唯一id
*
* @return 唯一id
*/
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 {

/**
* 测试 Curator 生成分布式id
*/
@Test
public void generateId() {
String id = CuratorUniqueID.generateId();
System.out.println(id);
}

/**
* 测试 Curator 生成唯一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();
}
}

/**
* 单机ZooKeeper
* 单线程生成10W个 分布式ID 测速
* 大约为:3060085 ms 大约为 51min
*/
@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");
}

/**
* 单机ZooKeeper
* 线程池开10个线程生成10W个 分布式ID 测速
* 大约为:3073690 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
/**
* 单机ZooKeeper
* 单线程生成10W个 分布式ID 测速
* 大约为:3060085 ms 大约为 51min
*/
@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
/**
* 单机ZooKeeper
* 线程池开10个线程生成10W个 分布式ID 测速
* 大约为:3073690 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;
}

}

结果:

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.

...

...

00:00
00:00