一、ETCD 之 事务
在etcdv3中,事务就是一个原子的、针对key-value存储操作的If / Then / Else 结构,事务提供了一个原语,用于请求归并到一起放在原子块中,例如then/else,这些原子块的执行条件,例如if以key-value存储里的内容为依据。事务可以用来保护key不受其他并发更新操作的修改,也可以构建CAS(Compare And Swap)操作,并以此作为更高层次(应用层)并发控制的基础。在一个事务请求中,etcd可以自动处理多个普通的请求。若对于那些修改key-value存储的请求,若用同一个事物所有操作产生的事件都拥有同样的revesion。然而,在一个事务中多次修改同一个key是被禁止的。
所有的事务都由一个比较“连接”来守护,类似于“if”声明。每个比较会检查后台存储的一个key。这个检查可以是如下内容:该key在后台存储是否有value?该key的value是否等同于某个给定的值?除了value,还可以检查这个key的revision或者version。有的比较都是原子的执行的。如果所有的比较都返回true,那么就说该事物成功了,并且会执行该事务success请求块里面的操作,反之则代表该事务失败了,转而执行该事务failure请求块里面的操作。
一个事务由一个 比较块 和 比较后的操作 两个部分组成,数据结构如下:
# Request message TxnRequest { repeated Compare compare = 1; repeated RequestOp success = 2; repeated RequestOp failure = 3; } # Response message TxnResponse { ResponseHeader header = 1; bool succeeded = 2; repeated ResponseOp responses = 3; }
所有的比较都由下面的message表示:
message Compare { enum CompareResult { EQUAL = 0; GREATER = 1; LESS = 2; NOT_EQUAL = 3; } enum CompareTarget { VERSION = 0; CREATE = 1; MOD = 2; VALUE= 3; } CompareResult result = 1; // target is the key-value field to inspect for the comparison. CompareTarget target = 2; // key is the subject key for the comparison operation. bytes key = 3; oneof target_union { int64 version = 4; int64 create_revision = 5; int64 mod_revision = 6; bytes value = 7; } }
在处理完上述比较块后会进行下一步的操作,比如说CRUD,如下所示:
message RequestOp { // request is a union of request types accepted by a transaction. oneof request { RangeRequest request_range = 1; PutRequest request_put = 2; DeleteRangeRequest request_delete_range = 3; } }
二、ETCD 之 watch API
watch api 提供了基于事件的接口,用于异步检测key的变换,etcd v3的watch机制会针对某个key 下的某个特定revision进行连续的检测,等待key发生变化后将更新信息返回给client
每个变换的数据结构都如下所示:
message Event { enum EventType { PUT = 0; DELETE = 1; } EventType type = 1; KeyValue kv = 2; KeyValue prev_kv = 3; }
watch是长期持续的操作,并且它使用gRPC中的流的方式进行传输Event数据,(这里的流是双向流);一个方面,client通过写入流来创建watch,另一个方面,client通过读取流来接受到watch的Event,单个watch流可以通过使用pre-watch标志Event,以达到在一个流中多路传输多个不同的watch event的目的,如下是request和response的数据结构
# Request message WatchCreateRequest { bytes key = 1; bytes range_end = 2; int64 start_revision = 3; bool progress_notify = 4; enum FilterType { NOPUT = 0; NODELETE = 1; } repeated FilterType filters = 5; bool prev_kv = 6; } # Response message WatchResponse { ResponseHeader header = 1; int64 watch_id = 2; bool created = 3; bool canceled = 4; int64 compact_revision = 5; repeated mvccpb.Event events = 11; }
三、ETCD 之 Lease API
租约是一种检测客户端活跃度的机制,Lease机制的应用比较广泛,如用于授权进行同步等操作,用于分布式锁等场景。租约是由生存限制的,集群为租约授予一个TTL,当key被授予某个Lease时,它的生存时间为Lease的生存时间。Lease的实际TTL值不低于最小的TTL,而该最小值是由etcd集群选择的。当Lease的TTL到期时,所有与之关联的key都将被删除。如果在TTL结束前没有收到租约及KeepAlive消息来维持租约,那么该租约将过期。etcd3所支持的Lease机制可以为etcd集群中的某个key或者多个key所关联,一个key最多关联一个Lease。
租约request和租约过期收到的response,以及撤销租约的数据结构如下:
# request message LeaseGrantRequest { int64 TTL = 1; int64 ID = 2; } # response message LeaseGrantResponse { ResponseHeader header = 1; int64 ID = 2; int64 TTL = 3; } # 撤销 message LeaseRevokeRequest { int64 ID = 1; }
下面时租约,KeepAlive的数据结构:
# request message LeaseKeepAliveRequest { int64 ID = 1; } # response message LeaseKeepAliveResponse { ResponseHeader header = 1; int64 ID = 2; int64 TTL = 3; }
四、ETCD 之 gRPC gateway & pure python
#watch curl http://localhost:2379/v3alpha/watch \ -X POST -d '{"create_request": {"key":"Zm9v"} }' & # {"result":{"header":{"cluster_id":"12585971608760269493","member_id":"13847567121247652255","revision":"1","raft_term":"2"},"created":true}} curl -L http://localhost:2379/v3alpha/kv/put \ -X POST -d '{"key": "Zm9v", "value": "YmFy"}' >/dev/null 2>&1 # {"result":{"header":{"cluster_id":"12585971608760269493","member_id":"13847567121247652255","revision":"2","raft_term":"2"},"events":[{"kv":{"key":"Zm9v","create_revision":"2","mod_revision":"2","version":"1","value":"YmFy"}}]}} # 事务 curl -L http://localhost:2379/v3alpha/kv/txn \ -X POST \ -d '{"compare":[{"target":"CREATE","key":"Zm9v","createRevision":"2"}],"success":[{"requestPut":{"key":"Zm9v","value":"YmFy"}}]}' # {"header":{"cluster_id":"12585971608760269493","member_id":"13847567121247652255","revision":"3","raft_term":"2"},"succeeded":true,"responses":[{"response_put":{"header":{"revision":"3"}}}]}
# 事务 from etcd3 import Client client = Client('127.0.0.1', 12379) #事务 start txn = client.Txn() txn.compare(txn.key('foo').value == 'bar') txn.success(txn.put('foo2', 'bar2')) txn.commit() #事务 end print(client.range('foo2').kvs) #租约 from etcd3 import Client client = Client('127.0.0.1', 12379) myleaseId = 303030 mylease = client.lease_grant(30, myleaseId) client.put('foo3', 'bar3', myleaseId) lease_info = client.lease_time_to_live(myleaseId) # lease_keep_alive(myleaseId) print(lease_info)