ceph源码阅读 erasure-code

这篇具有很好参考价值的文章主要介绍了ceph源码阅读 erasure-code。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

1、ceph纠删码

纠删码(Erasure Code)是比较流行的数据冗余的存储方法,将原始数据分成k个数据块(data chunk),通过k个数据块计算出m个校验块(coding chunk)。把n=k+m个数据块保存在不同的节点,通过n中的任意k个块还原出原始数据。EC包含编码和解码两个过程。

ceph中的EC编码是以插件的形式来提供的。EC编码有三个指标:空间利用率、数据可靠性和恢复效率。ceph提供以下几种纠删码插件:clay(coupled-layer)、jerasure、lrc、shec、isa。

clay:用于在修复失败的节点/OSD/rack时节省网络带宽和磁盘IO。

jerasure:开源库,目前ceph默认的编码方式。

isa:isa是Intel提供的EC库,利用intel处理器指令加速计算,只能运行在Intel CPU上。

lrc:将校验块分为全局校验块和局部校验块,减少单个节点失效后恢复过程的网络开销。

shec:shec(k,m,l),k为data chunk,m为coding chunk,l代表计算coding chunk时需要的data chunk数量。其最大允许失效数据块为:ml/k,恢复失效的单个数据块(data chunk)只需要额外读取l个数据块。

ceph源码阅读 erasure-code,Ceph专栏,ceph

2、erasure-code文件结构

erasure-code是ceph的纠删码核心模块,包含5个文件夹和ErasureCodeInterface、ErasureCode、ErasureCodePlugin,采用工厂模式。

ceph源码阅读 erasure-code,Ceph专栏,ceph

  • clay、isa、jerasure、lrc、shec是ceph的EC插件。
  • ErasureCodeInterface:提供EC插件的共有接口,每个接口有详细的说明。
  • ErasureCode:提供获取纠删码参数和核心编码、解码接口。
  • ErasureCodePlugin:提供纠删方式事件的注册、添加、删除登功能。

3、数据条带化

存储设备都有吞吐量限制,它会影响性能和伸缩性,所以存储系统一般都支持条带化(把连续的信息分片存储于多个设备)以增加吞吐量和性能。

  • 基本概念

块(chunk):基于纠删码编码时,每次编码将产生若干大小相同的块(要求这些块时有序的,否则无法解码)。ceph通过数量相等的PG将这些分别存储在不同的osd中。

条带(strip):如果编码对象太大,可分多次进行编码,每次完成编码的部分称为条带。同一个对内的条带时有序的。

分片(shared):同一个对象中所有序号相同的块位于同一个PG上,他们组成对象的一个分片,分片的编号就是块的序号。

空间利用率(rate):通过k/n计算。

对象尺寸: Ceph 存储集群里的对象有最大可配置尺寸(如 2MB 、 4MB 等等),对象尺寸必须足够大以便容纳很多条带单元、而且应该是条带单元的整数倍。

条带数量: Ceph 客户端把一系列条带单元写入由条带数量所确定的一系列对象,这一系列的对象称为一个对象集。客户端写到对象集内的最后一个对象时,再返回到第一个。

条带宽度: 条带都有可配置的单元尺寸(如 64KB )。 Ceph 客户端把数据等分成适合写入对象的条带单元,除了最后一个。条带宽度应该是对象尺寸的分片,这样对象才能 包含很多条带单元。

strip_width=chunk_size*strip_size

假设有EC(k=4,m=2),strip_size=4,chunk_size=1K,那么strip_width=4K。在ceph中,strip_width默认为4K。

ceph源码阅读 erasure-code,Ceph专栏,ceph

  • 数据条带化过程

如果要处理大尺寸图像、大 S3 或 Swift 对象(如视频)、或大的 CephFS 目录,你就能看到条带化到一个对象集中的多个对象能带来显著的读/写性能提升。当客户端把条带单元并行地写入相应对象时,就会有明显的写性能,因为对象映射到了不同的归置组、并进一步映射到不同 OSD ,可以并行地以最大速度写入。

