• etcd


    二、etcd介绍

    2.1 etcd发展背景与相关竞品介绍

    2013年CoreOS创业团队在构建一款开源,轻量级的操作系统ContainerLinux时,为了应对用户服务多副本之间协调的问题,自研开发的一款用于配置共享和服务发现的高可用KV分布式存储组件——ETCD。
    下面我们也针对Zookeeper和Consul两个选型做了一下对比:·       ZooKeeper
    ZooKeeper从高可用性,数据一致性,功能这三个方面而言是完全符合需求的,但CoreOS还是坚持自研etcd的原因总结有以下两点:1.  ZooKeeper不支持通过API安全地变更成员,需要人工修改节点配置并重启进程.如果操作有误,有可能导致脑裂等线上故障,同时CoreOS对适配云环境,集群规模的平滑调整,运行时配置的在线变更都是有期望目标的,这方面ZooKeeper的维护成本比较高。2.  高负载读写性能,ZooKeeper在大规模的实例连接情况下性能表现并不佳。
    etcd名字是由“/etc”文件夹和”d”分布式系统组成。“/etc”文件夹是用来存储单系统配置数据的,而“etcd”用于存储大规模分布式系统的配置数据,etcd集群可提供高稳定性,高可靠性,高伸缩性和高性能的分布式KV存储服务。etcd是基于复制状态机实现的,由Raft一致性模块,日志模块,基于boltdb持久化存储的状态机组成,可应用于分布式系统的配置管理,服务发现,分布式一致性等等。
    ZooKeeper与etcd一样,可解决分布式系统一致性和元数据存储等问题,但是etcd相较于ZooKeeper有以下几点优势:1.  动态集群成员关系重新配置2.  高负载下稳定的读写能力3.  多版本并发控制数据模型4.  可靠的键监控5.  Lease(租约)原语将连接和会话分离6.  分布式锁保证API安全性7.  ZooKeeper使用自己的RPC协议,使用受限;而etcd客户端协议是基于gRPC的,可支持多种语言。 ·       ConsulConsul与etcd解决的是不同的问题,etcd用于分布式一致性KV存储,而Consul侧重于端到端的服务发现,它提供了内置的健康检查,失败检测和DNS服务等等,另外Consul通过RESTfulHTTPAPIs提供KV存储能力.但是当KV使用量达到百万级时,会出现高延迟和内存压力等问题。
    一致性算法方面,etcd、Consul基于Raft算法实现数据复制,ZooKeeper则是基于Zab算法实现。Raft算法由Leader选举,日志同步,安全性组成,而Zab协议则由Leader选举、发现、同步、广播组成。分布式CAP方面,etcd、Consul和ZooKeeper都是CP系统,发生网络分区时,无法写入新数据。
    下表是针对三者的关键能力做了一下对比分析:

     

    2.2 etcd核心技术介绍

    基于Raft协议实现数据高可用和强一致性

    早期数据存储服务引入多副本复制技术方案来解决单点问题,但是无论是主从复制还是去中性化复制,都存在一定的缺陷。主从复制运维困难,且一致性与可用性难以兼顾;去中心化复制,存在各种写入冲突问题需要业务处理。而分布式一致性算法,正是解决多副本复制存在问题的关键。分布式一致性算法,又称为共识算法,最早是基于复制状态机背景下提出来的。Paxos作为第一个共识算法,过于复杂,不容易理解,难以在工程上落地。斯坦福大学的Diego提出的Raft算法,通过将问题拆解为三个子问题,易于理解,降低了工程落地难度。这三个子问题是:Leader选举,日志复制,安全性。

    Leader选举

    etcd(版本3.4+)中Raft协议定义集群节点有4种状态:Leader、Follower、Candidate、PreCandidate。
    正常情况下,Leader节点会按照心跳间隔时间,定时广播心跳消息给Follower节点,以维持Leader身份。Follower收到后回复心跳应答包消息给Leader。Leader都会带有一个任期号(term),任期表示从一次选举开始,赢得选举的节点在该任期内担当Leader。任期号单调递增,在Raft算法中充当逻辑时钟,用于比较各个节点数据新旧,识别过期Leader等等。
    当Leader节点异常时,Follower节点会接收Leader的心跳消息超时,当超时时间大于竞选超时时间后,会进入PreCandidate状态,不自增任期号,仅发起预投票(民意调查,防止由于节点数据远远落后于其他节点而发起无效选举),获得大多数节点认可后,进入Candidate状态.进入Candidate状态的节点,会等待一个随机时间,然后发起选举流程,自增任期号,投票给自己,并向其他节点发送竞选投票信息。
    当节点B收到节点A竞选消息后,有2种情况:1.  节点B判断节点A的数据至少和自己一样新,节点A任期号大于节点B任期号,并且节点B未投票给其他候选者,即可投票给节点A,节点A获得集群大多数节点支持,可成为新Leader。2.  如果节点B也发起了选举,并投票给自己,那么它将拒绝投票给节点A。此时若没有节点可以得到大多数投票支持,则只能等待竞选超时,开启新一轮选举。

    日志复制

    Raft日志结构如下图所示:


    Raft日志由有序索引的一个个条目组成,每个日志条目包含了任期号和提案内容.Leader通过维护两个字段来追踪各个Follower的进度信息.一个是NextIndex,表示Leader发送给该Follower节点的下一个日志条目索引;另一个是MatchIndex,表示该Follower节点已复制的最大日志条目索引。
    本文以Client提交“hello=world”提案,至接收到响应的整个流程为例,简单介绍etcd日志复制流程:1.  当Leader接收到Client提交的提案信息后,生成日志条目,同时遍历各个Follower的日志进度,生成对各个Follower追加日志的RPC消息;2.  通过网络模块将追加日志的RPC消息广播给各个Follower;3.  Follower接收到追加日志消息并持久化之后,回复Leader已复制最大日志条目索引,即MatchIndex;4.  Leader接收到Follower应答后,更新对应Follower的MatchIndex;5.  Leader根据各个Follower提交的MatchIndex信息,计算出日志条目已提交索引位置,该位置代表日志条目被一半以上节点持久化;6.  Leader通过心跳告知各个Follower已提交日志索引位置;7.  当Client的提案,被标识为已提交后,Leader回复Client该提案通过。通过以上流程,Leader同步日志条目给各个Follower,保证etcd集群的数据一致性。

    安全性

    etcd通过给选举和日志复制增加了一系列规则,来保证Raft算法的安全性。选举规则:1.  一个任期号,只能有一个Leader被选举,Leader选举需要集群一半以上节点支持;2.  节点收到选举投票时,如果候选者最新日志条目的任期号小于自己,拒绝投票,任期号相同但是日志比自己短,同样拒绝投票。日志复制规则:1.  Leader完全特性,如果某个日志条目在某个任期号中已被提交,则这个日志条目必然出现在更大任期号的所有Leader中;2.  只附加原则,Leader只能追加日志条目,不能删除已持久化的日志条目;3.  日志匹配特性,Leader发送日志追加信息时,会带上前一个日志条目的索引位置(用P表示)和任期号,Follower接收到Leader的日志追加信息后,会校验索引位置P的任期号与Leader是否一致,一致才能追加。

     

    boltdb存储技术

    ectd的另一个核心技术是boltdb存储,提供高效的b+树的检索能力,同时支持事务操作,他是支撑etcd高性能读写的关键能力之一。boltdb的实现参见了LMDB(LightningMemory-MappedDatabase)设计思路,基于高效快速的内存映射数据库方案.基于B+树的结构设计。数据文件设计上bolt使用一个单独的内存映射的文件,实现一个写入时拷贝的B+树,这能让读取更快。而且,BoltDB的载入时间很快,特别是在从crash恢复的时候,因为它不需要去通过读log(其实它压根也没有)去找到上次成功的事务,它仅仅从两个B+树的根节点读取ID。

     

    文件存储设计

    由于采用了单文件映射存储,所以bolt对文件按指定长度进行分块处理,每块存储不同的内容类型。默认使用4096字节的长度进行分块。每一块的开头有单独的pageid(int64)标识。
    文件块的类型有以下几种:

    类型

    Flag标识

    个数

    长度

    说明

    meta

    0x04

    2

    80字节。其余为空

    元数据信息。包含存储B+树root buck位置,可用块的数量freelist,当前最大块的offset等。分为metaA与metaB, 用来控制进行中的事务与已完成的事务(写事务只有一个进行中).  根据meta中的txid的值的大小来判断当前生效的是哪个meta。(txid会根据事务操作递增)

    freelist

    0x10

    1或多个

    Overflow引用

    变长

    Freelist用于管理当前可用的pageid列表。由Meta元数据信息中的freelist字段来定位所在的pageid位置。

    【data】bucket

    0x01

    1或多个

    Overflow引用

    变长

    存储bucket名称数据,bucket名称也是采用B+树结构存储。根root bucket的pageid由meat的root字段指定

    【data】branch

    0x01

    1或多个

    Overflow引用

    变长

    存储分支数据内容。分支数据结构中只有key信息,没有value信息。指向的都是下一级节点的pageid信息

    【data】leaf

    0x02

    1或多个

    Overflow引用

    变长

    存储叶子数据内容。

    数据文件全景结构


    说明:

    • metapage固定在page0与page1位置
    • Pagesize大小固定在4096字节
    • bolt的文件写入采用了本地序(小端序)的模式,比如16进制0x0827(2087)写入的内容为2708000000000000

    单文件方案的优势就是不需要做文件的合并删除等操作,只需要在原文件上追加扩展长度就可以了。

     

    查询设计

    boltdb提供了非常高效的查询能力,可以先看一下它的对象设计:


    从对象设计上,boltdb在加载时,会先loadmeta数据进内存,然后根据bucket,来定位数据块所在的位置,然后再根据key的值,来定位branchnode的位置,然后定位到叶子值节点。 我们以查询为例,来讲解一下,下面是一个基本的查询示例代码:

    tx, err := db.Begin(true) // 开启事务
     
      if err != nil {
     
          return
     
      }
     
      b := tx.Bucket([]byte("MyBucket")) // 根据名称查询bucket
     
      v := b.Get([]byte("answer20")) // 根据key进行查询 
     
      fmt.Println(string(v))
     
      tx.Commit()

    对应上面的代码,下面的序列图,可以更详细的了解一次查询的操作流程:

    上面最关键的代码就是search方法,下面是主要的代码片断,已添加了注释说明方便阅读。

    func (c *Cursor) search(key []byte, pgid pgid) {
        p, n := c.bucket.pageNode(pgid)
        if p != nil && (p.flags&(branchPageFlag|leafPageFlag)) == 0 {
            panic(fmt.Sprintf("invalid page type: %d: %x", p.id, p.flags))
        }
        // 把当前查询节点(page,node)压入栈
        e := elemRef{page: p, node: n}
        c.stack = append(c.stack, e)
     
        // If we're on a leaf page/node then find the specific node.
        if e.isLeaf() {
            c.nsearch(key)
            return
        }
        // if node cached seach by node's inodes field
        if n != nil {
            c.searchNode(key, n)
            return
        }
        // recursively to load branch page and call search child node again
        c.searchPage(key, p)
    }

    https://mp.weixin.qq.com/s/1VmMZlMEv-In9QKYeYOjiA

     
  • 相关阅读:
    杂谈:用 Sublime Text 2 写 ActionScript3
    Sublime写MarkDown实时预览
    Cocos2d-Lua (练手) 微信打飞机
    Lua 性能相关笔记
    Lua 学习笔记(十一)元表与元方法
    Lua 学习笔记(十)数据结构
    Lua 学习笔记(九)协同程序(线程thread)
    Lua 学习笔记(八)错误(error)
    Lua 学习笔记(七)编译、执行外部代码块
    Lua 学习笔记(六)迭代器
  • 原文地址:https://www.cnblogs.com/duanxz/p/15798298.html
Copyright © 2020-2023  润新知