「etcd」- Write

第一步、写请求 => Raft 日志
Raft 日志条目的数据结构信息:

type Entry struct {
Term uint64 `protobuf:”varint,2,opt,name=Term” json:”Term”`
Index uint64 `protobuf:”varint,3,opt,name=Index” json:”Index”`
Type EntryType `protobuf:”varint,1,opt,name=Type,enum=Raftpb.EntryType” json:”Type”`
Data []byte `protobuf:”bytes,4,opt,name=Data” json:”Data,omitempty”`
}

它由以下字段组成: 1)Term 是 Leader 任期号,随着 Leader 选举增加; 2)Index 是日志条目的索引,单调递增增加; 3)Type 是日志类型,比如是普通的命令日志(EntryNormal)还是集群配置变更日志(EntryConfChange); 3)Data 保存 put 提案内容;
第二步、Raft 日志 => WAL
它首先先将 Raft 日志条目内容(含任期号、索引、提案内容)序列化后保存到 WAL 记录的 Data 字段,然后计算 Data 的 CRC 值,设置 Type 为 Entry Type,以上信息就组成了一个完整的 WAL 记录。
最后,计算 WAL 记录的长度,顺序先写入 WAL 长度(Len Field),然后写入记录内容,调用 fsync 持久化到磁盘,完成将日志条目保存到持久化存储中。[……]

READ MORE

「etcd」- Authentication

整体架构
etcd 鉴权体系架构由控制面和数据面组成;
控制面(鉴权配置)

通过客户端工具 etcdctl 和鉴权 API 动态调整认证、鉴权规则,当 AuthServer 收到调整请求后,为了确保各节点间鉴权元数据一致性,会通过 Raft 模块进行数据同步;
当对应的 Raft 日志条目被集群半数以上节点确认后,Apply 模块通过鉴权存储 (AuthStore) 模块,执行日志条目的内容,将规则存储到 boltdb 的系列“鉴权表”里面;
数据面(鉴权执行)
数据面鉴权流程,由 Authentication(认证)和 Authorization(授权)流程组成。

Authentication(认证)
用户认证: 1)其目的是检查 client 的身份是否合法、防止匿名用户访问等。 2)当前 etcd 实现两种认证机制:Pwd Authn;Cert Authn;
Authorization(授权)
用户授权: 1)当通过认证后,在访问 MVCC 模块前,还需要通过授权流程,其目的是检查 Client 是否有权限操作你请求的数据路径。 2)etcd 实现 RBAC 机制,支持为每个用户分配一个角色,为每个角色授予最小化的权限;

参考文献
极客时间 /etcd 实战课(唐聪,腾讯云资深工程师,etcd 活跃贡献者) 《etcd 工作笔记:架构分析、优化与最佳实践》 etcd/Documentation versions[……]

READ MORE

「etcd」- Certificate Authentication

当对安全有更高要求的时候,你需要使用 HTTPS 协议加密通信数据,防止中间人攻击和数据被篡改等安全风险;
HTTPS 是利用非对称加密实现身份认证和密钥协商,因此使用 HTTPS 协议的时候,你需要使用 CA 证书给 client 生成证书才能访问
我们可以使用下面的 openssl 命令查看 client 证书的内容,x509 client 证书含有证书版本、序列号、签名算法、签发者、有效期、主体名等信息,我们重点要关注的是主体名中的 CN 字段;

# openssl x509 -noout -text -in client.pem

在 etcd 中,如果你使用了 HTTPS 协议并启用了 client 证书认证 (–client-cert-auth),它会取 CN 字段作为用户名,如果 CN=alice,则 alice 就是 client 发送请求的用户名;
特性特征
证书认证在稳定、性能都优于密码认证; 1)稳定:它不存在 Token 过期、使用更加方便、会让你少踩坑,避免了不少 Token 失效而触发的 Bug; 2)性能:证书认证无需像密码认证一样调用昂贵的密码认证操作 (Authenticate 请求),此接口支持的性能极低;[……]

READ MORE

「etcd」- Password Authentication

密码认证:etcd 支持为每个用户分配一个账号名称、密码。
创建用户
通过如下命令开启鉴权:

// 注意 etcd 会先要求创建一个 root 账号,它拥有集群的最高读写权限。

$ etcdctl user add root:root
User root created

$ etcdctl auth enable
Authentication Enabled

通过鉴权模块的 user 命令,给 etcd 增加 alice 账号。我们一起来看看 etcd 鉴权模块是如何基于我上面介绍的技术方案,来安全存储 alice 账号信息。

$ etcdctl user add alice:alice –user root:root
User alice created

etcd 的用户密码存储使用高安全性 hash 函数、随机 Salt 参数、可自定义的 hash 值计算迭代次数 cost;
鉴权模块收到此命令后,它会使用 bcrpt 库的 blowfish 算法,基于明文密码、随机分配的 salt、自定义的 cost、迭代多次计算得到一个 hash 值,并将加密算法版本、salt 值、cost、hash 值组成一个字符串,作为加密后的密码。
最后,鉴权模块将用户名 alice 作为 key,用户名、加密后的密码作为 value,存储到 boltdb 的 authUsers bucket 里面,完成一个账号创建。
用户认证
当启用鉴权后,执行如下命令,etcd server 会返回 user name is empty 错误,以达到防止匿名用户访问 etcd 数据目的:

$ etcdctl put hello world
Error: etcdserver: user name is empty

etcd server 收到 put hello 请求的时候,在提交到 Raft 模块前,它会从你请求的上下文中获取你的用户身份信息。如果 Client 未通过认证,那么在状态机应用 put 命令的时候,检查身份权限的时候发现是空,就会返回此错误给 client。
当你使用 alice 账号访问 etcd 的时候,你需要先调用鉴权模块的 Authenticate 接口,它会验证你的身份合法性。
鉴权模块首先会根据你请求的用户名 alice,从 boltdb 获取加密后的密码,因此 hash 值包含了算法版本、salt、cost 等信息,因此可以根据你请求中的明文密码,计算出最终的 hash 值,若计算结果与存储一致,那么身份校验通过。
当认证通过后,为了提高密码认证性能,会分配一个 Token(类似我们生活中的门票、通信证)给[……]

