前文分享etcd框架Go语言的实践,今天分享一下Java客户端的不分。再分享之前,先简单聊一下我查阅的资料的现状,以方便各位再开始Java客户端学习之前,有个心理预期。
etcd本身是Go语言编写的,所以在语言支持上,Go语言是支持的最好的。其他的就差强人意,这种场景有点像 Web3j
,有人再维护,但是从使用便捷程度上,总是不能一帆风顺直接上手。
而且还有一个原因,etcd的Java实现库太多了,各种库之间的细微差异也能让我搜索资料的时候难以准确找到最佳实践及其原理介绍。
大多数实现库都用了大量的异步操作,语法跟 Web3j
类似,我也不确定是哪种设计模式,如果你有 Web3j
使用经验,相信会更加容易上手。
Java 客户端比较
特性 | jetcd | etcd4j | spring-cloud-kubernetes | vertx-etcd-client |
---|---|---|---|---|
维护者 | etcd-io (CoreOS) | jurmous | Spring Cloud | Eclipse Vert.x |
etcd 兼容性 | v3 API | 主要支持 v2 API | v3 API | v3 API |
异步支持 | 是 | 是 | 是 | 是 |
依赖 | gRPC | Netty | Spring Cloud | Vert.x |
特点 | 官方支持,全面的功能 | 轻量级,简单易用 | 与 Spring Cloud 集成 | 与 Vert.x 生态系统集成 |
适用场景 | 大型项目,需要全面功能 | 简单使用,遗留系统 | Spring Cloud 项目 | Vert.x 项目 |
Watch 支持 | 是 | 是 | 是 | 是 |
事务支持 | 是 | 有限 | 通过 Spring 抽象 | 是 |
性能 | 高 | 中等 | 依赖 Spring 抽象 | 高 |
社区活跃度 | 高 | 低 | 高 | 中等 |
文档质量 | 详细 | 一般 | 详细 | 详细 |
学习曲线 | 中等 | 低 | 高(如果不熟悉 Spring) | 中等 |
详细比较
- jetcd
- 依赖较重(gRPC)
- 学习曲线可能稍陡
- 官方支持,与 etcd 版本同步更新
- 全面支持 etcd v3 API
- 性能优秀,适合大规模生产环境
- 优点:
- 缺点:
- etcd4j
- 主要支持 etcd v2 API,对 v3 支持有限
- 社区更新较慢
- 不适合需要 v3 API 特性的新项目
- 轻量级,容易集成
- API 简单直观
- 优点:
- 缺点:
- spring-cloud-kubernetes
- 依赖 Spring 生态系统,不适合非 Spring 项目
- 可能引入不必要的复杂性(如果只需要简单的 etcd 客户端)
- 与 Spring Cloud 和 Kubernetes 生态系统深度集成
- 提供服务发现和配置管理功能
- 优点:
- 缺点:
- vertx-etcd-client
- 与 Vert.x 绑定,不适合非 Vert.x 项目
- 社区相对较小
- 与 Vert.x 生态系统集成
- 非阻塞 API,适合高并发场景
- 优点:
- 缺点:
Java 客户端实践
下面我选择 jetcd
作为实现库,首先我们添加依赖项目:
<dependency>
<groupId>io.etcd</groupId>
<artifactId>jetcd-core</artifactId>
<version>0.7.0</version>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>31.1-jre</version>
</dependency>
如果你再运行当中遇到了 Exception in thread "main" java.lang.NoClassDefFoundError:
此类错误,请检查服务端版本,gRPC版本,客户端版本,以及依赖项缺失。这也是劝退的原因之一。
接下来我们来看Case,除了读写以外,我增加了监听的用例。总体来讲,语法比较熟悉 (我用过 Web3j
),下面是两个简单的例子,用来演示 jetcd
的基本使用。
package com.funtest.temp
import com.funtester.frame.SourceCode
import io.etcd.jetcd.ByteSequence
import io.etcd.jetcd.Client
import io.etcd.jetcd.Watch
import io.etcd.jetcd.kv.GetResponse
import io.etcd.jetcd.watch.WatchEvent
import java.nio.charset.Charset
import java.nio.charset.StandardCharsets
import java.util.concurrent.CompletableFuture
class TtcdTest extends SourceCode {
static Charset defaultCharset = StandardCharsets.UTF_8
// 创建客户端, 连接etcd
static def client = Client.builder().endpoints("http://localhost:2379").build()
// 创建KV客户端, 用于读写数据
static def kVClient = client.getKVClient()
static def watchClient = client.getWatchClient()
/**
* 监听etcd中的key变化, 有变化时打印出来
*/
static watch() {
def key = toByteSequence("key")// 监听的key
Watch.Listener listener = Watch.listener(watchResponse -> {// 监听器
for (WatchEvent event : watchResponse.getEvents()) {// 事件
println("watch change ------------------")// 打印
println("修改的类型Event type: " event.getEventType());// 事件类型, PUT, DELETE
println("修改的Key: " event.getKeyValue().getKey().toString(StandardCharsets.UTF_8));// 修改的Key, ByteSequence转字符串
println("修改后Value: " event.getKeyValue().getValue().toString(StandardCharsets.UTF_8));// 修改后的Value, ByteSequence转字符串
}
});
watchClient.watch(key, listener)// 监听key, 有变化时触发监听器
}
/**
* 写入数据, 读取数据
* @return
*/
static writeRead() {
kVClient.put(toByteSequence("key"), toByteSequence("FunTester")).get()// 写入key-value
CompletableFuture<GetResponse> getFuture = kVClient.get(toByteSequence("key"))// 读取key-value
GetResponse response = getFuture.get()// 获取结果, 阻塞等待, 直到获取到结果
println("Value: " response.getKvs().get(0).getValue().toString())// 打印结果
}
/**
* 字符串转ByteSequence
* @param str
* @param Charset
* @return
*/
static ByteSequence toByteSequence(String str, Charset = defaultCharset) {
return ByteSequence.from(str, defaultCharset);
}
}
下面我们来依次执行两个方法:
代码语言:javascript复制 public static void main(String[] args) {
watch()
writeRead()
}
下面是控制台打印:
代码语言:javascript复制watch change ------------------
修改的类型Event type: PUT
修改的Key: key
修改后Value: FunTester
Value: FunTester
可以看到是满足预期的。但是问题来了,JVM进程就是不退出,比较尴尬,即使我们加上关闭客户端的方法 client.close()
也不行,打开线程转储之后发现好几个 RUNNABLE
的线程,还有一个 forkjoin
线程池,现象跟 Web3j
很像,但是这次跟 Netty
相关,我也懒得深究原因了。