当前位置: 首页 > 后端技术 > Java

etcd核心应用场景及编码实践长图解

时间:2023-04-01 22:46:50 Java

大家好,我是字母哥,今天写一篇关于etcd的文章。其实网上有很多关于etcd的介绍。本文掌握了etcd的核心知识和编码技巧!本文首先深入浅出地为大家介绍什么是etcd。互联网上已经有很多这样的内容。etcd的应用场景有哪些?这些应用场景的核心原理是什么?说到底,不能只动嘴不动手。先搭建一个etcd单机版,然后使用java客户端操作etcd数据。本文旨在帮助大家了解etcd,从宏观的角度俯瞰etcd的全局,掌握etcd的基本操作技巧。以后我会写一系列的文章来对每个应用场景进行编码。期待大家关注我和我的公众号:字母哥。后续计划章节如下:《搭建高可用etcd集群》《基于etcd实现分布式锁(java代码实现)》《基于etcd实现配置变更通知(java代码实现)》《基于etcd实现服务注册与发现(java代码实现)》《基于etcd实现分布式系统节点leader选举(java代码实现)》1、用过linux大白话etcd和zookeeper的朋友请举手,是的,我看到了!linux中所有自动安装的系统软件配置文件都存放在一个名为/etc的目录中。“d”表示分布式,etcd是分布式模型,所以etcd的核心应用场景是:分布式系统的配置信息存储。网上很多文章的第一句话都是从英文官网抄来的:etcdisahighlyconsistentdistributedkey-valuestoragesystem。很多朋友看了之后问,这个东西和redis有什么区别?作者想说,真的不要这么比较,etcd从名字上就已经告诉你了,它存储的是配置信息(元数据)。它在架构应用上和redis不在一个层次,它的目标产品应该是zookeeper。虽然zookeeper在很多java分布式系统中被广泛使用,但是etcd作为后起之秀,有借kubernetes超越zookeeper的趋势。zookeeper是java写的,etcd是go语言写的。Zookeeper使用TCP协议,其交互消息规则是完全自定义的。如果不使用zookeeper提供的SDK,是无法操作数据的。而etcd使用的是google的gRPC协议,比较通用。对于一个请求,zookeeper打开一个套接字进行监控。etcd的监控通道可以重复使用。从IO性能和系统资源利用率来看,etcd无疑更胜一筹。Zookeeper使用ZAB协议保证集群节点配置信息的一致性,etcd使用Raft协议。如果您想了解更多有关raft协议的信息,请单击《raft协议中文介绍》。大部分功能和zookeeper一样。目前Java程序员使用zookeeper较多,其他程序员使用etcd较多。都是看习惯,不过我推荐etcd。2、etcd的四大核心机制etcd以key-value的形式存储数据。有了以下四种机制,etcd的应用场景更加广泛。前缀机制:前缀机制,也称为目录机制,客户端将两个键值对配置放入etcd中,如果一个键是“/test/key1”,另一个键是“/test/key2”。然后通过前缀“/test”查询etcd,返回一个包含key的列表作为“/test/key1”和“/test/key2”的键值对数据;watchmechanism:监控机制,watch机制监控某个key,也支持对前缀的范围监控。当监听的key或prefix范围发生变化时,客户端会收到变化通知;租约机制:租约机制(TTL,TimeToLive),支持为key-value增加一个生存时间,超过该时间key-value将过期并被删除。支持终止(删除key-value)、更新(增加TTL时间)等操作。Revision机制:每个key都有一个全局唯一的Revision号,每个事务加1,全局唯一,所以可以通过Revision来决定数据写入操作的顺序,对于实现分布式锁和队列很有帮助。第三,leader选举和client交互使用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的数量,它们的状态,namespace等,需要有一个统一的记录和管理的地方,就是etcd。最重要的是:etcd有watch监控的功能。一旦某个配置或某个状态发生变化,集群中的所有服务都可以通过watch监控机制实时获取消息,进而做出进一步的响应。etcd几乎所有的应用场景都是基于watch监控机制产生的,包括我们后面要介绍的服务注册发现和订阅通知。4.2.服务注册与发现其实kubernetes也是使用etcd来实现服务注册发现机制的,只是上图不好解释。我新画了两张图来说明etcd在实现服务注册发现机制中的作用。所谓服务注册的实现原理是:服务启动时,会写入一段配置数据到etcd中,这段配置数据描述了自己的服务名称、服务ip地址、服务端口等信息。所谓服务发现实现原理的一个例子:服务C的一个实例要访问??服务A,服务C向etcd询问服务A的访问地址,etcd返回结果:服务A有3个实例,地址列表如下:xxx.xxx.xxx。xxx:端口,yyy.yyy.yyy.yyy:端口,zzz.zzz.zzz.zzz:端口。服务C不需要访问3个实例,但是只有其中一个可以得到结果,所以它根据自己的负载均衡算法选择一个,称为:客户端负载均衡。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再来。示例:上图中的三个客户端代表三个服务,都必须对一定的资源数据进行操作。在尝试调用加锁API时,client1获取的revision=1优先获得加锁资格。加锁就是添加一条带修改的配置记录。所有其他服务通过监视机制监视锁的释放。当客户端尝试调用锁定API时,它会被分配一个修订版。而且是按照revision排序,监控距离和自己的revision相差最小,比自己的revision小,所以不会出现惊群效应。4.5.实现消息队列(纯废话)我觉得用etcd实现消息队列是纯废话。如有异议,欢迎留言!不是做不到,写个demo确实是可以的。把数据放入etcd,然后通过watch机制进行监控,这不就是一个典型的消息队列吗?废话!如果我只想实现消息数据的发布和订阅,其实有很多方法。还需要搭建etcd集群吗?spring的Event机制,java的响应式编程,就算自己建一个BlockQueue,也能发布订阅消息吗?我们之所以使用像Kafka、RocketMQ这样的消息队列,肯定是因为我们的异步数据已经达到了一定的规模。达到规模的异步消息数据传输根本就不是etcd的应用场景,正如本文开头所说:别忘了它叫etcd,它是分布式系统的一个存储配置信息,不是message中间件。5.etcd安装本文安装一个etcd单机版,可以在实验环境下使用。我们可以用它来做实验。后面会写一篇文章介绍etcd集群的安装方法。下载etcd安装包,访问github-etcd。我使用的是64位的linux操作系统,所以下载的安装包是:etcd-v3.5.4-linux-amd64.tar.gz。如果网络条件不允许,可以搜索“etcd国内下载加速”,选择合适的下载安装包即可安装。首先,解压安装包。解压后cd进入安装目录,将etcd和etcdctl这两个命令复制到/usr/local/bin/目录下。tarzxvfetcd-v3.5.4-linux-amd64.tar.gz;cdetcd-v3.5.4-linux-amd64;cpetcdetcdctl/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表示监听当前服务器的所有ips,监听端口为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/etcdctlput/dir1aaaOK#/usr/local/bin/etcdctlget/dir1/dir1aaa6.Jetcd编码实现配置管理下面是一个介绍通过javaAPI操作etcd数据。首先,通过将maven的坐标引入到jetcd中。我用的版本比较老,最新的版本已经是0.7.8了,但是我用的时候和netty的版本不一致,报错:一些netty相关的类找不到。所以我会回退到0.3.0版本,用法是一样的。io.etcdjetcd-core0.3.0下面的代码是使用jetcd操作配置数据etcd,实现数据的写操作、读操作、删除操作。详细使用方法见代码。以下代码为Junit5.importio.etcd.jetcd.ByteSequence;importio.etcd.jetcd.Client;importio.etcd.jetcd.KV;importio.etcd.jetcd.kv.GetResponse;导入io.etcd.jetcd.kv.PutResponse;导入org.junit.jupiter.api.*;导入java.nio.charset.StandardCharsets;导入java.util.concurrent.CompletableFuture;导入java.util.concurrent.ExecutionException;importstaticjunit.framework.TestCase.assertNotNull;//该注解配合函数的Order注解来确定测试用例函数的执行顺序@TestMethodOrder(MethodOrderer.OrderAnnotation.class)publicclassEtcdTest{privatestatic客户端etcdClient;@BeforeAllstaticvoidinit(){etcdClient=Client.builder()//这里的etcd服务列表可以有多个,以逗号分隔。endpoints("http://192.168.161.3:2379".split(",")).build();}@Test@Order(1)@DisplayName("etcd写配置操作")voidputKV()throwsExecutionException,InterruptedException{KVkv=etcdClient.getKVClient();ByteSequencekey=ByteSequence.from("key-str",StandardCharsets.UTF_8);字节序列值=字节序列。来自(“价值海峡”,StandardCharsets.UTF_8);//放入key-value配置信息CompletableFutureputRsp=kv.put(key,value);assertNotNull(putRsp.get().getHeader());}@Test@Order(2)@DisplayName("etcd读取配置操作")voidgetKV()throwsExecutionException,InterruptedException{KVkv=etcdClient.getKVClient();ByteSequencekey=ByteSequence.from("key-str",StandardCharsets.UTF_8);//通过键获取值CompletableFuturegetRsp=kv.get(key);字符串getBackValue=getRsp.get().getKvs().get(0).getValue().toString(StandardCharsets.UTF_8);System.out.println("通过key从etcd获取值:"+getBackValue);}@Test@Order(3)@DisplayName("删除配置操作")voiddeleteKV(){KVkv=etcdClient.getKVClient();ByteSequencekey=ByteSequence.from("key-str",StandardCharsets.UTF_8);//按键删除数据kv.delete(key);}}上面的代码只介绍了etcd最基本的key-value操作。其实etcd客户端也提供了很多API,我会在后续的文章中进行分发。锁,服务注册发现,配置变更监听,在分布式系统的leader选举内容中介绍过。//LeaseLeaselease=etcdClient.getLeaseClient();//MonitoringWatchwatch=etcdClient.getWatchClient();//ElectionElectionelection=etcdClient.getElectionClient();//锁锁lock=etcdClient.getLockClient();欢迎关注我的公告号:字母哥杂谈,回复003赠送PDF版作者专栏《docker修炼之道》,30余篇优质docker文章字母哥博客:zimug.com