READ MORE

「etcd」- TokenProvider

问题描述
身份验证这个过程开销极其昂贵,需要避免频繁、昂贵的密码计算匹配,提升密码认证的性能;
解决方案
当 etcd server 验证用户密码成功后,它就会返回一个 Token 字符串给 client,用于表示用户的身份。后续请求携带此 Token,就无需再次进行密码校验,实现了通信证的效果;
原理简述
etcd 目前支持两种 Token,分别为 Simple Token 和 JWT Token;
Simple Token
其核心原理是: 1)当一个用户身份验证通过后,生成一个随机的字符串值 Token 返回给 client,并在内存中使用 map 存储用户和 Token 映射关系; 2)当收到用户的请求时, etcd 会从请求中获取 Token 值,转换成对应的用户名信息,返回给下层模块使用;
Token 是身份的象征,若此 Token 泄露了,那你的数据就可能存在泄露的风险。etcd 生成的每个 Token,都有个过期时间 TTL 属性,Token 过期后 client 需再次验证身份,因此可显著缩小数据泄露的时间窗口,在性能上、安全性上实现平衡;
在 etcd v3.4.9 版本中,Token 默认有效期是 5min,etcd server 会定时检查 Token 是否过期,若过期则从 map 数据结构中删除此 Token;
已知问题: 1)它是有状态的,etcd server 需要使用内存存储 Token 和用户名的映射关系; 2)可描述性很弱(Simple Token 字符串本身并未含任何有价值信息),client 无法通过 Token 获取到过期时间、用户名、签发者等信息。因此 client 无法及时、准确获取到 Token 过期时间,所以 client 不容易提前去规避因 Token 失效导致的请求报错;
JWT Token
JWT 是 Json Web Token 缩写, 它是一个基于 JSON 的开放标准(RFC 7519)定义的一种紧凑、独立的格式,可用于在身份提供者和服务提供者间,传递被认证的用户身份信息;
它由 Header、Payload、Signature 三个对象组成, 每个对象都是一个 JSON 结构体: 1)Header,其包含 alg 和 typ 两个字段:alg 表示签名的算法,etcd 支持 RSA、ESA、PS 系列;typ 表示类型就是 JWT; 2)Payload,它表示载荷,包含用户名、过期时间等信息,可以自定义添加字段; 3)Signature = RSA256(base64UrlEncode(header) + “.” +base64UrlEncode(payload),RSA-Private-Key)
特性特[……]

READ MORE

「etcd」- RBAC

Role-Based Access Control
常用的权限控制方法有 ACL(Access Control List)、ABAC(Attribute-based access control)、RBAC(Role-based access control),etcd 实现的是 RBAC 机制;
它由下图中的三部分组成,User、Role、Permission。User 表示用户,如 alice。Role 表示角色,它是权限的赋予对象。Permission 表示具体权限明细,比如赋予 Role 对 key 范围在[key,KeyEnd]数据拥有什么权限。目前支持三种权限,分别是 READ、WRITE、READWRITE;

场景:授予用户读写权限
给 alice 用户赋予一个可读写[hello,helly]数据范围的读写权限
按照 RBAC 原理: 1)首先,需要创建一个 Role,这里我们命名为 admin, 2)然后,新增可读写 [hello,helly] 数据范围的权限,并赋予给 admin 角色, 3)并将 admin 的角色的权限授予 alice 用户;

# 创建一个 admin role

etcdctl role add admin –user root:root
Role admin created

# 分配一个可读写[hello,helly]范围数据的权限给 admin role

$ etcdctl role grant-permission admin readwrite hello helly –user root:root
Role admin updated

# 将用户 alice 和 admin role 关联起来,赋予 admin 权限给 user

$ etcdctl user grant-role alice admin –user root:root
Role admin is granted to user alice

然后当你再次使用 etcdctl 执行 put hello 命令时,鉴权模块会从 boltdb 查询 alice 用户对应的权限列表;
因为有可能一个用户拥有成百上千个权限列表,etcd 为了提升权限检查的性能,引入了区间树,检查用户操作的 key 是否在已授权的区间,时间复杂度仅为 O(logN);
在我们的这个案例中,很明显 hello 在 admin 角色可读写的[hello,helly) 数据范围内,因此它有权限更新 key hello,执行成功。你也可以尝试更新 key hey,因为此 key 未在鉴权的数据区间内,因此 etcd server 会返回”etcdserver: permissio[……]

READ MORE

「etcd」- Compactor

问题描述
我们知道 etcd 中的每一次更新、删除 key 操作,treeIndex 的 keyIndex 索引中都会追加一个版本号,在 boltdb 中会生成一个新版本 boltdb key 和 value。也就是随着你不停更新、删除,你的 etcd 进程内存占用和 db 文件就会越来越大。很显然,这会导致 etcd OOM 和 db 大小增长到最大 db 配额,最终不可写;
那么 etcd 是通过什么机制来回收历史版本数据,控制索引内存占用和 db 大小的呢?
解决方案
etcd 压缩机制
原理简述

