Ceph快照的原理和实现

来源:互联网 发布:淘宝 5元红包 买什么 编辑:程序博客网 时间:2024/06/02 02:28

ceph的基本的特性之一,就是支持rbd的snapshot和clone功能。Ceph都可以完成秒级别的快照。

ceph支持两种类型的快照,一种poo snaps,也就是是pool级别的快照,是给整个pool中的对象整体做一个快照。另一个是self managed snaps. 目前rbd的快照就这类,用户写时必须自己提供SnapContex信息。

这两种快照时互斥的,两种不能同时存在。

无论是pool级别的快照,还是rbd的快照,其实现的基本原理都是相同的。都是基于对象COW(copy-on-write)机制。本文举例时都用rbd实例。

基本概念和数据结构

head对象:也就是对象的原始对象,该对象可以写操作
snap对象:对某个对象做快照后的通过cow机制copy出来的快照对象,该对象只能读,不能写
snap_seq or seq:快照序号,每次做snapshot操作,系统都分配一个相应快照序号,该快照序号在后面的写操作的实现发挥重要的作用。
snapdir对象:当head对象被删除后,仍然有snap和clone对象,系统自动创建一个snapdir对象,来保存SnapSet信息。

在rbd端,librados/IoCtxImpl.h 定义了snap相关的数据结构

struct librados::IoCtxImpl {  ......  snapid_t snap_seq;  ::SnapContext snapc;  ......}struct SnapContext {  snapid_t seq;            // 'time' stamp  vector<snapid_t> snaps;  // existent snaps, in descending order}

SnapContext 数据结构用来在客户端(rbd端)保存Snap相关的信息。这个结构持久化存储在rbd的元数据中。

  • seq 为最新的快照序号
  • snaps 降序保存了该rbd的所有的快照序号。

IOCtxImpl里的 snapid_t snap_seq 一般也称之为snap_id, 如果open时,如果是snapshot,那么该snap_seq就是该snap对应的快照序号。否则snap_seq就为CEPH_NOSNAP(-2),来表示操作的不是快照。

struct SnapSet {  snapid_t seq;       //最新的快照序号  bool head_exists;   //head对象是否存储  vector<snapid_t> snaps;    // descending   //所有的快照序号  vector<snapid_t> clones;   // ascending    //所有的clone的对象  map<snapid_t, interval_set<uint64_t> > clone_overlap;   // clone对象之间overlap的部分  map<snapid_t, uint64_t> clone_size;  //clone对象的size}

数据结构SnapSet用于保存server端,也就是OSD端与快照相关的信息。seq保存最新的快照序号,head_exists保存head对象是否存在,snaps保存所有的快照序号。clones保存所有快照后的写操作需要clone的对象记录。

这里特别强调的是 clones 和 snaps的区别。并不是每次打快照后,都要拷贝对象,只有快照后,有写操作,才会触发copy操作,也就是clone操作。

clone_overlap 保存本次clone和上次clone的 overlap的部分,也就是重叠的部分。clone后,每次写操作,都要维护这个信息。这个信息用于以后数据恢复优化等其他操作才会用在到,这里不涉及。clone_size 保存每次clone后的对象的size

SnapSet数据结构持久化保存在head对象的xattr的扩展属性中。

  • 在Head对象的xattr中保存key 为”snapset”,value为SnapSet序列化的值
  • 在snap对象的xattr中保存key为user.cephos.seq的 snap_seq 值

RBD快照的原理

Rbd创建快照

Rbd快照基本步骤如下:

  1. 向monitor 发送请求,获取一个最新的快照序号snap_seq
  2. 把该image该快照的 snap_name 和 snap_seq 保存到rbd的元数据中

在rbd的元数据里,保存了所有快照的名字和对应的snap_seq号,并不会触发OSD端数据的操作,所以非常快。

快照后的写

当对一个image做了一个快照后,要对该image写,由于快照,需要copy-on-write机制。

客户端写操作,必须带SnapContex结构,也就是需要带最新的快照序号seq和所有的快照序号snaps. 在OSD端,对象的Snap相关的信息保存在SnapSet数据结构中,当写操作发生时,需要做如下的处理。

规则1
如果head对象不存在,创建该对象并写入数据,用SnapContext相应的信息更新SnapSet的信息。也就是snapSet的seq值设置为SnapContex的seq值,SnapSet中的snaps的值,设置为SnapContex的snaps值
规则2
如果写操作带的SnapContex的seq值,也就是最新的快照序号小于SnapSet的seq值,也就是OSD端保存的最新的快照序号, 直接返回-EOLDSNAP的错误。

ceph客户端始终保持最新的快照序号。如果客户端不是最新的快照需要,可能的情况是,多个客户端的情形下,其它的客户端有可能创建了快照,本客户端有可能没有获取到最新的快照序号,需客户端更新为最新的快照序号。

ceph也有一套Watcher回调通知机制,当别的的客户端做了快照,产生了以新的快照序号,当该客户端访问,osd端知道最新快照需要变化后,通知相应的连接客户端更新最新的快照序号。如果没有及时更新,也没有太大的问题,客户端更新重新发起写操作。

规则3
如果写操作带的SnapContex的seq值 等于SnapSet的seq值,做正常的的读写。

规则4
如果写操作带的SnapContex的seq值 大于SnapSet的seq值:

