ceph monitor paxos的实现(一)

ceph monitor的一个主要功能是使用paxos分布式式协议维护一个key/value数据库的一致性。所使用的数据库引擎一般是leveldb。

数据库接口抽象

为了适应不同的数据库引擎, ceph定义一个MonitorDBStore类来抽象对k/v数据库的操作。对后端数据库要求是支持事务或者原子性的key/value批量更新。它定义个一 Transaction类来说明一个事务包含的所有操作,并且这个类是可以序列化和反序列化的,以便在服务器之间传送:

struct Op {
    uint8_t type;
    string prefix;
    string key, endkey;
    bufferlist bl;
}

struct Transaction {
    list<Op> ops;
    uint64_t bytes, keys;

    Transaction() : bytes(0), keys(0) {}

    enum {
      OP_PUT    = 1,
      OP_ERASE  = 2,
      OP_COMPACT = 3,
    };

例如它定义个put、erase的成员函数操作:


// 设置一个key的value
void put(string prefix, string key, bufferlist& bl) {
      ops.push_back(Op(OP_PUT, prefix, key, bl));
      ++keys;
      bytes += prefix.length() + key.length() + bl.length();
}

// 删除一个key
void erase(string prefix, string key) {
      ops.push_back(Op(OP_ERASE, prefix, key));
      ++keys;
      bytes += prefix.length() + key.length();
}

而序列化、反序列化函数:

void encode(bufferlist& bl) const {
      ENCODE_START(2, 1,bl);
      ::encode(ops,bl);
      ::encode(bytes, bl);
      ::encode(keys, bl);
      ENCODE_FINISH(bl);
}

void decode(bufferlist::iterator& bl) {
      DECODE_START(2, bl);
      ::decode(ops, bl);
      if (struct_v >= 2) {
        ::decode(bytes,bl);
        ::decode(keys, bl);
      }
      DECODE_FINISH(bl);
}

ceph 主要用这个MonitorDBStore来为各个需要使用paxos的模块提供存储,为了各个模块不相互干扰,每个模块会选择一个前缀, 所有属于这个模块的数据都使用这个prefix再加上 一个key,才构成后端数据库真正的key, 具体结构时这样的:

prefix + '\0' + key

MonitorDBStore的API 主要是

int apply_transaction(MonitorDBStore::TransactionRef t)

负责把Transaction的每一条操作以原子方式在后端数据库执行,是一个同步操作,而

queue_transaction(MonitorDBStore::TransactionRef t,Context *oncommit)

是一个异步操作,事务完成后会回调一个从Context导出的类对象,类似于C语言中的回调函数。

除此以外,MonitorDBStore还有get操作

int get(const string& prefix, const string& key, bufferlist& bl);
int get(const string& prefix, const version_t ver, bufferlist& bl);

定义迭代器用来批量获取数据,它可以指定几个prefix, 并批量把数据追加到一个Transaction里面,以便在服务器见批量传数据, 可以预见加进去的数据操作是put操作

class WholeStoreIteratorImpl : public StoreIteratorImpl {
    KeyValueDB::WholeSpaceIterator iter;
    set<string> sync_prefixes;
public:
    WholeStoreIteratorImpl(KeyValueDB::WholeSpaceIterator(
    set<string> &prefixes) : StoreIteratorImpl(), iter(iter),
   sync_prefixes(prefixes)
    { }
    bool add_chunk_entry(TransactionRef tx
                         string &prefix,
                         string &key,
                         bufferlist &value,
                         uint64_t max);
}

paxos数据在MonitorDB上的存放格式

ceph内部使用了log来记录最近一段时间的操作,log存放在leveldb中,key的前缀‘paxos’被paxos核心模块保留。每一条log一个key, key的组成是paxos前缀+
index, index是用整数来表示的,顺序增加。为了加快log的查询, 还用"first_committed" "last_committed", 两个key来表示这段log, 前者是第一条log,后者是最后一条log。

monitor启动时的数据同步

每次monitor server启动时都会按照monmap中的服务器地址去连接其他monitor服务器,并同步数据。这个过程叫做bootstrap(). bootstrap的第一个目的是补全数据,从其他服务拉缺失的paxos log或者全量复制数据库,其次是在必要时形成多数派建立一个paxos集群或者加入到已有的多数派中。

启动时将自己加入到一个外部法人集合,因为刚开始自己肯定不是在多数派中:

// i'm outside the quorum
if (monmap->contains(name))
    outside_quorum.insert(name);

然后给其它所有它知道的服务器发送探测包:

  // probe monitors
  dout(10) << "probing other monitors" << dendl;
  for (unsigned i = 0; i < monmap->size(); i++) {
    if ((int)i != rank)
      messenger->send_message(new MMonProbe(monmap->fsid, MMonProbe::OP_PROBE, name, has_ever_joined),
                  monmap->get_inst(i));
  }
  for (set<entity_addr_t>::iterator p = extra_probe_peers.begin();
       p != extra_probe_peers.end();
       ++p) {
    if (*p != messenger->get_myaddr()) {
      entity_inst_t i;
      i.name = entity_name_t::MON(-1);
      i.addr = *p;
      messenger->send_message(new MMonProbe(monmap->fsid, MMonProbe::OP_PROBE, name, has_ever_joined), i);
    }
  }

任何一个服务器收到探测包都会比较自己的最后一次修改数据的版本是否落后于正在探测的服务器的数据:

 if (!is_probing() && !is_synchronizing()) {
    // If the probing mon is way ahead of us, we need to re-bootstrap.
    // Normally we capture this case when we initially bootstrap, but
    // it is possible we pass those checks (we overlap with
    // quorum-to-be) but fail to join a quorum before it moves past
    // us.  We need to be kicked back to bootstrap so we can
    // synchonize, not keep calling elections.
    if (paxos->get_version() + 1 < m->paxos_first_version) {
      dout(1) << " peer " << m->get_source_addr() << " has first_committed " << "ahead of us, re-bootstrapping" << dendl;
      bootstrap();
      goto out;

    }
  }

对于被探测的服务器,如果最后一条log的index number都跟不上对方的第一条记录的index number,意味着已经落后太多了,中间log记录已经缺失,不可能让paxos核心部分通过log来传播数据到本进程以获得数据的最终版本,本进程需要重启bootstrap从对方主动拉数据。此时不会带对方的探测包返回应答。正常情况,我们会报告本服务器的paxos状态:

r = new MMonProbe(monmap->fsid, MMonProbe::OP_REPLY, name, has_ever_joined);
  r->name = name;
  r->quorum = quorum;
  monmap->encode(r->monmap_bl, m->get_connection()->get_features());
  r->paxos_first_version = paxos->get_first_committed();
  r->paxos_last_version = paxos->get_version();
  m->get_connection()->send_message(r);