在了解 etcd 压缩模块实现细节前,我先给你画了一幅压缩模块的整体架构图。从图中可知,你可以通过 client API 发起人工的压缩 (Compact) 操作,也可以配置自动压缩策略。在自动压缩策略中,你可以根据你的业务场景选择合适的压缩模式。目前 etcd 支持两种压缩模式,分别是时间周期性压缩和版本号压缩;
当你通过 API 发起一个 Compact 请求后,KV Server 收到 Compact 请求提交到 Raft 模块处理,在 Raft 模块中提交后,Apply 模块就会通过 MVCC 模块的 Compact 接口执行此压缩任务;
Compact 接口首先会更新当前 server 已压缩的版本号,并将耗时昂贵的压缩任务保存到 FIFO 队列中异步执行。压缩任务执行时,它首先会压缩 treeIndex 模块中的 keyIndex 索引,其次会遍历 boltdb 中的 key,删除已废弃的 key;
以上就是压缩模块的一个工作流程。接下来我会首先和你介绍如何人工发起一个 Compact 操作,然后详细介绍周期性压缩模式、版本号压缩模式的工作原理,最后再给你介绍 Compact 操作核心的原理;
压缩特性初体验
在使用 etcd 过程中,当你遇到”etcdserver: mvcc: database space exceeded”错误时,若是你未开启压缩策略导致 db 大小达到配额,这时你可以使用 etcdctl compact 命令,主动触发压缩操作,回收历史版本;
如下所示,你可以先通过 endpoint status 命令获取 etcd 当前版本号,然后再通过 etcdctl compact 命令发起压缩操作即可;

# 获取 etcd 当前版本号
$ rev=$(etcdctl endpoint status –write-out=”json” | egrep -o ‘”revision”:[0-9]*’ | egrep -o ‘[0-9].*’)
$ echo $rev
9
# 执行压缩操作,指定压缩的版本号为当前版本号
$ etcd[……]

READ MORE

「etcd」- Data Model and REST API

Data Model
etcd 数据模型参考了 ZooKeeper,使用的是基于目录的层次模式。
key-value 存储引擎上,etcd 使用的是则是简单内存树,它的节点数据结构精简后如下,含节点路径、值、孩子节点信息。这是一个典型的低容量设计,数据全放在内存,无需考虑数据分片,只能保存 key 的最新版本,简单易实现。

REST API
API 相比 ZooKeeper 来说,使用了简单、易用的 REST API,提供了常用的 Get/Set/Delete/Watch 等 API,实现对 key-value 数据的查询、更新、删除、监听等操作。[……]

READ MORE

「etcd」- 租约(Lease)

解决方案
Lease,正是基于主动型上报模式,提供的一种活性检测机制;
Lease,租约,顾名思义,是 client 和 etcd server 之间存在一个约定,内容是 etcd server 保证在约定的 TTL(有效期内),不会删除 Client 关联到此 Lease 上的 key-value;
若未在 TTL 内续租,那么 etcd server 就会删除 Lease 及其关联的 key-value;
原理简述
所示如图,是 Lease 模块的整体架构:
Lessor
etcd 在启动的时候,创建 Lessor 模块的时候,它会启动两个常驻 goroutine: 1)RevokeExpiredLease,定时检查 Lease 是否过期,并发起撤销过期的 Lease 操作; 2)CheckpointScheduledLease,定时触发更新 Lease 的剩余到期时间的操作;
Lessor 模块向 Client 提供如下接口: 1)Grant,表示创建一个 TTL 为指定秒数的 Lease,Lessor 会将 Lease 信息持久化存储在 boltdb 中; 2)Revoke,表示撤销 Lease 并删除其关联的数据; 3)LeaseTimeToLive,表示获取一个 Lease 的有效期、剩余时间; 4)LeaseKeepAlive,表示为 Lease 续期;
特性特征
TTL 为时间点,而非分钟或毫秒值;
应用场景
基于 Lease 的 TTL 特性,解决类似 Leader 选举、Kubernetes Event 自动淘汰、服务发现场景中故障节点自动剔除等问题;[……]

READ MORE

「etcd」- 通过 Lease 特性,检测节点存活

解决方案
原理简述: 1)首先,需要创建一个与节点健康指标相关的 Lease,并将该 Lease 与节点健康指标 key 关联; 2)然后,在执行健康检查的时候,需要周期更新 Lease 的 TTL; 3)如果某个节点异常,则其将无法对 Lease 进行正常续期,那么随着时间消逝,对应的 Lease 则会过期,Lessor 主循环定时轮询过期的 Lease。获取到 ID 后,Leader 发起 revoke 操作,通知整个集群删除 Lease 和关联的数据;
第一步、创建 Lease 实例
为节点健康指标创建一个租约:

# 创建一个 TTL 为 600s 的 lease,etcd server 返回 LeaseID
$ etcdctl lease grant 600
lease 326975935f48f814 granted with TTL(600s)

# 查看 lease 的 TTL、剩余时间
$ etcdctl lease timetolive 326975935f48f814
lease 326975935f48f814 granted with TTL(600s), remaining(590s)

当 Lease server 收到 client 的创建一个有效期 600s 的 Lease 请求后,会通过 Raft 模块完成日志同步,随后 Apply 模块通过 Lessor 模块的 Grant 接口执行日志条目内容。然后 Lessor 的 Grant 接口会把 Lease 保存到内存的 ItemMap 数据结构中,然后它需要持久化 Lease,将 Lease 数据保存到 boltdb 的 Lease bucket 中,返回一个唯一的 LeaseID 给 client;
第二步、Key 关联 Lease
然后,将节点的健康指标数据关联到此 Lease 上:
KV 模块的 API 接口提供 –lease 参数,来将 key node 关联到对应的 LeaseID 上:

$ etcdctl put node healthy –lease 326975935f48f818
OK

// 查询的时候增加 -w 参数输出格式为 json,就可查看到 key 关联的 LeaseID;

