关于java:长篇图解etcd核心应用场景及编码实战

30次阅读

共计 7196 个字符,预计需要花费 18 分钟才能阅读完成。

大家好啊,我是字母哥,明天写一篇对于 etcd 的文章,其实网上也有很多对于 etcd 的介绍,我就简明扼要,总结提炼,冀望大家通过这一篇文章把握 etcd 的外围常识以及编码技能

  • 本文首先用大白话给大家介绍一下 etcd 是什么?这部分内容网上曾经有很多了。
  • etcd 有哪些利用场景?这些利用场景的外围原理是什么?
  • 最初不能光动嘴不入手。先搭建一个 etcd 单机版,再应用 java 的客户端操作 etcd 数据。

本文旨在帮忙大家了解 etcd,从宏观角度鸟瞰 etcd 全局,把握 etcd 的根本操作技能。后续我还会写一个系列的文章,将每一种利用场景代码化,期待大家关注我和我的公众号:字母哥杂谈。后续打算章节内容如下:

  • 《搭建高可用 etcd 集群》
  • 《基于 etcd 实现分布式锁(java 代码实现)》
  • 《基于 etcd 实现配置变更告诉(java 代码实现)》
  • 《基于 etcd 实现服务注册与发现(java 代码实现)》
  • 《基于 etcd 实现分布式系统节点 leader 选举(java 代码实现)》

一、文言 etcd 与 zookeeper

用过 linux 的敌人请举手,好的,我看见了!在 linux 中所有主动装置的系统软件配置文件都存储在一个名为 /etc 的目录中。“d”示意 distributed 分布式,etcd 为分布式模型,所以 etcd 的外围利用场景是:分布式系统的配置信息存储

网上很多文章上来第一句话照搬英文官网:etcd 是一个高度一致的分布式键值存储系统 。很多敌人看完就问了,这玩意和 redis 有啥区别?笔者要说,真的不要这么比,etcd 从名字上就曾经通知你了,它是存储配置信息(元数据) 的。和 redis 在架构利用上就不在一个层面,它对标的产品应该是 zookeeper。尽管 zookeeper 在很多 java 的分布式系统的利用中比拟宽泛,然而 etcd 作为后起之秀,乘 kubernetes 的东风,大有超过 zookeeper 的趋势。

  • zookeeper 是应用 java 写的,etcd 是应用 go 语言编写的。zookeeper 应用了 TCP 协定,其交互报文规定是齐全自定义的,如果不应用 zookeeper 提供的 SDK 就无奈操作数据。而 etcd 应用的是 google 的 gRPC 协定,普适性更好一些。
  • zookeeper 对于一次申请,开启一个 socket 进行监听。而 etcd 的监听管道 channel 能够重复被利用,从 IO 性能到系统资源的利用的角度,etcd 无疑是更优良的。
  • zookeeper 应用 zab 协定保障集群节点配置信息的一致性,etcd 应用 raft 协定。冀望具体理解 raft 协定的,点击《raft 协定中文介绍》。

大部分性能和 zookeeper 都是一样的,目前看 java 程序员用 zookeeper 的更多,其余程序员用 etcd 更多。都是基于习惯,但笔者举荐 etcd。

二、etcd 的 4 个外围机制

etcd 以 key-value 的模式进行数据的存储. 配合上面的这四种机制, 使得 etcd 的利用场景更加的宽泛.

  • Prefix 机制 :即前缀机制,也称 目录机制,客户端向 etcd 放入 2 个键值对配置, 如果一个 key 是“/test/key1″ , 另一个 key 是 ”/test/key2″. 则通过前缀 ”/test” 查问 etcd,返回一个列表蕴含 key 为“/test/key1″ 和 ”/test/key2″ 的键值对数据;
  • Watch 机制:即监听机制,watch 机制针对某个 key 进行监听, 也反对针对前缀进行范畴监听. 当被监听的 key 或前缀范畴发生变化的时候,客户端会收到变更告诉;
  • Lease 机制 :即租约机制(TTL,Time To Live),反对为 key-value 减少一个存活工夫, 超过这个工夫 key-value 将过期被删除. 反对解约(删除 key-value), 续约(减少 TTL 工夫) 等操作.
  • Revision 机制:每个 key 带有一个 全局惟一的 Revision 号,每一次事务加 1,它是全局惟一的,所以通过 Revision 能够断定数据写操作的程序, 对于实现分布式锁和队列十分有帮忙.