在上图中,客户端数据条带化到一个对象集(上图中的objectset1),它包含 4 个对象,其中,第一个条带单元是object0的stripe unit 0、第四个条带是object3的stripe unit 3,写完第四个条带,客户端要确认对象集是否满了。如果对象集没满,客户端再从第一个对象起写入条带(上图中的object0);如果对象集满了,客户端就得创建新对象集(上图的object set 2),然后从新对象集中的第一个对象(上图中的object 4)起开始写入第一个条带(stripe unit16)。

4、源码解析

  • 编码流程

ECUtil::encode是将原始数据按条带宽度进行分割,然后对条带数据编码,得到条带的数据块和校验块。把每个条带化数据块和校验块有序的链接,形成数据块和校验块。

int ECUtil::encode(
  const stripe_info_t &sinfo,
  ErasureCodeInterfaceRef &ec_impl,
  bufferlist &in,
  const set<int> &want,
  map<int, bufferlist> *out)
....

//文件每次按strip_width的大小进行encode编码
  for (uint64_t i = 0; i < logical_size; i += sinfo.get_stripe_width()) {
    map<int, bufferlist> encoded;
    bufferlist buf;
    buf.substr_of(in, i, sinfo.get_stripe_width());
    //调用对应的纠删码方式进行编码
    int r = ec_impl->encode(want, buf, &encoded);
    ceph_assert(r == 0);
    //将条带化的数据块和校验块追加到out
    for (map<int, bufferlist>::iterator i = encoded.begin();
	 i != encoded.end();
	 ++i) {
      ceph_assert(i->second.length() == sinfo.get_chunk_size());
      (*out)[i->first].claim_append(i->second);
    }
  }

....
  return 0;
}

接下来深入分析ec_impl->encode(want, buf,&encoded),ErasureCode是ErasureCodeInterface的子类,因此调用ErasureCode::encode。在ErasureCode::encode主要是进行map<int, bufferlist>*encoded的内存分配(encode_prepare())和数据块的编码(encode_chunks)。

int ErasureCode::encode(const set<int> &want_to_encode,
                        const bufferlist &in,
                        map<int, bufferlist> *encoded)
{
  unsigned int k = get_data_chunk_count();
  unsigned int m = get_chunk_count() - k;
  bufferlist out;
  //encoded的内存块分配
  int err = encode_prepare(in, *encoded);
  if (err)
    return err;
  //进行编码操作
  encode_chunks(want_to_encode, encoded);
  for (unsigned int i = 0; i < k + m; i++) {
    if (want_to_encode.count(i) == 0)
      encoded->erase(i);
  }
  return 0;
}

ErasureCode::encode_prepare()进行参数初始化和内存分配。

int ErasureCode::encode_prepare(const bufferlist &raw,
                                map<int, bufferlist> &encoded) const
{
  unsigned int k = get_data_chunk_count();
  unsigned int m = get_chunk_count() - k;
  //每个块的大小
  unsigned blocksize = get_chunk_size(raw.length());
  //空白块的个数
  unsigned padded_chunks = k - raw.length() / blocksize;
  bufferlist prepared = raw;

  //将数据raw按blocksize大小有序分割,并将分割后的块有序写入到encoded
  for (unsigned int i = 0; i < k - padded_chunks; i++) {
    bufferlist &chunk = encoded[chunk_index(i)];
    chunk.substr_of(prepared, i * blocksize, blocksize);
    chunk.rebuild_aligned_size_and_memory(blocksize, SIMD_ALIGN);
    ceph_assert(chunk.is_contiguous());
  }
  if (padded_chunks) {
    unsigned remainder = raw.length() - (k - padded_chunks) * blocksize;
    bufferptr buf(buffer::create_aligned(blocksize, SIMD_ALIGN));

    raw.copy((k - padded_chunks) * blocksize, remainder, buf.c_str());
    buf.zero(remainder, blocksize - remainder);
    encoded[chunk_index(k-padded_chunks)].push_back(std::move(buf));

    for (unsigned int i = k - padded_chunks + 1; i < k; i++) {
      bufferptr buf(buffer::create_aligned(blocksize, SIMD_ALIGN));
      buf.zero();
      encoded[chunk_index(i)].push_back(std::move(buf));
    }
  }
  for (unsigned int i = k; i < k + m; i++) {
    bufferlist &chunk = encoded[chunk_index(i)];
    chunk.push_back(buffer::create_aligned(blocksize, SIMD_ALIGN));
  }

  return 0;
}

