彻底搞懂 etcd 系列文章(六):etcd 核心 API v3

网友投稿 735 2022-05-30

0 专辑概述

etcd 是云原生架构中重要的基础组件,由 CNCF 孵化托管。etcd 在微服务和 Kubernates 集群中不仅可以作为服务注册与发现,还可以作为 key-value 存储的中间件。

《彻底搞懂 etcd 系列文章》将会从 etcd 的基本功能实践、API 接口、实现原理、源码分析,以及实现中的踩坑经验等几方面具体展开介绍 etcd。预计会有 20 篇左右的文章,笔者将会每周持续更新,欢迎关注。

1 Etcd API 概述

本文将会开始介绍 etcd3 API 的核心设计,主要针对常见的 API 接口服务。对于理解 etcd 基本思想有很大的帮助。所有 etcd3 API 均在 gRPC 服务中定义,该服务对 etcd 服务器可以理解的远程过程调用(RPC)进行分类。

2 proto3

etcd v3 的通信基于 gRPC,proto 文件是定义服务端和客户端通讯接口的标准。即客户端该传什么样的参数,服务端该返回什么样子的参数,客户端该怎么调用,是阻塞还是非阻塞,是同步还是异步。在进行核心 API 的学习之前,gRPC 推荐使用 proto3,我们需要对 proto3 的基本语法有初步的了解。proto3 是原有 Protocol Buffer 2(被称为 proto2)的升级版本,删除了一部分特性,优化了对移动设备的支持,另外增加了对android和ios的支持,使得 gRPC 可以顺利的在移动设备上使用。

2.1 Protocol Buffer 是什么

Protocol buffer 是一个灵活、高效、自动化的结构化数据序列化机制--类似于 xml,但是更小、更快并且更简单。一旦定义好数据如何构造,就可以使用特殊生成的源代码轻松地在各种数据流中使用各种语言编写和读取结构化数据。甚至可以更新之前定义的数据结构而不打破已部署的使用"旧有"格式编译的程序。

2.2 定义消息类型

首先让我们来看一个非常简单的例子。假定我们有这样的需求,我们要定义一个搜索请求消息,每个消息都包含一个查询字符串,和你感兴趣的特定页面编号,以及每个页面的命中个数。

syntax = "proto3";

message SearchRequest {

string query = 1;

int32 page_number = 2;

int32 result_per_page = 3;

}

文件的第一行指定了你使用的是 proto3 的语法:如果你不指定,protocol buffer 编译器就会认为你使用的是proto2的语法。这个语句必须出现在.proto文件的非空非注释的第一行。

我们看到,搜索请求消息结构中定义指定了三个字段(name/value pairs)。每个字段都有一个名称和类型。

2.3 指定字段类型

在上述例子中,所有的字段都是值类型:两个整形(page_number and result_per_page) 和一个字符串 (query)。然而,你也可以指定你的字段的组合类型,包括枚举和其他消息类型。

2.4 分配标识——tag

消息中的每个字段都有一个唯一的数字标识。这些标识用来在消息的二进制格式中识别你的字段,并且,一旦你的消息投入使用,这些标识就不应该再被修改。

注意,标识是由1到15使用一个字节来编码,包括标识数字和字段类型(你可以在Protocol Buffer 编码中查看更多详细)。

标识16到2047占用两个字节。所以你应该保留1到15,用作出现最频繁的消息类型的标识。记得为将来会继续增加并可能频繁出现的元素留一点儿标识区间,也就是说,不要一下子把1—15全部用完,为将来留一点儿哦。

你可以指定的最小的标识数字是1,最大是228,或者536,870,911。你也不能使用19000 到 19999之间的数字(FieldDescriptor::kFirstReservedNumber through FieldDescriptor::kLastReservedNumber),因为它们被Protocol Buffers保留使用——如果你在自己的.proto文件中使用了一个保留数字,protocol buffer 编译器将会提示。同样的,你不能使用任何之前保留的标识。

2.5 指定字段规则

消息字段可以是下边中的一种:

singular(单个):符合语法规则的消息包含零个或者一个这样的字段(最多一个)。

repeated(重复): 一个字段在合法的消息中可以重复出现一定次数(包括零次)。重复出现的值的次序将被保留。在proto3中,重复出现的值类型字段默认采用压缩编码。

2.6 添加更多消息类型

多个消息类型可以定义在一个.proto文件中。这个在你定义多个关联的消息的时候非常有用,——这样,举个例子吧,如果你想定义你的搜索消息类型的响应消息格式,你可以在同一个.proto文件中添加如下的内容:

message SearchRequest {

string query = 1;

int32 page_number = 2;

int32 result_per_page = 3;

}

message SearchResponse {

彻底搞懂 etcd 系列文章(六):etcd 核心 API v3

...

}

2.7 添加注释

在你的 .proto 文件中添加注释, 使用C/C++-风格的 // 语法,像下边这样:

message SearchRequest {

string query = 1;

int32 page_number = 2;  // Which page number do we want?

int32 result_per_page = 3;  // Number of results to return per page.

}

