经常有开发者在邮件列表中会问到Ceph Snapshot的实现方式,受限于目前有限的实现文档和复杂的代码结构和代码量,弄清楚Ceph Snapshot并不是一件容易的事。正好最近在重构Ceph存储引擎层的DBObjectMap,涉及到处理Snapshot间clone的问题,重新梳理了一次在Ceph IO路径中占了非常大比重的snapshot相关代码流程,在这里并不会重点展现里面的代码或者数据结构,而是从高层设计角度展现Snapshot的实现。

在阅读下文前务必先了解Ceph的基本情况和使用场景。Why Ceph and how to use Ceph?

Ceph Snapshot使用场景

多数人尝试Ceph的Snapshot往往从Ceph的RBD库入手,也就是所谓的块存储。利用librbd通过简单的命令可以快速创建卷和Snapshot。

rbd create image-name –size 1024 -p pool

rbd snap create pool/image-name –snap snap-name

第一条命令创建了一个名为”image-name”的卷,在这个过程中librbd库只是创建了一个metadata而没有实际向Ceph申请空间。关于librbd如何利用Rados实现块存储和管理更多的细节会在以后的文章中讲到,这里先留个坑。

第二条命令对”image-name”卷创建了一个名为”snap-name”的Snapshot,创建以后,对”image-name”卷的任意写操作之后都可以在任意时间回滚到创建”snap-name”的Snapshot时的数据。如下面这条命令

rbd snap rollback pool/image-name –snap snap-name

在用户实际尝试过程中,会发现Ceph对于卷的操作和管理非常轻量,任意时刻,任意卷大小,任意集群大小的卷创建都是相同的操作量级,在其背后实质上也是完全相同的操作。开发者会对如何实现Snapshot更敢兴趣,因为Snapshot的实现方式决定了如何有效的使用Snapshot。

Ceph Snapshot实现

在阐述之前,首先要了解Ceph有Pool的概念,也就是上面命令上涉及到的-p pool。一个Ceph Cluster可以创建多个Pool,每个Pool是逻辑上的隔离单位,不同的Pool可以有完全不同的数据处理方式。如Replication Size(副本数),Placement Groups(PG),CRUSH Rules,Snapshots,Ownership都是利用Pool进行隔离的。

因此,对Ceph的任意操作都需要先指定Pool才能进行,上面的image操作都是在一个名为”pool”的Pool上进行,名为”image-name”的Image也是存储在”pool”中。

除了Pool概念外,Ceph实质上有两种Snapshot模式,并且两种Snapshot是不能同时应用到同一个Pool中。

  1. Pool Snapshot: 对整个Pool打一个Snapshot,该Pool中所有的对象都会受影响
  2. Self Managed Snapshot: 用户管理的Snapshot,简单的理解就是这个Pool受影响的对象是受用户控制的。这里的用户往往是应用如librbd。

我们在前面利用rbd命令的操作实质上是使用第二种模式,因此我们先首先介绍第二种模式的实现。

在前面提到,Snapshot也是利用Pool隔离的,两种Snapshot mode的实现是基本相似的,如何使用是造成两种模式分离的重要原因。每个Pool都有一个snap_seq字段,该字段可以认为是整个Pool的Global Version。所有存储在Ceph的Object也都带有snap_seq,而每个Object会有一个Head版本的,也可能会存在一组Snapshot objects,不管是Head版本还是snapshot object都会带有snap_seq,那么接下来我们看librbd是如何利用该字段创建Snapshot的。

  1. 用户申请为”pool”中的”image-name”创建一个名为”snap-name”的Snapshot
  2. librbd向Ceph Monitor申请得到一个”pool”的snap sequence,Ceph Monitor会递增该Pool的snap_seq,然后返回该值给librbd。
  3. librbd将新的snap_seq替换原来image的snap_seq中,并且将原来的snap_seq设置为用户创建的名为”snap-name”的Snapshot的snap_seq

