前文分享 etcd 框架 Go 语言的实践,今天分享一下 Java 客户端的不分。再分享之前,先简单聊一下我查阅的资料的现状,以方便各位再开始 Java 客户端学习之前,有个心理预期。
etcd 本身是 Go 语言编写的,所以在语言支持上,Go 语言是支持的最好的。其他的就差强人意,这种场景有点像 Web3j
,有人再维护,但是从使用便捷程度上,总是不能一帆风顺直接上手。
而且还有一个原因,etcd 的 Java 实现库太多了,各种库之间的细微差异也能让我搜索资料的时候难以准确找到最佳实践及其原理介绍。
大多数实现库都用了大量的异步操作,语法跟 Web3j
类似,我也不确定是哪种设计模式,如果你有 Web3j
使用经验,相信会更加容易上手。
Java 客户端比较
详细比较
jetcd
优点:
官方支持,与 etcd 版本同步更新
全面支持 etcd v3 API
性能优秀,适合大规模生产环境
缺点:
依赖较重(gRPC)
学习曲线可能稍陡
etcd4j
优点:
轻量级,容易集成
API 简单直观
缺点:
主要支持 etcd v2 API,对 v3 支持有限
社区更新较慢
不适合需要 v3 API 特性的新项目
spring-cloud-kubernetes
优点:
与 Spring Cloud 和 Kubernetes 生态系统深度集成
提供服务发现和配置管理功能
缺点:
依赖 Spring 生态系统,不适合非 Spring 项目
可能引入不必要的复杂性(如果只需要简单的 etcd 客户端)
vertx-etcd-client
优点:
与 Vert.x 生态系统集成
非阻塞 API,适合高并发场景
缺点:
与 Vert.x 绑定,不适合非 Vert.x 项目
社区相对较小
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);
}
}
复制代码
下面我们来依次执行两个方法:
public static void main(String[] args) {
watch()
writeRead()
}
复制代码
下面是控制台打印:
watch change ------------------
修改的类型Event type: PUT
修改的Key: key
修改后Value: FunTester
Value: FunTester
复制代码
可以看到是满足预期的。但是问题来了,JVM 进程就是不退出,比较尴尬,即使我们加上关闭客户端的方法 client.close()
也不行,打开线程转储之后发现好几个 RUNNABLE
的线程,还有一个 forkjoin
线程池,现象跟 Web3j
很像,但是这次跟 Netty
相关,我也懒得深究原因了。
评论