  1. 用SnapContex的 seq和 snaps更新 SnapSet的的seq和snaps的值
  2. 对当前head对象做copy-on-write操作, clone出一个新的快照对象,该快照对象的snap序号为最新的序号,并把clone操作记录在clones列表里,也就是把最新的快照序号加入到clones队列中。
  3. 写入最新的数据到head对象中
操作序列 操作 Rbd 保存的元数据 OSD对象 1 write data1 snapContext{ seq=0,snaps={} } SnapSet={ seq = 0,head_exists= true, snaps={},clones={} },obj1_head(data1) 2 create snapshot name=”snap1” (“snap1”,1) 3 write data2 snapContext{ seq=1,snaps={1} } snapSet={ seq = 1,head_exists= true,snaps={1},clones={1},obj1_1(data1) obj1_head(data2) 4 create snapshot name=”snap3” (“snap1”,1),(“snap3”,3) 5 create snapshot name=”snap6” (“snap1”,1)(“snap3”,3)(“snap6”,6) 6 write data3 snapContext={ Seq=6, snaps={6,3,1}} snapSet={seq = 6,head_exists= true,snaps {6,3,1},clones{1,6},obj1_1(data1),obj1_6(data2) obj1_head(data3)

示列1 快照写操作示列

如示列1所示:

  1. 在操作1里,第一次写操作,写入的数据为data1,SnapContext的初始seq为0, snaps列表为空, 按规则1,OSD端创建并写数据,用SnapContext的数据更新SnapSet中的数据。
  2. 在操作2里,创建了该rbd一个快照,名字为snap1, 并向monitor申请分配一个快照序号为:1
  3. 操作3里,写入数据data2,写操作带的SnapContext,seq为1, snaps列表为{1}, 在OSD端处理,此时SnapContext的seq大于SnapSet的seq,操作按照规则4:

    • 更新SnapSet的seq为1,snaps更新为{1}
    • 创建快照对象obj1_1,拷贝当前head数据data1到快照对象obj1_1(快照对象名字下划线为快照序号,ceph目前快照对象的名字中含有快照序号),此时快照对象obj1_1的数据为data1,并在clones中添加clone记录, clones={1}
    • 向head对象obj1_head中写入数据data2
  4. 操作4,操作5连续分别做了两次快照操作,快照的名字为snap3,snap6,分配的快照序号分别为3,6 (在Ceph里,快照序号是有monitor分配的全局唯一,所有单个rbd的快照序号不一定连续)

  5. 操作6,写入数据data3,此时写操作带的SnapContext为,seq=6, snaps为{6,3,1}共三个快照。此时SnapSet的seq为1,操作按规则4:
    • 更新SnapSet的seq为6,snaps为{6,3,1}
    • 创建快照对象obj1_6,拷贝当前head数据data2到快照对象obj1_6,更新clones={1,6}
    • 向head对象obj1_head中写入数据data3

快照的读

快照读取时,输入参数为rbd 的name和快照的名字。Rbd的客户端通过访问rbd的元数据,来获取快照对应的snap_id,也就是快照对应的snap_seq

在osd端,获取head对象保存的SnapSet数据结构。这个需要根据snaps和clones来计算快照所对应的正确的 快照对象。

例如在示列1中,最后的SnapSet ={seq=6, snaps={6,3,1},clones={1,6}, ……}, 这时候读取seq为 3的快照, 由于seq 为3 的快照并没有写入数据,也就没有对应的clone对象,通过计算可知,seq为3的快照和snap 为 6的快照对象数据是一样的,所有就读取obj1_6对象

可知,osd端根据SnapSet中保存的snaps和clones记录,不难推测具体快照对应的数据对象。

快照的rollback

快照的回滚,就是把当前的head对象,回滚到某个快照对象。 具体操作如下:

  1. 删除当前head对象的数据
  2. copy 相应的snap对象到head对象

快照的删除

删除快照时,直接删除SnapSet相关的信息,并删除相应的快照对象。需要计算该快照
是否被其它快照对象共享。例如在示列1中,obj1_6 是被 snap为3 和snap 为6 的对象共享,他们数据是相同的。当删除snap为6的快照时,不能直接删除obj1_6就需要计算,做相应的处理。

ceph的删除是延迟删除,并不直接删除。

3 0