从上面的操作中,对于版本控制实现熟悉的同学们可能就大致猜测出Ceph对于Snapshot的实现了。每个Snapshot都掌握者一个snap_seq,Image可以看成一个Head Version的Snapshot,每次IO操作对会带上snap_seq发送给Ceph OSD,Ceph OSD会查询该IO操作涉及的object的snap_seq情况。如”object-1″是”image-name”中的一个数据对象,那么初始的snap_seq就”image-name”的snap_seq,当创建一个Snapshot以后,再次对”object-1″进行写操作时会带上新的snap_seq,Ceph接到请求后会先检查”object-1″的Head Version,会发现该写操作所带有的snap_seq大于”object-1″的snap_seq,那么就会对原来的”object-1″克隆一个新的Object Head Version,原来的”object-1″会作为Snapshot,新的Object Head会带上新的snap_seq,也就是librbd之前申请到的。

Ceph的实现当然比上面提到的要复杂很多,要考虑更多的异常情况还有管理Object Snaps上。

上述提到的是第二种Snapshot Mode,那么第一种模式实际上更简单。既然第二种方式是应用(librbd)自己申请snap_seq,然后进行管理,那么第一种是的场景可以是命令如”rados mksnap snap-name -p pool”进行全局pool的Snapshot,应用是不需要知道snap_seq的。这条命令会递增”pool”的snap_seq,然后接下来所有”pool”下的objects对会受影响,因为所有的接下来的IO操作都会自动继承”pool”的snap_seq,对object进行clone。在CephFS里用到这个模式管理全局的Snapshot。

所以,更简单的讲,这两者mode的区别就在于应用进行IO请求时是否附带snap_seq。

Object Snapshot的存储管理

上面提到的都是如何利用snap_seq向底层存储查找相应的对象然后返回,那么底层的存储引擎是如何管理一个Object的不同版本的呢。

首先,任一个Object都是通过ObjectStore接口进行访问,目前Ceph Master分支支持MemStore和FileStore两种,FileStore是默认的存储接口实现。以后的文章也会介绍具体的FileStore实现。

在Ceph中,每一个Object都有三种类型的存储接口,分别是最主要的Object存储,xattr存储和omap存储。Object存储就是用户实际数据的存放,xattr主要用来给CephFS提供XATTR数据存放,omap存储可以理解成一个k/v存储并且与某一个object相关联。而一个Object的元数据(pool,PG,name等等)都有一个object_info_t的结构进行管理,有一个SnapSetContext结构管理Snapshots,两者都作为一个object的k/v存储持久化。默认的FileStore是利用LevelDB作为键值存储,然后通过DBObjectMap类对LevelDB进行映射管理。

在Snapshot的实现上,最重要的其实就是Clone操作,那么在FileStore层面,Object数据存储是实际上就是一个文件,Object间克隆依赖OSD数据目录的文件系统,如Ext4或者XFS会直接完全拷贝数据,使用Btrfs会利用ioctl的BTRFS_IOC_CLONE_RANGE命令,kv数据克隆通过一个巧妙的KeyMapping实现COW策略(略微复杂,后面文章解读),而xattr则完全copy实现(xattr在Ceph中较少用到)。

来自:http://www.ustack.com/blog/ceph-snapshot/