$ etcdctl get node -w=json | python -m json.tool
{
“kvs”:[
{
“create_revision”:24,
“key”:”bm9kZQ==”,
“Lease”:3632563850270275608,
“mod_rev[……]

READ MORE

「etcd」- MVCC(Multiversion concurrency control)

问题描述
在 etcd v2 时,仅能保留最新版本 key-value 数据、丢弃历史版本,而 etcd 核心特性 watch 又依赖历史版本。所以,etcd v2 为了缓解这个问题,会在内存中维护一个较短的全局事件滑动窗口,保留最近的 1000 条变更事件;
但是,在集群写请求较多等场景下,它依然无法提供可靠的 Watch 机制;
解决方案
为了能够更好地处理数据多版本问题,诞生 MVCC(Multiversion concurrency control)机制;
原理简述
MVCC 机制正是基于多版本技术实现的一种乐观锁机制,它乐观地认为数据不会发生冲突,但是当事务提交时,具备检测数据是否冲突的能力;
版本号(revision)
在 MVCC 数据库中,你更新一个 key-value 数据的时候,它并不会直接覆盖原数据,而是新增一个版本来存储新的数据,每个数据都有一个版本号;
随着时间增长,你每次修改操作,版本号都会递增。每修改一次,生成一条新的数据记录。当你指定版本号读取数据时,它实际上访问的是版本号生成那个时间点的快照数据。当你删除数据的时候,它实际也是新增一条带删除标识的数据记录;

版本号它是一个逻辑时间,所示如图,其为 etcd MVCC 版本号时间序列图;

# 更新 key hello 为 world1
$ etcdctl put hello world1
OK
# 通过指定输出模式为 json,查看 key hello 更新后的详细信息
$ etcdctl get hello -w=json
{
“kvs”:[
{
“key”:”aGVsbG8=”,
“create_revision”:2,
“mod_revision”:2,
“version”:1,
“value”:”d29ybGQx”
}
],
“count”:1
}
# 再次修改 key hello 为 world2
$ etcdctl put hello world2
OK
# 确认修改成功,最新值为 wolrd2
$ etcdctl get hello
hello
world2
# 指定查询版本号,获得了 hello 上一次修改的值
$ etcdctl get hello –rev=2
hello
world1
# 删除 key hello
$ etcdctl del hello
1
# 删除后指定查询版本号 3,获得了 hello 删除前的值
$ etcdctl get hello –rev=3
hello
wor[……]

READ MORE

「MVCC」- 概述原理

整体架构
当执行 put 请求时,流程如图所示(图示已省略部分暂时无需关注的模块),Apply 模块通过 MVCC 模块来执行 put 请求,以持久化 KV 数据;

MVCC 模块将请求请划分成两个类别,分别是: 1)读事务(ReadTxn),读事务负责处理 range 请求, 2)写事务(WriteTxn),写事务负责 put/delete 操作;
treeIndex and boltdb
整个 MVCC 特性由 treeIndex、Backend/boltdb 组成,读写事务基于 treeIndex、Backend/boltdb 提供的能力,实现对 KV 的增删改查功能;
treeIndex 与 boltdb 关系如图:当发起 get hello 命令时,从 treeIndex 中获取 key 的版本号,然后再通过这个版本号,从 boltdb 获取 value 信息;

treeIndex
解释:该模块基于内存版 B-tree 实现 key 索引管理,它保存用户 key 与版本号(revision)的映射关系等信息;
用途:对于 etcd v2 来说,当你通过 etcdctl 发起一个 put hello 操作时,etcd v2 直接更新内存树,这就导致历史版本直接被覆盖,无法支持保存 key 的历史版本。在 etcd v3 中引入 treeIndex 模块正是为解决这个问题,支持保存 key 的历史版本,提供稳定的 Watch 机制和事务隔离等能力;
行为:etcd 在每次修改 key 时,会生成一个全局递增的 revision,然后通过数据结构 B-tree 保存用户 key 与 revision 间的关系,再以 revision 作为 boltdb key,以用户的 key-value 等信息作为 boltdb value,保存到 boltdb;
B-tree
Q:为什么 etcd 不使用哈希表、平衡二叉树? 从功能特性上分析, 因 etcd 支持范围查询,因此保存索引的数据结构也必须支持范围查询才行。所以哈希表不适合,而 B-tree 支持范围查询;B 树支持的范围查询很有限,如果在同一个 B 树节点的范围查询就还好,如果是跨 B 树节点就比较麻烦了。而 B+树的范围查询就很方便了; 从性能上分析,平横二叉树每个节点只能容纳一个数据、导致树的高度较高,而 B-tree 每个节点可以容纳多个数据,树的高度更低,更扁平,涉及的查找次数更少,具有优越的增、删、改、查性能;
Google 的开源项目 btree,使用 Go 语言实现了一个内存版的 B-tree,对外提供了简单易用的接口。etcd 正是基于 btree 库实现了一个名为 treeIndex[……]

READ MORE

「Consensus Algorithm」- Raft

Standford 大学的 Diego 提出的 Raft 算法正是为可理解性、易实现而诞生的;
它通过问题分解,将复杂的共识问题拆分成三个子问题,分别是: 1)Leader 选举:当 Leader 故障后集群能快速选出新 Leader; 2)日志复制:集群只有 Leader 能写入日志,Leader 负责复制日志到 Follower,并强制 Follower 与自己保持相同; 3)安全性:一个任期内集群只能产生一个 Leader、已提交的日志条目在发生 Leader 选举时,一定会存在更高任期的新 Leader 日志中、各个节点的状态机应用的任意位置的日志条目内容应一样等;
Leader 选举
在 Raft 中,集群 Leader 才能处理写提案,该部分将记录:Leader 是如何产生的,以及 Leader 崩溃后其他节点如何竞选;
节点状态
Follower,跟随者, 同步从 Leader 收到的日志,etcd 启动的时候默认为此状态;
Candidate,竞选者,可以发起 Leader 选举;
Leader,集群领导者, 唯一性,拥有同步日志的特权,需定时广播心跳给 Follower 节点,以维持领导者身份;
任期与任期号(Term)
Raft 将时间划分成一个个任期,任期用连续的整数表示,每个任期从一次选举开始,赢得选举的节点在该任期内充当 Leader 的职责;
随着时间的消逝,集群可能会发生新的选举,任期号也会单调递增; 通过任期号,可以比较各个节点的数据新旧、识别过期的 Leader 等,它在 Raft 算法中充当逻辑时钟,发挥着重要作用;
状态变迁