  // did we discover a peer here?
  if (!monmap->contains(m->get_source_addr())) {
    dout(1) << " adding peer " << m->get_source_addr()
        << " to list of hints" << dendl;
    extra_probe_peers.insert(m->get_source_addr());
  }

主要内容包括我们是否是多数派的一员(通过返回多数派成员列表),以及我的paxos log的第一条记录号和最后一条记录号。

一旦一个发出探测包的服务器收到一个应答也会检查paxos log是否过时:

if (paxos->get_version() < m->paxos_first_version &&
    m->paxos_first_version > 1) {  // no need to sync if we're 0 and they start at 1.
      dout(10) << " peer paxos versions [" << m->paxos_first_version
           << "," << m->paxos_last_version << "]"
           << " vs my version " << paxos->get_version()
           << " (too far ahead)"
           << dendl;
      cancel_probe_timeout();
      sync_start(other, true);
      m->put();
      return;
    }
    if (paxos->get_version() + g_conf->paxos_max_join_drift < m->paxos_last_version) {
      dout(10) << " peer paxos version " << m->paxos_last_version
           << " vs my version " << paxos->get_version()
           << " (too far ahead)"
           << dendl;
      cancel_probe_timeout();
      sync_start(other, false);
      m->put();
      return;
    }

一种情况是我的最后一条log记录和对方的第一条log记录之间有空隙,中间有缺失,只能主动从对方拉数据,道理与上面相同。还有一种是根据配置变量paxos_max_join_drift,数据并没有缺失,但是要传的log超过一个阀值,不如全量从对方复制数据。

输入探测方发现不需要在这个阶段复制数据,并且对方就是多数派的一员,那么可以肯定它的数据是和其他服务器同步的,至少应该乐观的认为,:-) ,所以直接加入到多数派去:

if (m->quorum.size()) { // 多数派列表非空
   if (monmap->contains(name) &&
        !monmap->get_addr(name).is_blank_ip()) {
      // i'm part of the cluster; just initiate a new election
      // 我的地址他们都知道了, 通过start_election选举后可以加入多数派
      start_election();
   } else {
    // 需要通知leader把我的地址修改了,然后会probe time会超时后重启bootstrap
    dout(10) << " ready to join, but i'm not in the monmap or my addr is blank, trying to join" << dendl;
      messenger->send_message(new MMonJoin(monmap->fsid, name, messenger->get_myaddr()),
                              monmap->get_inst(*m->quorum.begin()));
   }
}
else {
    //如果对方也不是当前多数派的一员,并且是属于monmap的一员,那么把它列入到在多数派外面的人
    if (monmap->contains(m->name)) {
      dout(10) << " mon." << m->name << " is outside the quorum" << dendl;
      outside_quorum.insert(m->name);
    } else {
      dout(10) << " mostly ignoring mon." << m->name << ", not part of monmap" << dendl;
      m->put();
      return;
    }

    //一旦发现不在多数派的人数超过2F + 1 (包括自己), 说明集群不存在多数派,就可以通过选举来形成多数派
    unsigned need = monmap->size() / 2 + 1;
    dout(10) << " outside_quorum now " << outside_quorum << ", need " << need << dendl;
    if (outside_quorum.size() >= need) {
      if (outside_quorum.count(name)) {
        dout(10) << " that's enough to form a new quorum, calling election" << dendl;
        start_election();
      } else {
        dout(10) << " that's enough to form a new quorum, but it does not include me; waiting" << dendl;
      }
    } else {
      dout(10) << " that's not yet enough for a new quorum, waiting" << dendl;
    }
  }

本章总结

ceph monitor通过bootstrap过程,探测服务器列表中的各个服务器,比对log的最小记录号和最大记录号,直到本机数据的log历史(第一条记录和最后一条记录)都与所有其他服务器有交集,说明本机没有漏掉数据,从而进入多数派的形成过程,为paxos核心部分只通过传播log就可以同步数据创造条件。在boostrap阶段,服务器分析是否存在一个多数派,必要是通过进入竞选形成多数派。在这个阶段的全量同步和部分数据传输,没有介绍,因为相对简单,可以通过阅读ceph源码获得。
本章并未涉及ceph paxos设计最核心部分,有时间再介绍。

时间: 2024-10-29 16:29:06

ceph monitor paxos的实现(一)的相关文章

ceph monitor功能的兼容性管理

软件需要兼容旧版本 ceph是在一个不断进化的软件,会增加功能也会取消旧的功能,如何在ceph monitor的不同版本间保持兼容或者防止不兼容而产生错误,是需要认真思考的问题.而我们阅读代码时往往忽略了兼容性管理的代码,而重点关心它的业务代码,所以这里特别写一篇文章谈谈兼容性的管理. ceph monitor兼容主要包括两方面: 通讯时检查对方的兼容性 代码访问本机数据时的兼容性 Messenger的创建和功能位features的设置 ceph使用原生的Messenger的方式来通讯,在通讯开

about ceph monitor&#039;s consistency