三、Leader 选举与客户端交互

应用 etcd 的时候,为了保障高可用,通常采纳集群的部署形式。部署奇数个节点,通常倡议是 3 个或 5 个,因为 etcd 集群之间须要 通过网络交互保障配置信息的一致性。分布式多节点保障了高可用,然而节点太多了也不好,越多的节点网络耗费越大。至于为什么是奇数个?这就波及到 Leader 选举的问题,奇数个不便投票出后果。

etcd 应用 raft 算法保障集群内各个节点之间数据一致性。raft 算法将集群内的节点分为 Leader, Follower, Candidate(候选人)这三个角色。

  • 集群初始化的时候,每个节点都是 Follower 角色。通过 raft 算法选举投票,选出一个节点作为 Leader。
  • Leader 作为主节点,与其余节点维持心跳,并同步数据至其余节点。
  • 当 Follower 一段时间内没有收到 leader 的心跳,就会将本人角色改为 Candidate 候选者,并发动一次新的选举,选举新的 Leader。

客户端在操作 etcd 集群数据的时候:

  • 读操作:客户端能够拜访任意节点进行数据的读操作
  • 写操作:客户端拜访任意节点进行写操作,如果该节点是 Follower,则将申请转发给 Leader。由 Leader 负责数据的写操作(增删改),将数据长久化,并向 Follower 发送同步数据的音讯。

四、etcd 的利用场景

4.1. kubernetes 大脑

目前,etcd 的最典型的利用场景就是作为 Kubernetes 集群的大脑。

如果把 kubernetes 比作一个大饭店,那么 etcd 就是这个饭店的进销存 + 客户关系管理系统。

  • kubernetes 作为容器编排服务,将面向客户提供的各种服务进行正当的资源分配,服务编排。
  • 不可避免地,有一些 kubernetes 集群的配置和状态数据,例如 pod 的数量、它们的状态、命名空间等。须要有一个对立的记录、治理的中央,它就是 etcd。

最重要的是:etcd 具备 watch 监听的性能,一旦某个配置或者某个状态产生变更,集群内所有的服务全都能够通过 watch 监听机制实时获取到音讯,进而做出进一步的响应。 简直 etcd 的所有利用场景,都是基于 watch 监听机制产生的,包含咱们前面为大家介绍的服务注册发现和订阅告诉。

4.2. 服务注册与发现

其实 kubernetes 也利用 etcd 实现服务注册发现机制,然而下面的那张图不太好阐明,我新画了两张图阐明 etcd 在实现服务注册发现机制中的作用。

所谓的服务注册实现原理就是:服务在启动的时候,向 etcd 写入一条配置数据,该条配置数据阐明本人的服务名称,服务 ip 地址,服务端口等信息。

所谓的服务发现实现原理举例:服务 C 的某个实例心愿拜访服务 A,服务 C 向 etcd 询问服务 A 的拜访地址,etcd 响应后果:服务 A 有三个实例,地址列表如:xxx.xxx.xxx.xxx: 端口yyy.yyy.yyy.yyy: 端口zzz.zzz.zzz.zzz: 端口。服务 C 不须要拜访三个实例,拜访其中一个就能够失去后果,所以它依照本人的负载平衡算法选了一个,这个就叫做:客户端负载平衡。

4.3. 健康检查与状态变更告诉