Follower => Candidate 1)当 Follower 接收 Leader 心跳消息超时后,它会转变成 Candidate 状态;
Candidate => Leader 1)Candidate 可发起竞选 Leader 投票,若获得集群多数节点的支持后,它就可转变成 Leader 节点;
Leader => Follower 1)现有 Leader 发现新的 Leader 任期号,那么它就需要转换到 Follower 节点; 2)当 Leader 崩溃恢复后,再次启动将成为 Follower 状态;
场景:after Leader Crash
假设:Leader A;Follower B;Follower C;

当 Leader 正常时: 1)Leader 将周期(心跳间隔时间)广播 MsgHeartbeat 消息给 Follower,以维持 Leader 身份; 2)Follower 收到 MsgHeartbeat 后,回复 Ms[……]

READ MORE

「etcd」- 事务

问题描述
在 etcd v2 的时候, etcd 提供了 CAS(Compare and swap),然而其只支持单 key,不支持多 key,因此无法满足类似转账场景的需求。严格意义上说 CAS 称不上事务,无法实现事务的各个隔离级别;
解决方案
etcd v3 为了解决多 key 的原子操作问题,提供了全新迷你事务 API,同时基于 MVCC 版本号,它可以实现各种隔离级别的事务;
事务,它就是为了简化应用层的编程模型而诞生的,etcd 实现事务 ACID 特性的;
原理简述
基础使用
基本结构如下:

client.Txn(ctx).If(cmp1, cmp2, …).Then(op1, op2, …,).Else(op1, op2, …)

从上面结构中你可以看到,事务 API 由 If 语句、Then 语句、Else 语句组成,这与我们平时常见的 MySQL 事务完全不一样;
它的基本原理是,在 If 语句中,你可以添加一系列的条件表达式,若条件表达式全部通过检查,则执行 Then 语句的 get/put/delete 等操作,否则执行 Else 的 get/put/delete 等操作;
那么 If 语句支持哪些检查项:

key 的最近一次修改版本号 mod_revision,简称 mod。你可以通过它检查 key 最近一次被修改时的版本号是否符合你的预期。比如当你查询到 Alice 账号资金为 100 元时,它的 mod_revision 是 v1,当你发起转账操作时,你得确保 Alice 账号上的 100 元未被挪用,这就可以通过 mod(“Alice”) = “v1” 条件表达式来保障转账安全性;

key 的创建版本号 create_revision,简称 create。你可以通过它检查 key 是否已存在。比如在分布式锁场景里,只有分布式锁 key(lock) 不存在的时候,你才能发起 put 操作创建锁,这时你可以通过 create(“lock”) = “0”来判断,因为一个 key 不存在的话它的 create_revision 版本号就是 0;

key 的修改次数 version。你可以通过它检查 key 的修改次数是否符合预期。比如你期望 key 在修改次数小于 3 时,才能发起某些操作时,可以通过 version(“key”) < “3”来判断;

key 的 value 值。你可以通过检查 key 的 value 值是否符合预期,然后发起某些操作。比如期望 Alice 的账号资金为 200, value(“Alice”[……]

READ MORE

「etcd」- Watch

问题描述
在 Kubernetes 中,各种各样的控制器实现 Deployment、StatefulSet、Job 等功能强大的 Workload。控制器的核心思想是监听、比较资源实际状态与期望状态是否一致,若不一致则进行协调工作,使其最终一致。那么当修改一个 Deployment 的镜像时,Deployment 控制器是如何高效的感知到期望状态发生变化呢?
解决方案
etcd 的 Watch 特性,实现将变化数据从 0 到 1 推送给 Client;
原理简述
使用方法
通过下面的 watch 命令,带版本号监听 key hello,集群版本号可通过 endpoint status 命令获取,空集群启动后的版本号为 1;

# bin/etcdctl watch hello -w=json –rev=1

// 当命令 watch 执行后,然后执行的增量 put hello 修改操作,它同样可持续输出最新的变更事件给你;

$ etcdctl put hello world1
$ etcdctl put hello world2

当执行后,两个事件记录分别对应上面的两次的修改,事件中含有 key、value、各类版本号等信息,还可以通过比较 create_revision 和 mod_revision 区分此事件是 add 还是 update 事件;
整体架构

当通过 etcdctl 发起一个 watch key 请求的时候,
etcd 的 gRPCWatchServer 收到 watch 请求后,会创建一个 serverWatchStream,它负责接收 client 的 gRPC Stream 的 create watcher / cancel watcher 请求 (recvLoop goroutine),并将从 MVCC 模块接收的 Watch Events 转发给 client(sendLoop goroutine);
当 serverWatchStream 收到 create watcher 请求后,serverWatchStream 会调用 MVCC 模块的 WatchStream 子模块分配一个 watcher id,并将 watcher 注册到 MVCC 的 WatchableKV 模块;
在 etcd 启动的时候,WatchableKV 模块会运行 syncWatchersLoop 和 syncVictimsLoop goroutine,分别负责不同场景(最新事件推送、异常场景重试、历史事件推送机制)下的事件推送,它们也是 Watch 特性可靠性的核心之一;
从架构图中可以看到 Watch 特性的核心实现是 Wat[……]

READ MORE

「etcd」- 安装及升级

通过 goreman 安装
极客时间/etcd 实战课(唐聪,腾讯云资深工程师,etcd 活跃贡献者)
goreman,进程管理工具,其使我们能够快速创建、停止本地的多节点 etcd 集群(测试目的);
流程大致如下(具体细节,诸如 目录结构、文件权限 等等,需要结合环境进行调整):

# 下载 goreman 程序
# https://github.com/mattn/goreman/releases/tag/v0.3.13
wget https://github.com/mattn/goreman/releases/download/v0.3.13/goreman_v0.3.13_linux_amd64.tar.gz

# 下载 etcd v3.4.9 程序
# https://github.com/etcd-io/etcd/releases/v3.4.9
wget https://github.com/etcd-io/etcd/releases/download/v3.4.9/etcd-v3.4.9-linux-amd64.tar.gz

# 下载 goreman Procfile 文件,它描述 etcd 进程名、节点数、参数等信息
wget https://raw.githubusercontent.com/etcd-io/etcd/v3.4.9/Procfile

# 启动一个 3 节点的本地集群
./goreman -f Procfile start[……]

READ MORE

「etcd」- 常见问题处理

问题篇。为你分析 etcd 使用过程中的各类典型问题,和你细聊各种异常现象背后的原理、最佳实践;
性能优化篇。通过读写链路的分析,为你梳理可能影响 etcd 性能的每一个瓶颈;
实战篇。带你从 0 到 1 亲手参与构建一个简易的分布式 KV 数据库,进一步提升你对分布式存储系统的认知;
Kubernetes 实践篇。为你分析 etcd 在 Kubernetes 中的应用,让你对 Kubernetes 原理有更深层次的理解;
etcd 应用篇。介绍 etcd 在分布式锁、配置系统、服务发现场景中的应用
端口及功能
etcd docs | Configuration flags
在 etcd v3.1 中,参考文档: 1)2379:客户端通信 2)2380:节点通讯;
etcd 端口接受:TLS 连接;非 TLS 连接;同时接受两种连接;
参考文献
极客时间/etcd 实战课(唐聪,腾讯云资深工程师,etcd 活跃贡献者) 《etcd 工作笔记:架构分析、优化与最佳实践》 etcd/Documentation versions[……]

