随着移动互联网技术的快速发展,在新业务、新领域、新场景的驱动下,基于传统大型机的服务部署方式,不仅难以适应快速增长的业务需求,而且持续耗费高昂的成本,从而使得各大生产厂商以及企业只能望洋兴叹。此时,分布式系统的出现无疑给大家带来了些许振奋。而后随着大数据、区块链技术以及云计算技术的蓬勃发展,将分布式系统推向新的高潮。
据不完全统计,截止目前,几乎在全球的任何一家互联网企业,无论规模大小,或多或少都有使用到分布式技术。基于不同的业务场景以及实现方案,有些可能注重计算,有些或许注重存储。无论是基于具体哪种场景、模型,无不表明分布式系统在企业技术发展过程中的重要性以及必要性。
在本文中,我将主要围绕 Etcd 这一个分布式 K-V 数据库为主,探讨一下分布式存储技术的核心原理以及源码实现。
首先,我们来了解下什么是Etcd ?
Etcd 是一个分布式的,一致的 Key-Value 存储,主要用于共享配置和服务发现。Etcd由 CoreOS 开发并维护,通过 Raft 一致性算法处理日志复制以保证强一致性。Raft 是一个来自 Stanford 的新的一致性算法,适用于分布式系统的日志复制,Raft 通过选举的方式来实现一致性,在 Raft 中,任何一个节点都可能成为 Leader。google 的容器集群管理系统Kube.NETes、开源 PaaS 平台 Cloud Foundry 以及 CoreOS 的 Fleet 均已广泛使用Etcd。
正如上述所述,Etcd 是一个 K-V 存储,其 Etcd Server 采用树形的结构来组织储存数据,类似 linux 的文件系统,其也具备目录和文件的分层结构,我们称之为 Nodes 。下面我们了解下 Etcd Key 的相关操作以及目录情况,具体如下所示:
[administrator@JAVALangOutOfMemory ~ ]% Docker exec etcd /bin/sh -c "/usr/local/bin/etcd --version"
etcd Version: 3.3.8
Git SHA: 33245c6b5
Go Version: go1.9.7
Go OS/Arch: linux/amd64
[administrator@JavaLangOutOfMemory ~ ]% docker exec etcd /bin/sh -c "export ETCDCTL_API=3 ; /usr/local/bin/etcdctl endpoint health"
127.0.0.1:2379 is healthy: successfully committed proposal: took = 3.0925ms
[administrator@JavaLangOutOfMemory ~ ]% docker exec etcd /bin/sh -c "export ETCDCTL_API=3 ; /usr/local/bin/etcdctl put /luga/foo /luga/bar"
OK
[administrator@JavaLangOutOfMemory ~ ]% docker exec etcd /bin/sh -c "export ETCDCTL_API=3 ; /usr/local/bin/etcdctl get / --prefix "
/luga/foo
/luga/bar
基于上述的命令行操作,我们可以看出,当创建某个 Key 时,若不指定路径,则默认创建到根目录 “/” 下面,若指定目录的话,则创建的 Key 位于所指定的目录下。
我们现在了解下 Etcd 的架构,具体简要架构如下图所示:
基于上述架构图,基于分层的形式,我们可以将 Etcd 分为以下4层,依次为表现层、网络层、应用层、数据层。具体如下:
表现层
此层级主要包含相关命令行操作工具,以及 Restful 的 Api。客户端可以通过命令行或者是 Restful Api 的方式与 Etcd 集群进行通信。
网络层
此层级主要包含代理和 SDK ,ETCD 提供了基于三种协议的通信方式,分别为 HTTP、TCP以及 gRPC等。
应用层
应用层主要包含Raft协议、复制状态机、多版本并发控、Watch、K-V 数据存储、分布式事务等核心功能。强一致性算法的具体实现,是Etcd 的核心算法。
数据层
数据层主要涉及两部分内容:一部分为内存数据,一部分为磁盘数据。其中内存中维护的数据主要是 Key 与 Revision 之间的 B-tree 索引。磁盘里面存储的文件有三部分,一部分就是核心的数据文件,在 Snap下面的 db 文件中保存,还有就是 Raft 协议依赖的Wal 日志文件 和 Snap 快照文件。
针对架构图中的关键组件进行简要描述如下:
HTTP Server:接受客户端发出的 API 请求以及其它 Etcd 节点的同步与心跳信息请求。
Store:用于处理 Etcd 支持的各类功能的事务,包括数据索引、节点状态变更、监控与反馈、事件处理与执行等等,是 Etcd 对用户提供的大多数 API 功能的具体实现。
Raft:强一致性算法的具体实现,是 Etcd 的核心算法。
WAL(Write Ahead Log,预写式日志):是 Etcd 的数据存储方式,Etcd 会在内存中储存所有数据的状态以及节点的索引,此外,Etcd 还会通过 WAL 进行持久化存储。WAL 中,所有的数据提交前都会事先记录日志。其中,Snapshot 是为了防止数据过多而进行的状态快照;而 Entry 则表示存储的具体日志内容。
通常情况下,一个完整的工作流主要涉及以下活动:一个用户的请求发送过来,会经由 HTTP Server 转发给 Store 进行具体的事务处理,如果涉及到节点数据的修改,则交给 Raft 模块进行状态的变更、日志的记录,然后再同步给别的 Etcd 节点以确认数据提交,最后进行数据的提交,再次同步。
那么,Etcd 主要应用于哪些场景呢?
通过官网定义,Etcd 是一个高可用强一致性的键值数据库在很多分布式系统架构中得到了广泛的应用,其最经典的使用场景就是服务发现。那么有人问了,Zookeeper不香吗?基于 Zookeeper 当前的业务使用场景,结合微服务体系及 云原生K8S 生态支持层面,综合对比分析, Zookeeper 仍存在以下相关缺陷,具体如下:
1、复杂性,Zookeeper 基于Paxos 强一致性算法也以复杂难懂而闻名于世,除此之外,ZooKeeper 的使用也比较复杂,需要安装客户端,而且官方只提供了 Java 和 C 两种语言的接口,其移植性及可扩展性有限。
2、生态发展滞后,无论是基于项目版本的更新还是所拥抱的生态,都表现的差强人意,尤其是在容器化生态中。
3、笨重,Zookeeper 基于Java 语言开发,面向企业级应用,故基于Java 生态体系时不时会引入大量的依赖,从而使得维护交往笨重。
相比较而言,Etcd 虽作为后起之秀,但其已经融入云原生生态领域,并且基于 Go 语言开发,高性能,基于 HTTP 作为接口使用简单、方便,使用 Raft 算法保证强一致性让用户易于理解。除此,基于 Etcd 所默认的持久化机制与安全机制使得其在云原生生态领域能够得到进一步的发展。
为什么 Etcd 在服务发现领域能够独占鳌头呢?具体主要涉及以下:
1、强一致性、高可用。基于 Raft 算法的 Etcd 就是一个强一致性高可用的服务存储目录。
2、注册服务和监控服务健康状态的机制。用户可以在 Eetcd 中注册服务,并且对注册的服务设置 key TTL,定时保持服务的心跳以达到监控健康状态的效果。
3、查找和连接服务的机制。通过在 Etcd 指定的主题(由服务名称构成的服务目录)下注册的服务也能在对应的主题下查找到。
那么,Etcd 如何保证数据一致性呢?
首先,Etcd 使用 Raft 协议来维护 Cluster 内各个 Nodes 状态的一致性。也就是说,Etcd Cluster 是一个分布式系统,由多个 Nodes 相互通信构成整体对外服务,每个 Node 都存储了完整的数据,并且通过 Raft 协议保证每个 Node 维护的数据是一致的。
其次,Etcd Cluster 中的每个 Node 都维护了一个状态机,并且任意时刻,Cluster 中至多存在一个有效的主节点,即:Leader Node。由 Leader 处理所有来自客户端写操作,通过 Raft 协议保证写操作对状态机的改动会可靠的同步到其他 Follower Nodes。具体可参考下 Etcd 算法机制,如下所示:
基于数据一致性问题,分布式系统中常见的三种一致性模型:
1、强一致性:当更新操作在某个副本上执行成功后,之后所有的读操作都要能够获得最新的数据。
2、弱一致性:当更新某数据时,用户读到最新的数据需要一段时间的延迟。
3、最终一致性:它是一种特殊的弱一致性,当某个数据更新后,在经过一个时间片段,所有后续的操作能够获得新数据,在这个时间片段内,数据可能是不一致的。
Raft 是分布式领域中的强一致性算法,当其中某个节点收到客户端的一组指令时,它必须与其它节点沟通,以保证所有的节点以相同的顺序收到相同的指令,最终所有的节点会产生一致的结果。
如何选举 Leader 节点?
针对 Etcd ,Raft 通过『领导选举机制』选举出一个 Leader,由它全权管理日志复制来实现一致性。一个 Raft 集群包含若干个服务器节点,每一个节点都有一个唯一标识 ID,并且在任何时刻,每一个服务器节点都处于下面三个状态之一:
1、Leader(领导人):Leader 处理所有的客户端请求,在通常情况下,系统中只有一个Leader 并且其他节点都是 Follower。
2、Follower(跟随者):Follower 不会发送任何请求,只是简单地响应来自 Leader
Candidate 的请求,如果一个客户端与 Follower 联系,那么 Follower 会把请求重定向至 Leader。
3、Candidate(候选人):如果 Follower 接收不到来自 Leader 的消息,那么它就会变成 Candidate 并发起一次选举,获得集群中大多数选票(超过 n/2+1)的候选人将成为新的Leader。
假设 Etcd Cluster 中有 3 个 Node,Cluster 启动之初并没有被选举出的 Leader。此时,Raft 算法使用随机 Timer 来初始化 Leader 选举流程。比如说上面 3 个 Node 上都运行了 Timer(每个 Timer 的持续时间是随机的),而 Node1 率先完成了 Timer,随后它就会向其他两个 Node 发送成为 Leader 的请求,其他 Node 接收到请求后会以投票回应然后第一个节点被选举为 Leader。成为 Leader 后,该 Node 会以固定时间间隔向其他 Node 发送通知,确保自己仍是 Leader。有些情况下当 Follower 们收不到 Leader 的通知后,比如说 Leader 节点宕机或者失去了连接,其他 Node 就会重复之前的选举流程,重新选举出新的 Leader。具体可参考如下所示:
针对如何判断写入是否成功?
Etcd 通常情况下默认为写入请求被 Leader 处理并分发给了其他的 “多数节点” 后,就是一个成功的写入。“多数节点” 的数量的计算公式是 Quorum=N/2+1,N 为总结点数。也就是说,Etcd 并发要将数据写入所有节点才算一次写,而是写入 “多数节点” 即可。
最后,我们来看下如何确定 Etcd Cluster 的节点数?
基于多数节点的计算公式,可以简单理解,Etcd Cluster 推荐最少节点数为 3 个,因为 1 和 2 个 Instance 的容错节点数都是 0,一旦有一个节点宕掉整个集群就不能正常工作了。
进一步的讲,当我们需要决定 Etcd Cluster 中 Instances 的数量时,强烈推荐奇数数量的节点,比如:3、5、7、…等,具体依据实际的业务场景进行决策。
因时间有限,源码解析以及剩余的部分内容暂未在本文中详述,后续将进行进一步的深入解析。