连接上文:服务 C 下一次拜访服务 A 的时候,还须要拜访 etcd 么?答案是不须要 ,它拜访过一次之后,就会本人保护一个服务 A 拜访地址的列表, 除非这个列表发生变化,否则是不会再次去询问 etcd 的。
那么一个服务怎么晓得另一个服务的列表发生变化呢?比方:服务 A 的实例注册状态发生变化。可能是因为某种原因挂掉了,可能是 OOM 或者是网络问题等。

  • 服务在注册到 etcd 之后,会保留一个对于该服务的注册配置信息,该注册配置信息由一个 TTL,etcd 同时会与该服务维持心跳。一旦超过 TTL 工夫,无奈失去服务的心跳响应,etcd 就认为该节点的衰弱状态呈现了问题,就会将该节点下线(注册配置信息删除)。
  • 服务在注册到 etcd 之后,会放弃对 etcd 状态数据变更的监听,一旦获取监听后果:服务 A 的实例状态产生变更,该服务就会从 etcd 从新拉取服务 A 的注册列表。

4.4. 分布式锁

跨过程跨零碎的多线程操作公共资源,产生多线程竞争,为了防止线程不平安,须要应用分布式锁。如果多线程在单个过程内产生资源竞争,就是用 Lock 就能够了,不须要分布式锁。比方:你在 mysql 库外面有一个用户余额数据,多个过程内的线程同时更改这个值,可能产生并发的数据笼罩。为了防止这样的问题,多个过程排排队,A 先来,A 开释了锁 B 再来,B 开释了锁 C 再来。

举例:上图的 3 个 client 代表三个服务,都要操作某个资源数据。

  • 在尝试调用加锁 API 的时候,client1 获取到的 revision=1, 它优先取得加锁的资格。加锁就是加一个带有 revision 的配置记录。其余的所有的服务,都通过 watch 机制监听锁的开释。
  • client 在尝试调用加锁 API 的时候,被调配了 revision。并且依照 revision 进行了排序,监听间隔本人 revision 差值最小,而且小于本人的 Revision,不会产生惊群效应。

4.5. 实现音讯队列(纯扯淡)

我感觉应用 etcd 实现音讯队列,是一种纯扯淡的做法。如果大家有什么异议,欢送留言!

不是说做不了,的确写个 demo 是能够的。往 etcd 外面放数据,再通过 watch 机制进行监听,这不就是一个典型的音讯队列么?扯淡!如果我只为了实现音讯数据的公布订阅,其实有很多方法,我还用搭一个 etcd 集群?Spring 的 Event 机制,java 的响应式编程,哪怕本人搞一个 BlockQueue 呢,是不是都能实现音讯的公布订阅。

咱们之所以应用 kafka、RocketMQ 这样的音讯队列,必定是因为咱们的异步数据达到肯定的规模了。达到规模的异步音讯数据传递基本就不是 etcd 的利用场景,正如本文结尾所述:别忘了它叫做 etc 阿就 d,它就是一个为分布式系统存储配置信息的,不是消息中间件。

五、etcd 装置

本文为大家装置一个能够用于试验环境的 etcd 单机版, 咱们能够用它进行试验, 后续我还会写文章介绍 etcd 集群的装置形式.
下载 etcd 的安装包, 拜访 github-etcd, 我应用的是 linux 操作系统 64 位, 所以下载的安装包是:etcd-v3.5.4-linux-amd64.tar.gz . 如果网络条件不容许, 能够搜寻 ”etcd 国内下载减速 ”, 抉择适合的下载安装包进行装置即可.

首先将安装包解压, 解压之后 cd 进入装置目录, 将 etcd 和 etcdctl 两个命令 copy 到 /usr/local/bin/ 目录上面.

tar zxvf etcd-v3.5.4-linux-amd64.tar.gz;
cd etcd-v3.5.4-linux-amd64;
cp etcd etcdctl /usr/local/bin/;

通过 etcd --version 命令查看 etcd 的版本, 同时能够验证装置后果. 如果不想敲全门路, 能够把 /usr/local/bin 目录退出零碎的 PATH 环境变量.

/usr/local/bin/etcd --version

启动 etcd, 这里的 listen-client-urls 和 advertise-client-urls 配置的作用是容许近程连贯,0.0.0.0示意监听以后服务器的所有 ip, 监听端口是 2379. 如果你的服务器有多块网卡, 多个固定 ip, 你想指定 etcd 服务在某一个 ip 上提供服务, 就能够用这个 ip 替换0.0.0.0

