基础
Etcd 提供了以下几个 gRPC Service 和相关 API,详见 官方文档-etcd3 API。相关的 gRPC Service 定义可见 etcd 源码的 api/etcdserverpb/rpc.proto
文件(源码版本为 v3.5.1)。
- KV:创建/更新/删除/获取 key-value 对。
- Watch:监听 keys 的变更。
- Lease:客户端保持 keep-alive 的消息原语。
- Auth:认证和鉴权相关,同时包含用户的增删改查等操作。
- Cluster:提供集群 member 的配置管理功能,member 的增删改查等操作。
- Maintenance:提供恢复快照、对存储进行碎片整理等操作。
本文只介绍 KV、Watch 两个 Service。
KV Service
KV Service 的定义如下所示。可以看到,KV Service 提供了以下 gRPC API:
- Range:获取范围内的 key。
- Put:设置给定 key 对应的 value。Put 请求会增加一次 revision,并且在 Event 历史中为该 key 生成一个 Event。
- DeleteRange:删除给定范围的 key。DeleteRange 请求会增加一次 revision,并且在 Event 历史中,为每个被删除的 key 生成一个删除事件。
- Txn(Transaction):在一个 txn 请求中处理多个操作,一个 txn 请求会增加一次 revision,并且请求中的每个操作涉及的 key 会生成相同 revision 的 Event。注意:不容许在一个 txn 中多次修改同一个 key。
- Compact:对 etcd 中的历史 revision 及其 key-value 进行压缩。该操作会定期进行压缩,否则历史 revision 及其对应的 key-value 会无限制的持续增长。
option (google.api.http) = {}
的作用是在 Protobuf 文件中定义 gRPC API 对应的 HTTP API。在使用 gRPC-Gateway 插件的时候,该插件可以将对应的 HTTP API 自动转换为对应的 gRPC 请求,并将 gRPC 响应转换为 HTTP 响应。
service KV {
rpc Range(RangeRequest) returns (RangeResponse) {
option (google.api.http) = {
post: "/v3/kv/range"
body: "*"
};
}
rpc Put(PutRequest) returns (PutResponse) {
option (google.api.http) = {
post: "/v3/kv/put"
body: "*"
};
}
rpc DeleteRange(DeleteRangeRequest) returns (DeleteRangeResponse) {
option (google.api.http) = {
post: "/v3/kv/deleterange"
body: "*"
};
}
rpc Txn(TxnRequest) returns (TxnResponse) {
option (google.api.http) = {
post: "/v3/kv/txn"
body: "*"
};
}
rpc Compact(CompactionRequest) returns (CompactionResponse) {
option (google.api.http) = {
post: "/v3/kv/compaction"
body: "*"
};
}
}
Range API
Range API 中的 RangeRequest 消息体的定义如下所示(其中 1、2、3 这些数字表示的是字段对应的编号),
-
key:如果 range_end 没有给定,则该请求表示仅查找这个 key。
-
range_end:代表请求的上限。
- 如果 range_end 是 '\0',则查询的范围为大于等于 key 的所有key;
- 如果 range_end 比给定的 key 长一个 bit,则表示查询的是所有带有以 key 为前缀的 key;
- 如果 key 和 range_end 都是'\0',则范围查询返回所有key。
-
limit:请求返回的 key 的数量限制,也就是说只返回 range 范围内前 limit 个 key-value,哪怕范围内的 key-value 数量超过了 limit。limit 在分页时会非常有用,一次只返回 range 范围内的部分结果,然后根据需要和返回结果,获取更多数据。limit 的使用可以减少网络带宽的使用,提高查询效率。
如果不设置 limit,则请求将返回 range 范围内的所有 key-value,没有数量上的限制(除非超过 gRPC 消息的大小限制)。不设置 limit 在 key-value 数量较大时,可能会导致网络负载增大,并可能影响客户端和服务端的性能。
-
revision:如果 revision 未指定或者指定了小于或等于零的值,则返回当前最新的 key-value 数据。如果指定了其他值,则返回在该 revision 时的 key-value 数据,此时如果相应的 revision 已被 compact,则返回 ErrCompacted。
-
sort_order:返回结果的排序顺序。
-
sort_target:返回结果的排序方式,是按照 Key 进行排序,还是按照 version 进行排序。
-
serializable:serializable 表示 range 请求是否使用串行读。range 请求默认是采用线性读的方式,线性读相比串行读会有较高的延迟和较低的吞吐量,但是可以反映集群当前的一致性。
-
keys_only:仅仅返回 keys,不返回 values。
-
count_only:仅仅返回 keys 的数量。
-
min_mod_revision:key mod revision 的下边界,对于比该 mod revision 更小的 key 将会被过滤。
-
max_mod_revision:key mod revision 的上边界,对于比该 mod revision 更大的 key 将会被过滤。
-
min_create_revision:key create revision 的下边界,对于比该 create revision 更小的 key 将会被过滤。
-
max_create_revision:key create revision 的上边界,对于比该 create revision 更大的 key 将会被过滤。
message RangeRequest {
enum SortOrder {
NONE = 0; // default, no sorting
ASCEND = 1; // lowest target value first
DESCEND = 2; // highest target value first
}
enum SortTarget {
KEY = 0;
VERSION = 1;
CREATE = 2;
MOD = 3;
VALUE = 4;
}
bytes key = 1;
bytes range_end = 2;
int64 limit = 3;
int64 revision = 4;
SortOrder sort_order = 5;
SortTarget sort_target = 6;
bool serializable = 7;
bool keys_only = 8;
bool count_only = 9;
int64 min_mod_revision = 10;
int64 max_mod_revision = 11;
int64 min_create_revision = 12;
int64 max_create_revision = 13;
}
RangeResponse 消息体的定义如下,
- header:使用 ResponseHeader 消息体,ResponseHeader 是每个 gRPC API Response 中的通用消息体。包含以下字段,
- cluster_id:产生响应的集群的 ID。
- member_id:产生响应的 member ID。
- revision:产生响应时,集群的 revision。
- raft_term:产生响应时,集群的 raft term。
- kvs 是 mvccpb.KeyValue 的列表。mvccpb.KeyValue 中包含以下字段,
- key:key 的内容。
- value:value 的内容。
- create_revision:这个 key 创建时的 revision。
- mod_revision:这个 key 最后一次修改时的 revision。
- version:这个 key 经过几次修改。删除会将该值设置为 0。
- lease:lease 是附加给 key 的租约 id。当附加的租约过期时,key 将被删除。如果 lease 为 0,则表示没有租约附加到 key。
- more:表示在请求的范围内是否还有更多的 key,这个仅在设置了 limit 才有效。
- count:满足请求范围的 keys 的数量。
message RangeResponse {
ResponseHeader header = 1;
repeated mvccpb.KeyValue kvs = 2;
bool more = 3;
int64 count = 4;
}
message ResponseHeader {
uint64 cluster_id = 1;
uint64 member_id = 2;
int64 revision = 3;
uint64 raft_term = 4;
}
message KeyValue {
bytes key = 1;
int64 create_revision = 2;
int64 mod_revision = 3;
int64 version = 4;
bytes value = 5;
int64 lease = 6;
}
Put API
Put 请求的消息体是 PutRequest,如下所示,
- key/value:要创建/更新的 key-value 内容。
- lease:和 key 关联的租约 id,如果为 0 表示没有租约。
- prev_kv:如果 prev_kv 为 true,则 etcd 会在改变该 key 之前先获取到前一个该 key 及其 value 的内容,并在 PutResponse 中返回。
- ignore_value:如果为 true,则使用它当前的 value 来更新 key。如果 key 不存在则返回 error。
- ignore_lease:如果为 true,则使用它当前的 lease。如果 key 不存在则返回 error。
message PutRequest {
bytes key = 1;
bytes value = 2;
int64 lease = 3;
bool prev_kv = 4;
bool ignore_value = 5;
bool ignore_lease = 6;
}
Put 请求返回的消息体是 PutResponse:
- header:通用的 ResponseHeader 消息体。
- prev_kv:如果 PutRequest 中设置了 prev_kv 为 true,它就是该 key 前一个的 key-value 内容。
message PutResponse {
ResponseHeader header = 1;
mvccpb.KeyValue prev_kv = 2;
}
DeleteRange API
DeleteRange 请求的消息体是 DeleteRangeRequest,如下所示,
- key/range_end:同 Range 请求 key/range_end。
- prev_kv:同 Put 请求中的 prev_kv。
message DeleteRangeRequest {
bytes key = 1;
bytes range_end = 2;
bool prev_kv = 3;
}
返回的消息体是 DeleteRangeResponse,如下所示,
- header:通用的 ResponseHeader 消息体。
- deleted:该请求删除的 key 的数量。
- prev_kvs:同 Put 请求中的 prev_kv,但是由于 DeleteRange 可能会删除一批 key,因此这些 key 及其 value 都会被返回。
message DeleteRangeResponse {
ResponseHeader header = 1;
int64 deleted = 2;
repeated mvccpb.KeyValue prev_kvs = 3;
}
TXN API
etcd 事务采用 If/Then/Else 这样的原语。在事务请求中可以处理多个操作,etcd 针对事务只增加一次 revision,事务中生成的 event 都具有相同的 revision。txn 请求使用的消息体如下所示,
- 如果 compare 中的所有比较都为 true 的话,则顺序处理 success 中定义的操作,否则顺序处理 failure 中的操作。
- 其中 compare 中,可以相同的 key 进行两次不同的比较。
message TxnRequest {
repeated Compare compare = 1;
repeated RequestOp success = 2;
repeated RequestOp failure = 3;
}
其中,Compare 消息体如下所示,主要是检查 key 的 create_version/mod_version/version/value/lease 与指定值的比较,
- result:比较方式,是大于/等于/小于/不等于。
- target:对 key 的什么信息进行比较,是 create_version/mod_version/version/value/lease 其中的一种。
- key:要比较的 key。
- target_union:根据 target 类型,指定用于比较的实际值。
比如想要对键 /foo 的当前值进行检查,检查它是否等于 bar。此时,result 为 EQUAL,target 是 VALUE,key 为 /foo,target_union 中为 value,内容为 bar。
message Compare {
enum CompareResult {
EQUAL = 0;
GREATER = 1;
LESS = 2;
NOT_EQUAL = 3;
}
enum CompareTarget {
VERSION = 0;
CREATE = 1;
MOD = 2;
VALUE = 3;
LEASE = 4;
}
CompareResult result = 1;
CompareTarget target = 2;
bytes key = 3;
oneof target_union {
int64 version = 4;
int64 create_revision = 5;
int64 mod_revision = 6;
bytes value = 7;
int64 lease = 8;
}
}
RequestOp 消息中如下所示,
- request_range/request_put/request_delete_range:依次使用的是RangeRequest/PutRequest/DeleteRangeRequest 消息体,参考上述的介绍。
- request_txn:使用的是 TxnRequest 消息体,意味着事务可以嵌套使用事务,也就是在 success 或 failure 中可以定义更多的事务。比如在 success 中先进行 PUT,然后再执行一条事务,该条事务先进行检查是否 PUT 成功,然后再决定进一步的操作。
message RequestOp {
oneof request {
RangeRequest request_range = 1;
PutRequest request_put = 2;
DeleteRangeRequest request_delete_range = 3;
TxnRequest request_txn = 4;
}
}
txn 请求返回的消息为 TxnResponse 消息体,TxnResponse 将按顺序包含上述处理操作对应的处理结果,
- header:通用的 ResponseHeader 消息体。
- succeeded:如果 TxnRequest 中的 compare 比较为 true 则 succeeded 被设置为 true,否则是 false。
message TxnResponse {
ResponseHeader header = 1;
bool succeeded = 2;
repeated ResponseOp responses = 3;
}
message ResponseOp {
oneof response {
RangeResponse response_range = 1;
PutResponse response_put = 2;
DeleteRangeResponse response_delete_range = 3;
TxnResponse response_txn = 4;
}
}
Compact API
Compact 请求使用的 CompactionRequest 消息体定义如下,该接口的主要作用是压缩 key-value 存储到给定 revision。
- revision:表示 compact 操作结束的 revision。
- physical:设置为 true 时 RPC 将会等待该请求,直到被压缩的内容从后端存储中删除。
message CompactionRequest {
int64 revision = 1;
bool physical = 2;
}
Compact 请求应答的消息体 CompactionResponse 如下,只包含了一个通用的 ResponseHeader 消息体。
message CompactionResponse {
ResponseHeader header = 1;
}
Watch Service
Watch Service 的定义如下所示,它只有一个 gRPC API,也就是 Watch。
service Watch {
rpc Watch(stream WatchRequest) returns (stream WatchResponse) {
option (google.api.http) = {
post: "/v3/watch"
body: "*"
};
}
}
Watch API
Watch 请求的消息体是 WatchRequest,如下所示。可以看到,无论是创建一个 watcher,还是取消一个 watcher 使用的都是 WatchRequest 消息体。
-
create_request:使用 WatchCreateRequest 消息体,表示创建一个 watcher。
-
key/range_end:同 Range API。
-
start_revision:表示从 start_revision(包含) 开始监听。如果这个值没有设置则从创建 WatchResponse 时候的 revision 开始监听。监听历史是从最后一个 compact revision 开始
-
progress_notify:如果设置了,即使没有最新 event,还是会定期的发送不带任何 event 的 WatchResponse,但是会带当前最新的 revision。etcd 将基于当前的负载决定发送频率。该字段的主要作用是,
- 充当心跳,帮助 client 验证其 watch 请求仍然有效,并且 client 与 etcd 之间的连接是健康的。
- client 可以通过定期的通知来知道 etcd 集群最新的 revision,确保自己没有错过任何更新。
- 如果 client 与 etcd 之间的连接暂时断开,那么在重新建立连接后,client 可以使用收到的最新 revision 来恢复其 watch 状态,从而继续接收到重要的更新。
-
filters:配置 event 的过滤选项。
-
prev_kv:如果设置了,WatchResponse 则会返回 event 发生时的上一个 key-value 内容。
-
watch_id:如果 watch_id 被设置了且不为 0,则表示使用 id 为 watch_id 的 watcher。由于 etcd 创建 watcher 不是同步的,因此这个可以用于确保在同一个 strem 上顺序创建多个 watcher。在同一个 strem 上创建一个已存在的 watcher 会返回错误。
-
fragment:如果设置了,则允许分成多个 WatchResponse 返回。
-
-
cancel_request:使用 WatchCancelRequest 消息体,表示取消一个 watcher。
- watch_id:表示要取消的 watcher id,取消之后就不再有更多 event 发送过来。
-
progress_request:使用 WatchProgressRequest 消息体,类似于 WatchCreateRequest 中的 progress_notify。
message WatchRequest {
oneof request_union {
WatchCreateRequest create_request = 1;
WatchCancelRequest cancel_request = 2;
WatchProgressRequest progress_request = 3;
}
}
message WatchCreateRequest {
bytes key = 1;
bytes range_end = 2;
int64 start_revision = 3;
bool progress_notify = 4;
enum FilterType {
// filter out put event.
NOPUT = 0;
// filter out delete event.
NODELETE = 1;
}
repeated FilterType filters = 5;
bool prev_kv = 6;
int64 watch_id = 7;
bool fragment = 8;
}
message WatchCancelRequest {
int64 watch_id = 1;
}
message WatchProgressRequest {
}
Watch 请求返回的消息体是 WatchResponse,如下所示,
- header:通用的 ResponseHeader 消息体 。
- watch_id:和该 response 相关的 watcher 的 id。
- created:如果该 response 是创建 watcher 请求的回复,则 created 设置为 true。
- canceled:如果该 response 是取消 watcher 请求的回复,则 canceled 设置为 true。
- compact_revision:如果 watcher 是从已被 compact 的 revision 开始监听的话,则 compact_revision 被设置为最小未 compact 的 revision。client 在收到这个信息之后,应该根据该信息重新创建 watcher 并设置 start_revision。例如,client 想要 watch 从 revision 100 开始的变更,但是服务器返回了
compact_revision
为 200 的WatchResponse
,这意味着 revision 100 到 199 的数据已经不再存在。client 应该根据这个信息更新其 watch 请求,使用一个大于或等于 200 的 revision 作为新的起点。 - cancel_reason:表示 watcher 被取消的原因。
- fragment:表示回复 watch response 是否被分成多个 response 返回。
- events:返回的 event 列表。
- type:表示该 event 的类型,是 PUT 还是 DELETE。
- kv:表示该 event 涉及的 key-value 内容。PUT event 包含当前的 key-value 内容。kv.Version=1 则表示 PUT event 是刚创建了该 key。DELETE event 包含被删除的 key,它的 revision 设置为删除时的 revision。
- prev_kv:在 event 发生之前的 key-value 内容,需要 WatchCreateRequest 中的 prev_kv 设置为 true。
message WatchResponse {
ResponseHeader header = 1;
int64 watch_id = 2;
bool created = 3;
bool canceled = 4;
int64 compact_revision = 5;
string cancel_reason = 6;
bool fragment = 7;
repeated mvccpb.Event events = 11;
}
message Event {
enum EventType {
PUT = 0;
DELETE = 1;
}
EventType type = 1;
KeyValue kv = 2;
KeyValue prev_kv = 3;
}
相关链接
etcd3 API:https://etcd.io/docs/v3.4/learning/api/
彻底搞懂 etcd 系列文章:https://cloud.tencent.com/developer/article/1690342