极速体验"/>
jetcd实战之一:极速体验
欢迎访问我的GitHub
这里分类和汇总了欣宸的全部原创(含配套源码):
关于jetcd
- jetcd是etcd v3版本的官方java客户端工具,java项目通过该库可以对etcd执行各种操作,当前最新发布版本是0.5.0
- jetcd官方github:
- etcd在线api文档:/
关于jetcd实战系列
《jetcd实战系列》是欣宸新的原创系列,旨在与大家一起学习如何用jetcd操作etcd,除了基本增删改查,还会涉及到version、监听、租约等etcd特有功能;
系列文章链接
- jetcd实战之一:极速体验
- jetcd实战之二:基本操作
- jetcd实战之三:进阶操作(事务、监听、租约)
本篇概览
作为《jetcd实战系列》的开篇,主要是为整个系列做准备工作,包括以下内容:
- 梳理实战涉及到的应用和库的版本信息;
- 基于docker-compose部署etcd集群;
- 新建gradle工程,作为整个实战系列的父工程;
- 编写helloworld应用,验证jetcd可以正常访问etcd集群;
源码下载
- 本篇实战中的完整源码可在GitHub下载到,地址和链接信息如下表所示():
名称 | 链接 | 备注 |
---|---|---|
项目主页 | 该项目在GitHub上的主页 | |
git仓库地址(https) | .git | 该项目源码的仓库地址,https协议 |
git仓库地址(ssh) | git@github:zq2599/blog_demos.git | 该项目源码的仓库地址,ssh协议 |
-
这个git项目中有多个文件夹,kubebuilder相关的应用在jetcd-tutorials文件夹下,如下图红框所示:
-
jetcd-tutorials文件夹下有多个子项目,本篇的是helloworld:
版本信息
实战系列所用的etcd是部署在docker环境下的三个实例组建的集群:
- etcd:3.4.7
- docker:20.10.5(Community)
- docker-compose:1.28.5
- jetcd:0.5.0
- jdk:1.8.0_271
- springboot:2.4.4
- IDEA:2020.2.3 (Ultimate Edition)
- gradle:6.8.3
- 电脑操作系统:macOS Big Sur 11.2.3
部署集群
- 确认docker和docker-compose已正常运行
- 新建docker-compose.yml文件:
version: '3'
services:etcd1:image: "quay.io/coreos/etcd:v3.4.7"entrypoint: /usr/local/bin/etcdcommand:- '--name=etcd1'- '--data-dir=/etcd_data'- '--initial-advertise-peer-urls=http://etcd1:2380'- '--listen-peer-urls=:2380'- '--listen-client-urls=:2379'- '--advertise-client-urls=http://etcd1:2379'- '--initial-cluster-token=etcd-cluster'- '--heartbeat-interval=250'- '--election-timeout=1250'- '--initial-cluster=etcd1=http://etcd1:2380,etcd2=http://etcd2:2380,etcd3=http://etcd3:2380'- '--initial-cluster-state=new'ports:- 2379:2379volumes:- ./store/etcd1/data:/etcd_dataetcd2:image: "quay.io/coreos/etcd:v3.4.7"entrypoint: /usr/local/bin/etcdcommand:- '--name=etcd2'- '--data-dir=/etcd_data'- '--initial-advertise-peer-urls=http://etcd2:2380'- '--listen-peer-urls=:2380'- '--listen-client-urls=:2379'- '--advertise-client-urls=http://etcd2:2379'- '--initial-cluster-token=etcd-cluster'- '--heartbeat-interval=250'- '--election-timeout=1250'- '--initial-cluster=etcd1=http://etcd1:2380,etcd2=http://etcd2:2380,etcd3=http://etcd3:2380'- '--initial-cluster-state=new'ports:- 2380:2379volumes:- ./store/etcd2/data:/etcd_dataetcd3:image: "quay.io/coreos/etcd:v3.4.7"entrypoint: /usr/local/bin/etcdcommand:- '--name=etcd3'- '--data-dir=/etcd_data'- '--initial-advertise-peer-urls=http://etcd3:2380'- '--listen-peer-urls=:2380'- '--listen-client-urls=:2379'- '--advertise-client-urls=http://etcd3:2379'- '--initial-cluster-token=etcd-cluster'- '--heartbeat-interval=250'- '--election-timeout=1250'- '--initial-cluster=etcd1=http://etcd1:2380,etcd2=http://etcd2:2380,etcd3=http://etcd3:2380'- '--initial-cluster-state=new'ports:- 2381:2379volumes:- ./store/etcd3/data:/etcd_data
- 从上述脚本可见,宿主机的2379、2380、2381三个端口被用来映射三个etcd容器的2379端口;
- 执行命令docker-compose up -d,如下,可见三个容器都已经创建,名称分别是28_etcd1_1、28_etcd2_1、28_etcd3_1,容器名和当前目录名有关:
zhaoqin@zhaoqindeMacBook-Pro-2 28 % docker-compose up -d
Creating network "28_default" with the default driver
Creating 28_etcd2_1 ... done
Creating 28_etcd3_1 ... done
Creating 28_etcd1_1 ... done
- etcd集群已启动成功,来试试基本操作命令是否正常,执行以下命令新建一个键值对,键是/aaa/foo,值是111:
docker exec 28_etcd1_1 /usr/local/bin/etcdctl put /aaa/foo 111
- 查看命令如下(我这换了个容器):
docker exec 28_etcd2_1 /usr/local/bin/etcdctl get /aaa/foo -w fields
- 得到的是完整的结果,除了键值还有Revision、ModRevision、Version、Lease等字段:
"ClusterID" : 10316109323310759371
"MemberID" : 15168875803774599630
"Revision" : 2
"RaftTerm" : 2
"Key" : "/aaa/foo"
"CreateRevision" : 2
"ModRevision" : 2
"Version" : 1
"Value" : "111"
"Lease" : 0
"More" : false
"Count" : 1
新建gradle工程,作为整个实战系列的父工程
- 接下来新建一个gradle工程,整个实战系列都是在此父工程下开发;
- 新建gradle工程名为jetcd-tutorials,其build.gradle内容如下:
import java.time.OffsetDateTime
import java.time.format.DateTimeFormatter// gradle自身会用到的相关设置
buildscript {// 仓库repositories {// 本地mavenLocal()// 如果有私服就在此配置,如果没有请注释掉maven {url 'http://192.168.50.43:8081/repository/aliyun-proxy/'}// 阿里云maven {url '/'}// 中央仓库mavenCentral()// grandle插件maven {url '/'}}// 子模块会用到的变量ext {springBootVersion = '2.4.4'}
}// 插件
plugins {id 'java'id 'java-library'// 有这个声明,子模块可以使用org.springframework.boot插件而无需指定版本,但是apply=false表示当前模块不使用此插件id 'org.springframework.boot' version "${springBootVersion}" apply falseid 'io.spring.dependency-management' version '1.0.11.RELEASE'
}// gradle wrapper指定版本
wrapper {gradleVersion = '6.8.3'
}// 取当前时间
def buildTimeAndDate = OffsetDateTime.now()// 根据时间生成字符串变量
ext {projectVersion = project.versionbuildDate = DateTimeFormatter.ISO_LOCAL_DATE.format(buildTimeAndDate)buildTime = DateTimeFormatter.ofPattern('HH:mm:ss.SSSZ').format(buildTimeAndDate)
}// 针对所有project的配置,包含根项目
allprojects {group 'com.bolingcavalry'version '1.0-SNAPSHOT'apply plugin: 'java'apply plugin: 'idea'apply plugin: 'io.spring.dependency-management'// 编译相关参数compileJava {sourceCompatibility = JavaVersion.VERSION_1_8targetCompatibility = JavaVersion.VERSION_1_8options.encoding = 'UTF-8'optionspilerArgs = ['-Xlint:all', '-Xlint:-processing']}// Copy LICENSEtasks.withType(Jar) {from(project.rootDir) {include 'LICENSE'into 'META-INF'}}// 生成jar文件时,MANIFEST.MF的内容如下jar {manifest {attributes('Created-By': "${System.properties['java.version']} (${System.properties['java.vendor']} ${System.properties['java.vm.version']})".toString(),'Built-By': 'travis','Build-Date': buildDate,'Build-Time': buildTime,'Built-OS': "${System.properties['os.name']}",'Specification-Title': project.name,'Specification-Version': project.version,'Specification-Vendor': 'Will Zhao','Implementation-Title': project.name,'Implementation-Version': project.version,'Implementation-Vendor': 'Will Zhao')}}// 仓库repositories {// 本地mavenLocal()// 如果有私服就在此配置,如果没有请注释掉maven {url 'http://192.168.50.43:8081/repository/aliyun-proxy/'}// 阿里云maven {url '/'}// 中央仓库mavenCentral()// grandle插件maven {url "/"}}
}// 类似maven的dependencyManagement,这里将所有jar的版本指定好,子模块在依赖时可以不用指定版本
allprojects { project ->buildscript {dependencyManagement {imports {mavenBom "org.springframework.boot:spring-boot-starter-parent:${springBootVersion}"mavenBom "org.junit:junit-bom:5.7.0"}dependencies {dependency 'org.projectlombok:lombok:1.16.16'dependency 'org.apachemons:commons-lang3:3.11'dependency 'commons-collections:commons-collections:3.2.2'dependency 'io.etcd:jetcd-core:0.5.0'dependency 'org.slf4j:slf4j-log4j12:1.7.30'}}}
}// 坐标信息
group 'com.bolingcavalry'
version '1.0-SNAPSHOT'
- 现在根模块已经建好,后面整个系列的代码都会写在这个根模块下面;
编写helloworld应用
- 接下来写个helloworld应用验证jetcd能不能操作etcd集群;
- 在根模块下面新建名为helloworld的gradle子模块,其build.gradle内容如下:
plugins {id 'java'
}// 子模块自己的依赖
dependencies {implementation 'io.etcd:jetcd-core'implementation 'org.projectlombok:lombok'// annotationProcessor不会传递,使用了lombok生成代码的模块,需要自己声明annotationProcessorannotationProcessor 'org.projectlombok:lombok'implementation 'org.slf4j:slf4j-log4j12'testImplementation('org.junit.jupiter:junit-jupiter')
}test {useJUnitPlatform()
}group 'com.bolingcavalry'
version '1.0-SNAPSHOT'
- 新增HelloWorld.java,代码很简单,getKVClient方法用来生成客户端实例,put方法向etcd写入键值对,get方法去etcd查询指定键的值:
package com.bolingcavalry;import io.etcd.jetcd.ByteSequence;
import io.etcd.jetcd.Client;
import io.etcd.jetcd.KV;
import io.etcd.jetcd.kv.GetResponse;
import io.etcd.jetcd.kv.PutResponse;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import static com.googlemon.base.Charsets.UTF_8;@Slf4j
public class HelloWorld {/*** 新建key-value客户端实例* @return*/private KV getKVClient(){String endpoints = "http://192.168.50.239:2379,http://192.168.50.239:2380,http://192.168.50.239:2381";Client client = Client.builder().endpoints(endpoints.split(",")).build();return client.getKVClient();}/*** 将字符串转为客户端所需的ByteSequence实例* @param val* @return*/private static ByteSequence bytesOf(String val) {return ByteSequence.from(val, UTF_8);}/*** 查询指定键对应的值* @param key* @return* @throws ExecutionException* @throws InterruptedException*/public String get(String key) throws ExecutionException, InterruptedException{log.info("start get, key [{}]", key);GetResponse response = getKVClient().get(bytesOf(key)).get();if (response.getKvs().isEmpty()) {log.error("empty value of key [{}]", key);return null;}String value = response.getKvs().get(0).getValue().toString(UTF_8);log.info("finish get, key [{}], value [{}]", key, value);return value;}/*** 创建键值对* @param key* @param value* @return* @throws ExecutionException* @throws InterruptedException*/public PutResponse put(String key, String value) throws ExecutionException, InterruptedException {log.info("start put, key [{}], value [{}]", key, value);return getKVClient().put(bytesOf(key), bytesOf(value)).get();}
}
- 接下来咱们写个单元测试类来验证上述代码是否有效:
package com.bolingcavalry;import io.etcd.jetcd.kv.PutResponse;
import org.junit.jupiter.api.MethodOrderer;
import org.junit.jupiter.api.Order;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.TestMethodOrder;
import java.util.concurrent.ExecutionException;
import static org.junit.jupiter.api.Assertions.*;@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
class HelloWorldTest {// 用与测试的键private static final String KEY = "/abc/foo-" + System.currentTimeMillis();// 用于测试的值private static final String VALUE = "/abc/foo";@org.junit.jupiter.api.Test@Order(2)void get() throws ExecutionException, InterruptedException {String getResult = new HelloWorld().get(KEY);assertEquals(VALUE, getResult);}@Test@Order(1)void put() throws ExecutionException, InterruptedException {PutResponse putResponse = new HelloWorld().put(KEY, VALUE);assertNotNull(putResponse);assertNotNull(putResponse.getHeader());}
}
- 代码写完后按照下图红框指示执行单元测试,可见jetcd操作etcd成功:
- 至此,《jetcd实战》系列的开篇就完成了,咱们搭建好了etcd集群,还初步体验过jetcd的基本功能,接下来的章节会从基本操作开始,由浅入深的学习jetcd;
你不孤单,欣宸原创一路相伴
- Java系列
- Spring系列
- Docker系列
- kubernetes系列
- 数据库+中间件系列
- DevOps系列
欢迎关注公众号:程序员欣宸
微信搜索「程序员欣宸」,我是欣宸,期待与您一同畅游Java世界…
更多推荐
jetcd实战之一:极速体验
发布评论