READ MORE

「Programming Languages」- 自动化构建工具

make Ant(Another Neat Tool)

章节列表
「GUN make」- 编写 Makefile文件 「Autoconf」- 安装 「M4」- 概念术语:依赖(dependency) 「M4」- A general-purpose macro processor」 「GNU Make」 「Apache Maven」- 构建工具[……]

READ MORE

「Autoconf」- 安装

从发行版的源中安装
使用源码编译安装
下载Autoconf:https://www.gnu.org/software/autoconf/autoconf.html
进入源码目录,准备,编译,安装:

#!/bin/bash

################################################################################
# Autoconf-2.69
################################################################################
# ./configure –prefix=/usr
./configure –prefix=/usr/local
make && make install

安装的可执行程序
autoconf Produces shell scripts that automatically configure software source code packages to adapt to many kinds of Unix-like systems; the configuration scripts it produces are independent—running them does not require the autoconf program
autoheader A tool for creating template files of C #define statements for configure to use
autom4te A wrapper for the M4 macro processor
autoreconf Automatically runs autoconf, autoheader, aclocal, automake, gettextize, and libtoolize in the correct order to save time when changes are made to autoconf and automake template files
autoscan Helps to create a configure.in file for a software package; it examines the source files in a directory tree, searching them for common portability issues, and creates a configure.scan file[……]

READ MORE

「Automake」- 安装

访问主页
安装依赖
Automake依赖于Autoconf,所以要先安装要求版本的Autoconf包。
从发行版的源中安装

#!/bin/sh

# CentOS automake 1.5
yum install -y automake15

使用源码编译安装

#!/bin/bash

################################################################################
# Automake-1.13
# Dependencies: Autoconf-2.69+
################################################################################
# ./configure –prefix=/usr –docdir=/usr/share/doc/automake-1.13
./configure –prefix=/usr/local –docdir=/usr/local/share/doc/automake-1.13
make && make install

################################################################################
# Automake-1.15
################################################################################
# 1. 下载源码并进入源码目录

# 2. 准备,编译,安装
./configure –prefix=/usr –docdir=/usr/share/doc/automake-1.15

make && make install

################################################################################
# Automake-1.16
# Dependencies: Autoconf-2.69+
################################################################################
# ./configure –prefix=/usr –docdir=/usr/share/doc/automake-1.16
./configure –prefix=/usr/local –docdir=/usr/local/share/doc/automak[……]

READ MORE

「CMake」

安装程序
安装最新版本
Kitware APT Repository

sudo apt-get update
sudo apt-get install gpg wget

wget -O – https://apt.kitware.com/keys/kitware-archive-latest.asc 2>/dev/null \
| gpg –dearmor – | sudo tee /usr/share/keyrings/kitware-archive-keyring.gpg >/dev/null

# Ubuntu 20.04 LTS
echo ‘deb [signed-by=/usr/share/keyrings/kitware-archive-keyring.gpg] https://apt.kitware.com/ubuntu/ focal main’ \
| sudo tee /etc/apt/sources.list.d/kitware.list >/dev/null
sudo apt-get update

sudo rm /usr/share/keyrings/kitware-archive-keyring.gpg
sudo apt-get install kitware-archive-keyring

sudo apt-get install cmake

参考文献
Homepage: CMake[……]

READ MORE

「Gradle」

Gradle,强大的 JVM 构建系统;
Gradle 是一个构建工具,重点是构建自动化和支持多语言开发。如果在任何平台上构建、测试、发布、部署软件,那么 Gradle 提供了一个灵活的模型,可以支持从编译和打包代码到发布网站的整个开发生命周期。Gradle 设计用于支持 Java,Scala,Android,C/C++、Groovy 等多种语言和平台的构建自动化,并与开发工具(包括 Eclipse,IntelliJ 和 Jenkins)紧密集成;
Gradle 有一个 GUI 界面,使用“gradle –gui”打开。如果要使用它,则必须安装 default-jre(debian 中的一个包)才能提供 GUI 功能;
安装
从发行版的源中安装

#!/bin/bash

################################################################################
# Kali GNU/Linux Rolling
################################################################################

# “全家桶”安装
apt-get install $(apt-cache search gradle | grep -i gradle | awk ‘{printf “%s “, $1}’)

# 只安装 Gradle
apt-get install gradle