以上的工作完成后,可以开始正式的编码encode_chunks()。这里假设编码方式为jerasure,选用RS码。

int ErasureCodeJerasure::encode_chunks(const set<int> &want_to_encode,
				       map<int, bufferlist> *encoded)
{
  char *chunks[k + m];
  for (int i = 0; i < k + m; i++)
    chunks[i] = (*encoded)[i].c_str();
  jerasure_encode(&chunks[0], &chunks[k], (*encoded)[0].length());
  return 0;
}

jerasure_encode()是调用jerasure库的编码函数。

void ErasureCodeJerasureReedSolomonVandermonde::jerasure_encode(char **data,
                                                                char **coding,
                                                                int blocksize)
{
  jerasure_matrix_encode(k, m, w, matrix, data, coding, blocksize);
}
  • 解码流程

ECUtil::decode函数有两个,挑个简单的来分析。

下面的decode()函数初始化数据,并进行解码。

int ECUtil::decode(
  const stripe_info_t &sinfo,
  ErasureCodeInterfaceRef &ec_impl,
  map<int, bufferlist> &to_decode,
  bufferlist *out) {
  ceph_assert(to_decode.size());

  uint64_t total_data_size = to_decode.begin()->second.length();
  ....

  for (uint64_t i = 0; i < total_data_size; i += sinfo.get_chunk_size()) {
    map<int, bufferlist> chunks;
    for (map<int, bufferlist>::iterator j = to_decode.begin();
	 j != to_decode.end();
	 ++j) {
      chunks[j->first].substr_of(j->second, i, sinfo.get_chunk_size());
    }
    bufferlist bl;
    int r = ec_impl->decode_concat(chunks, &bl);
    ceph_assert(r == 0);
    ceph_assert(bl.length() == sinfo.get_stripe_width());
    out->claim_append(bl);
  }
  return 0;
}

ErasureCode::decode_concat()进行解码,并且链接哥哥数据块,还原出原数据。

int ErasureCode::decode_concat(const map<int, bufferlist> &chunks,
			       bufferlist *decoded)
{
  set<int> want_to_read;

  for (unsigned int i = 0; i < get_data_chunk_count(); i++) {
    want_to_read.insert(chunk_index(i));
  }
  map<int, bufferlist> decoded_map;
  //解码核心部分
  int r = _decode(want_to_read, chunks, &decoded_map);
  if (r == 0) {
    //将解码后的数据块链接
    for (unsigned int i = 0; i < get_data_chunk_count(); i++) {
      decoded->claim_append(decoded_map[chunk_index(i)]);
    }
  }
  return r;
}

同样地,这里也是假设使用纠删码插件为jerasure。jerasure_decode()是调用jerasure库的解码函数。

int ErasureCodeJerasure::decode_chunks(const set<int> &want_to_read,
				       const map<int, bufferlist> &chunks,
				       map<int, bufferlist> *decoded)
{
  unsigned blocksize = (*chunks.begin()).second.length();
  int erasures[k + m + 1];//记录丢失块的编号
  int erasures_count = 0;//丢失块的个数
  char *data[k];
  char *coding[m];
  for (int i =  0; i < k + m; i++) {
    if (chunks.find(i) == chunks.end()) {
      erasures[erasures_count] = i;
      erasures_count++;
    }
    if (i < k)
      data[i] = (*decoded)[i].c_str();
    else
      coding[i - k] = (*decoded)[i].c_str();
  }
  erasures[erasures_count] = -1;

  ceph_assert(erasures_count > 0);
  return jerasure_decode(erasures, data, coding, blocksize);
}

参考资料:

1、体系结构 - Ceph Documentation

2、ceph源码分析 常涛

3、ceph设计原理与实现文章来源地址https://www.toymoban.com/news/detail-691347.html

