├── .gitignore ├── README.md ├── SUMMARY.md ├── _asserts ├── design │ └── raft.pdf └── images │ ├── append_entry.jpg │ ├── arch_design.jpg │ ├── c_s.jpg │ ├── cap.jpg │ ├── cluster_deploy.jpg │ ├── consensus_algorithm.jpg │ ├── dis_type_storage.jpg │ ├── distribute_system.jpg │ ├── etcd_arch.jpg │ ├── etcdctl_3.jpg │ ├── etcdctl_v2.jpg │ ├── filter.jpg │ ├── osi_protocol.jpg │ ├── peer-to-peer.jpg │ ├── raft_2.jpg │ ├── raft_fig_6.jpg │ ├── saft_raft.jpg │ ├── serve_rule.jpg │ ├── source_code.jpg │ ├── state.jpg │ ├── state_machine.jpg │ ├── tcp_ip.jpg │ ├── term.jpg │ └── vote.jpg ├── appendix └── README.md ├── arch ├── README.md ├── data │ ├── README.md │ ├── b-tree.md │ ├── boltdb.md │ ├── snap.md │ └── wal.md ├── logic │ ├── README.md │ ├── dis_trans.md │ ├── kv.md │ ├── mvcc.md │ ├── raft.md │ ├── state_machine.md │ └── subpub.md ├── network │ ├── README.md │ ├── proxy.md │ └── sdk.md └── presentation │ ├── README.md │ └── command.md ├── cluster ├── README.md ├── multi │ ├── docker.md │ ├── k8s.md │ ├── source.md │ └── yum.md ├── multinode.md ├── singlenode.md └── singlenode │ ├── docker.md │ ├── source.md │ └── yum.md ├── conclusion └── README.md ├── introduce ├── README.md ├── cap.md ├── eight_question.md ├── model.md ├── network.md ├── storage.md ├── storage │ ├── acid.md │ ├── consensus_algorithm.md │ ├── consistency.md │ ├── mvcc.md │ ├── raft.md │ └── transation.md └── summary.md ├── ops ├── README.md ├── backup.md ├── monitor.md ├── other.md └── summary.md ├── revision └── README.md ├── sourceCode ├── README.md ├── core │ └── README.md └── example │ └── edit.md └── usage ├── README.md ├── config.md ├── kvstore.md ├── lock.md ├── queue.md ├── subpub.md └── summary.md /.gitignore: -------------------------------------------------------------------------------- 1 | _book 2 | .DS_Store -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 前言 2 | 3 | 从2016年使用Kubernetes时接触到etcd,到如今已有接近三年的时间了。etcd在kubernetes集群中,作为一个核心的存储组件,具有举足轻重的地位。 4 | 但是一直以来也没有深入探查过它的内部细节。直到最近,分布式系统,尤其是分布式存储变得越来越热,越来越重要,才静下心来,打算深入探究下etcd的内部细节。 5 | 6 | 糟糕的是这个过程并不顺利,本来想着可以通过阅读书籍的方式来理解,但找了很久,也没有找到一本结合架构、源码以及使用案例来剖析的书籍,以至于最后不得不翻开etcd源码,一行行阅读etcd的源码,剖析内部实现细节。 7 | 经过一段时间的阅读与实践,终于对etcd有了一个全方位的认识,在这里将我获得的这些知识总结出来,希望能够帮助对etcd感兴趣的同学,以及那些对分布式存储,对分布式系统感兴趣的同学。 8 | 9 | 在本书中,不仅会介绍etcd原理和应用,而且会花更多的篇幅介绍etcd源码,既然谈到源码,就不得不啰嗦两句其实现语言Go语言,因为etcd是全部用Go语言编写的,是CNCF收录的核心项目之一。 10 | Go语言诞生于2009年,截止今年才刚刚十年的时间。 正如历史上其他一切事物一样,Go也有其自生的梦想与使命,2009年,我自认为不管是在计算机发展史上,还是人类发展史上,都是一个相当重要 11 | 的年份。 2008年金融危机爆发,经济下行全球产生信任危机。 从此绝大多数行业开始走向转折,互联网、大数据、云计算行业紧跟时代步伐, 在之后的岁月里,异军突起,开始了草莽式的发展。 12 | 13 | 正是这样的历史时刻,Go语言作为Google的金宝贝(golden son)在一大批大牛的怀抱中诞生了,诞生之初就自诩为互联网时代的C语言。 然而如今来看,Go语言也确实有这样的潜力,同时也已经在很多领域 14 | 占据了一席之地,尤其在云计算领域已经建立了难以撼动的地位。 15 | 16 | 在写作本书的过程中,为了使代码阅读没那么枯燥,我根据理论与实践结合的方式,由浅入深,由理论到代码实现,再到源码中代码的使用,单元测试以及服务部署等全方位的角度,对etcd进行全面的剖析。 17 | 只是希望,后续对etcd感兴趣的同学,想把etcd应用在自己项目中,对Go语言,对分布式系统热爱的同学,都能有所收获。 本书按大的方向来拆分,可以分为三个部分: 理论介绍、源码实战、应用运维。 18 | 在组织上,为了能够达到循序渐进,便于理解的目的。我将内容拆分为了以下章节。 第一章介绍分布式系统的一些核心概念,包括分布式系统CAP理论,分布式系统网络通信,分布式存储一致性等一系列的概念。 19 | 当然,为了避免文字枯燥的表达,我会尽量采用图表的方式解释。 第二章主要描述etcd本身的架构。 在解析etcd架构的时候,我会结合后续源码,从整体架构到其中每一个组件的核心功能,进行更为深入,细致的拆分。 20 | 目的是争取能将整个架构拆解到大家都能实现的地步。 在前两章的基础上,第三章讲解etcd的部署,通过单机以及多机两种部署方式,实际感受下etcd的魅力所在,也为后面的章节做好铺垫。 第四章可以说是最重心的一章。 21 | 因为这一章,我们直面go语言,直面etcd源码,通过etcd提供的代码,来利用raft算法实现一个简单版本的分布式kv存储数据库。然后在这个基础上,进一步对etcd进行拆解,直达etcd内部,包括raft协议的实现,mvcc多版本并发控制, 22 | 内存B-tree索引,boltdb数据持久化,watch机制,分布式事务等一系列的核心概念。在整个过程中,我会通过将各个模块拆分的方式,讲解每一个模块的用法以及实现。 包括写一个实用的程序代码,以及为各个模块写单元测试。 23 | 理解了源码,相信对于etcd的应用案例也就了然于胸了,第五章重点介绍使用案例,包括分布式队列,分布式锁,分布式kv,配置中心等。第六章简单讲解下日常的运维与监控问题, 最后一章作为总结。 24 | 25 | 相信,在读完本书过后,大家不仅能对etcd有一个较为深入的认识,同时也希望大家能够了分布式系统,了解分布式存储。 更多的将etcd应用到自己的项目中,将etcd发扬广大,将go语言发扬光大。 26 | 27 | 我从4月份开始,通过对etcd的使用,以及etcd源码的阅读,决定写这样一本书籍。 现在也才刚刚开始,这可能是一个漫长的过程,但是我会坚持每天抽时间,力所能及的更新。 28 | 29 | 最后,本书使用gitbook编写,源码地址在[github](https://github.com/csunny/etcd-from-arch-to-souce-code)上, 如果有想对本书做贡献的同学,也可以通过github进行贡献。 30 | 31 | 作者 32 | 2019年4月于上海 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /SUMMARY.md: -------------------------------------------------------------------------------- 1 | # Table of contents 2 | 3 | * [前言](README.md) 4 | 5 | * [修订记录](revision/README.md) 6 | 7 | * [分布式系统介绍](introduce/README.md) 8 | * [分布式系统模型](introduce/model.md) 9 | * [CAP理论](introduce/cap.md) 10 | * [分布式系统通信](introduce/network.md) 11 | * [分布式系统八大问题](introduce/eight_question.md) 12 | * [分布式存储](introduce/storage.md) 13 | * [一致性问题](introduce/storage/consistency.md) 14 | * [共识算法](introduce/storage/consensus_algorithm.md) 15 | * [Raft协议](introduce/storage/raft.md) 16 | * [事务ACID](introduce/storage/acid.md) 17 | * [分布式事务](introduce/storage/transation.md) 18 | * [并发控制](introduce/storage/mvcc.md) 19 | * [小结](introduce/summary.md) 20 | 21 | * [架构解析](arch/README.md) 22 | * [表现层](arch/presentation/README.md) 23 | * [命令行](arch/presentation/command.md) 24 | * [网络层](arch/network/README.md) 25 | * [Proxy代理](arch/network/proxy.md) 26 | * [SDK](arch/network/sdk.md) 27 | * [应用层](arch/logic/README.md) 28 | * [Raft协议](arch/logic/raft.md) 29 | * [复制状态机](arch/logic/state_machine.md) 30 | * [多版本并发控制](arch/logic/mvcc.md) 31 | * [K-V存储](arch/logic/kv.md) 32 | * [发布订阅](arch/logic/subpub.md) 33 | * [分布式事务](arch/logic/dis_trans.md) 34 | * [数据层](arch/data/READMD.md) 35 | * [内存数据]() 36 | * [索引](arch/data/b-tree.md) 37 | * [磁盘数据]() 38 | * [日志](arch/data/wal.md) 39 | * [快照](arch/data/snap.md) 40 | * [数据文件](arch/data/boltdb.md) 41 | * [小结](arch/summary.md) 42 | 43 | * [集群部署](cluster/README.md) 44 | * [单节点部署](cluster/singlenode.md) 45 | * [源码编译安装](cluster/singlenode/source.md) 46 | * [yum 安装](cluster/singlenode/yum.md) 47 | * [Docker部署](cluster/singlenode/docker.md) 48 | * [K8s部署](cluster/singlenode/k8s.md) 49 | * [多节点部署](cluster/multinode.md) 50 | * [源码编译安装](cluster/multi/source.md) 51 | * [yum 安装](cluster/multi/yum.md) 52 | * [Docker部署](cluster/multi/docker.md) 53 | * [K8s部署](cluster/multi/k8s.md) 54 | * [小结](cluster/summary.md) 55 | 56 | * [源码阅读](sourceCode/README.md) 57 | * [从简单的例子开始](sourceCode/example/README.md) 58 | * [实现一个简单的分布式kv数据库](sourceCode/example/smkv.md) 59 | 60 | * [核心代码](sourceCode/core/README.md) 61 | * [raft源码解析](sourceCode/core/raft.md) 62 | * [wal源码解析](sourceCode/core/wal.md) 63 | * [mvcc源码解析](sourceCode/core/mvcc.md) 64 | * [b-tree索引](sourceCode/core/b-tree.md) 65 | * [kv-store源码解析](sourceCode/core/kv-store.md) 66 | * [proxy源码解析](sourceCode/core/proxy.md) 67 | * [clientV3源码解析](sourceCode/core/clientv3.md) 68 | * [etcdv3事务STM](sourceCode/core/stm.md) 69 | * [etcd watch源码解析](sourceCode/core/watch.md) 70 | * [小结](sourceCode/summary.md) 71 | 72 | * [使用案例](usage/README.md) 73 | * [分布式锁](usage/lock.md) 74 | * [分布式队列](usage/queue.md) 75 | * [配置中心](usage/config.md) 76 | * [分布式k-v](usage/kvstore.md) 77 | * [消息订阅](usage/subpub.md) 78 | 79 | * [小结](usage/summary.md) 80 | 81 | * [运维指南](ops/README.md) 82 | * [集群监控](ops/monitor.md) 83 | * [数据备份](ops/backup.md) 84 | * [其他](ops/other.md) 85 | * [小结](ops/summary.md) 86 | 87 | * [总结](conclusion/README.md) 88 | 89 | * [附录](appendix/README.md) 90 | 91 | -------------------------------------------------------------------------------- /_asserts/design/raft.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csunny/etcd-from-arch-to-souce-code/f54609cb45fc8c76769f97dfa5c3579731346c1b/_asserts/design/raft.pdf -------------------------------------------------------------------------------- /_asserts/images/append_entry.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csunny/etcd-from-arch-to-souce-code/f54609cb45fc8c76769f97dfa5c3579731346c1b/_asserts/images/append_entry.jpg -------------------------------------------------------------------------------- /_asserts/images/arch_design.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csunny/etcd-from-arch-to-souce-code/f54609cb45fc8c76769f97dfa5c3579731346c1b/_asserts/images/arch_design.jpg -------------------------------------------------------------------------------- /_asserts/images/c_s.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csunny/etcd-from-arch-to-souce-code/f54609cb45fc8c76769f97dfa5c3579731346c1b/_asserts/images/c_s.jpg -------------------------------------------------------------------------------- /_asserts/images/cap.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csunny/etcd-from-arch-to-souce-code/f54609cb45fc8c76769f97dfa5c3579731346c1b/_asserts/images/cap.jpg -------------------------------------------------------------------------------- /_asserts/images/cluster_deploy.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csunny/etcd-from-arch-to-souce-code/f54609cb45fc8c76769f97dfa5c3579731346c1b/_asserts/images/cluster_deploy.jpg -------------------------------------------------------------------------------- /_asserts/images/consensus_algorithm.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csunny/etcd-from-arch-to-souce-code/f54609cb45fc8c76769f97dfa5c3579731346c1b/_asserts/images/consensus_algorithm.jpg -------------------------------------------------------------------------------- /_asserts/images/dis_type_storage.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csunny/etcd-from-arch-to-souce-code/f54609cb45fc8c76769f97dfa5c3579731346c1b/_asserts/images/dis_type_storage.jpg -------------------------------------------------------------------------------- /_asserts/images/distribute_system.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csunny/etcd-from-arch-to-souce-code/f54609cb45fc8c76769f97dfa5c3579731346c1b/_asserts/images/distribute_system.jpg -------------------------------------------------------------------------------- /_asserts/images/etcd_arch.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csunny/etcd-from-arch-to-souce-code/f54609cb45fc8c76769f97dfa5c3579731346c1b/_asserts/images/etcd_arch.jpg -------------------------------------------------------------------------------- /_asserts/images/etcdctl_3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csunny/etcd-from-arch-to-souce-code/f54609cb45fc8c76769f97dfa5c3579731346c1b/_asserts/images/etcdctl_3.jpg -------------------------------------------------------------------------------- /_asserts/images/etcdctl_v2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csunny/etcd-from-arch-to-souce-code/f54609cb45fc8c76769f97dfa5c3579731346c1b/_asserts/images/etcdctl_v2.jpg -------------------------------------------------------------------------------- /_asserts/images/filter.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csunny/etcd-from-arch-to-souce-code/f54609cb45fc8c76769f97dfa5c3579731346c1b/_asserts/images/filter.jpg -------------------------------------------------------------------------------- /_asserts/images/osi_protocol.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csunny/etcd-from-arch-to-souce-code/f54609cb45fc8c76769f97dfa5c3579731346c1b/_asserts/images/osi_protocol.jpg -------------------------------------------------------------------------------- /_asserts/images/peer-to-peer.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csunny/etcd-from-arch-to-souce-code/f54609cb45fc8c76769f97dfa5c3579731346c1b/_asserts/images/peer-to-peer.jpg -------------------------------------------------------------------------------- /_asserts/images/raft_2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csunny/etcd-from-arch-to-souce-code/f54609cb45fc8c76769f97dfa5c3579731346c1b/_asserts/images/raft_2.jpg -------------------------------------------------------------------------------- /_asserts/images/raft_fig_6.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csunny/etcd-from-arch-to-souce-code/f54609cb45fc8c76769f97dfa5c3579731346c1b/_asserts/images/raft_fig_6.jpg -------------------------------------------------------------------------------- /_asserts/images/saft_raft.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csunny/etcd-from-arch-to-souce-code/f54609cb45fc8c76769f97dfa5c3579731346c1b/_asserts/images/saft_raft.jpg -------------------------------------------------------------------------------- /_asserts/images/serve_rule.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csunny/etcd-from-arch-to-souce-code/f54609cb45fc8c76769f97dfa5c3579731346c1b/_asserts/images/serve_rule.jpg -------------------------------------------------------------------------------- /_asserts/images/source_code.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csunny/etcd-from-arch-to-souce-code/f54609cb45fc8c76769f97dfa5c3579731346c1b/_asserts/images/source_code.jpg -------------------------------------------------------------------------------- /_asserts/images/state.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csunny/etcd-from-arch-to-souce-code/f54609cb45fc8c76769f97dfa5c3579731346c1b/_asserts/images/state.jpg -------------------------------------------------------------------------------- /_asserts/images/state_machine.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csunny/etcd-from-arch-to-souce-code/f54609cb45fc8c76769f97dfa5c3579731346c1b/_asserts/images/state_machine.jpg -------------------------------------------------------------------------------- /_asserts/images/tcp_ip.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csunny/etcd-from-arch-to-souce-code/f54609cb45fc8c76769f97dfa5c3579731346c1b/_asserts/images/tcp_ip.jpg -------------------------------------------------------------------------------- /_asserts/images/term.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csunny/etcd-from-arch-to-souce-code/f54609cb45fc8c76769f97dfa5c3579731346c1b/_asserts/images/term.jpg -------------------------------------------------------------------------------- /_asserts/images/vote.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/csunny/etcd-from-arch-to-souce-code/f54609cb45fc8c76769f97dfa5c3579731346c1b/_asserts/images/vote.jpg -------------------------------------------------------------------------------- /appendix/README.md: -------------------------------------------------------------------------------- 1 | #### 附录 2 | -------------------------------------------------------------------------------- /arch/README.md: -------------------------------------------------------------------------------- 1 | # 架构介绍 2 | 3 | 如下图所示,ETCD三节点集群简易架构图。 从图可以看出,ETCD中节点之间是通过对等网络的方式进行通信的。但是在不同的时期每个节点所扮演的角色不一样。 4 | 严格来说,ETCD节点有三种角色: 5 | - Leader 6 | - Flower 7 | - Candidate 8 | 节点角色会在以上三种角色中进行切换。其中每个节点又主要有以下几个核心模块: 9 | - 接口层 10 | - Raft协议 11 | - 选主 12 | - wal日志 13 | - snap快照 14 | 15 | - 复制状态机 16 | - 持久存储K-V数据库 17 | 18 | 当然以上也仅仅是粗略的划分, 但也能大概说明问题。 这里我们结合上述架构图来解释各个模块的功能,以及一个完整的ETCD集群的工作流程。 19 | 20 | 21 | ![](https://raw.githubusercontent.com/csunny/etcd-from-arch-to-souce-code/master/_asserts/images/etcd_arch.jpg) 22 | 23 | 下面是一个更为细致的划分,通过分层的方式,将etcd划分为4层,依次为表现层、网络层、应用层、数据层。 24 | 25 | #### 表现层 26 | 主要包含了命令行工具,以及restful的api。客户端可以通过命令行或者是restful api的方式与etcd集群进行通信。 27 | 28 | #### 网络层 29 | 主要包含代理和SDK,ETCD提供了三种协议的通信方式,分别为HTTP、TCP、gRPC。 30 | 31 | #### 应用层 32 | 应用层包含了Raft协议、复制状态机、多版本并发控、Watch、K-V数据存储、分布式事务等核心功能。 33 | 34 | #### 数据层 35 | 数据层有两部分,一部分为内存数据,一部分是磁盘数据。 其中内存中维护的数据主要是Key与revision之间的B-tree索引。 36 | 磁盘里面存储的文件有三部分,一部分就是核心的数据文件,在snap下面的db文件中保存,还有就是raft协议依赖的wal日志文件 37 | 和snap快照文件。 38 | 39 | ![](https://raw.githubusercontent.com/csunny/etcd-from-arch-to-souce-code/master/_asserts/images/arch_design.jpg) 40 | -------------------------------------------------------------------------------- /arch/data/README.md: -------------------------------------------------------------------------------- 1 | #### 数据层 2 | -------------------------------------------------------------------------------- /arch/data/b-tree.md: -------------------------------------------------------------------------------- 1 | #### 内存中维护的B-tree索引 -------------------------------------------------------------------------------- /arch/data/boltdb.md: -------------------------------------------------------------------------------- 1 | #### 数据文件 -------------------------------------------------------------------------------- /arch/data/snap.md: -------------------------------------------------------------------------------- 1 | #### 快照 -------------------------------------------------------------------------------- /arch/data/wal.md: -------------------------------------------------------------------------------- 1 | #### wal 日志 -------------------------------------------------------------------------------- /arch/logic/README.md: -------------------------------------------------------------------------------- 1 | #### 应用逻辑层 -------------------------------------------------------------------------------- /arch/logic/dis_trans.md: -------------------------------------------------------------------------------- 1 | #### 分布式事务 -------------------------------------------------------------------------------- /arch/logic/kv.md: -------------------------------------------------------------------------------- 1 | #### kv 存储逻辑 -------------------------------------------------------------------------------- /arch/logic/mvcc.md: -------------------------------------------------------------------------------- 1 | #### 多版本并发控制 -------------------------------------------------------------------------------- /arch/logic/raft.md: -------------------------------------------------------------------------------- 1 | #### raft共识协议 -------------------------------------------------------------------------------- /arch/logic/state_machine.md: -------------------------------------------------------------------------------- 1 | #### 复制状态机 -------------------------------------------------------------------------------- /arch/logic/subpub.md: -------------------------------------------------------------------------------- 1 | #### 发布订阅 -------------------------------------------------------------------------------- /arch/network/README.md: -------------------------------------------------------------------------------- 1 | ## 网络层 2 | 3 | 在前面的介绍中,我们不仅介绍了ISO OSI七层网络模型,还介绍了传输层的TCP/UDP协议,以及应用层的HTTP协议,以及RPC协议。 所以我们在etcd架构分层的过程中,单独分离出来一层网络层。 4 | 5 | 在etcd中,网络层几乎用到了我们前面提到的很多网络知识,包括tcp、rpc、http等等。所以,根据以上知识点,我这里将 6 | proxy跟sdk这两部分划分到了网络层。 7 | 8 | 代理层又分为三种代理 9 | Proxy 10 | - httpproxy 11 | - tcpproxy 12 | - grpcproxy 13 | 14 | etcd提供了两个版本的sdk 15 | 16 | SDK 17 | - clientv2 18 | - clientv3 19 | 20 | 21 | 接下来, 我们详细介绍下,每部分的内容。 22 | -------------------------------------------------------------------------------- /arch/network/proxy.md: -------------------------------------------------------------------------------- 1 | ## 代理 2 | 3 | etcd根据协议的不同, 提供了三种不同形式的代理。分别为: 4 | - tcpproxy 5 | - grpcproxy 6 | - httpproxy 7 | 8 | 下面我们看一个使用tcpproxy的demo 9 | 10 | ```go 11 | package main 12 | 13 | import ( 14 | "fmt" 15 | "net" 16 | "net/http" 17 | "net/http/httptest" 18 | "net/url" 19 | "go.etcd.io/etcd/proxy/tcpproxy" 20 | ) 21 | 22 | func main() { 23 | l, err := net.Listen("tcp", "127.0.0.1:0") 24 | if err != nil { 25 | fmt.Println(err) 26 | } 27 | 28 | defer l.Close() 29 | 30 | want := "hello proxy" 31 | 32 | ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { 33 | fmt.Fprint(w, want) 34 | })) 35 | 36 | defer ts.Close() 37 | 38 | u, err := url.Parse(ts.URL) 39 | 40 | fmt.Println(u) 41 | if err != nil { 42 | fmt.Println(err) 43 | } 44 | var port uint16 45 | fmt.Sscanf(u.Port(), "%d", &port) 46 | 47 | 48 | p := tcpproxy.TCPProxy{ 49 | Listener: l, 50 | Endpoints: []*net.SRV{{ 51 | Target: u.Hostname(), Port: port, 52 | }}, 53 | } 54 | p.Run() 55 | } 56 | ``` 57 | 58 | 如此,客户端可以运行 `curl http://127.0.0.1:port` 查看。 59 | 60 | 61 | 62 | 63 | -------------------------------------------------------------------------------- /arch/network/sdk.md: -------------------------------------------------------------------------------- 1 | ## SDK 2 | 3 | 针对不同的版本,etcd提供了两个版本的sdk, 下面针对不同版本,来使用熟悉下。 4 | 5 | ### v2 6 | 7 | **Install** 8 | 9 | ``` 10 | go get go.etcd.io/etcd/client 11 | ``` 12 | 13 | **使用** 14 | 15 | ``` 16 | package main 17 | 18 | import ( 19 | "context" 20 | "fmt" 21 | "time" 22 | 23 | "go.etcd.io/etcd/client" 24 | ) 25 | 26 | func main() { 27 | cfg := client.Config{ 28 | Endpoints: []string{"http://127.0.0.1:2379"}, 29 | Transport: client.DefaultTransport, 30 | HeaderTimeoutPerRequest: time.Second, 31 | } 32 | 33 | c, err := client.New(cfg) 34 | if err != nil { 35 | fmt.Println(err) 36 | } 37 | 38 | kapi := client.NewKeysAPI(c) 39 | 40 | // set /foo bar 41 | resp, err := kapi.Set(context.Background(), "/foo", "bar", nil) 42 | if err != nil { 43 | fmt.Println(err) 44 | } 45 | 46 | fmt.Println(resp) 47 | 48 | // get /foo 49 | resp, err = kapi.Get(context.Background(), "/foo", nil) 50 | if err != nil { 51 | fmt.Println(err) 52 | } 53 | 54 | fmt.Println(resp) 55 | } 56 | ``` 57 | 58 | **异常处理** 59 | etcd client 可能会返回以下几种类型的异常 60 | - context error 61 | 每一个API调用的第一个参数都是context。 context可以被取消或者是检测其期限。如果context被取消了或者到了生命周期, 62 | 将会返回context异常。 63 | - cluster error 64 | 每一次API调用都会挨个发送请求到etcd集群,直到获得响应。 如果一个发送到etcd集群中某个节点的请求失败了,异常将会添加到异常列表里面, 如果整个集群中所有节点都失败了,则会返回一个集群异常。 65 | - response error 66 | 如果从集群中获得的响应是无效的, 会返回响应异常。 67 | 68 | 69 | #### 注意事项 70 | 71 | 1. 在节点正常工作的情况下,etcd/client优先会使用同一个集群节点。他会保存socket连接,提高客户端跟服务器之间的效率。 这个并不会影响数据的一致性, 因为复制到每个有效节点中的数据已经是一致的。 72 | 73 | 74 | 2. etcd/client 采用了round-robin的负载均衡策略来选择可用的节点进行交互。 例如,如果其中一个节点失效了,会转发到其他的节点。 75 | 76 | 3. 默认情况下etcd/client不能处理服务器端的SIGSTOPed, tcp keepalive机制在这种情况下是无用的,因为操作系统仍旧会发送tcp包。我们也有计划去提高这方面的功能,但就目前来说,优先级并不高,所以没必要刻意去关注。 77 | 78 | 4. etcd/client 不会检测成员健康状态。 如果成员从集群中分裂出来,etcd/client 会检索到过时的数据。故而,用户需要自己关注集群状态。 79 | 80 | 81 | ### V3 82 | etcd/clientv3 是一个官方的V3版本的Go语言etcd客户端。 83 | 84 | **安装** 85 | ``` 86 | go get go.etcd.io/etcd/clientv3 87 | ``` 88 | 89 | **使用** 90 | ```go 91 | package main 92 | 93 | import ( 94 | "context" 95 | "fmt" 96 | "time" 97 | 98 | "go.etcd.io/etcd/clientv3" 99 | ) 100 | 101 | func main() { 102 | cli, err := clientv3.New(clientv3.Config{ 103 | Endpoints: []string{"10.103.113.127:2379"}, 104 | DialTimeout: 5 * time.Second, 105 | }) 106 | 107 | if err != nil { 108 | fmt.Println(err) 109 | } 110 | defer cli.Close() 111 | 112 | var timeout time.Duration 113 | timeout = 5 * time.Second 114 | ctx, cancel := context.WithTimeout(context.Background(), timeout) 115 | 116 | resp, err := cli.Get(ctx, "magic") 117 | defer cancel() 118 | 119 | if err != nil { 120 | fmt.Println(err) 121 | } 122 | 123 | fmt.Println(resp) 124 | 125 | for { 126 | wrsp := cli.Watch(ctx, "magic") 127 | for rsp := range wrsp { 128 | fmt.Println(rsp) 129 | for _, ev := range rsp.Events { 130 | fmt.Println(ev) 131 | // fmt.Printf("%s %q : %q\n", ev.Type, ev.Kv.Key, ev.Kv.Value) 132 | } 133 | } 134 | } 135 | } 136 | ``` 137 | 138 | **异常处理** 139 | etcd/clientv3 会返回两种类型的异常: 140 | - context error 141 | - gRPC error 142 | 143 | 144 | 145 | 146 | 147 | 148 | -------------------------------------------------------------------------------- /arch/presentation/README.md: -------------------------------------------------------------------------------- 1 | ## 表现层 2 | 3 | 在ETCD的整个架构中,我将ECTD分为了四层。分别为表现层、网络层、应用层和数据层。表现层这里主要指的是命令行以及restful的api。 也就是我们可以通过etcd提供的命令行工具,或者是restful api与etcd整个集群进行交互。 4 | 5 | 由于etcd同时兼容v2、v3两个版本,所以命令行工具也同时有v2和v3, 可以使用如下命令进行切换 6 | ``` 7 | export ETCDCTL_API=2 # 切换到v2版本 8 | export ETCDCTL_API=3 # 切换到v3版本 9 | ``` 10 | 11 | 在使用之前,也可以通过命令来查看当前的命令行是那个版本的。 直接在终端运行命令 `etcdctl` 即可查看 12 | 13 | v2版本 14 | 15 | ![](\_asserts\images\etcdctl_v2.jpg) 16 | 17 | v3版本 18 | 19 | ![](\_asserts\images\etcdctl_3.jpg) -------------------------------------------------------------------------------- /arch/presentation/command.md: -------------------------------------------------------------------------------- 1 | ## 命令行 2 | 3 | etcdctl 是etcd集群的命令行工具。 我们可以在服务器或者是在脚本中通过etcdctl来进行集群的操作、管理和维护。 4 | 5 | 前面已经提到了,etcdctl也有两个版本,我们在使用过程中,需要注意版本对应关系。 首先我们来看看etcdctl v2的使用。 6 | 7 | ### V2 8 | #### 配置 9 | --debug 10 | - 输出curl命令,可以用来重现request请求 11 | 12 | ``` 13 | etcdctl --debug get magic 14 | ------------ 15 | start to sync cluster using endpoints(http://127.0.0.1:4001,http://127.0.0.1:2379) 16 | cURL Command: curl -X GET http://127.0.0.1:4001/v2/members 17 | cURL Command: curl -X GET http://127.0.0.1:2379/v2/members 18 | got endpoints(http://10.103.113.127:2379) after sync 19 | Cluster-Endpoints: http://10.103.113.127:2379 20 | cURL Command: curl -X GET http://10.103.113.127:2379/v2/keys/magic?quorum=false&recursive=false&sorted=false 21 | 22 22 | ``` 23 | 24 | --no-sync 25 | - 在发送request请求之前不会同步集群信息 26 | - 使用此选项可访问未发布的客户端 27 | - 没有这个参数,--endpoint的值将被etcd集群覆盖 28 | 29 | --output, -o 30 | - 用指定的格式输出响应 (simple, extended或者json) 31 | - 默认: simple 32 | 33 | --discovery-srv, -D 34 | - domain name to query for SRV records describing cluster endpoints 35 | - 默认: none 36 | - 环境变量: ETCDCTL_DISCOVERY_SRV 37 | 38 | --peers 39 | - 逗号分割的集群地址列表 40 | - 默认: http://127.0.0.1:2379 41 | - 环境变量: ETCDCTL_PEERS 42 | 43 | --endpoints 44 | - 逗号分割的集群地址列表 45 | - 默认:http://127.0.0.1:2379 46 | - 环境变量:ETCDCTL_ENDPOINT 47 | - 没有--no-sync 参数的时候,将会被etcd集群覆盖. 48 | 49 | --cert-file 50 | - 使用SSL认证的HTTPS客户端证书文件 51 | - 默认值:none 52 | - 环境变量: ETCDCTL_CERT_FILE 53 | 54 | --key-file 55 | - 使用SSL认证的HTTPS客户端key文件 56 | - 默认: none 57 | - 环境变量 ETCDCTL_KEY_FILE 58 | 59 | --ca-file 60 | - ca证书认证 61 | - 默认: none 62 | - 环境变量: ETCDCTL_CA_FILE 63 | 64 | --username, -u 65 | - 用户名:密码认证 66 | - 默认: none 67 | - 环境变量: ETCDCTL_USERNAME 68 | 69 | --timeout 70 | - 每次请求的连接超时时间 71 | - 默认 1s 72 | 73 | --total-timeout 74 | - 命令异常超时时间 75 | - 默认:5s 76 | 77 | 78 | #### 使用案例 79 | 80 | ##### 设置key值 81 | 给/foo/bar 这个key设置一个值 82 | ``` 83 | etcdctl set /foo/bar "Hello World" 84 | Hello World 85 | ``` 86 | 87 | 设置一个具有60s过期时间的key 88 | ``` 89 | etcdctl set /foo/bar "Hello World" --ttl 60 90 | Hello World 91 | ``` 92 | 93 | 带条件的设置, 当之前的值为"Hello World" 时,设置值为 "Goodbye world" 94 | 95 | ``` 96 | etcdctl set /foo/bar "Goodbye world" --swap-with-value "Hello world" 97 | Goodbye world 98 | ``` 99 | 100 | 带条件的设置,当/foo/bar在etcd中的索引为12时,设置对应key的值 101 | 102 | ``` 103 | etcdctl set /foo/bar "Goodbye world" --swap-with-index 12 104 | Goodbye world 105 | ``` 106 | 107 | 当key不存在时,创建一个新key 108 | 109 | ``` 110 | etcdctl mk /foo/new_bar "Hello world" 111 | Hello world 112 | ``` 113 | 114 | 按照次序常见一个新的key 在文件夹/fooDir下 115 | 116 | ``` 117 | etcdctl mk --in-order /fooDir "Hello world" 118 | ``` 119 | 120 | 当目录不存在时,创建一个新目录 121 | 122 | ``` 123 | etcdctl mkdir /fooDir 124 | ``` 125 | 126 | 更新一个已存在的key /foo/bar 127 | 128 | ``` 129 | etcdctl update /foo/bar "Hala mundo" 130 | Hala mundo 131 | ``` 132 | 133 | 创建或者更新一个叫/mydir 的目录 134 | 135 | ``` 136 | etcdctl setdir /mydir 137 | ``` 138 | 139 | ##### 检索key 140 | 获取当前key的值 141 | 142 | ``` 143 | etcdctl get /foo/bar 144 | Hello world 145 | ``` 146 | 147 | 获取key的值以及一些额外的元数据信息 148 | 149 | ``` 150 | etcdctl -o extended get /foo/bar 151 | 152 | Key: /foo/bar 153 | Created-Index: 279 154 | Modified-Index: 279 155 | TTL: 0 156 | Index: 280 157 | 158 | Hello World 159 | ``` 160 | 161 | ##### 列举目录 162 | 163 | ``` 164 | etcdctl ls 165 | 166 | ``` 167 | 168 | 递归列举出所有的目录 169 | 170 | ``` 171 | etcdctl ls --recursive 172 | ``` 173 | 174 | ##### 删除key 175 | 176 | ``` 177 | etcdctl rm /foo/bar 178 | ``` 179 | 180 | 删除一个空的目录或者键值对 181 | 182 | ``` 183 | etcdctl rmdir /path/to/dir 184 | or 185 | etcdctl rm /path/to/dir --dir 186 | ``` 187 | 188 | 递归删除key以及子key 189 | 190 | ``` 191 | etcdctl rm /foo/bar --recursive 192 | ``` 193 | 194 | 带条件的删除 195 | 196 | ``` 197 | etcdctl rm /foo/bar --with-value "Hello world" 198 | ``` 199 | 200 | ##### Watch监听 201 | 监听key的下一个变化 202 | 203 | ``` 204 | etcdctl watch /foo/bar 205 | ``` 206 | 207 | 持续监听 208 | 209 | ``` 210 | etcdctl watch /foo/bar --forever 211 | 212 | Hello world 213 | 214 | .... client hangs forever until ctrl+C printing values as key change 215 | ``` 216 | 217 | ##### 返回码 218 | 219 | etcd会返回以下返回码 220 | 221 | ``` 222 | 0 Success 223 | 1 Malformed etcdctl arguments 224 | 2 Failed to connect to host 225 | 3 Failed to auth (client cert rejected, ca validation failure, etc) 226 | 4 400 error from etcd 227 | 5 500 error from etcd 228 | ``` 229 | 230 | 231 | ##### Endpoint 232 | 可以通过endpoint进行远程连接etcd集群,进行相应的操作 233 | 234 | ``` 235 | etcdctl --endpoint http://10.0.28.1:4002 my-key to-a-value 236 | etcdctl --endpoint http://10.0.28.1:4002,http://10.0.28.2:4002,http://10.0.28.3:4002 etcdctl set my-key to-a-value 237 | ``` 238 | 239 | ##### 用户名和密码 240 | 241 | 如果etcd集群经过认证保护,可以通过指定用户名和密码的方式进行访问 242 | 243 | ``` 244 | ETCDCTL_USERNAME="root:password" etcdctl set my-key to-a-value 245 | ``` 246 | 247 | ##### DNS 发现 248 | 为了通过SRV记录进行etcd的集群服务发现, 指定 --discovery-srv参数或者ETCDCTL_DISCOVERY_SRV 环境变量。 此操作优先级高于 --endpoint 参数 249 | 250 | ``` 251 | ETCDCTL_DISCOVERY_SRV="some-domain" etcdctl set my-key to-a-value 252 | etcdctl --discovery-srv some-domain set my-key to-a-value 253 | ``` 254 | 255 | 256 | ### V3 257 | 258 | 259 | 260 | ### 参考 261 | - https://github.com/etcd-io/etcd/blob/master/etcdctl/README.md 262 | - https://github.com/etcd-io/etcd/blob/master/etcdctl/READMEv2.md 263 | 264 | 265 | -------------------------------------------------------------------------------- /cluster/README.md: -------------------------------------------------------------------------------- 1 | ## etcd集群 2 | 3 | etcd作为一个可靠的分布式键值存储服务,为了保证整个系统的高可用,以及发挥raft算法的魅力,要求进行集群化部署。 4 | 一个etcd集群最少节点要求是3个成员组成。 建议的部署节点是5个节点,这样即使集群中有两个节点发生异常,集群仍然是 5 | 可用的。 6 | 7 | -------------------------------------------------------------------------------- /cluster/multi/docker.md: -------------------------------------------------------------------------------- 1 | #### Docker 部署 2 | -------------------------------------------------------------------------------- /cluster/multi/k8s.md: -------------------------------------------------------------------------------- 1 | #### k8s 部署 -------------------------------------------------------------------------------- /cluster/multi/source.md: -------------------------------------------------------------------------------- 1 | #### 源码安装 -------------------------------------------------------------------------------- /cluster/multi/yum.md: -------------------------------------------------------------------------------- 1 | #### yum 安装 2 | -------------------------------------------------------------------------------- /cluster/multinode.md: -------------------------------------------------------------------------------- 1 | ## 多节点 2 | 3 | 前面提到单节点etcd其实只是用于测试或者debug,不管是etcd的 -------------------------------------------------------------------------------- /cluster/singlenode.md: -------------------------------------------------------------------------------- 1 | ## 单节点 2 | 3 | 一般来说,不会部署单节点etcd集群,因为单节点情况下也就失去了etcd具有的意义,但为了方便使用,我们这里还是会使用到单节点的etcd,一般用于测试,或者是本地debug。 尤其在我们阅读源码的过程中,单节点的etcd可以用来本地测试,debug,所以我们还是简单来了解下。 4 | 5 | 6 | -------------------------------------------------------------------------------- /cluster/singlenode/docker.md: -------------------------------------------------------------------------------- 1 | #### Docker 部署 -------------------------------------------------------------------------------- /cluster/singlenode/source.md: -------------------------------------------------------------------------------- 1 | #### 源码安装 -------------------------------------------------------------------------------- /cluster/singlenode/yum.md: -------------------------------------------------------------------------------- 1 | ## yum 安装 2 | 3 | 在CentOS系统下,我们可以直接通过yum进行安装 4 | yum -y install etcd 5 | -------------------------------------------------------------------------------- /conclusion/README.md: -------------------------------------------------------------------------------- 1 | #### 总结 -------------------------------------------------------------------------------- /introduce/README.md: -------------------------------------------------------------------------------- 1 | ## 分布式系统介绍 2 | 3 | 分布式系统(distrubuted system) 是建立在网络之上的软件系统。在分布式系统大规模采用之前,传统的系统架构绝大多数是建立在IBM等大型机上的。 4 | 随着互联网技术的发展以及摩尔定律的失效,传统基于大型机的服务部署方式,不仅难以适应快速增长的业务需求,而且具有高昂的成本。 所以基于以上原因, 5 | 基于小型服务器的分布式系统,能够按模块分开开发部署,不仅能够适应快速增长的业务需求,同时也极大的降低了硬件成本。 所以互联网,移动互联网时代, 6 | 分布式系统有了飞越式的发展。 7 | 8 | 而,正真将分布式系统推向高潮的功臣,是大数据技术以及云计算技术。当然区块链也算一个,但目前区块链技术还有一些争议。毫无疑问,当下,在任何一家 9 | 互联网公司,或多或少都有用到分布式技术。有些是注重计算,有些是注重存储。无论具体哪种,都表明分布式系统的重要性与必要性。 10 | 11 | 在本书中,我们主要围绕ETCD这一个分布式K-V数据库为主,探讨一下分布式存储技术的核心原理以及源码实现。 -------------------------------------------------------------------------------- /introduce/cap.md: -------------------------------------------------------------------------------- 1 | ## CAP理论 2 | 3 | 只要提到分布式系统,CAP原理就是没法绕开的。 因为CAP理论是分布式系统的基石。既然这样,那么什么是CAP原理,它又是如何产生的呢? 4 | 5 | 6 | #### 起源 7 | CAP原理最早出现在2000年,是加州大学伯克利分校(University of California, Berkeley)的Eric Brewer教授在ACM组织的Principles of Distributed Computing(PODC)研讨会上提出猜想。 8 | 在2002年,麻省理工学院(MIT)赛斯·吉尔伯特和南希·林奇发表了布鲁尔猜想的证明, 使之成为一个定理。 9 | 10 | 11 | #### 定义 12 | 在理论计算科学中,CAP定理(CAP theorem), 又称为布鲁尔定理(Brewer's theorem), 它指出对于一个分布式计算系统来说,不可能同时满足以下三点: 13 | 一致性(Consistency) (等同于所有节点访问同一份最新的数据副本) 14 | 可用性(Avaliability) (每次请求都能获取到非错的响应——但是不保证获取的数据为最新数据) 15 | 分区容错性(Partition tolerance)(以实际效果而言,分区相当于对通信的时限要求。系统如果不能在时限内达成数据一致性,就意味着发生了分区的情况, 16 | 必须就当前操作在C和A之间作出选择) 17 | 18 | 19 | ![](https://raw.githubusercontent.com/csunny/etcd-from-arch-to-souce-code/master/_asserts/images/cap.jpg) 20 | 21 | 根据定理, 分布式系统只能满足三项中的两项而不可能满足全部三项。理解CAP理论的最简单方式是想象两个节点分处分区两侧。 允许至少一个节点更新状态会导致数据不一致, 22 | 即丧失了C性质。如果为了保证数据一致性,将分区一侧的节点设置为不可用, 那么又丧失了A性质。 除非两个节点可以互相通信, 才能既保证C又保证A,这又导致丧失了P性质。 23 | 也就是说,如图所示的三者交叉的位置,是不可能实现的。 24 | 25 | 26 | #### 应用场景 27 | 既然CAP三者同时满足的条件是不可能的, 所以在系统设计的时候就需要作出取舍,比如弱化某些特性来支持其他两者。 28 | 29 | 弱化一致性 30 | 对结果不敏感的应用, 可以允许在新版本上线后过一段时间才能更新成功,不保证强一致性,保持最终状态的一致性。 31 | 典型的如,静态网站。 32 | 33 | 弱化可用性 34 | 对结果一致性很敏感的应用,如银行取款机,当系统发生故障时停止服务。 如清结算,转账等。 目前的paxos、raft等算法都是为保证强一致而设计的。这些系统会在 35 | 内部异常时拒绝或者阻塞客户端请求。 36 | 37 | 弱化分区容错性 38 | 现实中, 网络分区出现概率较小, 但难以避免。实践中,网络通信通过双通道等机制增强可靠性,达到高稳定的网络通信。 39 | -------------------------------------------------------------------------------- /introduce/eight_question.md: -------------------------------------------------------------------------------- 1 | ## 分布式系统八大谬论 2 | 3 | 分布式应用大多数都运行在复杂的环境中。 这也导致了其更容易比单体服务发生问题。早在20世纪90年代在Sun MicroSystem中就被提出了分布式系统的八大谬论。 4 | 在这里我们来认识下这八大缪论是什么? 5 | - 网络是可靠的 6 | - 延迟为零 7 | - 带宽是无限的 8 | - 网络是安全的 9 | - 拓扑不会变 10 | - 只存在一个管理员 11 | - 传输代价为零 12 | - 网络是同质的 13 | 14 | #### 网络是可靠的 15 | 在局域网中,网络看起来是可靠的。 毕竟,随着硬件技术以及底层通信技术的发展,网络组件具有更高的可靠性。而且即使单一的组件出现故障,还有很多冗余,但情况确实如此吗? 16 | 随着网络环境的越来越复杂,网络管理员很可能犯错误,尤其是在网络配置上。某些情况下,高达1/3的网络更改会导致网络的可靠性故障。软件和硬件都可能出现故障,尤其是路由器, 17 | 它占到所有故障的1/4左右。 “不可中断”的电源供应有可能出现断电、管理员可能进行不明智的网络设备更改、可能出现网络阻塞、可能遇到DDos攻击,以及软件和防火墙的更新或 18 | 布丁失败。 网络会因为自然和非自然的因素导致故障, 设计一个能应对这些情况的网络需要很高的技巧。 尤其是在分布式系统中,跨数据中心,跨城市的网络通信更容易出现故障。 19 | 20 | #### 延迟为零 21 | 延迟和带宽不同,延迟是指花费在应答上的时间。常见的延迟除了服务器处理延迟,还有网络延迟,包括传输延迟、节点延迟和阻塞延迟。我们也可以通过ping命令在操作系统上进行 22 | 测试。 如ping www.baidu.com 23 | 24 | #### 带宽是无限的 25 | 虽然大多数现在电缆可以应对无限带宽,另外最近随着5G技术的发展,带宽已经达到了一个比较高的水准,但这样就能理解带宽是无限了吗? 绝大多数的分布式系统目前还遭受 26 | 着带宽占满,被限流的痛苦。 目前绝大多数数据中心也仅仅是万兆带宽,而互联网、大数据时代,由于信息的爆炸增长,对带宽的需求也在不断的增加。 27 | 28 | #### 网络是安全的 29 | 网络安全的问题,一直都是安全人员面临的一个重大挑战,同时也有很多措施被安全人员所提出来。尽管如此,每年还是有数以亿计的损失发生在网络安全领域。数据加密的技术与风控 30 | 系统也在快速的发展,以适用互联网时代的数据安全挑战。 31 | 32 | #### 拓扑不会改变 33 | 恰恰相反,拓扑不仅会变化,而且是经常性的发生变化。 尤其在高速成长的业务中,系统的拓扑结构每天都在发生巨大的变化。 所以拓展性是分布式系统设计中非常核心的一个特性。 34 | 一个不能很好拓展的系统,不是合格的分布式系统。 35 | 36 | #### 只存在一个管理员 37 | 由于分布式系统的复杂性,往往在一个分布式系统当中存在多种角色,而这多种角色中又包含多个管理员。 38 | 39 | #### 传输代价为零 40 | 传输代价显然不是零,我们通过使用云计算厂商提供的存储服务就知道,很多对象存储,都是以传输进行收费,比如下行流量。 41 | 42 | #### 网络都是同质的 43 | 今天,或许已经很少见到同质网络了。 由于在分布式系统中,硬件、操作系统、传输协议等的不同,网络更多的是以异构的形式存在。 44 | 45 | #### 小结 46 | 正因为具有以上谬论,所以我们在设计分布式系统的时候,坚决不能做以上假设,否则可能导致灾难性的后果。 47 | -------------------------------------------------------------------------------- /introduce/model.md: -------------------------------------------------------------------------------- 1 | ## 分布式系统模型 2 | 3 | 从分布式系统角色的角度来说,我们可以简单的将分布式系统分为对等和非对等分布式系统。而进一步我们又可以将其划分为以下三种模型: 4 | 5 | 一、 C/S模型 6 | 7 | ![](https://raw.githubusercontent.com/csunny/etcd-from-arch-to-souce-code/master/_asserts/images/c_s.jpg) 8 | 9 | 当前占据主导地位的是非对称分布式系统,也就是系统之间不同的服务之间承担着不同的角色。 10 | 典型的就是C/S架构模型: 客户端发送请求到服务器,服务接收请求进行响应。 11 | 12 | 二、 Peer-to-Peer模型 13 | 14 | ![](https://raw.githubusercontent.com/csunny/etcd-from-arch-to-souce-code/master/_asserts/images/peer-to-peer.jpg) 15 | 16 | 如果在整个过程中,两个服务的角色是对等的,两者都可以接受请求,并对消息做出响应。典型的就是点对点分布式系统。 对于点对点的分布式系统,当下使用 17 | 最广泛,也是最火热的要属于区块链技术。 18 | 19 | 三、Filter模型 20 | 21 | ![](https://raw.githubusercontent.com/csunny/etcd-from-arch-to-souce-code/master/_asserts/images/filter.jpg?) 22 | 23 | 类似于中间件,一个服务发送消息给到另一个中间服务,中间服务在将消息发送给第三方服务的时候会进行处理,这也是一种非常普遍的模型。 24 | 25 | 26 | 当然,在一个分布式系统当中,随着系统的演变发展,一个系统当中往往都是以上三种模型以共存的方式协作。ETCD也不例外,在ETCD中虽然以Peer-to-Peer为主要 27 | 的角色,但C/S跟Filter也是其中不可或缺的配角。 28 | -------------------------------------------------------------------------------- /introduce/network.md: -------------------------------------------------------------------------------- 1 | ## 分布式系统通信 2 | 3 | 前面提到分布式系统是由很多服务器节点共同组成的,这些服务器有些可能是在同城同数据中心,有些可能是同城不同数据中心,还有些可能在不同城市的不同数据中心。 4 | 而要使这些服务器之间相互协作,就不可避免得要使用网络进行通信。而讲到网络通信,又不可避免要提到TCP/IP网络协议。 5 | 6 | #### 协议层 7 | 分布式系统是复杂的。在分布式系统中,往往有多台计算机相关联,他们之间必须以某种方式进行连接。 程序分别部署在不同服务器上,并且在这些不同服务器上的服务要 8 | 相互协作来完成分布式任务。 9 | 10 | 处理复杂问题最简单的方式就是将一个大而复杂的系统切割为多个小而简单的系统。其中每一部分都有自己的结构, 但是必须定义接口让这些独立的应用之间可以相互交流。 11 | 在分布式系统中,将此部分成为协议层,并且有清晰的定义。 他们遵循同一个技术栈,每一层都会同时跟上下层进行通信,层间的通信被协议所定义。依照上述逻辑,我们来 12 | 了解下ISO OSI Protocol,也就是大名鼎鼎的七层网络协议。 13 | 14 | #### ISO OSI Protocol 15 | 尽管OSI(Open Systems Interconnect)协议并没有完整实现,但OSI协议已经是事实上的通信标准协议。 16 | 17 | 18 | ![](https://raw.githubusercontent.com/csunny/etcd-from-arch-to-souce-code/master/_asserts/images/osi_protocol.jpg) 19 | 20 | #### OSI层 21 | 每一层的功能分别如下: 22 | - 网络层提供交互和路由 23 | - 传输层提供数据的交换 24 | - 会话层建立连接,管理和终止应用之间的连接 25 | - 表现层提供不同数据的不同表现形式 26 | - 应用层支持应用程序和终端用户 27 | 28 | #### TCP/IP 协议 29 | 30 | ![](https://raw.githubusercontent.com/csunny/etcd-from-arch-to-souce-code/master/_asserts/images/tcp_ip.jpg) 31 | 32 | 从上图我们可以清晰的看到,TCP/UDP都是属于传输层的协议,也就是四层协议。 在本书中,由于ETCD都是采用的TCP的传输协议, 33 | 所以在这里我们详细探讨下Go语言实现的TCP协议,以及如何利用net包进行TCP编程,这对后面理解etcd中的网络模型非常重要。 34 | 35 | 36 | #### Go语言网络编程之TCP 37 | 38 | **Server** 39 | ```go 40 | package main 41 | 42 | import ( 43 | "fmt" 44 | "net" 45 | ) 46 | var c chan string 47 | 48 | func main() { 49 | c = make(chan string, 1) 50 | server() 51 | } 52 | func server() { 53 | ln, err := net.Listen("tcp", ":8888") 54 | if err != nil { 55 | fmt.Println(err) 56 | } 57 | for { 58 | conn, err := ln.Accept() 59 | if err != nil { 60 | fmt.Println(err) 61 | } 62 | go handleConnection(conn) 63 | } 64 | } 65 | func handleConnection(conn net.Conn) { 66 | var buf [512]byte 67 | for { 68 | n, err := conn.Read(buf[0:]) 69 | if err != nil { 70 | fmt.Println(err) 71 | } 72 | value := (string(buf[0:n])) 73 | c <- value 74 | _, err2 := conn.Write(buf[0:n]) 75 | if err2 != nil { 76 | fmt.Println(err2) 77 | } 78 | go consumer() 79 | // select { 80 | // case <-c: 81 | // fmt.Println("--->", value) 82 | // } 83 | } 84 | } 85 | 86 | func consumer() { 87 | fmt.Println("--->", <-c) 88 | } 89 | 90 | ``` 91 | 以上程序是一个简单的Go语言TCP Server, 服务器端通过net.Listen("tcp", ":8888") 监听8888端口。 然后有个for循环挂起,用来跟客户端进行数据交换。 92 | handlerConnection实现了数据处理的逻辑。 在数据处理逻辑中,服务器会将客户端发送过来的数据放到通道c中,然后调用协程进行异步消费。 93 | 94 | **Client** 95 | ```go 96 | package main 97 | 98 | import ( 99 | "net" 100 | "fmt" 101 | "bufio" 102 | "os" 103 | ) 104 | 105 | func main(){ 106 | client() 107 | } 108 | 109 | func client(){ 110 | conn, err := net.Dial("tcp", "localhost:8888") 111 | if err != nil{ 112 | fmt.Println(err) 113 | return 114 | } 115 | in := bufio.NewReader(os.Stdin) 116 | 117 | for { 118 | inputV, _ := in.ReadString('\n') 119 | _, err := conn.Write([]byte(inputV)) 120 | if err != nil{ 121 | fmt.Println(err) 122 | } 123 | } 124 | 125 | } 126 | ``` 127 | 客户端就更简单了,客户端首先通过net.Dail("tcp", "localhost:8888") 与服务器建立连接, 然后有个有个for循环一直接受客户端的输入,并将客户端输入传送到服务器。 128 | 这里最后客户端并没有处理服务器最后传过来的数据。 当然也可以用conn.Read() 方法来读取服务器传输过来的数据。 129 | 130 | 131 | 上面就是一个完整的不同角色之间进TCP通信的例子,在etcd中,大量的使用了TCP的方式进行不同节点之间的数据传输。所以理解上面的例子,是后续理解etcd Raft协议的核心前提, 132 | 当然在etcd中除了直接利用TCP之外,还有一些建立在TCP之上的网络协议,比如HTTP,RPC。 133 | 134 | #### RPC&gRPC 135 | // TODO 136 | 137 | #### HTTP 138 | // TODO 139 | 140 | 141 | 142 | 143 | -------------------------------------------------------------------------------- /introduce/storage.md: -------------------------------------------------------------------------------- 1 | ## 分布式存储 2 | 3 | 前面简单介绍了分布式系统,这里重点介绍下分布式存储。 当然分布式存储也分为很多种类,在这里简单分为以下四类: 4 | 5 | 6 | ![](https://raw.githubusercontent.com/csunny/etcd-from-arch-to-souce-code/master/_asserts/images/dis_type_storage.jpg) 7 | 8 | 9 | 一、 分布式文件存储 10 | 分布式文件存储主要用来存储各类无结构数据,如文件,数据块等。系统以对象的形式组织,对象之间没有联系,这样的数据称为Blob,也就是二进制文件。 分布式文件存储一般都是将一个文件以 11 | chunk的形式切分为很多个小文件。 然后通过一致性hash以及其变种算法,将文件分别存放在不同的服务器磁盘上。 达到海量数据存储,备份的目的。 目前市面上使用的分布式文件存储系统已经 12 | 有很多成熟的产品。如各大云厂商提供的,如阿里云OSS、AWS S3等。 同时也有一些非常优秀的开源产品,如openstack swift, ceph等都是非常优秀的分布式文件存储系统。 其中swift为 13 | 跟OSS类似,都为对象存储。swift是通过一致性hash-ring的方式将数据分别存放在不同数据中心的数据盘上,并采取一定的冗余策略对数据进行冗余,以防止丢失。 分布式文件存储,往往也会作为 14 | 其他存储类型的底层存储服务。 如数据库的备份等一些主要场景。 15 | 16 | 二、 分布式键值系统 17 | 说起键值系统,相信大家第一时间会想到redis,memcache这样的缓存数据库,但是不管是redis还是memcache其本身设计并不是分布式的。 像redis还是采用的主-从架构。跟分布式系统对等节点的方式 18 | 有本质的区别。 而我们要讲的etcd就是一个严格的分布式k-v存储系统。 在etcd2.0的版本,其还是基于内存的内存数据库,可以处理的数据量并不大。 但随着etcdv3的升级,etcd将数据写入到了磁盘中, 19 | 极大的扩展了etcd的使用场景。 使得etcd不仅可以作为配置中心,还可以做缓存,半结构化数据存储,等一系列的场景。 当然了,相比与redis提供的丰富的数据结构,etcd还是差的很远,所以尝试用etcd 20 | 来代替redis的同学,可以先歇一歇。 21 | 22 | 三、 分布式表格存储 23 | 分布式表格存储是介于k-v存储与数据库之前的一种存储方式,除了能够支持简单的CURD之外,而且可以扫描某个主键的范围,也就是可以进行简单的范围查找。分布式表格存储更多的是对于单表的操作, 24 | 不支持一些特别复杂的操作,如多表关联,多表嵌套,嵌套子查询等。 分布式表格系统中,同一个表格的多个数据行也不要求包含相同类型的列。 25 | 26 | 四、 分布式数据库 27 | 数据库主要是用来存储结构化的数据,主要有关系型数据库SQL、非关系型数据库NoSQL、还有一些其他的数据库类型。如图数据库,时序数据库等。分布式数据库是为了应对互联网环境下,海量的数据, 28 | 高速的业务扩张等原因逐步发展起来的。 相信了解数据库的同学都知道,互联网业务一般都是高速增长,很多上周评估迁移的数据资源,这周就已经吃紧了,数据库管理人员就又不得不对数据进行迁移跟扩容, 29 | 这个重复的过程在保证业务可用的前提下,可谓是相当痛苦。 正式在这样的大背景下,数据库人员为了解决扩容、迁移、高可用等痛点,提出并开发了分布式数据库。 我们都知道数据库有很多复杂的查询, 30 | 同时还要支持事务、并发控制等一系列核心功能。 所以数据库的分布式是一个很大的挑战,比上面所有提到的分布式系统都要复杂。 但是,这些年随着分布式系统的发展跟成熟,行业里面也出现了很多 31 | 分布式数据库,其中就有蚂蚁的OceanBase,阿里云的DRDS,大数据生态中的HBase,开源的Tidb等,还有一些其他形式的分布式数据库。 其中tidb跟我们的etcd又有很多相通的地方。 如都是采用Raft 32 | 一致性算法,都是采用Go语言编写的等等。 所以对数据库有兴趣的同学可以了解下tidb。 33 | 34 | 35 | 36 | -------------------------------------------------------------------------------- /introduce/storage/acid.md: -------------------------------------------------------------------------------- 1 | ## 事务ACID -------------------------------------------------------------------------------- /introduce/storage/consensus_algorithm.md: -------------------------------------------------------------------------------- 1 | ## 共识算法 2 | 3 | 在上一节我们提到了,为了多节点中多副本数据的一致性,提出来一致性协议,而针对一致性协议又衍生出了很多的一致性算法。 4 | 简单来讲,共识算法又可以分为下面几部分: 5 | - 开放网络 6 | - 区块链公链 7 | - pow (proof of power) 8 | - pos (proof of stake) 9 | - dpos (Delegated proof of stake) 10 | - ripple 11 | - 区块链联盟链 12 | - pbft (Practical Byzantine Fault Tolerance)) 13 | - dbft(Delegated Byzantine Fault Tolerant)) 14 | - 内部网络 15 | - 分布式一致性系统 16 | - paxos 17 | - raft 18 | 19 | ![](https://raw.githubusercontent.com/csunny/etcd-from-arch-to-souce-code/master/_asserts/images/consensus_algorithm.jpg) 20 | 21 | 当然在这里,我们不打算介绍区块链领域里面的那些共识算法,感兴趣的同学,可以访问我的[github](https://github.com/csunny)来查看,在里面我有解释, 22 | 以及具体的代码实现。本书中,我们只介绍raft共识算法。 -------------------------------------------------------------------------------- /introduce/storage/consistency.md: -------------------------------------------------------------------------------- 1 | ## 一致性问题 2 | 3 | 在前面介绍的分布式系统CAP理论中,我们了解了一致性。在这里我们进一步讨论下 4 | 简单来说,一致性可以分为以下几类: 5 | 6 | - 强一致性 7 | - 弱一致性 8 | - 最终一致性 9 | 10 | #### 强一致性 11 | 这种一致性级别是符合用户级别的,它要求系统写入什么,就立马读出来什么。用户体验好,但是强一致性的实现难度大,成本高。 绝大多数的系统都在一定成都上弱化了一致性。 12 | 比如:银行转账按道理来说是一个强一致的系统,但是整个过程中,还是有时间差,也就是在很短的时间内,存在不一致的时间窗口。我们分析下张三给李四通过银行转账的一个流程, 13 | 张三给李四转账1000元,系统首先会从张三的账户扣除1000元,此时小张可能并没收到1000元,此时有个系统处理时间,也就是有个不一致的时间窗口。 当然了由于转账整个操作 14 | 是在一个数据库事务中,由于事务的ACID特性,所以不用担心钱转出去了对方没收到这种情况。 15 | 16 | #### 弱一致性 17 | 若一致性,其实很好理解,就是系统对一致性要求不高,即使系统不一致,也能很好的提供服务,不影响用户的体验。 最典型的就是web服务,web服务由于面对不同的客户端,客户端更新策略 18 | 也是由用户发起的, 所以在同样的时间内,不同的用户看到的内容不一样。 可能只会在某些特定的时间内,系统才表现出一致性的特点。 19 | 20 | #### 最终一致性 21 | 最终一致性,也是弱一致性的一种特殊情况,就是系统在某一个终态下是一致的。 22 | 23 | 24 | 对于一个分布式存储系统来说,对一致性往往有很高的要求。所以如何在分布式条件下,保持系统的强一致性,是一个较大的挑战。 etcd显然是一个强一致的键值数据库。 再一次操作生效之后, 25 | 整个系统都会对外表现出强一致的特性。 那么分布式存储系统中的一致性是怎么产生的,强一致性系统为什么难以实现,都有哪些挑战呢? 26 | 27 | 在分布式存储系统中,通常会通过维护多个副本的方式进行容错,以提高系统的可用性。 正是由于多副本的维护,所以产生了多副本之间的一致性问题。 28 | 还记得我们前面提到的分布式系统八大缪论吗? 如果你还记得,那么你知道: 29 | - 节点之间的网络通讯是不可靠的, 包括任意的延迟和内容故障。 30 | - 节点处理可能是错误的,甚至节点自身会出现宕机。 31 | - 同步调用会让系统变得不可扩展。 32 | 33 | 由于以上种种问题,在多个节点之间维护副本数据的一致性,变的颇具挑战。不管是学术界或是工业界,都比较关心这样的一致性问题。所以这些年也有很多的一致性算法被提出来,用来解决分布式系统中的 34 | 一致性问题。 -------------------------------------------------------------------------------- /introduce/storage/mvcc.md: -------------------------------------------------------------------------------- 1 | ## 并发控制 -------------------------------------------------------------------------------- /introduce/storage/raft.md: -------------------------------------------------------------------------------- 1 | ## Raft协议 2 | 3 | ### 摘要 4 | Raft是一个管理副本日志的共识算法。 它的作用跟Paxos一样,并且比Paxos更加高效,但是它的架构跟Paxos有很大的不同。这使得raft比Paxos 5 | 更容易理解,并能更有效的应用在生产系统中。为了让算法更容易理解,raft将关键组件拆分成了不同部分,比如Leader 选举,日志副本,安全,和状态机。 6 | 结果使得用户可以更加容易的学习使用raft算法。Raft同时提供了一种新的机制来改变集群成员,以此来保证安全性。 7 | 8 | ### 简介 9 | 共识算法使得一群节点可以作为一个节点组来一起合作对外提供服务,并且在内部允许部分节点的失效。 正因如此,他们在高可用大规模系统中扮演着重要的角色。 10 | 在过去的数十年间,Paxos一直占据着绝对的主导地位。绝大多数的共识算法的实现都是基于Paxos或者受到Paxos算法的影响。此外在共识算法的学习中,Paxos 11 | 也是一门必修课。 12 | 13 | 不幸的是,Paxos算法非常难理解,即使后续做了很多简化理解的尝试,但还是未能改变此种现状。 此外,Paxos复杂的架构设计在实现时也困难重重。这也导致不管是 14 | 系统工程师还是共识算法理论的学习者都深受其困。 15 | 16 | 在跟Paxos算法缠斗许久之后,我们终于发现了一种新的共识算法,它不仅可以让系统的构建更加容易,而且也能让学习者更容易理解。我们采取了非比寻常的方式, 17 | 只为让共识算法变更容易理解并且更利于在系统架构中使用。所以我们问自己: 我们可以定义一种共识算法,它比Paxos更实用,更易于理解。同时,我们也希望此算法 18 | 能够成为系统构建的核心算法,变成系统架构中最重要的基础设施。 所以不仅仅算法能够正常工作变的至关重要,对算法的观察跟监控也相当重要。 19 | 20 | 当然,我们做到了,它就是Raft共识算法。 在设计Raft算法的时候我们使用了很多特殊的技术手段来提高它的可理解性,包括分解(raft被分解成了Leader选举,日志副本,安全) 21 | 以及状态的减少。(相比于Paxos, raft减少了不确定性的程度以及服务器之间不一致的方式)通过对43个学生理解情况的对比,我们得出了Raft算法比Paxos算法更容易理解的结论: 22 | 在学习两种算法之后,43个人中有33人能够理解Raft算法,而能够理解Paxos算法的人却屈指可数。 23 | 24 | Raft在很多方面跟现存的共识算法都比较类似, 但是它也具有一些新的特性: 25 | - 强壮的Leader 26 | raft 使用了比其他共识算法更健壮的领导关系。 比如: 日志只能从leader到其他的服务器。 这种策略简化了日志副本的管理,并且让raft更加容易理解。 27 | - Leader 选举 28 | raft使用了随机时间选举leader。任何的共识算法都会检测节点的心跳,raft算法在此基础上添加了一点小小的机制,也就是随机时间,来解决冲突。 29 | - 成员变化 30 | raft管理集群中成员变化的机制是通过一种称为联合共识的方式进行的。 31 | 32 | 我们相信raft是要比Paxos和其他共识算法更优的算法,无论是用于教育还是工程实现。它要更为简单和易于实现。 对于生产系统来说它的描述足够完整。 33 | raft有几个开源的实现,并且已经被一些公司所采用。它的安全性已经经过了实践证明,同时效率和性能也要优于一般的共识算法。 34 | 35 | 简介剩余的第二部分介绍了复制状态机,第三部分讨论了Paxos算法的优劣,第四部分描述可理解的一般性方法,第5-8部分介绍raft算法, 36 | 第9部分对raft算法进行评估, 最后第10部分描述相关工作。 37 | 38 | ### 复制状态机 39 | 共识算法通常出现在复制状态机的上下文中。在这种方法中, 服务器集合上的状态机模型会保证集群具有相同的状态跟副本,即使有一些服务宕机也可以继续运作。 40 | 复制状态机主要用于分布式状态中解决分区容错的问题。比如: 具有单Leader节点的大规模集群,如GFS,HDFS以及RAMCloud,通常使用复制状态机来管理leader选举和存储配置信息 41 | ,并且即使在leader奔溃之后也能保持。比如在Zookeeper和Chubby中都使用了复制状态机。 42 | 43 | 复制状态机经典的实现方式是采用日志副本, 正如下图所示,每一个服务存储一个包含一系列命令的日志副本,状态机按次序以此执行。每一份日志在同样的次序下保存了相同的命令 44 | ,所以每一个状态机处理相同的命令序列。 因为状态机是确定性的, 所以每一个计算机具有相同的状态和相同的输出序列。 45 | 46 | 保持副本日志的一致性是共识算法的工作。服务器上的共识模块从客户端接收命令并将其添加到他们的日志当中。 它与其他服务器上的共识算法进行交互, 47 | 确保每一份日志最终都会保证在相同的序列下保持相同的命令,即使一些服务失效了。 一旦命令被复制,每一个服务的复制状态机将在日志序列中处理他们,并且输出会返回给客户端。 48 | 结果,服务组成一个单一格式的,高可用的状态机。 49 | 50 | 经典的共识算法系统都具有以下特性: 51 | - 安全: 52 | (绝对不会返回不正确的结果)在所有的非拜占庭容错条件下, 包括网络依赖,分区,丢包,复制或者重新排序等情况下都能确保安全。 53 | - 高可用 54 | 只要服务中主要的服务节点可以操作并且彼此通信,那么它就是完整可用的。 因此一个典型的5个节点的集群允许2个节点失效。服务恢复之后又可以重新加入集群。 55 | - 保持日志一致性的时候不依赖时钟 56 | 错误的始终和极端的消息延迟,在最坏的情况下,也只是会影响可用性,并不影响一致性。 57 | - 命令的完成取决于集群中的绝大多数节点: 58 | 一般来说,只要集群中绝大多数的节点作出了响应,则认为此次命令完成。个别表现较慢的服务器, 不会影响到整个系统的性能。 59 | 60 | ### Paxos 算法 61 | 在过去的数十年间,Paxos算法几乎编程了共识算法的代名词。 它是最普遍被用来教学的协议。绝大多数的共识算法的实施都是以此为基础。 62 | Paxos首次定义了一个可以就单一节点达成共识的协议。 比如,单节点日志副本。我们将此子集称为单一法令Paxos算法。Paxos将多个实例组合在一起来处理 63 | 一系列的决策,如日志(多-Paxos) Paxos确保安全和活跃度, 并且支持集群成员改变,它的正确性已经得到了证明,在使用中也很高效。 64 | 65 | 不幸的是,Paxos有两个大的缺点。 第一个缺点是Paxos很难理解。 对Paxos算法完整的解释非常不透明。鲜有人能够理解,需要花费很大的经历。 66 | 所以在下面的参考目录中有一些文章做响应的解释,但是这些解释也仅仅面向的是单一指令的Paxos,即使如此,也已经很难理解了。 67 | 68 | ### 为可理解而设计 69 | 在设计raft算法的时候有几个目标: 它必须是一个完整的并且实用的分布式系统基础设施。 所以它减少了很多需要开发者做的设计工作; 无论在任何条件下,它都必须 70 | 保证安全性和可用性;对普通使用者来说,还需要保证高效。 但这些并不是我们最重要的目标,我们最重要的目标是可理解性。它必须被绝大多数开发者所理解, 这样系。 71 | 者就可以在实际的系统中使用他们。 在设计raft算法的时候,有几个点需要关注,我们必须权衡并作出选择。 在我们评估可理解性的时候,我们会问: 解释起来有多难? 72 | 实现起来有多难? 73 | 74 | 我们意识到,这种分析具有高度的主观性: 尽管如此,我们还是使用了两种一般适用性的技术。第一个就是众所周知的问题分解。我们将问题拆解成了几个可以被解决,被解释, 75 | 被独立理解的部分。比如说,我们将raft协议拆分为了leader选举,日志复制,安全和成员管理几部分。 76 | 77 | 第二个就是做减法,我们通过减少状态的数量来减少状态空间, 使得系统更加连贯并消除一些可能的不确定性。尤其对于日志来说,不允许有遗漏, raft限制了日志彼此可能不一致的方式。 78 | 尽管在绝大多数情况下我们尽力去消除不确定性, 但在一些情况下,不确定性反而提高了可理解性。尤其是,随机数本身就具有不确定性,但是他们是非常好的方式去处理所有的选择问题。 79 | 80 | 我们在raft算法中,通过随机数的方式来进行leader选举。 81 | 82 | 83 | ### Raft 共识算法 84 | 在第二部分的描述中,我们知道raft是一种管理副本日志的共识算法。 图二对共识算法做了一个概述。 图三列举了算法核心的特性。在后续的内容中,会逐个进行详细的讨论。 85 | raft实现共识算法首先选举一个leader,然后赋予leader完整的能力去管理日志副本。 leader接受客户端的输入,然后将其复制到其他服务器,并且当其将日志复制到其他服务器 86 | 之后进行广播。通过Leader的方式可以简化副本日志的管理。 比如, leader可以在不经过跟其他节点共识的前提下决定将新的输入放入到日志的什么位置,并且数据的流向始终是从 87 | leader到其他服务器。Leader也可以失效或者与其他服务器失连,这种情况下,将会选举一个新的Leader节点。 88 | 89 | ![](\_asserts\images\raft_2.jpg) 90 | 通过Leader的方式,raft算法将一个共识问题,拆分成了三个相关的子问题 91 | - Leader 选举 92 | 当系统中没有leader节点时,必须选举产生新的Leader 93 | - 日志副本 94 | Leader必须从客户端接受日志的输入并将其复制到其他服务器。 95 | - 安全 96 | 最关键的raft的安全特性是图三所示的状态机安全性。 97 | 98 | ![](\_asserts\images\saft_raft.jpg) 99 | 100 | 在展示了共识算法之后,下一部分讨论高可用以及不同时刻节点角色的转换。 101 | 102 | #### 5.1 Raft基础 103 | 一个raft集群包含几个服务器。5个节点是最常见的数量,这样就可以容忍系统最多出现两个节点失效。在一个集群中任意一个节点的角色只能是leader,flower或者candidate. 104 | 在一个运行正常的集群中,只有一个leader节点,其余节点都是follower角色。 followers是被动的: 他们不会主动发起请求,他们仅仅只是响应leader的请和candidate的请求。 105 | 106 | Leader处理所有客户端的请求(如果一个客户端连接的是follower, follower会将请求重定向到leader节点)。 第三种角色candidate, 是用于选举的角色,也就是只有在选举的 107 | 时候才会出现candidate角色。 下面讨论各个角色之间的转换。 108 | 109 | raft算法将时间划分为任意长度, 正如下图5所示的一样。 任期是连续的整数。每一次任期都是由选举开始,选举期间会有很多候选人尝试成为leader。 如果一个候选人赢得了选举, 110 | 它将在剩余的任期时间内成为leader节点。有些情况下,会因为选票被分裂,任期会因为没有leader而结束; 紧接着一个新的任期将开始。 111 | 112 | 在一个任期内,不同的服务可能会在不同的时间观察到事务, 一些情况下,一个服务或许无法观察到一次选举,甚至整个任期内都无法发现。 任期在raft中扮演着逻辑时钟的角色,他们 113 | 允许服务器去检测过时的信息,比如之前人气的leaders. 每一个服务都会存一个当前任期数,这个数字随着时间单调递增。 如果一个服务的当前任期比其他服务的当前任期要小,他们 114 | 会将自己的任期更新为更大的任期值。 如果一个候选节点或者leader节点发现自己的任期过期了,他会立即恢复到follower的状态。如果服务接受到了过期任期的请求,则会拒绝此次 115 | 请求。 116 | 117 | Raft服务之间通过远程过程调用(RPCs)进行通信, 最基本的共识算法需要两种类型的RPCs. 请求投票的RPC调用,由候选节点发起, 以及数据写入的RPC,是由leader节点发起,follower 118 | 节点追加日志到wal。 如果发出去的RPC请求未获得响应, 服务会进行重试。同时为了提高性能,RPC请求都是并发执行的。 119 | 120 | #### 5.2 leader选举 121 | raft采用心跳机制来追踪leader选举。 当服务启动时,都是followe的角色。 只要一个服务可以从leader或者candidate接受到有效的RPCs请求,就会保持follower的状态。 Leader会定期的 122 | 发送心跳检测请求到follower节点,以此来确保他们之间的正常通信。 如果一个follower节点在固定的时间内没有接受到leader或者candidate的请求, 他们会嘉定没有有效的leader,并 123 | 会变为一个选举节点进行新一轮的leader选举。 124 | 125 | 开始一次选举时, 一个follower节点首先会将自己的任期值自增,并将自己的状态转换为candidate。 然后为自己投票,并通过RequestVote RPCs的方式对集群中其他节点进行广播。 126 | 候选人会一直持续这种状态,直到以下任意一种情况发生: (a) 赢得选举, (b)其他的节点获得选举 或者(c) 一段时间之后没有产生owner。 下面分别进行讨论。 127 | 128 | 如果候选人获得集群中绝大多数节点的投票,那么它将赢得选举。 在一个任期内,每一个服务至多给一个候选人进行投票。 这些策略保证了在一个任期内,至多有一个节点赢得选举。一旦一个节点 129 | 赢得选举,它就会编程leader节点。 紧接着会给所有其他节点发送心跳消息,以此来建立自己的权利同时阻止产生新的选举。 130 | 131 | 当等待投票时,一个candidate可能会接收到来自其他节点的AppendEntries RPC的请求,如果对方的任期值比当前candidate的任期值要大的话,candidate会恢复到follower状态。 如果对方的 132 | 任期值要比candidate的任期值小,则保持candidate的状态,并拒绝来自对方的请求。 133 | 134 | 还有一种可能的情况是,候选者既没赢也没输: 如果有很多的follower节点在同一时间编程候选人,投票可能会发生分裂,从而导致没有节点获得绝大多数选票。 当发生此种情况时,候选将超时并且会 135 | 发起新一轮的选举。 但是,如果不采取额外的措施,分割投票的情况可能会一直进行下去。 136 | 137 | Raft使用随机选举超时时间来确保投票分割不会一直持续。 为了阻止投票分割,选举超时时间会在一个固定的时间区间(150-300ms)随机选择。 这会使得在大多数情况下,只有一台服务器会超时;它会赢得 138 | 选举并且在其他服务器超时之前发送心跳到其他节点。 同样的机理被用于选票分割。 每一个候选人重置它的随机选择超时时间,一直等到直到下一次选举开始。 这会减少下一次选举时投票分割的可能性。 139 | 140 | 选举是一个可理解性指导我们进行架构选择的一个鲜活的例子。 起初我们计划采用一个投票系统: 每一个候选人指定一个唯一的排名,用于在竞争候选人之间作出选择。 如果一个候选人发现了更高排名的候选人, 141 | 它会将自己的状态置为follower,以此来让更高排名的候选人赢得选举。 我们发现这种方式创建了一系列子问题,我们也对这种算法进行了一些调节,但是发现总有一些边角料的问题发生。 最终,我们得出结论, 142 | 还是随机重试的方式更为使用,也更容易理解。 143 | 144 | #### 5.3日志复制 145 | 一旦leader被选举出来,它就开始处理客户端的请求。 每一个客户端请求包含的命令将会被复制状态机所执行。 leader将命令作为一个新的输入添加到自己的日志中, 然后并行广播AppendEntries RPCs 146 | 到每一个其他的服务器,进行日志复制。当输入被安全复制之后, leader会将输入应用到它的状态机中并且给客户端返回结果。 如果follower节点宕机或者运行缓慢,或者网络丢包,leader会重试AppendEntries RPCs(即使已经对客户端进行了响应) 147 | 148 | 日志形式组织为图6的方式。 每一个日志输入存储一个带任期值的状态机命令。日志中的任期值用于检测日志间的一致性。 此外每一个日志还会带一个整型的索引值来标记它在日志中的位置。 149 | ![](\_asserts\images\raft_fig_6.jpg) 150 | 151 | leader决定了什么时间是安全的去应用日志到状态机中; 这样的一次输入叫提交(committed)。 Raft保证提交会持久化并且被所有的状态机执行。一旦leader创建的日志被复制到绝大多数节点, 152 | 日志就会被提交。 这也会提交之前所有的输入到日志中,包括先前节点创建的输入。 下面5.4一节会对leader变更之后的细节进行进一步的探讨, 同时还有提交时候安全的定义。 leader会一直追踪 153 | 其提交的最大索引。一旦follower发现一个新的日志输入被提交,他会按次序将其应用在其状态机中。 154 | 155 | 我们设计的Raft算法日志机制可以在不同的服务器之间保持日志的高度一致性。不仅简化了系统行为,而且更可预测,此外它也是保证安全的一个重要组件。 Raft保持了以下属性, 他们共同构成图三所示的日志 156 | 匹配属性: 157 | - 如果在不同日志中的两次输入有相同的索引和任期,他们一定存储了相同的指令。 158 | - 如果在不同日志中的两次输入有相同的索引和任期,先前所有的日志一定是相同的。 159 | 160 | 161 | 第一条是遵循以下规律,一个leader在给定的任期和索引下,至多创建一条输入,输入的日志不会改变其在日志中的位置。 第二条特性被简单的AppendEntries一致性所保证。 当发送一次AppendEntries 162 | RPC时候,leader的日志中包含先前的索引和任期,并在新的索引跟任期的前面。 如果follower没有在他的日志中发现相同的索引跟任期,则拒绝写入。 一致性检查扮演着至关重要的作用,初始化的日志满足日志匹配特性。综上,当AppendEntries返回成功时, leader就知道follower的日志跟自己的日志相同。 163 | 164 | 集群正常运作情况下, leader的日志跟follower日志是一致的, 所以AppendEntries一致性检查不会失败。 但是leader节点异常可能会使得日志不一致。 这种不一致可能会导致一系列的leader或follower奔溃。 165 | 图7表示leader和follower日志可能不同的几种可能的情况。 follower可能会缺失leader已经存在的日志, 也有可能会比leader有多余的日志, 或者两种情况都会出现。 这种日志缺失或者多余的情况可能会跨越几个 166 | 任期。 167 | 168 | 在Raft算法中,leader通过强制follower复制自己的日志来处理这种情况。 这也意味着在follower中的冲突日志会被leader强制覆盖。 5.4小节会说明当加上限制的时候,此种做法是安全的。 169 | 170 | 171 | 为了使follower的日志跟自己一致, leader必须找出两个日志达成共识的最新一条日志,并删除follower节点上在这条日志之后的所有日志,并将leader上最新的日志复制到follower节点。 172 | 这些所有的行为都在AppendEntries RPCs过程中发生的。 leader为follower保存下一个索引,也就是leader上面下一条日志的索引值将会发送到follower。 如果一个follower节点的 173 | 日志跟leader节点不一致。 AppendEntries一致性检查将会在下一次AppendEntries RPC的时候失败。 最终下一条索引值将会是leader跟follower匹配的那个值。此时,AppendEntries 174 | 就会成功, 这个过程中删除了follower中跟leader中不一致的日志,并将leader中新的日志应用到follower中。 一旦AppendEntries成功了,也就意味着leader跟follower的日志完全一致 175 | 了, 并且在剩余的任期时间内一直保持。 176 | 177 | 如果必要的话, raft协议可以被优化来减少拒绝AppendEntries RPCs的次数。 比如,当拒绝一次AppendEntries 请求的时, follower节点可以包含冲突日志的任期以及其为此任期存储的第一个索引。 178 | 利用这个信息, leader可以减nextIndex的值来绕过该任期内所有冲突的记录。每个冲突记录需要一个AppendEntries RPC,而不是每个记录需要RPC。实际上,我们怀疑这种优化的必要性,因为失效总会发生 179 | 并且不可能总是出现不一致的情况。 180 | 181 | 基于此机制, leader无需使用特殊的手段去保证日志的一致性。 只要它正常运作,日志就会自动保持一致。 leader绝对不会重写或者覆盖自身的日志(leader只能追加)。 182 | 183 | 日志复制机制展示了第二部分描述的理想的共识特性。 raft可以接收,复制并应用新的记录只要绝大多数的节点正常工作。正常使用中,日志会通过RPC复制到集群中的绝大多数节点。 单一节点的延迟不会影响整个集群的性能。 184 | 185 | 186 | #### 5.4 安全 187 | 前面部分描述了raft算法leader选举以及日志复制的部分。 但是截止目前描述的内容不足以证明此机制可以保证复制状态机在每个节点上以相同次序执行相同的指令。 188 | 189 | 此部分通过添加约束的方式来完成leader选举,这种约束确保了任何给定的任期内包含所有先前日志的提交。 给定选举约束之后,我们可以让提交规则更加明确。 最终我们呈现了 Leader Completeness Property 190 | 并且展示了leader如何修正复制状态机的行为。 191 | 192 | #### 5.4.1 193 | 在所有的基于leader选举的共识算法中, leader一定最终存储所有的已提交的日志。在一些共识算法中,即使leader没有包涵所有已存在的日志,也可以被选举,比如Viewstamped Replication 。 这些算法使用额外的机制来确保日志的确实和传输。但是这种措施增加了额外的机制以及复杂性。 相反raft使用了一种简单的方法来保证日志, 先前的记录一定会存在于leader节点中。也就是说日志只能朝着一个方向流动,从leader->follower。 194 | 195 | raft通过投票过程来限制没有完整日志的candidate获得选举。 candidate必须得到绝大多数的投票才能赢得选举, 这也就意味着绝大多数服务器上具有已经被提交的日志。 只要发现candidate的日志比其他的节点更老, 196 | 那么candidate就没法赢得选票。 197 | 198 | Raft通过对比索引和任期的方式来确定,那个日志是更新的日志。 如果具有相同索引的日志有不同的任期, 那么具有更大任期值的日志更新。 如果日志有相同的任期, 那么日志更长的更新。 199 | 200 | #### 5.4.2 201 | 一旦日志被存储到绝大多数的服务器上, 那么leader就知道其在当前任期下被提交了。如果leader在提交之前发生奔溃,下一任leader将会尝试完成日志复制。 202 | 203 | ![](\_asserts\images\term.jpg) 204 | 205 | ![](https://raw.githubusercontent.com/csunny/etcd-from-arch-to-souce-code/master/_asserts/images/state_machine.jpg) 206 | 207 | #### 参考 208 | - [raft](https://raft.github.io) 209 | - [raft动画](http://thesecretlivesofdata.com/raft/) -------------------------------------------------------------------------------- /introduce/storage/transation.md: -------------------------------------------------------------------------------- 1 | ## 分布式事务 -------------------------------------------------------------------------------- /introduce/summary.md: -------------------------------------------------------------------------------- 1 | #### 小结 -------------------------------------------------------------------------------- /ops/README.md: -------------------------------------------------------------------------------- 1 | #### 运维指南 -------------------------------------------------------------------------------- /ops/backup.md: -------------------------------------------------------------------------------- 1 | #### 数据备份 2 | -------------------------------------------------------------------------------- /ops/monitor.md: -------------------------------------------------------------------------------- 1 | #### 监控 -------------------------------------------------------------------------------- /ops/other.md: -------------------------------------------------------------------------------- 1 | #### 其他 -------------------------------------------------------------------------------- /ops/summary.md: -------------------------------------------------------------------------------- 1 | #### 小结 -------------------------------------------------------------------------------- /revision/README.md: -------------------------------------------------------------------------------- 1 | #### 版本历史 2 | 3 | - v0.2.2: 2019-4-27 4 | - 一致性问题,共识算法 5 | 6 | - v0.2.0: 2019-4-26 7 | - 分布式系统介绍,分布式系统模型,cap理论,分布式系统通信,分布式系统八大谬论 8 | 9 | - v0.1.0: 2019-4-25 10 | - 初始化项目目录结构 11 | 12 | -------------------------------------------------------------------------------- /sourceCode/README.md: -------------------------------------------------------------------------------- 1 | ## 源码解析 2 | 3 | ### 环境搭建 4 | 5 | 前面已经提到,etcd是用Go语言开发的,所以在阅读源码之前需要搭建Go 语言的开发环境。 6 | 7 | --- 8 | 9 | #### 编辑器 10 | 11 | 打出编辑器这三个字的时候,不自觉地,我打了个激灵(😓)。在程序员这个圈子里,对于编辑器的选择,就好像说“PHP是世界上最好的语言”一样。是个梗,而且一直争论不休,从未有谁能够完胜别人。 当然,我这里提编辑器,并不想争论各个编辑器的优劣。我只是介绍几种选择方案,以及我自己的开发环境。 12 | 13 | 由于在本系列文章里面,主要是围绕IPFS的Go语言相关的源码。一方面是因为Go语言的实现更全,代码完成度更高。此外也有我自己作为技术人员的一点小偏执。所以以下编辑器主要围绕Go语言来展开。 14 | 15 | **Goland** 16 | 17 | JetBrains,想必这个名字在程序员的圈子里,绝大多数人都听过,或者正在使用他们的产品。是的,goland就是大名鼎鼎的JetBrains出品的一款针对Go语言的编辑器。可以说简单、无脑容易上手,同时也很好用。也支持一些常见的快捷键以及自定义环境,可以说是一款相当不错的编辑器。 18 | 19 | 嗯,当然有缺点,那就是慢!此外,JetBrains出品的产品都是高度语言定制化。Python有pycharm,java有idea,前端有webstorm。作为一个经常需要同时写几门开发语言的同学来说,相当受罪。 之前我也有过一段时间与编辑器的斗争,最后还是毅然而然的抛弃了JetBrains 全家桶。 20 | 21 | **VIM** 22 | 23 | 毫无疑问,VIM确实非常强大。 我这里说VIM打开文件的速度超越任何编辑器,相信大家没意见吧? 同时VIM也是最轻量的编辑工具,同时也不限开发语言。 24 | 25 | 尽管VIM有这么多优点,但是其缺点也相当明显,VIM本身没有带任何的语法提示、语法校验、格式化等等人性化的功能。 需要配置插件。 所以在VIM使用过程中,要维护一个相当长的配置文件,从插件到快捷键。如果对VIM熟悉的同学来说,使用起来确实相当快捷,但是对对于那些初学者,或者对VIM不够熟悉的同学来说,以VIM为日常开发环境反而影响开发效率。 26 | 27 | 但,我知道,很多人不愿意舍弃VIM,尤其是在unix环境下开发的同学。 28 | 29 | **VSCode** 30 | 31 | 在遇到Vscode之前,其实我觉得大多数编辑器都没啥区别。直到开始使用Vscode,慢慢变成vscode忠实用户。 我才知道,原来天下编辑器共分两类:一类是Vscode,一类是其他。 32 | 33 | 请原谅我对Vscode的推崇,因为它确实好用。 34 | 35 | **打造VIM+Vscode+Go语言的开发环境** 36 | 37 | Vscode的安装 38 | 39 | - Mac安装 40 | 41 | mac下安装vscode相当简单,在官网下载一个vscode dmg然后安装就ok。[官网地址](https://code.visualstudio.com/) 42 | 43 | - Windows下安装 44 | 45 | 跟mac下一样,下载对应的exe文件,然后安装即可。 46 | 47 | - Linux下安装 48 | 49 | 俗话说的好,你都使用Linux了,安装软件的事情,肯定相当熟悉。 50 | 51 | Go语言安装 52 | 53 | ##### Windows安装: 54 | 55 | 有两种安装方式,源码安装以及MSI安装,本文介绍以应用程序安装。 56 | - 首先下载相应的安装包:https://redirector.gvt1.com/edgedl/go/go1.11.2.windows-amd64.msi 57 | - 下载成功之后直接双击安装 58 | - 设置环境变量 59 | - 创建工作目录. 在User/Magic/go 60 | - 将工作目录也添加到环境变量 61 | 62 | ##### Mac安装: 63 | - 下载pkg包安装 64 | https://redirector.gvt1.com/edgedl/go/go1.11.2.darwin-amd64.pkg 65 | - 创建工作目录 66 | > mkdir -p /Users/magic/go 67 | 68 | - 设置环境变量 69 | > export GOPATH=/Users/magic/go 70 | > export GOBIN=`$`GOPATH/bin 71 | > export PATH=`$`PATH:/Users/magic/go/bin 72 | 73 | 74 | ##### Linux安装: 75 | Linux下安装更加简单,设置环境变量的方式跟mac下基本一致。 76 | - Linux下之后可以用yum install go(CentOS). apt-get install go (Ubuntu) 77 | - 设置环境变量 78 | 79 | ##### Git的安装: 80 | 81 | - 关于Git的安装,直接访问: 82 | https://www.liaoxuefeng.com/wiki/0013739516305929606dd18361248578c67b8067c8c017b000 83 | 84 | VsCode配置 85 | 86 | VsCode上手非常容易,其实不需要做任何配置都可以玩的很溜。当然,为了进一步提高开发效率,所以还是可以做一些必要的配置。 87 | 88 | **插件安装** 89 | 安装完成打开vscode, 点击左侧栏目的红色序号标注的1,然后在搜索框搜索Go,然后第一个就是Vscode的go语言插件,如果未安装,右侧序号2会是Install,点击安装即可。序号3区域是对此插件的介绍,以及其默认的一些配置和快捷键。 90 | 91 | 除了Go插件,开发Go语言还需要以下插件 92 | 93 | - Go 94 | - Code Runner 快捷运行Go代码 常见的快捷键是command+R(mac)windows是(ctrl+R) 95 | - Vim 在vscode里面使用vim编辑模式 96 | - VSCode Icons 根据文件名称显示图标 97 | - Path Intellisense 自动补全文件路径 98 | - TODO Highlight 高亮TODO 99 | 100 | 必要的插件就这几个吧,其他的插件,按照自己的需求选择性安装即可。 101 | 102 | ![](https://diycode.b0.upaiyun.com/photo/2018/e03b4c4e69771bc2c82a7767c6278d3b.png) 103 | **快捷键设置** 104 | 在VSCode中快捷键的设置相当重要。 查寻快捷键的命令command+shift+p, 修改快捷键command+k,command+s 然后调出修改快捷键的文件,根据自己喜好修改即可。 105 | 106 | 常见的快捷键 107 | 打开新窗口 command+T 108 | 打开文件夹 command+O 109 | 新建文件 command + N 110 | 关闭文件 command + W 111 | 分屏 command+D command+shift+D 112 | 还有一些其他快捷键,自己可以摸索使用。这里就不一一列举啦。 113 | 114 | **ETCD代码下载** 115 | 116 | 这里我们主要剖析etcd代码 117 | 如果之前安装了Go语言,直接使用go get进行下载 118 | 119 | ``` 120 | // 下载代码 121 | cd $GOPATH/src/ 122 | git clone https://github.com/etcd-io/etcd.git go.etcd.io/etcd 123 | ``` 124 | 当然,由于etcd的源码是用go moduler进行依赖管理的,所以源码可以不用放在GOPATH路径下。源码下载完成,就可以直接运行代码了,如果要debug代码的话, 125 | 在debug之前,需要安装一个go语言的库。 126 | ``` 127 | go get github.com/derekparker/delv 128 | ``` 129 | 130 | Debug lanch 配置文件 131 | ``` 132 | { 133 | // Use IntelliSense to learn about possible attributes. 134 | // Hover to view descriptions of existing attributes. 135 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 136 | "version": "0.2.0", 137 | "configurations": [ 138 | { 139 | "name": "Launch", 140 | "type": "go", 141 | "request": "launch", 142 | "mode": "auto", 143 | "remotePath": "", 144 | "port": 2345, 145 | "host": "127.0.0.1", 146 | "program": "${file}", // workspaceRoot 代表当前打开文件的跟目录 147 | "env": { 148 | "GOPATH": "/Users/magic/go" 149 | }, 150 | "args": [], 151 | "showLog": true 152 | } 153 | ] 154 | } 155 | 156 | ``` 157 | 158 | 执行F5 运行debug显示如下, 由此说明我们开发环境搭建ok,可以正常运行ETCD代码,进行debug。 159 | 160 | 以上,我们就搭建好了etcd的开发环境。 后面我们会在这个环境中进行代码的调试与源码分析。 161 | 162 | 163 | 164 | #### 项目地址 165 | - https://github.com/csunny/etcd-from-arch-to-souce-code 166 | 167 | #### 参考文章: 168 | 169 | - Docker安装: https://docs.docker.com/engine/installation/#time-based-release-schedule 170 | - Go安装: https://golang.org/doc/install 171 | - Git:https://www.liaoxuefeng.com/wiki/0013739516305929606dd18361248578c67b8067c8c017b000 172 | - VScode: https://code.visualstudio.com/ 173 | - Sublime: https://www.sublimetext.com/ -------------------------------------------------------------------------------- /sourceCode/core/README.md: -------------------------------------------------------------------------------- 1 | ## 核心代码 -------------------------------------------------------------------------------- /sourceCode/example/edit.md: -------------------------------------------------------------------------------- 1 | ## 2 | ## 前言 3 | 4 | 在第一节中,对IPFS下了很多定义,在第二节中又引入了很多概念。 对于一个技术人员来说,前面两节无疑是枯燥的。那么接下来,开始熟悉的节奏,开发环境搭建。 5 | 6 | --- 7 | 8 | ## 编辑器 9 | 10 | 打出编辑器这三个字的时候,不自觉地,我打了个激灵(😓)。在程序员这个圈子里,对于编辑器的选择,就好像说“PHP是世界上最好的语言”一样。是个梗,而且一直争论不休,从未有谁能够完胜别人。 当然,我这里提编辑器,并不想争论各个编辑器的优劣。我只是介绍几种选择方案,以及我自己的开发环境。 11 | 12 | 由于在本系列文章里面,主要是围绕IPFS的Go语言相关的源码。一方面是因为Go语言的实现更全,代码完成度更高。此外也有我自己作为技术人员的一点小偏执。所以以下编辑器主要围绕Go语言来展开。 13 | 14 | **Goland** 15 | 16 | JetBrains,想必这个名字在程序员的圈子里,绝大多数人都听过,或者正在使用他们的产品。是的,goland就是大名鼎鼎的JetBrains出品的一款针对Go语言的编辑器。可以说简单、无脑容易上手,同时也很好用。也支持一些常见的快捷键以及自定义环境,可以说是一款相当不错的编辑器。 17 | 18 | 嗯,当然有缺点,那就是慢!此外,JetBrains出品的产品都是高度语言定制化。Python有pycharm,java有idea,前端有webstorm。作为一个经常需要同时写几门开发语言的同学来说,相当受罪。 之前我也有过一段时间与编辑器的斗争,最后还是毅然而然的抛弃了JetBrains 全家桶。 19 | 20 | **VIM** 21 | 22 | 毫无疑问,VIM确实非常强大。 我这里说VIM打开文件的速度超越任何编辑器,相信大家没意见吧? 同时VIM也是最轻量的编辑工具,同时也不限开发语言。 23 | 24 | 尽管VIM有这么多优点,但是其缺点也相当明显,VIM本身没有带任何的语法提示、语法校验、格式化等等人性化的功能。 需要配置插件。 所以在VIM使用过程中,要维护一个相当长的配置文件,从插件到快捷键。如果对VIM熟悉的同学来说,使用起来确实相当快捷,但是对对于那些初学者,或者对VIM不够熟悉的同学来说,以VIM为日常开发环境反而影响开发效率。 25 | 26 | 但,我知道,很多人不愿意舍弃VIM,尤其是在unix环境下开发的同学。 27 | 28 | **VSCode** 29 | 30 | 在遇到Vscode之前,其实我觉得大多数编辑器都没啥区别。直到开始使用Vscode,慢慢变成vscode忠实用户。 我才知道,原来天下编辑器共分两类:一类是Vscode,一类是其他。 31 | 32 | 请原谅我对Vscode的推崇,因为它确实好用。 33 | 34 | **打造VIM+Vscode+Go语言的开发环境** 35 | 36 | Vscode的安装 37 | 38 | - Mac安装 39 | 40 | mac下安装vscode相当简单,在官网下载一个vscode dmg然后安装就ok。[官网地址](https://code.visualstudio.com/) 41 | 42 | - Windows下安装 43 | 44 | 跟mac下一样,下载对应的exe文件,然后安装即可。 45 | 46 | - Linux下安装 47 | 48 | 俗话说的好,你都使用Linux了,安装软件的事情,肯定相当熟悉。 49 | 50 | Go语言安装 51 | 52 | Go语言的安装这里就不仔细讲了,网上一大堆文章。另外在我的另一个专栏里面有一篇 [《Go语言环境搭建》](https://xiaozhuanlan.com/topic/8930542671) 如果对Go语言安装有问题的同学也可以看看。 53 | 54 | VsCode配置 55 | 56 | VsCode上手非常容易,其实不需要做任何配置都可以玩的很溜。当然,为了进一步提高开发效率,所以还是可以做一些必要的配置。 57 | 58 | **插件安装** 59 | 安装完成打开vscode, 点击左侧栏目的红色序号标注的1,然后在搜索框搜索Go,然后第一个就是Vscode的go语言插件,如果未安装,右侧序号2会是Install,点击安装即可。序号3区域是对此插件的介绍,以及其默认的一些配置和快捷键。 60 | 61 | 除了Go插件,开发Go语言还需要以下插件 62 | 63 | - Go 64 | - Code Runner 快捷运行Go代码 常见的快捷键是command+R(mac)windows是(ctrl+R) 65 | - Vim 在vscode里面使用vim编辑模式 66 | - VSCode Icons 根据文件名称显示图标 67 | - Path Intellisense 自动补全文件路径 68 | - TODO Highlight 高亮TODO 69 | 70 | 必要的插件就这几个吧,其他的插件,按照自己的需求选择性安装即可。 71 | 72 | ![](https://diycode.b0.upaiyun.com/photo/2018/e03b4c4e69771bc2c82a7767c6278d3b.png) 73 | **快捷键设置** 74 | 在VSCode中快捷键的设置相当重要。 查寻快捷键的命令command+shift+p, 修改快捷键command+k,command+s 然后调出修改快捷键的文件,根据自己喜好修改即可。 75 | 76 | 常见的快捷键 77 | 打开新窗口 command+T 78 | 打开文件夹 command+O 79 | 新建文件 command + N 80 | 关闭文件 command + W 81 | 分屏 command+D command+shift+D 82 | 还有一些其他快捷键,自己可以摸索使用。这里就不一一列举啦。 83 | 84 | **IPFS代码下载** 85 | 这里我们主要剖析go-ipfs代码 86 | 如果之前安装了Go语言,直接使用go get进行下载 87 | 88 | ``` 89 | // 下载代码 90 | go get -u -d github.com/ipfs/go-ipfs 91 | // 安装 92 | cd $GOPATH/src/github.com/ipfs/go-ipfs 93 | make install 94 | 95 | ``` 96 | 97 | 这样IPFS的源码下载、安装就成功了。由于在IPFS中,代码的依赖管理是采用的gx,而不是目前使用官方的dep,所以对于之前使用dep管理依赖的同学,要调整下。 安装go-ipfs依赖包 98 | ``` 99 | go get -u github.com/whyrusleeping/gx 100 | ``` 101 | 102 | 到这里,整个开发环境就准备的差不多了,接下来我们来运行一个简单的命令,并打个断点,debug个一把,作为本章内容的终结。 103 | 104 | 在debug之前,需要安装一个go语言的库。 105 | ``` 106 | go get github.com/derekparker/delv 107 | ``` 108 | 109 | Debug lanch 配置文件 110 | ``` 111 | { 112 | // Use IntelliSense to learn about possible attributes. 113 | // Hover to view descriptions of existing attributes. 114 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 115 | "version": "0.2.0", 116 | "configurations": [ 117 | { 118 | "name": "Launch", 119 | "type": "go", 120 | "request": "launch", 121 | "mode": "auto", 122 | "remotePath": "", 123 | "port": 2345, 124 | "host": "127.0.0.1", 125 | "program": "${file}", // workspaceRoot 代表当前打开文件的跟目录 126 | "env": { 127 | "GOPATH": "/Users/magic/go" 128 | }, 129 | "args": ["ls", "QmfEE1CwpwfJcj54AAhz16HU3VkeUkP41Ta953Y1Evi3qb", "--resolve-type"], 130 | "showLog": true 131 | } 132 | ] 133 | } 134 | 135 | ``` 136 | 137 | 执行F5 运行debug显示如下, 由此说明我们开发环境搭建ok,可以正常运行IPFS代码,进行debug。 138 | ![](https://diycode.b0.upaiyun.com/photo/2018/1bc2832fd30b6fcb03be89eb9a5d5e91.png) 139 | 140 | 以上,我们就搭建好了go-ipfs的开发环境。 后面我们会在这个环境中进行代码的调试与源码分析。 141 | 142 | 143 | ## 参考 144 | - [gx依赖管理](https://github.com/whyrusleeping/gx) 145 | 146 | 147 | 148 | 149 | -------------------------------------------------------------------------------- /usage/README.md: -------------------------------------------------------------------------------- 1 | #### 使用案例 -------------------------------------------------------------------------------- /usage/config.md: -------------------------------------------------------------------------------- 1 | #### 配置中心 2 | 3 |  4 | 在etcd的典型应用场景中,其中一种就是作为配置中心。用来存储配置信息。由etcd本身的高可用特性来保证配置中心的高可用。 5 | 6 | 接下来我们首先了解下配置中心有什么特点? 7 | 8 | 1. 配置中心数据量较小,一般服务器内存即可满足存储要求。 9 | 2. 配置通常对历史版本比较关心, 利用etcd可以保存配置的版本信息。 10 | 3. 配置的变更是比较频繁的,配置变更之后,系统需要知道并作出响应。etcd watch机制可以实现配置更新后,系统实时感知。 11 | 4. 配置的准确性与一致性非常重要,etcd的CP特性可以保证配置的可用性与一致性。 12 | 5. 同一份配置会被多个客户端访问。 13 | 6. 配置会被不同的部门使用,所以需要namespace来进行租户管理。 14 | 15 | 16 | 17 | 基于以上特性,etcd作为一款分布式内存数据库,可以满足上面出现的场景,所以etcd可以用户配置中心的核心存储组件。 18 | 当然对于简单的配置中心来讲,etcd原生自带的功能就可以满足,但如果要满足更多的功能,还需要结合实际的业务场景做一些定制化的工具开发,在etcd之上构建一层或者多层 19 | 自定义的服务来满足实际需求。 20 | 21 | -------------------------------------------------------------------------------- /usage/kvstore.md: -------------------------------------------------------------------------------- 1 | #### 分布式kv存储 -------------------------------------------------------------------------------- /usage/lock.md: -------------------------------------------------------------------------------- 1 | #### 锁 2 | -------------------------------------------------------------------------------- /usage/queue.md: -------------------------------------------------------------------------------- 1 | #### 队列 -------------------------------------------------------------------------------- /usage/subpub.md: -------------------------------------------------------------------------------- 1 | #### 发布订阅 -------------------------------------------------------------------------------- /usage/summary.md: -------------------------------------------------------------------------------- 1 | #### 总结 --------------------------------------------------------------------------------