基于版本一致性算法


摘要

本文主要讨论基于版本的一致性算法,实现高可用和低延迟并且Lazy Read式的强数据一致性。可以利用在分布式的文件系统的元数据管理。它主要解决以下内容:

  1. 避免使用锁提供一致性

  2. 避免复制日志提供一致性

  3. 快速故障恢复

  4. 自由集群扩容或缩容

缺点:无法提供高并发,需要使用其它机制提供高负载能力。

简介

基于版本一致性算法是一个类Paxos算法,File Store是由该算法实现的分布式文件管理系统(以下简称系统),本文以该系统为例说明该算法。

该算法定义了三个阶段:选举期,平稳期和故障期。系统大部分时间是在平稳期,就算是发生故障时,系统也不会立刻进入选举期,只有在有读写操作失败后才会由外部触发新的选举。选举成功后,系统会立刻恢复到平稳期。

该系统中所有服务节点都是平等的Citizen,但他们会在不同时期担任以下三种角色中的一种:

Leader - 负责写操作,通知到所有节点备份和节点状态管理

Follower - 负责任何节点发起的选举,负责数据的备份和读取

Candidate - 负责选举

 

选举

理论上任何一个节点都可以随时将自己设置为候选人从而发起选举。但通常只有外部访问失败时才需要通知集群中某个节点发起选举。

public bool StartElection()
{
   //使用一个新的选举号
   this.ElectionNo++;
   if (SendMessage("Vote", this))
  {
       this.Type = CitizenType.Leader;
       return true;
  }
   return false;
}

每次选举最多只能产生一个Leader。为了实现这个要求,每个节点都持有一个线性增长的选举号。节点发起选举时首先将自己的选举号增加1个点,然后发起选举给到所有节点。只有得到大多数节点的同意后选举才算成功,否则选举失败。

所有节点在接收到选举请求时都需要立刻执行选举操作:

public bool DoVote(int election_no, Citizen citizen)
{
   if (election_no > this.VotedNo)
  {
       this.VotedNo = election_no;
       this.Type = CitizenType.Citizen;
       this.Leader = citizen;
       return true;
  }
   return false;
}

接收到选举号后比较自己已经投出的选举号,如果是新选举号则同意这次选举。

如果同一时间有多个节点发起选举,选民会第一时间更新自己投出的选举号(原子操作)因此不会同时同意两个候选人发起的选举。就算毫秒间先后同意了两个候选人,然后候选人做后续的写操作时也会失败,从而修改自身的错误信息。

 

备份

只有Leader才有数据的写入权,当一个Leader接受到Client的写请求时:

public bool Write(int index, Bucket bucket)
{
   bucket.LeaderNo = this.ElectionNo;
   bucket.VersionNo++;
   if (SendMessage("Write", bucket))
  {
       //save data to local
       return true;
  }
   this.Type = CitizenType.Citizen;
   return false;
}

Leader首先会把数据加上自己当选的选举号和数据的版本号,然后通知所有节点写入备份,只有得到大多数节点的认可才更新本地信息,否则修正自己的角色。

所有节点在接收到备份请求时都需要立刻执行备份操作:

public bool DoWrite(Bucket bucket, Citizen citizen)
{
   if (bucket.LeaderNo < this.VotedNo)
  {
       return false;
  }
   if (bucket.LeaderNo> this.VotedNo)
  {
       this.VotedNo = bucket.LeaderNo;
       this.Type = CitizenType.Citizen;
       this.Leader = citizen;
  }
   //save bucket to local disk
   return true;
}

首先比较数据的创建者是否为自己投的Leader,只有自己认可的或高于自己认可的Leader的请求才处理,否则不执行任何操作。另外高于自己认可的Leader的请求时,需要同步自己的Leader信息。

 

读取

因为Leader随时都可能更改,但为了提高并发能力,数据一致性的确认只能在读取时处理,所以数据读取时,首先要确保本地数据是最新的,如果不是最新需要通知其它节点读取最新的。


public Bucket Read(int index)
{
   if (!EnsureRecovery(index))
  {
       return null;
  }
   if (SendMessage("Read", index))
  {
       //get bucket from local disk
       return new Bucket();
  }
   else
  {
       this.Type = CitizenType.Citizen;
       return null;
  }
}

public bool EnsureRecovery(int index)
{
   // get bucket from local disk;
   var bucket = new Bucket();
   if (this.VotedElectionNo == bucket.LeaderNo)
  {
       // only indicate it is recoveried
       return true;
  }
   if (SendMessage("Read", index))
  {
       // get max leader no from majority
       var max_leader_no = 0;
       this.VotedElectionNo = max_leader_no;
       bucket.LeaderNo = max_leader_no;
       bucket.VersionNo = 0;
       //save bucket to local disk
       return true;
  }
   return false;
}

首先判断数据创建者是否为自己认可的Leader,如果不是自己认可的Leader创建,则立刻通知节点同步数据。需要注意的是,就算是自己认可的Leader创建的数据也不能说明本地的数据已经最新,可能该节点已经失联,也可能该节点在选举和写数据时失联。因此还需要向所有节点发送读数据确认数据已经最新,然后才能返回数据。

所有节点在接收到读取请求时都需要立刻执行读取操作:

public Tuple<bool, Bucket> DoRead(int index, int versionNo, Citizen citizen)
{
   //get bucket from local
   var bucket = this.buckets[index];

   if (versionNo < bucket.LeaderNo)
  {
       return new Tuple<bool, Bucket>(false, bucket);
  }
   if (versionNo == bucket.LeaderNo)
  {
       return new Tuple<bool, Bucket>(true, null);
  }
   return new Tuple<bool, Bucket>(false, null);
}

读取本地数据并与要读取数据的版本号比较,如果本地版本比较高则返回本地数据。如果版本相同只返回true,否则证明自己的数据已经无效。注意发现数据不同步后,并不需要及时解决数据一致性,直到有读取操作时才去确保数据一致性。

因此写入和读取都需要得到大多数节点的确认,因此不存在数据一致性问题。最差的情况是,多数节点在读取时失联,从而会导致读取失败,这种情况只能由客户端重试。

扩容

为了防止不一致性,该系统的节点扩/缩容需要缓慢进行。

扩容时,首先新节点的加入必须得到所有节点的确认,即所有节点都已经成功更新本地配置。然后确保只有一个节点加入成功后才能增加新的节点。

缩容时,首先是停掉一个节点,然后再通知所有节点移除该节点,并得到所有节点确认。

注意:无论缩容还是扩容都需要得到所有节点的确认,否则不能进行下一步操作。

 

优化

该系统理论上是强数据一致性的,由上面流程也能看出,所有的读取都需要得到大多数节点的确认,这需要消耗多次的网络连接。因此建议:

  1. 系统需要尽量保证节点间的通讯稳定和高速

  2. 系统保存的数据应该尽量少,比较适合保存元数据

  3. 应对读多写少,而且对数据一致性要求不高的情况下,可以修改大多数读确认的定义

 

原文链接:http://blog.zhumingwu.cn/2018/08/15/%E5%9F%BA%E4%BA%8E%E7%89%88%E6%9C%AC%E4%B8%80%E8%87%B4%E6%80%A7%E7%AE%97%E6%B3%95.html

posted @ 2018-10-25 10:30  zhumingwu  阅读(493)  评论(0编辑  收藏  举报