etcd 安装 http://play.etcd.io/install
https://github.com/mattn/goreman
Linux下多进程管理工具对开发和运维都很有用,常见的功能全面的主流工具主要有monit、supervisor。不过开发中使用则推荐轻量级小工具goreman。
goreman是对Ruby下广泛使用的foreman的重写,毕竟基于golang的工具简单易用多了。顺便提一句:goreman的作者是mattn,在golang社区挺活跃的日本的一名程序员。foreman原作者也实现了一个golang版:forego,不过没有goreman好用,举个例子:coreos的etcd就是使用的goreman来一键启停单机版的etcd集群。
安装
go工具安装都非常简单:
go get github.com/mattn/goreman
goreman help
当然,记得先把GOPATH、GOROOT环境变量配置好,并记得把$GOPATH/bin添加到$PATH
使用
由于是小工具,参考goreman help基本就足够了。简单的使用步骤:
新建一个Procfile文件,如果改名则需要goreman -f指定。
在包含Procfile的目录下执行:goreman start
关闭时直接ctrl-c推出,goreman会自动把所有启动的进程都shut down
举例
以Apache kafka的使用为例,了解的朋友应该知道,kafka使用时通常需要启动两个进程:一个zookeeper,一个kafka broker,因此可以编写一个kafka开发环境的Procfile:
zookeeper: bash ~/tool/kafka_2.11-0.8.2.1/bin/zookeeper-server-start.sh config/zookeeper.properties
broker: bash ~/tool/kafka_2.11-0.8.2.1/bin/kafka-server-start.sh config/server.properties
然后执行goreman start,可以看到不同颜色区分的zookeeper、kafka broker进程的启动日志:
11:04:10 zookeeper | Starting zookeeper on port 5000
11:04:10 broker | Starting broker on port 5001
…
关闭时,直接ctrl-c,则两个bash进程也会被自动关闭。
高级用法
上述是最简单的使用场景:直接使用goreman start,不过有个缺点,即goreman绑定到了当前的session,而且不能灵活控制多个进程启停以及顺序。而实际开发过程中,通常需要经常单独启停某个正在开发的模块相关的进程,比如上面例子中的kafka-broker,而Zookeeper通常不需要频繁启停。
可以使用更高级的goreman run命令来实现,如:
goreman run start zookeeper
goreman run start broker
goreman run status
goreman run stop broker
goreman run restart broker
总结
多进程管理是目前开发尤其是互联网web、服务器后端很常用的工具,尤其上云之后,云应用普遍推崇的microservices微服务架构进一步增加了后端进程数。而goreman很适合开发环境使用,能够一键式管理多个后台进程,并及时清理环境。不过真正的生产环境,还是使用monit/m、supervisor等更成熟稳定、功能全面的多进程管理工具。
使用goreman搭建etcd的本地伪分布式。
首先需要了解goreman的使用,一个能够根据指定文件启动多个进程的工具。
主要参考:
https://github.com/etcd-io/etcd
https://github.com/etcd-io/etcd/blob/master/Procfile
编写Procfile.learner文件
go get github.com/mattn/goreman
etcd1: etcd –name infra1 –listen-client-urls http://127.0.0.1:2379 –advertise-client-urls http://127.0.0.1:2379 –listen-peer-urls http://127.0.0.1:12380 –initial-advertise-peer-urls http://127.0.0.1:12380 –initial-cluster-token etcd-cluster-1 –initial-cluster ‘infra1=http://127.0.0.1:12380,infra2=http://127.0.0.1:22380,infra3=http://127.0.0.1:32380’ –initial-cluster-state new –enable-pprof
etcd2: etcd –name infra2 –listen-client-urls http://127.0.0.1:22379 –advertise-client-urls http://127.0.0.1:22379 –listen-peer-urls http://127.0.0.1:22380 –initial-advertise-peer-urls http://127.0.0.1:22380 –initial-cluster-token etcd-cluster-1 –initial-cluster ‘infra1=http://127.0.0.1:12380,infra2=http://127.0.0.1:22380,infra3=http://127.0.0.1:32380’ –initial-cluster-state new –enable-pprof
etcd3: etcd –name infra3 –listen-client-urls http://127.0.0.1:32379 –advertise-client-urls http://127.0.0.1:32379 –listen-peer-urls http://127.0.0.1:32380 –initial-advertise-peer-urls http://127.0.0.1:32380 –initial-cluster-token etcd-cluster-1 –initial-cluster ‘infra1=http://127.0.0.1:12380,infra2=http://127.0.0.1:22380,infra3=http://127.0.0.1:32380’ –initial-cluster-state new –enable-pprof
#proxy: bin/etcd grpc-proxy start –endpoints=127.0.0.1:2379,127.0.0.1:22379,127.0.0.1:32379 –listen-addr=127.0.0.1:23790 –advertise-client-url=127.0.0.1:23790 –enable-pprof
然后执行
goreman -f ./Procfile.learner start
快速入门
单机启动
etcd
本地集群启动
使用goreman启动本地三节点
Procfile
goreman -f Procfile start
#go get 慢解决
#go env -w GOPROXY=https://goproxy.cn,direct
查看节点列表
etcdctl –write-out=table –endpoints=localhost:2379 member list
管理节点
关闭某节点
goreman run stop etcd2
重启
goreman run restart etcd2
etcd命令行
获取etcd版本号
#目前版本是v3,之前有版本是v2
etcdctl version
写入 put
普通写入
#成功设置后返回OK
etcdctl put foo bar
带上lease
创建lease
#60的意思是60秒返回: lease 694d73f278bcd80a granted with TTL(60s)
etcdctl lease grant 60
普通写入命令加上lease id
#注意点
#1. 如果lease过期的话会提示lease不存在,抛出异常
#2. 如果lease过期,key会被自动删除
#3. lease可以续期和撤销看后面lease的管理
etcdctl put foo bar1 –lease=694d73f278bcd80a
读取 get
准备测试数据
etcdctl put foo bar
etcdctl put foo1 bar1
etcdctl put foo2 bar2
etcdctl put foo3 bar3
获取单个key
简单
etcdctl get foo
只显示value
etcdctl get foo –print-value-only
获取详细信息
etcdctl get foo -w=json
显示16进制
etcdctl get foo –hex
获取range
前闭后开range
#特别注意这个是前闭后开,即包含foo,foo1,foo2 不含foo3
etcdctl get foo foo3
前缀匹配
etcdctl get –prefix foo
前缀匹配+条数限制
etcdctl get –prefix –limit=1 foo
获取之前版本的key
etcd版本号的理解
对etcd集群键值存储的每次修改都会增加etcd集群的全局修订, 类似git commitId
取值逻辑 可以理解为是一个快照
历史版本的时候如果key不存在,则不展示
历史版本的时候key存在,则展示当时版本的value
举例
foo = bar # revision = 2
foo1 = bar1 # revision = 3
foo = bar_new # revision = 4
foo1 = bar1_new # revision = 5
如果版本号不存在的话会抛出异常 Error: etcdserver: mvcc: required revision is a future revision
语法
etcdctl get foo –prefix –rev=3
读取大于等于key的值
测试数据
a =123
b = 456
z = 789
读取命令
etcdctl get –from-key b
删除 del
普通删除
etcdctl del a
范围删除
etcdctl del foo foo4
前缀删除
etcdctl del –prefix foo
大于等于key的删除
etcdctl del –from-key b
返回删除前的值
前面的四个删除命令返回的是影响的key的数量
比如成功删了1个key,则返回1;但是没有值
需要带上旧值的话
etcdctl del –prev-kv zoo
查看变化 watch
简单用法
watch单个key
etcdctl watch foo
range key
etcdctl watch foo foo9
prefix
etcdctl watch –prefix foo
多个key
etcdctl watch -i
watch foo
watch zoo
历史变化watch
指定版本
etcdctl watch –rev=2 foo
获取上一次变化
etcdctl watch –prev-kv foo
process
语法
watch a
progress
用处
类似乐观锁
实验
客户端1 watch
etcdctl watch foo
客户端2 修改key
etcdctl put foo bar1
客户端1输出
PUT
foo
bar1
注意点
客户端1 watch可以连续监听不是一次性的 zk的watch是一次性的
合并版本 compact
etcdctl compact 5
作用
rev=1 到 rev=4的数据清除了
为了节约空间
租约 lease
新建租约
etcdctl lease grant 60
使用租约
etcdctl put –lease=32695410dcc0ca06 foo bar
撤销租约
etcdctl lease revoke 32695410dcc0ca06
如果key使用了租约,租约过期key会被删除
实验
新建租约
#694d73f278bcd85a granted with TTL(60s)
etcdctl lease grant 60lease
创建key,使用租约
etcdctl put hello world –lease=694d73f278bcd85a
撤销租约
etcdctl lease revoke 694d73f278bcd85
获取key
结果返回为空
续约
etcdctl lease keep-alive 32695410dcc0ca06
效果是如果不中断这个续约步骤会一直续约
image
image
获取租约信息 租约失效了会报错
根据租约id
etcdctl lease timetolive 694d5765fc71500b
获取租约相关的key
#输出结果lease 694d73f278bcd860 granted with TTL(60s), remaining(46s), attached keys([hello])
etcdctl lease timetolive –keys 694d5765fc71500b
etcd是一个分布式一致性键值存储系统,用于共享配置和服务发现,专注于:
·简单:良好定义的,面向用户的API (gRPC)
·安全: 带有可选客户端证书认证的自动TLS
·快速:测试验证,每秒10000写入
·可靠:使用Raft适当分布
etcd是Go编写,并使用Raft一致性算法来管理高可用复制日志,架构如下图所示:
图1 etcd架构图
2 ETCD优势
etcd可实现的功能,Zookeeper都能实现,那么为什么要用etcd而非直接使用Zookeeper呢?相较之下,Zookeeper有如下缺点:
1.复杂。Zookeeper的部署维护复杂,管理员需要掌握一系列的知识和技能;而Paxos强一致性算法也是素来以复杂难懂而闻名于世;另外,Zookeeper的使用也比较复杂,需要安装客户端,官方只提供了java和C两种语言的接口。
2.Java编写。这里不是对Java有偏见,而是Java本身就偏向于重型应用,它会引入大量的依赖。而运维人员则普遍希望机器集群尽可能简单,维护起来也不易出错。
3.发展缓慢。Apache基金会项目特有的“Apache Way”在开源界饱受争议,其中一大原因就是由于基金会庞大的结构以及松散的管理导致项目发展缓慢。
而etcd作为一个后起之秀,其优点也很明显。
1.简单。使用Go语言编写部署简单;使用HTTP作为接口使用简单;使用Raft算法保证强一致性让用户易于理解。
2.数据持久化。etcd默认数据一更新就进行持久化。
3.安全。etcd支持SSL客户端安全认证。
最后,etcd作为一个年轻的项目,正在高速迭代和开发中,这既是一个优点,也是一个缺点。优点在于它的未来具有无限的可能性,缺点是版本的迭代导致其使用的可靠性无法保证,无法得到大项目长时间使用的检验。然而,目前CoreOS、Kubernetes和Cloudfoundry等知名项目均在生产环境中使用了etcd,所以总的来说,etcd值得你去尝试。
3 安装
3.1下载
下载地址:https://github.com/coreos/etcd/releases选择合适的版本进行下载。
3.2运行
直接运行命令./etcd,或直接双击etcd.ext就可以启动了,非常简单。默认使用2379端口为客户端提供通讯,并使用端口2380来进行服务器间通讯。
3.3配置
为方便使用通常将etcd路径放在环境变量的Path下,同时在使用etcdctl(etcd的客户端命令行)之前设置环境变量ETCDCTL_API=3,否则默认的API版本为2:
etcdctlversion
etcdctlversion: 3.2.7
APIversion: 2
正确设置后,API版本编程3:
etcdctl version
etcdctl version:3.2.7
API version: 3.2
3.4使用etcdctl
通过下面的put和get命令来验证连接并操作etcd:
D:\etcd预研\etcd-v3.2.7-windows-amd64\etcd-v3.2.7-windows-amd64>etcdctlput hello world
OK
D:\etcd预研\etcd-v3.2.7-windows-amd64\etcd-v3.2.7-windows-amd64>etcdctlget hello
hello
world
3.5总结
上面操作完成之后,就有一个可运行的简单etcd服务器和一个可用的etcdctl客户端。
4 技术实现
4.1搭建本地集群
提供了Procfile用于简化搭建本地多成员集群。通过少量命令来启动多成员集群:
install goreman program to control Profile-based applications.
go get github.com/mattn/goreman
goreman -f Procfile start
注1:必须先安装go,请见章节Go语言安装 注2: 这里所说的Procfile文件是来自etcd的gitub项目的根目录下的Procfile文件,但是需要修改一下。
完成搭建后可通过使用etcdctl来和已经启动的集群交互:
etcdctl-w=”table” –endpoints=localhost:12379 member list
图2集群信息图
4.2 etcdctl和etcd交互
用户通常通过设置或者获取key的值来和etcd交互。这一节描述如何使用etcdctl来操作,etcdctl是一个和etcd服务器交互的命令行工具。这里描述的概念也适用于gRPC API或者客户端类库API。
1)写入key
应用通过写入key来储存key到etcd中。每个存储的key被通过Raft协议复制到所有etcd集群成员来达到一致性和可靠性。这是设置key foo的值为bar的命令:
etcdctl putfoo bar
OK
2)读取key
应用可以从etcd集群中读取key的值。查询可以读取单个key,或者某个范围的key。
假设etcd集群存储有下面的key:
foo = bar
foo1 = bar1
foo3 = bar3
这是读取key for的值的命令:
etcdctl getfoo
foo
bar
3)删除key
应用可以从etcd集群中删除一个key或者特定范围的key。下面是删除key foo的命令:
etcdctl delfoo
1 #删除了一个key
4)观察key的变化
应用可以观察一个key或者特定范围内的key来监控任何更新。这是在key foo上进行观察的命令:
etcdctlwatch foo
#在另外一个终端: etcdctl put foo bar
foo
bar
5)观察key的历史改动
应用可能想观察etcd中key的历史改动。例如,应用想接收到某个key的所有修改。如果应用一直连接到etcd,那么watch就足够好了。但是,如果应用或者etcd出错,改动可能发生在出错期间,这样应用就没能实时接收到这个更新。为了保证更新被接收,应用必须能够观察到key的历史变动。为了做到这点,应用可以在观察时指定一个历史修订版本,就像读取key的过往版本一样。
假设我们完成了下列操作序列:
etcdctl putfoo bar # revision = 2
etcdctl putfoo1 bar1 # revision = 3
etcdctl putfoo bar_new # revision = 4
etcdctl putfoo1 bar1_new # revision = 5
这是观察历史改动的例子:
和etcd交互
#从修订版本2开始观察key foo
的改动
etcdctlwatch –rev=2 foo
PUT
foo
bar
PUT
foo
bar_new
6)压缩修订版本
如我们提到的,etcd保存修订版本以便应用可以读取key的过往版本。但是,为了避免积累无限数量的历史数据,压缩过往的修订版本就变得很重要。压缩之后,etcd删除历史修订版本,释放资源来提供未来使用。所有修订版本在压缩修订版本之前的被替代的数据将不可访问。
这是压缩修订版本的命令:
etcdctlcompact 5
compactedrevision 5
#在压缩修订版本之前的任何修订版本都不可访问
etcdctl get–rev=4 foo
Error: rpcerror: code = 11 desc = etcdserver: mvcc: required revision has been compacted
7)授予租约
应用可以为etcd集群里面的key授予租约。当key被附加到租约时,它的生存时间被绑定到租约的生存时间,而租约的生存时间相应的被time-to-live (TTL)管理。租约的实际TTL值是不低于最小TTL,由etcd集群选择。一旦租约的TTL到期,租约就过期并且所有附带的key都将被删除。
这是授予租约的命令:
和etcd交互
#授予租约,TTL为10秒
etcdctllease grant 10
lease32695410dcc0ca06 granted with TTL(10s)
#附加key
foo到租约32695410dcc0ca06
etcdctl put –lease=32695410dcc0ca06 foo bar
OK
8)撤销租约
应用通过租约id可以撤销租约。撤销租约将删除所有它附带的key。
假设我们完成了下列的操作:
etcdctllease grant 10
lease 32695410dcc0ca06granted with TTL(10s)
etcdctl put–lease=32695410dcc0ca06 foo bar
OK
这是撤销同一个租约的命令:
etcdctllease revoke 32695410dcc0ca06
lease32695410dcc0ca06 revoked
etcdctl getfoo
#空应答,因为租约撤销导致foo被删除
9)维持租约
应用可以通过刷新key的TTL来维持租约,以便租约不过期。
假设我们完成了下列操作:
etcdctllease grant 10
lease32695410dcc0ca06 granted with TTL(10s)
这是维持同一个租约的命令:
etcdctllease keep-alive 32695410dcc0ca0
lease32695410dcc0ca0 keepalived with TTL(100)
lease32695410dcc0ca0 keepalived with TTL(100)
lease32695410dcc0ca0 keepalived with TTL(100)
…
注: 上面的这个命令中,etcdctl不是单次续约,而是etcdctl会一直不断的发送请求来维持这个租约。
4.3 Java和etcd交互
Etcd在GitHub中开放了etcdv3的Java Client jetcd,可直接使用Java和etcd服务端通讯,交互方式类似于etcdctl与etcd的交互,只不过是用代码代替命令行进行实现,API接口在当前目录。
4.4 Go和etcd交互
由于etcd是用golang编写的,所有可直接用Go语言来调用etcd的grpcAPI与etcd集群进行交互。
5 应用场景
5.1服务发现
服务发现(Service Discovery)要解决的是分布式系统中最常见的问题之一,即在同一个分布式集群中的进程或服务如何才能找到对方并建立连接。从本质上说,服务发现就是想要了解集群中是否有进程在监听udp或tcp端口,并且通过名字就可以进行查找和连接。要解决服务发现的问题,需要有下面三大支柱,缺一不可。
·一个强一致性、高可用的服务存储目录。基于Raft算法的etcd天生就是这样一个强一致性高可用的服务存储目录。
·一种注册服务和监控服务健康状态的机制。用户可以在etcd中注册服务,并且对注册的服务设置key TTL,定时保持服务的心跳以达到监控健康状态的效果。
·一种查找和连接服务的机制。通过在etcd指定的主题下注册的服务也能在对应的主题下查找到。为了确保连接,我们可以在每个服务机器上都部署一个proxy模式的etcd,这样就可以确保能访问etcd集群的服务都能互相连接。
https://www.jianshu.com/p/d63265949e52
https://jin-yang.github.io/post/golang-raft-etcd-introduce.html
1.1 分布式基础
分布式系统
分布式系统由多个不同的服务节点组成,节点与节点之间通过消息进行通信和协调。根据消息机制的不同,分布式系统的运行模型可以分为异步模型系统 和同步模型系统。
分布式系统的一致性
在一个分布式系统中,保证集群中所有节点中的数据完全相同,并且能够对某个提案达成一致,是分布式系统正常工作的核心。
但由于引入了多个节点,分布式系统中常会出现各种各样非常复杂的情况,包括节点宕机、通信受到干扰/阻断、节点间运行速度存在差异等等,即当多个节点通过异步通讯方式组成网络集群时,这种异步网络默认是不可靠的,那么,如何保证这些不可靠节点的状态最终能达成相同一致的状态,此即分布式系统的一致性问题,而解决此类问题的算法即为共识算法。
1.2 分布式理论
ACID原则
原子性(Atomicity):每次操作是原子的
一致性(Consistency):数据库的状态是一致的
隔离性(Isolation):各种操作彼此之间互相不影响
持久性(Durability):状态的改变是持久的
ACID是传统数据库常用的设计理念,追求强一致性。
BASE理论
基本可用(Basically Available):指分布式系统在出现故障的时候,允许损失部分可用性
软状态(Soft State):允许系统存在中间状态,而该中间状态不会影响系统整体可用性
最终一致性(Eventual Consistency):系统中的所有数据副本经过一定时间后,最终能够达到一致的状态
CAP理论
一个分布式系统最多只能同时满足一致性(Consistency)、可用性(Availability)和分区容忍性(Partition tolerance)这三项中的两项,其中:
一致性(Consistency):强一致性,所有节点在同一时间的数据完全一致
可用性(Availability):分布式系统可以在正常响应的时间内提供相应的服务
分区容忍性(Partition tolerance):在遇到某节点或网络分区故障的时候,仍然能够对外提供满足一致性和可用性的服务
CAP原理最早是2000年由Eric Brewer在ACM组织的一个研讨会上提出猜想,后来Lynch等人进行了证明,该原理被认为是分布式系统领域的重要原理之一。
FLP不可能原理
在网络可靠,但允许节点失效(即便只有一个)的最小化异步模型系统中,不存在一个可以解决一致性问题的确定性共识算法。
这个定理告诉我们,不要浪费时间去为异步分布式系统设计在任意场景上都能够实现共识的算法,异步系统完全没有办法保证能在有限时间内达成一致。
该原理见于由Fischer、Lynch和Patterson三位科学家于1985年发表的论文《Impossibility of Distributed Consensus with One Faulty Process》,该定理被认为是分布式系统中重要的原理之一。
1.3 共识算法
拜占庭将军问题
拜占庭将军问题是 Leslie Lamport 在《 The Byzantine Generals Problem》 论文中提出的分布式领域的容错问题,它是分布式领域中最复杂、最严格的容错模型。
在该模型下,系统不会对集群中的节点做任何的限制,它们可以向其他节点发送随机数据、错误数据,也可以选择不响应其他节点的请求,这些无法预测的行为使得容错这一问题变得非常复杂。
解决非拜占庭将军容错的一致性问题
拜占庭将军问题是对分布式系统容错的最高要求,但在日常工作中使用的大多数分布式系统中不会面对所有这些复杂的问题,我们遇到更多的还是节点故障、宕机或者不响应等情况,这就大大简化了系统对容错的要求,解决此类问题的常见算法:
paxos
raft
zab
Leslie Lamport 提出的 Paxos 可以在没有恶意节点的前提下保证系统中节点的一致性,也是第一个被证明完备的共识算法
解决拜占庭将军容错的一致性问题
解决此类问题的常见算法:
pow
pos
dpos
1.4 复制状态机
复制状态机(replicated state machine)通常是基于复制日志实现的,每一个节点存储一个包含一系列指令的日志,并且按照日志的顺序进行执行。
由于每一个日志都按照相同的顺序包含相同的指令,所以每一个节点都执行相同的指令序列后,都会产生相同的状态,即,如果各节点的初始状态一致,每个节点都执行相同的操作序列,那么他们最后能得到一个一致的状态。
而保证复制日志相同就是共识算法的工作了。
论文原文: In search of an Understandable Consensus Algorithm (Extended Version)
论文翻译:Raft 一致性算法论文译文。
Raft算法在论文中有详细的描述,建议阅读,本小节只针对算法中的关键点作部分说明。
2.1 Paxos与Raft
早在 1990 年,Leslie Lamport向 ACM Transactions on Computer Systems (TOCS) 提交了关于 Paxos 算法的论文The Part-Time Parliament。但Paxos 很难理解,而且,Paxos 需要经过复杂的修改才能应用于实际中。实际上,目前工程实践中所有声称基于Paxos实现的系统都非真正的Paxos系统。
Raft是一种在可理解性上更容易的一种一致性算法。可理解性是作者非常强调的一点,引用作者在论文中的结语:
算法的设计通常会把正确性,效率或者简洁作为主要的目标。尽管这些都是很有意义的目标,但是我们相信,可理解性也是一样的重要。在开发者把算法应用到实际的系统中之前,这些目标没有一个会被实现,这些都会必然的偏离发表时的形式。除非开发人员对这个算法有着很深的理解并且有着直观的感觉,否则将会对他们而言很难在实现的时候保持原有期望的特性。
2.2 Raft的可理解性设计
为使得大多数人能够很容易理解,Raft在设计上采用了一下两种方式:
问题分解:将问题分解成为若干个可解决的、可被理解的小问题。在 Raft 中,把问题分解成为了领导选取(leader election)、日志复制(log replication)、安全(safety)和成员变化(membership changes)。
状态空间简化:减少需要考虑的状态的数量。在 Raft 中,使用随机化选举超时来简化了领导选取算法,随机化方法使得不确定性增加,但是它减少了状态空间。
2.3 选举和日志复制
选举和日志复制的具体过程,参见论文描述,或动画演示。
2.3.1 Raft的一致性原则
选举安全原则(Election Safety):
一个任期(term)内最多允许有一个领导人被选上
领导人只增加原则(Leader Append-Only):
领导人永远不会覆盖或者删除自己的日志,它只会增加条目
日志匹配原则(Log Matching Property):
如果在不同日志中的两个条目有着相同的索引和任期号,则它们所存储的命令是相同的
如果在不同日志中的两个条目有着相同的索引和任期号,则它们之间的所有条目都是完全一样的。
领导人完全原则(Leader Completeness):
如果一个日志条目在一个给定任期内被提交,那么这个条目一定会出现在所有任期号更大的领导人中
状态机安全原则(State Machine Safety):
如果一台服务器将给定索引上的日志条目应用到了它自己的状态机上,则所有其他服务器不会在该索引位置应用不同的条目
2.3.2 Raft的安全性约束
在所有的以领导人为基础的一致性算法中,领导人最终必须要存储全部已经提交的日志条目。
Raft 算法在领导人选取部分加入了一个限制,这个限制能够保证对于固定的任期,任何的领导人都拥有之前任期提交的全部日志条目,即:
Raft 使用投票的方式来阻止没有包含全部日志条目的服务器赢得选举。RequestVote RPC 实现了这个限制:这个 RPC(远程过程调用)包括候选人的日志信息,如果它自己的日志比候选人的日志要新,那么它会拒绝候选人的投票请求。
2.4 集群成员变更
将集群成员变更纳入到算法中是Raft易于应用到实践中的关键。它支持自动化配置,即配置可以在集群运行期间进行动态变更而不影响可用性。
在Raft的论文中,简要说明了一种一次变更多个节点的方式,但是没有在安全性和可用性上给出更多的说明。而实际上,Raft的开源实现,如Etcd,都采用了更加简洁的一次只能变更一个节点的算法。
在实际工程实现过程中,Raft作者也更加推荐一次变更一个节点的方式,首先因为简单,其次所有的集群变更方式都可以通过 一次变更一个节点的方式达到任何想要的集群状态。
为了保证安全性,Raft对集群配置的调整采用了两阶段的方式。从一个配置直接切换到另一个配置是不安全的,因为不同的服务器会在不同的时间点进行切换,而在某个时间点有可能两个服务器同时被选举成为领导人。
两阶段变更的方式:
第一阶段:共同一致(过渡阶段)
领导者收到从旧配置到新配置的变更请求时,创建共同一致的日志并复制给其他节点
追随者以最新的配置做决定,领导者需要以已经提交的配置来做决定
新旧配置中所有机器都可能成为领导者
达成一致要在新旧配置上均获得大多数支持
第二阶段:切换到新配置
提交新配置的日志到所有节点,一旦新配置的日志被提交,即完成变更
2.5 日志压缩
在实际的系统中,Raft 产生的日志在持续的正常操作中不断增长,但不可以无限的增长下去。
快照(snapshot)是最简单的压缩方式。在快照中,全部的当前系统状态都被写入到快照中,存储到持久化的存储中,然后在那个时刻之前的全部日志都可以被丢弃。
Raft快照基本思想:
每个服务器独立创建快照,只包括已经被提交的日志
快照值存储了当前的状态、最后的索引位置和任期号
快照完成后,删除最后索引位置之前的所有日志和快照
领导人必须偶尔的发送快照给一些落后的跟随者
2.6 客户端交互
客户端和 Raft 进行交互包括两方面内容:
客户端是如何发现领导者的
Raft 是如何支持线性化语义(linearizable semantics)的
线性化语义,指每一次操作立即执行,在它调用和收到回复之间只执行一次
客户端发现领导者
Raft 中的客户端将所有请求发送给领导人。
当客户端启动的时候,它会随机挑选一个服务器进行通信。
如果客户端第一次挑选的服务器不是领导人,那么那个服务器会拒绝客户端的请求并且提供它最近接收到的领导人的信息(附加条目请求包含了领导人的网络地址)。
如果领导人已经崩溃了,那么客户端的请求就会超时;客户端之后会再次重试随机挑选服务器的过程。
支持线性化语义
客户端对于每一条指令都赋予一个唯一的序列号。
状态机跟踪每条指令最新的序列号和相应的响应。
如果接收到一条指令,它的序列号已经被执行了,那么就立即返回结果,而不重新执行指令。
本章总结
本章回顾了分布式理论的核心知识点,并重点梳理了Raft算法的相关内容,包括设计原则、集群变更、快照思想和客户端交互。本章没有对算法过程进行详细叙述,因为关于算法在论文中有清晰而详细的描述,强烈建议阅读论文原文。
参考资料
Raft 一致性算法论文译文:https://www.infoq.cn/article/raft-paper
共识算法:Raft:https://www.jianshu.com/p/8e4bbe7e276c
CoreOS 实战:剖析 etcd:https://www.infoq.cn/article/coreos-analyse-etcd/
微信 PaxosStore:深入浅出 Paxos 算法协议:https://www.infoq.cn/article/wechat-paxosstore-paxos-algorithm-protocol?utm_source=related_read&utm_medium=article
谈谈分布式系统中的复制:http://www.voidcn.com/article/p-sfcgwsjt-zg.html
Bully算法:http://www.distorage.com/%E5%88%86%E5%B8%83%E5%BC%8F%E7%B3%BB%E7%BB%9F%E6%8A%80%E6%9C%AF%E7%B3%BB%E5%88%97-%E9%80%89%E4%B8%BB%E7%AE%97%E6%B3%95/
分布式一致性与共识算法:https://draveness.me/consensus
membership:https://zhuanlan.zhihu.com/p/29678067
分布式系统中的FLP不可能原理、CAP理论与BASE理论:https://zhuanlan.zhihu.com/p/35608244
https://blog.51cto.com/12632727/1901317
https://yq.aliyun.com/articles/622694
http://dockone.io/article/2425
https://www.jianshu.com/p/12028d2e28a8
https://blog.csdn.net/xxb249/article/details/80787817
https://blog.csdn.net/xxb249/article/details/80779577