前文分享 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) 中等

详细比较

  1. jetcd
  2. etcd4j
  3. spring-cloud-kubernetes
  4. vertx-etcd-client

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 相关,我也懒得深究原因了。


↙↙↙阅读原文可查看相关链接,并与作者交流