    ceph 的mon节点维护了整个ceph 存储集群的map状态.     因为mon节点是多个的, 所以要求所有mon节点的map状态一致, mon使用paxos算法(分布式一致性算法)来修改map的状态, 确保所有mon节点的map数据一致.     因此mon节点并不是通过集群配置文件如ceph.conf来保持map一致的, 而是map文件.     每次map文件变更前, 需要多数monitor节点同意, 确保多数或所有(quorum)节点的map文件一致, 并且map文件是增量更

openstack 与 ceph (monitor初始化)

Monitor 节点初始化 执行下面步骤 操作系统初始化 当前环境中, ceph 存储集群统一部署 centos 7.1 操作系统, 并在 /etc/hosts 中登记集群中的服务器主机名, 并完成时间同步 主机名验证 [root@hh-yun-ceph-cinder015-128055 ~]# hostname hh-yun-ceph-cinder015-128055.vclound.com [root@hh-yun-ceph-cinder017-128057 ~]# hostname hh-

Ceph分布式存储学习指南2.3 纵向扩展Ceph集群—添加monitor和OSD

2.3 纵向扩展Ceph集群-添加monitor和OSD 现在,我们有了一个单节点的Ceph集群.我们应该扩展它,让它成为一个分布式的.可靠的存储集群.要纵向扩展这个Ceph集群,我们应该添加更多的monitor节点和OSD.根据前面的计划,我们将把ceph-node2和ceph-node3机器同时作为monitor和OSD节点加入到集群中. 2.3.1 添加Ceph monitor Ceph存储集群需要最少一个monitor处于运行状态.要提供高可用性,则需要大于1的奇数个monitor,例如

Ceph分布式存储实战.

云计算与虚拟化技术丛书 Ceph分布式存储实战 Ceph中国社区 著 图书在版编目(CIP)数据 Ceph分布式存储实战/Ceph中国社区著. -北京:机械工业出版社,2016.11 (云计算与虚拟化技术丛书) ISBN 978-7-111-55358-8 I. C- II. C- III. 分布式文件系统 IV. TP316 中国版本图书馆CIP数据核字(2016)第274895号 Ceph分布式存储实战 出版发行:机械工业出版社(北京市西城区百万庄大街22号 邮政编码:100037) 责任编

Ceph分布式存储实2.2 RADOS架构

2.2 RADOS架构 RADOS系统主要由两个部分组成,如图2-2所示. 1)OSD:由数目可变的大规模OSD(Object Storage Devices)组成的集群,负责存储所有的Objects数据. 2)Monitor:由少量Monitors组成的强耦合.小规模集群,负责管理Cluster Map.其中,Cluster Map是整个RADOS系统的关键数据结构,管理集群中的所有成员.关系和属性等信息以及数据的分发.   图2-2 RADOS系统架构图示 对于RADOS系统,节点组织管理和

ceph install

Ceph : performance, reliability and scalability storage solution Contents 1 Introduction 1.1 Testing case 2 Installation 2.1 First node 2.2 Other nodes 3 Deploy 3.1 Cluster 3.2 Monitor 3.2.1 Add the first monitor 3.2.2 Add a monitor 3.2.3 Remove a mo

开发漫谈:Ceph什么鬼?产品架构解析

在昨天的文章中,笔者介绍到Ceph的主要目标是提供高可扩展性和提供对象存储.块存储和文件系统的存储机制,是一个高扩展.高容错.高一致的开源分布式存储平台. 那么,Ceph的产品架构究竟是什么样的呢?Ceph的产品生态优势什么样的呢? Ceph架构图 从Ceph的架构图我们可以看到,从下到上,一共分为四个层次:基础存储系统RADOS(Reliable, Autonomic, Distributed Object Store,即可靠的.自动化的.分布式的对象存储):基础库librados:高层应用接

Ceph分布式存储学习指南3.2 Ceph RADOS

3.2 Ceph RADOS RADOS是Ceph存储系统的核心,也称为Ceph存储集群.Ceph的所有优秀特性都是由RADOS提供的,包括分布式对象存储.高可用性.高可靠性.没有单点故障.自我修复以及自我管理等.因此,RADOS层在Ceph存储架构中扮演着举足轻重的角色.Ceph的数据访问方法(如RBD.CephFS.RADOSGW和librados)的所有操作都是在RADOS层之上构建的. 当Ceph集群接收到来自客户端的写请求时,CRUSH算法首先计算出存储位置,以此决定应该将数据写入什么