解析Ceph: Snapshot的更多相关文章

  1. 解析CEPH: 存储引擎实现之一 filestore

    Ceph作为一个高可用和强一致性的软件定义存储实现,去使用它非常重要的就是了解其内部的IO路径和存储实现.这篇文章主要介绍在IO路径中最底层的ObjectStore的实现之一FileStore. Ob ...

  2. 解析Ceph: RBDCache 背后的世界

    转自:https://www.ustack.com/blog/ceph-internal-rbdcache/ RBDCache 是Ceph的块存储接口实现库 Librbd 的用来在客户端侧缓存数据的目 ...

  3. 解析 Ceph: FileJournal 的作用

      很多的用户在提到 Ceph 性能的时候都会提到“写放大”这点,实际上就是 FileJournal 在起作用.只要使用默认的 FileStore,所有数据包括 metadata 都会在 FileJo ...

  4. 解析Ceph: 数据的端到端正确性和 Scrub 机制

    转自:https://www.ustack.com/blog/ceph-internal-scrub/ Ceph 的主要一大特点是强一致性,这里主要指端到端的一致性.众所周知,传统存储路径上从应用层到 ...

  5. 解析Ceph: 恢复与数据一致性

    转自:https://www.ustack.com/blog/ceph-internal-recovery-and-consistency/ 作为一个面向大规模的分布式存储系统,故障处理是作为一个常态 ...

  6. Ceph源码解析:读写流程

    转载注明出处,整理也是需要功夫的,http://www.cnblogs.com/chenxianpao/p/5572859.html 一.OSD模块简介 1.1 消息封装:在OSD上发送和接收信息. ...

  7. ceph 初始化函数解析

    global_pre_init 预初始化函数,解析ceph.conf配置文件, 初始化定义global_context 和 config的全局变量. 全局预初始化函数 CINIT_FLAG_UNPRI ...

  8. 理解 QEMU/KVM 和 Ceph(1):QEMU-KVM 和 Ceph RBD 的 缓存机制总结

    本系列文章会总结 QEMU/KVM 和 Ceph 之间的整合: (1)QEMU-KVM 和 Ceph RBD 的 缓存机制总结 (2)QEMU 的 RBD 块驱动(block driver) (3)存 ...

  9. Ceph 的用户管理与认证

    目录 文章目录 目录 前言 Ceph 的用户管理 用户管理常规操作 CephX 认证系统 身份认证原理 使用 ceph-authtool 进行密钥环管理 注意事项 前言 常规的身份认证系统无非三点: ...

随机推荐

  1. list() and tuple()

    >>> l = list('sdfsdf') >>> l ['s', 'd', 'f', 's', 'd', 'f'] >>> t = tuple ...

  2. MatrixTurn源码阅读

    在看cacheAsBitmap 相关资料时,找到bit101的一篇文章,http://www.bytearray.org/?p=290 全文如下: One of the feature I would ...

  3. Java中Volatile关键字详解

    一.基本概念 先补充一下概念:Java并发中的可见性与原子性 可见性: 可见性是一种复杂的属性,因为可见性中的错误总是会违背我们的直觉.通常,我们无法确保执行读操作的线程能适时地看到其他线程写入的值, ...

  4. flash wmode参数详解

    在做web开发中可能会遇到flash遮挡页面中元素的情况,无论怎么设置flash容器和层的深度(z-index)也无济于事,现有的解决方案是在插入flash的embed或object标签中加入”wmo ...

  5. asp.net读取xml方法

    这个适合刚学asp.net的同学,大神直接略过好了,asp.net经常会有很多用到XML的地方,比如全国省市的联动,以及一些菜单读取等等都有xml的影子,直接贴代码,以便我以后用到的时候忘了,注释我写 ...

  6. git/github初级运用自如(zz)

    ----//git/github环境配置 一 .  github上创建立一个项目 用户登录后系统,在github首页,点击页面右下角“New Repository” 填写项目信息: project n ...

  7. C#中的选择查询相关

    看代码实现: using System; using System.Collections.Generic; using System.Linq; using System.Text; using S ...

  8. Oracle自定义数据类型 2 (调用对象方法)

    调用对象方法 调用对象方法基于类型创建表后,就可以在查询中调用对象方法 A. 创建基于对象的表语法: create   table   <表名>   of   <对象类型>意义 ...

  9. 自定义View实现图片的绘制、旋转、缩放

    1.图片 把一张JPG图片改名为image.jpg,然后拷贝到项目的res-drawable中. 2.activity_main.xml <LinearLayout xmlns:android= ...

  10. 读取raw目录中的文件数据

    try { InputStream is2 = getResources().openRawResource(R.raw.info); InputStreamReader isr2 = new Inp ...