2.8 保留字段

如果你通过删除整个字段更新了消息类型,或者将整个字段其注释掉,未来用户在编写新的类型的时候能够复用这些注释掉的标识数字。然而,这会引起一些严重的问题,如果他们后来加载了同一个.proto的旧版,包括数据损坏,安全隐私bug等等。一个确保这种问题不会发生的办法是,保留你要删除的字段的标识。Protocol buffer 编译器将会提示以后用户使用这些保留的字段标识。

message Foo {

reserved 2, 15, 9 to 11;

reserved "foo", "bar";

}

注意不要混淆同一个保留语句中的字段名称和标识。

2.9 .proto编译之后会生成什么

一个 .proto 文件的编译之后,编译器会为你选择的语言生成代码。你在文件中描述的消息类型,包括获取和设置字段的值,序列化你的消息到一个输出流,以及从一个输入流中转换出你的消息。

对于 C++,编译器会为每个 .proto 文件生成一个 .h 和一个 .cc 的文件,为每一个给出的消息类型生成一个类。

对于 Java,编译器会生成一个java文件,其中为每一个消息类型生成一个类,还有特殊的用来创建这些消息类实例的Builder类,

Python编译器生成一个模块,其中为每一个消息类型生成一个静态的描述器,在运行时,和一个 metaclass 一起使用来创建必要的 Python 数据访问类。

对于 Go,编译器为每个消息类型生成一个 .pb.go 文件。

0 专辑概述

etcd 是云原生架构中重要的基础组件,由 CNCF 孵化托管。etcd 在微服务和 Kubernates 集群中不仅可以作为服务注册与发现,还可以作为 key-value 存储的中间件。

《彻底搞懂 etcd 系列文章》将会从 etcd 的基本功能实践、API 接口、实现原理、源码分析,以及实现中的踩坑经验等几方面具体展开介绍 etcd。预计会有 20 篇左右的文章,笔者将会每周持续更新,欢迎关注。

1 Etcd API 概述

本文将会开始介绍 etcd3 API 的核心设计,主要针对常见的 API 接口服务。对于理解 etcd 基本思想有很大的帮助。所有 etcd3 API 均在 gRPC 服务中定义,该服务对 etcd 服务器可以理解的远程过程调用(RPC)进行分类。

2 proto3

etcd v3 的通信基于 gRPC,proto 文件是定义服务端和客户端通讯接口的标准。即客户端该传什么样的参数,服务端该返回什么样子的参数,客户端该怎么调用,是阻塞还是非阻塞,是同步还是异步。在进行核心 API 的学习之前,gRPC 推荐使用 proto3,我们需要对 proto3 的基本语法有初步的了解。proto3 是原有 Protocol Buffer 2(被称为 proto2)的升级版本,删除了一部分特性,优化了对移动设备的支持,另外增加了对android和ios的支持,使得 gRPC 可以顺利的在移动设备上使用。

Protocol buffer 是一个灵活、高效、自动化的结构化数据序列化机制--类似于 xml,但是更小、更快并且更简单。一旦定义好数据如何构造,就可以使用特殊生成的源代码轻松地在各种数据流中使用各种语言编写和读取结构化数据。甚至可以更新之前定义的数据结构而不打破已部署的使用"旧有"格式编译的程序。

首先让我们来看一个非常简单的例子。假定我们有这样的需求,我们要定义一个搜索请求消息,每个消息都包含一个查询字符串,和你感兴趣的特定页面编号,以及每个页面的命中个数。

syntax = "proto3";

message SearchRequest {

string query = 1;

int32 page_number = 2;

int32 result_per_page = 3;

}

文件的第一行指定了你使用的是 proto3 的语法:如果你不指定,protocol buffer 编译器就会认为你使用的是proto2的语法。这个语句必须出现在.proto文件的非空非注释的第一行。

我们看到,搜索请求消息结构中定义指定了三个字段(name/value pairs)。每个字段都有一个名称和类型。

在上述例子中,所有的字段都是值类型:两个整形(page_number and result_per_page) 和一个字符串 (query)。然而,你也可以指定你的字段的组合类型,包括枚举和其他消息类型。

消息中的每个字段都有一个唯一的数字标识。这些标识用来在消息的二进制格式中识别你的字段,并且,一旦你的消息投入使用,这些标识就不应该再被修改。

注意,标识是由1到15使用一个字节来编码,包括标识数字和字段类型(你可以在Protocol Buffer 编码中查看更多详细)。

标识16到2047占用两个字节。所以你应该保留1到15,用作出现最频繁的消息类型的标识。记得为将来会继续增加并可能频繁出现的元素留一点儿标识区间,也就是说,不要一下子把1—15全部用完,为将来留一点儿哦。

你可以指定的最小的标识数字是1,最大是228,或者536,870,911。你也不能使用19000 到 19999之间的数字(FieldDescriptor::kFirstReservedNumber through FieldDescriptor::kLastReservedNumber),因为它们被Protocol Buffers保留使用——如果你在自己的.proto文件中使用了一个保留数字,protocol buffer 编译器将会提示。同样的,你不能使用任何之前保留的标识。