/usr/local/bin/etcd  --listen-client-urls 'http://0.0.0.0:2379' --advertise-client-urls 'http://0.0.0.0:2379'

etcd 启动之后, 能够通过 etcdctl 命令向 etcd 中增加配置, 如下所示应用 put 命令增加一个 key=/dir1,value=aaa 的键值对数据. 能够应用 get 命令获取该配置信息.

# /usr/local/bin/etcdctl put /dir1 aaa
OK
# /usr/local/bin/etcdctl get /dir1
/dir1
aaa

六、jetcd 的编码实现配置管理

上面为大家介绍通过 java API 的形式操作 etcd 的数据, 首先通过 maven 的坐标引入 jetcd. 我应用的版本绝对比拟旧, 最新的版本曾经是 0.7.8, 不过我在应用的时候呈现了与 netty 版本不统一的状况, 报错: 找不到 netty 相干的一些类. 所以我就回退到 0.3.0 版本, 应用形式上都是一样的.

<dependency>
    <groupId>io.etcd</groupId>
    <artifactId>jetcd-core</artifactId>
    <version>0.3.0</version>
</dependency>

上面的代码是应用 jetcd 操作 etcd 的配置数据, 实现了数据的写操作, 读操作, 删除操作. 具体用法看代码吧. 上面的代码是 Junit 5 的单元测试用例的写法.

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 org.junit.jupiter.api.*;

import java.nio.charset.StandardCharsets;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

import static junit.framework.TestCase.assertNotNull;

// 这个注解配合函数的 Order 注解, 决定测试用例函数的执行程序
@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class EtcdTest {
  private static Client etcdClient;

  @BeforeAll
  static void  init(){etcdClient = Client.builder()
             // 这里的 etcd 服务列表能够写多个, 用逗号分隔
            .endpoints("http://192.168.161.3:2379".split(","))
            .build();}

  @Test
  @Order(1)
  @DisplayName("etcd 写配置操作")
  void putKV() throws ExecutionException, InterruptedException {KV kv = etcdClient.getKVClient();
    ByteSequence key = ByteSequence.from("key-str", StandardCharsets.UTF_8);
    ByteSequence value = ByteSequence.from("value-str", StandardCharsets.UTF_8);
    //put key-value 配置信息
    CompletableFuture<PutResponse> putRsp = kv.put(key,value);
    assertNotNull(putRsp.get().getHeader());
  }


  @Test
  @Order(2)
  @DisplayName("etcd 读配置操作")
  void getKV() throws ExecutionException, InterruptedException {KV kv = etcdClient.getKVClient();
    ByteSequence key = ByteSequence.from("key-str", StandardCharsets.UTF_8);
    // 通过 key 获取值
    CompletableFuture<GetResponse> getRsp = kv.get(key);
    String getBackValue = getRsp.get().getKvs().get(0).getValue().toString(StandardCharsets.UTF_8);
    System.out.println("从 etcd 通过 key 获取 value 值为:" + getBackValue);
  }


  @Test
  @Order(3)
  @DisplayName("删除配置操作")
  void deleteKV() {KV kv = etcdClient.getKVClient();
    ByteSequence key = ByteSequence.from("key-str", StandardCharsets.UTF_8);
    // 通过 key 删除数据
    kv.delete(key);
  }
}

下面的代码只介绍了 etcd 的最根本的 key-value 操作, 其实 etcd 客户端还提供了很多的 API, 这些都将在我后续的文章中分布式锁, 服务注册发现, 配置变更监听, 分布式系统 Leader 选举的内容中为大家介绍.

// 租约
Lease lease=etcdClient.getLeaseClient();
// 监听
Watch watch =etcdClient.getWatchClient();
// 选举
Election election =etcdClient.getElectionClient();
// 锁
Lock lock=etcdClient.getLockClient();

欢送关注我的布告号:字母哥杂谈,回复 003 赠送作者专栏《docker 修炼之道》的 PDF 版本,30 余篇精品 docker 文章。字母哥博客:zimug.com

正文完
 0