安装的可执行程序
gradle 强大的 JVM 构建系统。用于构建;
~/.gradle/wrapper/dists
cat build.gradle
关于 gradlew 命令
gradlew(Gradle Wrapper)会给 Gradle 项目的使用者(下称用户)带来好处,而不是原始开发者(下称作者)。如果作者在他的 Gradle 项目中设置了 gradlew,那么其他用户在构建时,可以直接执行以下命令:

./gradlew [task]

每一个 gradlew 都会绑定到一个特定版本的 Gradle,当用户第一次执行上面的命令时,gradlew 会自动地下载并安装对应版本的 Gradle;
这就带来两个好处:

用户不必自己下载、安装、配置 Gradle
用户进行项目构建时能够确保使用正确的 Gradle 版本(特别是在构建历史项目时特别明显)

在配置了包含 gradlew 的 Gradle 项目中,会存在以下的重要文件:

gradlew (Unix) 或者 gr[……]

READ MORE

「Libtool」

参考文献
Homepage: http://www.gnu.org/software/libtool/[……]

READ MORE

「M4」- A general-purpose macro processor」

安装部署
从发行版的源中安装
WIP
从源码中编译安装
LFS / M4: http://www.linuxfromscratch.org/lfs/view/7.10/chapter06/m4.html
安装的可执行程序
m4,copies the given files while expanding the macros that they contain [These macros are either built-in or user-defined and can take any number of arguments. Besides performing macro expansion, m4 has built-in functions for including named files, running Unix commands, performing integer arithmetic, manipulating text, recursion, etc. The m4 program can be used either as a front-end to a compiler or as a macro processor in its own right.]
相关文档
让这世界再多一份GNU m4 教程 (全文整理): http://blog.csdn.net/timekeeperl/article/details/50738164 GNU M4 笔记: https://my.oschina.net/ReJaVu/blog/184503 GNU M4宏处理器快速理解: http://utxz.com/142.html
参考文献
Homepage: https://www.gnu.org/software/m4/m4.html Wikipedia/M4: https://en.wikipedia.org/wiki/M4_(computer_language)[……]

READ MORE

「GNU Make」

make 命令相关内容,make有众多实现,比如NetBSD make,本文的make指的是GNU make; 大家都知道 make 是个什么东西,一个构建工具(虽然有很多构建工具,但make是大型项目的首选)。
make version 4.2
相关链接
GNU make: https://www.gnu.org/software/make/manual/make.html
参考文献
https://www.gnu.org/software/make/manual/make.pdf Homepage:http://www.gnu.org/software/make/ Doc: https://www.gnu.org/software/make/manual/make.html[……]

READ MORE

「Linux下,安装GNU Make」

从发行版的源中直接安装

#!/bin/bash

# Kali GNU/Linux Rolling
apt-get install make

Linux下,使用源码编译安装(Compilation)

#!/bin/sh

tar -xf make-4.1.tar.gz

cd make-4.1

./configure –prefix=/usr/local

# 构建,make的构建需要make;
make && make install

安装的可执行程序
make 主程序文件,能自动确定出哪些程序包需要(重新)编译,然后执行相应的命令。
参考文献
LFS / Make: http://www.linuxfromscratch.org/lfs/view/7.10/chapter06/make.html[……]

READ MORE

「Linux下,make命令行选项详解」