消息字段可以是下边中的一种:

singular(单个):符合语法规则的消息包含零个或者一个这样的字段(最多一个)。

repeated(重复): 一个字段在合法的消息中可以重复出现一定次数(包括零次)。重复出现的值的次序将被保留。在proto3中,重复出现的值类型字段默认采用压缩编码。

多个消息类型可以定义在一个.proto文件中。这个在你定义多个关联的消息的时候非常有用,——这样,举个例子吧,如果你想定义你的搜索消息类型的响应消息格式,你可以在同一个.proto文件中添加如下的内容:

message SearchRequest {

string query = 1;

int32 page_number = 2;

int32 result_per_page = 3;

}

message SearchResponse {

...

}

在你的 .proto 文件中添加注释, 使用C/C++-风格的 // 语法,像下边这样:

message SearchRequest {

string query = 1;

int32 page_number = 2;  // Which page number do we want?

int32 result_per_page = 3;  // Number of results to return per page.

}

如果你通过删除整个字段更新了消息类型,或者将整个字段其注释掉,未来用户在编写新的类型的时候能够复用这些注释掉的标识数字。然而,这会引起一些严重的问题,如果他们后来加载了同一个.proto的旧版,包括数据损坏,安全隐私bug等等。一个确保这种问题不会发生的办法是,保留你要删除的字段的标识。Protocol buffer 编译器将会提示以后用户使用这些保留的字段标识。

message Foo {

reserved 2, 15, 9 to 11;

reserved "foo", "bar";

}

注意不要混淆同一个保留语句中的字段名称和标识。

一个 .proto 文件的编译之后,编译器会为你选择的语言生成代码。你在文件中描述的消息类型,包括获取和设置字段的值,序列化你的消息到一个输出流,以及从一个输入流中转换出你的消息。

对于 C++,编译器会为每个 .proto 文件生成一个 .h 和一个 .cc 的文件,为每一个给出的消息类型生成一个类。

对于 Java,编译器会生成一个java文件,其中为每一个消息类型生成一个类,还有特殊的用来创建这些消息类实例的Builder类,

Python编译器生成一个模块,其中为每一个消息类型生成一个静态的描述器,在运行时,和一个 metaclass 一起使用来创建必要的 Python 数据访问类。

对于 Go,编译器为每个消息类型生成一个 .pb.go 文件。

值类型的消息字段可以是一下类型中的一种——这个表格展示了可以在.proto文件中使用的类型,以及自动生成的相应语言的类型:

3 gRPC 服务

发送到etcd服务器的每个API请求都是一个gRPC远程过程调用。etcd3 中的 RPC 接口定义根据功能分类到服务中。

处理 etcd 键值的重要服务包括:

KV 服务,创建,更新,获取和删除键值对。

监视,监视键的更改。

租约,消耗客户端保持活动消息的基元。

锁,etcd 提供分布式共享锁的支持。

选举,暴露客户端选举机制。

etcd3 中的所有 RPC 都遵循相同的格式。每个 RPC 都有一个函数名,该函数将 NameRequest 作为参数并返回 NameResponse 作为响应。例如,这是 Range RPC 描述:

service KV {

Range(RangeRequest) returns (RangeResponse)

...

}

etcd API 的所有响应都有一个附加的响应标头,其中包括响应的群集元数据:

message ResponseHeader {

uint64 cluster_id = 1;

uint64 member_id = 2;

int64 revision = 3;

uint64 raft_term = 4;

}

Cluster_ID - 产生响应的集群的 ID。

Member_ID - 产生响应的成员的 ID。

Revision - 产生响应时键值存储的修订版本号。

Raft_Term - 产生响应时,成员的 Raft 称谓。

应用服务可以通过 Cluster_ID 和 Member_ID 字段来确保,当前与之通信的正是预期的那个集群或者成员。

应用服务可以使用修订号字段来知悉当前键值存储库最新的修订号。当应用程序指定历史修订版以进行时程查询并希望在请求时知道最新修订版时,此功能特别有用。

应用服务可以使用 Raft_Term 来检测集群何时完成一个新的 leader 选举。

4 小结

本篇主要介绍了 Etcd API 中涉及的 proto3 和 gRPC 请求响应相关的内容,了解这些是我们学习 Etcd RPC 调用的前提。下面的文章开始介绍 etcd 中这几个重要的服务和接口。

etcd 与 Zookeeper、Consul 等其它 k-v 组件的对比

彻底搞懂 etcd 系列文章(一):初识 etcd

彻底搞懂 etcd 系列文章(二):etcd 的多种安装姿势

彻底搞懂 etcd 系列文章(三):etcd 集群运维部署

彻底搞懂 etcd 系列文章(四):etcd 安全

彻底搞懂 etcd 系列文章(五):etcdctl 的使用

etcd docs

Go 云原生 分布式

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:敏捷实践:一周的Sprint太短,可以调吗
下一篇:excel表格中制作比赛排序自动评分表的方法是什么
相关文章