到了这里,关于ceph源码阅读 erasure-code的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Ceph源码分析-在C++中,符号“&“和“*“有不同的用法。

    在C++中,符号\\\"\\\"和\\\"*\\\"有不同的用法。 \\\"\\\"符号: 在变量声明时,\\\"\\\"用于定义引用类型。例如: int a = 10; int ref = a;  这里的\\\"ref\\\"是一个引用,它引用了变量\\\"a\\\",对\\\"ref\\\"的修改会影响到\\\"a\\\"的值。 在函数参数中,\\\"\\\"用于传递参数的引用。例如: void foo(int num) { ... }  这里的\\\"num\\\"是一个引

    2024年02月01日
    浏览(36)
  • 到了这个年纪,就应该阅读Spring源码了,源码阅读指南-编译加运行

    Spring的源码地址 https://github.com/spring-projects/spring-framework 我们先把他clone下来 没梯子的话多clone几遍就好了 我们这里使用5.x的版本 设置里面gradle设置按照如下图设置即可 😊gradle介绍(插叙手法) 由于Spring源码都是使用Gradle来管理项目 按理来说我们gradle和maven类似,需要在本

    2023年04月09日
    浏览(82)
  • 论文阅读:TinyGPT-V 论文阅读及源码梳理对应

    引言 TinyGPT-V来自论文:TinyGPT-V: Efficient Multimodal Large Language Model via Small Backbones,是一篇基于较小LLM作为backbone的多模态工作。相关工作已经开源,地址为:Github 之所以选择这篇文章,是因为比较具有落地意义,且便于本地运行,查看和调试。 以下代码只给出核心部分,会省

    2024年01月16日
    浏览(50)
  • ArrayList集合源码阅读

    ArrayList 是 Java 中的一种集合(Collection)类,它可以用来存储一组对象。下面是一些 ArrayList 的重要知识点: ArrayList 是动态数组,它的大小可以根据需要自动增长或缩小。 ArrayList 是通过数组实现的,每个元素可以通过其索引进行访问。 ArrayList 可以存储任意类型的对象,包括

    2023年04月25日
    浏览(38)
  • 下载和阅读Android源码

    源码下载是我们分析源码的开始,Android源码可以全量下载,也可以单个下载。 1.全量下载 官方文档 https://source.android.com/source/downloading ,只要按照上面一步步做就可以了,但是由于需要翻墙,国内无法直接访问,而整个Android项目源码巨大,即便是翻墙后下载也很慢,所以还

    2023年04月08日
    浏览(44)
  • Android源码在线阅读

    ⭐博客同步更新在blogs-index ⭐推荐在github上阅读 推荐几个我在用的Android源码在线阅读网站,方便随时随地学习Android代码。 代码比较齐全,由深圳次元空间网络科技有限公司部署服务。 Project(s) ​ 指定搜索的仓库,select all选择所有的仓库,invert selection对当前选中进行取反操

    2024年02月14日
    浏览(29)
  • Object源码阅读

    native:本地栈方法,使用C语言中实现的方法。

    2024年02月08日
    浏览(16)
  • netty源码阅读--服务启动

    netty是一个非常成熟的NIO框架,众多apache的顶级项目底层通信框架都是用的是netty,本系列博客主要是记录自己复习netty源码的过程,重在理解netty的关键如:如何启动,如何接受网络数据、netty的内存管理机制以及编解码器等,废话不多说,直接跟着netty源码中的MQTT的官方示例

    2023年04月22日
    浏览(27)
  • 源码阅读:classnames

    classnames 一个简单的 JavaScript 实用程序,用于有条件地将类名连接在一起。 可以通过 npm 包管理器从 npm 注册表上下载: classNames 函数接受任意数量的参数,可以是字符串或对象。参数 \\\'foo\\\' 是 { foo: true } 的缩写。如果与给定键关联的值是假的,则该键将不会包含在输出中。 在

    2024年02月14日
    浏览(18)
  • JDK源码阅读环境搭建

    新建java项目JavaSourceLearn ,这里我创建的是maven 打开Project Structure 找到本地JDK安装位置将src.zip解压到项目java包中 整理下项目结构,删除用不到的目录 提示 : 添加源码到项目之后首次运行需要较长时间进行编译,建议先设置好 进程堆 ,防止编译失败 打开File - Settings - Build, Exe

    2024年02月05日
    浏览(43)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包