命令行语法格式(SYNOPSIS)
make [OPTION]… [TARGET]…
命令简述(DESCRIPTION)
make自动确定大程序的哪些部分需要重新编译,并发出命令以重新编译它们。 make的示例中使用的是C语言,但是make可以在任何语言下使用,只要这些语言的编译器可以使用shell命令运行。 实际上,make可以使用在任何任务中。
在使用make之前,要先有一个makefile,makefile文件描述了程序各个文件之间的关系和处理各个文件的命令。 make默认在在当前目录中寻找makefile文件,实际上是在当前目录中依次查找makefiles,GNUmakefile, makefile, Makefile。通常建议使用的名字是Makefile,因为这样靠近目录列表开头,会和README这样的重要文件靠的近,显得规整一些。不建议使用GNUmakefile,因为其他版本的make(make 有很多实现,比如NetBSD make。)都不认识。 如果使用了 -f(-f file, –file=file, –makefile=FILE) 选项,make 将使用 -f 指定的文件。如果 -f 的参数是 `-‘,表示从标准输入中读取 makefile。
当makefile存在之后,就可以执行make命令。 make在构建的时候,除了使用makefile之外,还要使用到文件的最后修改时间来决定是否要更新文件。 如果目标文件依赖的文件发生了修改或者目标文件不存在,make就会更新目标文件。 然后,make命令对要更新的文件使用makefile中记录的命令。
命令支持的选项及含义(OPTIONS)
-b, -m 为了兼容其他版本的make,这些命令已经被忽略。
-B, –always-make 无条件的构建所有的目标(targets)。
-C dir, –directory=dir 读取makefile或者做其他事情之前,先进入指定的文件夹。 如果指定了多个 -C 选项,则相对于前一个依次解释执行。如 `-C / -C etc’ == `-C /etc’。 用于 make 的递归调用。
-d 除了正常调用外,打印 debug 信息。 debug 信息显示将要重建的文件、将要被比较的文件时间和比较结果、需要被重建的文件、哪条隐含的规则被考虑和哪条被采用。 关于 make 决定怎么去做的一切内容。
–debug[=FLAGS] 除了正常调用外,打印 debug 信息。 如果 FLAGS 未指定,是使用 -d 选项是相同的。 a:显示所有的 debug 信息; b:显示基本的 debug 信息; v:更多详细的基本 debug 信息; i:显示隐含的规则; j:调用[……]

READ MORE

「GUN make」- 编写 Makefile文件

问题描述
内容:关于 makefile 的语法规则;
解决方案
Makefile 编写入门教程 https://mp.weixin.qq.com/s/OYYZL7EWru1Np0-1lVEvoA
关于 make 命令相关内容,参见另外一篇文章,点击查看;
Doc: https://www.gnu.org/software/make/manual/ 手册也就 200 页多一点;
阮一峰: http://www.ruanyifeng.com/blog/2015/02/make.html 博客写很好的;
关于更过 makefile 文件的内容,需要看文档。我们自己把文档打印了一份,方便查阅;
快速开始
基本 Makefile 结构实例:

ipl.bin : ipl.nas Makefile
../z_tools/nask.exe ipl.nas ipl.bin ipl.lst

如果想要制作 ipl.bin 就要具有 ipl.nas 与 Makefile 文件。如果具有里这两个文件,则执行执行下一行的命令;
  简单的说,makefile 定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至可以在 makefile 中执行 shell 脚本。makefile 带来的好处就是——“自动化编译”,一旦写好,只需要一个 make 命令,整个工程完全自动编译,极大的提高了软件开发的效率;
  关于程序的编译和链接
  一般来说,无论是 C 还是 C++,首先要把源文件编译成中间代码文件,在 Windows 下也就是 .obj 文件,UNIX 下是 .o 文件,即 Object File,这个动作叫做编译(compile),一般来说,每个源文件都应该对应于一个中间目标文件(O 文件或是 OBJ 文件)。然后再把大量的 Object File 合成执行文件,这个动作叫作链接(link);
  编译时,编译器需要的是语法的正确,函数与变量的声明的正确。对于后者,通常是你需要告诉编译器头文件的所在位置(头文件中应该只是声明,而定义应该放在 C/C++文件中),只要所有的语法正确,编译器就可以编译出中间目标文件;
  链接时,主要是链接函数和全局变量,所以,我们可以使用这些中间目标文件(O 文件或是 OBJ 文件)来链接我们的应用程序。链接器并不管函数所在的源文件,只管函数的中间目标文件(Object File),在大多数时候,由于源文件太多,编译生成的中间目标文件太多,而在链接时需要明显地指出中间目标文件名,这对于编译很不方便,所以,我们要给中间目标文件打个包,在 Windows 下这种包叫“库文件”(Libra[……]

READ MORE

「Maven」- 概念术语

问题描述
官方 Maven – Maven in 5 Minutes 文档,其展示 Maven 快速开始的方法;
该笔记将记录:我们将围绕该文档,学习 Maven 的概念术语,形成 Maven 的基本认识;
解决方案
我们摘抄 Maven in 5 Minutes 文档的部分内容,以便于逐步展开对概念术语的学习;
第一步、创建项目

mvn archetype:generate \
-DgroupId=com.mycompany.app \
-DartifactId=my-app \
-DarchetypeArtifactId=maven-archetype-quickstart \
-DarchetypeVersion=1.4 \
-DinteractiveMode=false

目录结构:

my-app
|– pom.xml
`– src
|– main
| `– java
| `– com
| `– mycompany
| `– app
| `– App.java
`– test
`– java
`– com
`– mycompany
`– app
`– AppTest.java

pom.xml

<project xmlns=”http://maven.apache.org/POM/4.0.0″ xmlns:xsi=”http://www.w3.org/2001/XMLSchema-instance”
xsi:schemaLocation=”http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd”>
<modelVersion>4.0.0</modelVersion> // POM Model 版本;针对 Maven 3 版本,[……]

READ MORE

「M4」- 概念术语:依赖(dependency)

依赖范围(scope)
编译、测试、运行,在这三个阶段中,会使用不同的 classpath,而 Scope 则用于控制依赖与这三种 classpath 的关系;
Maven 具有如下几种 Scope:
compile:编译依赖范围。如果没有指定,就会默认使用该依赖范围。使用此依赖范围的 Maven 依赖,对于编译、测试、运行三种 classpath 都有效; 例如 spring-core,在编译 、测试、运行的时候都需要使用该依赖;
test:测试依赖范围。使用此依赖范围的 Maven 依赖,只对于测试 classpath 有效,在编译主代码或者运行项目的使用时将无法使用此类依赖; 例如 JUnit,它只有在编译测试代码及运行测试的时候才需要;
provided:已提供依赖范围。使用此依赖范围的 Maven 依赖,对于编译和测试 classpath 有效,但在运行时无效; 例如 servlet-api,编译和测试项目的时候需要该依赖,但在运行项目的时候,由于容器已经提供,就不需要 Maven 重复引入;
runtime:运行时依赖范围。使用此依赖范围的 Maven 依赖,对于测试和运行 classpath 有效,但在编译主代码时无效; 例如 JDBC 驱动实现,项目主代码的编译只需要 JDK 提供的 JDBC 接口,只有在执行测试或运行项目的时候才需要实现上述接口的具体 JDBC 驱动;
system:系统依赖范围。该依赖与三种 classpath 的关系,和 provided 依赖范围完全一致. 但是,使用 system 范围的依赖时必须通过 systemPath 元素显式地指定依赖文件的路径。 由于此类依赖不是通过 Maven 仓库解析的,而且往往与本机系统绑定,可能造成构建的不可移植因此应该谨慎使用。systemPath 元素可以引用环境变量;
import ( Maven 2. 0.9 及以上):导入依赖范围。该依赖范围不会对三种 classpath 产生实际的影响;
依赖传递
假设 A 依赖于 B,B 依赖于 C,我们说 A 对于 B 是第一直接依赖,B 对于 C 是第二直接依赖,A 对于 C 是传递性依赖。
Scope
第一直接依赖的范围和第二直接依赖的范围决定了传递性依赖的范围。即 C 的 Scope 由 A B 的 Scope 决定;
Maven – Introduction to the Dependency Mechanism
依赖调节
当依赖出现冲突时 1)最优路径优先:即传递的层次少,即最先出现的依赖; 2)第一声明优先:即最先定义的依赖;
可选依赖(optional)
optional:标记该依赖是否为可选依赖;[……]

READ MORE