如何 0 成本启动全员 AI 技能提升?戳> 了解详情
写点什么

分布式存储 cephfs 读取优化方案

  • 2018-12-05
  • 本文字数:12917 字

    阅读完需:约 42 分钟

分布式存储cephfs读取优化方案

继上次分享的 分布式存储系统 Ceph 架构及使用场景解析分布式存储Ceph之PG状态详解 ,这次分享 cephfs 读写优化方面的知识。


用户需要从 cephfs 存储系统中检索一个大文件指定关键字的一行信息, 并且对延迟和性能要求比较高。

2. 原始方案

2.1 流程图

2.2 说明

  • 假如用户拉取的文件大小是 16M, 文件按照 4M 切分,散落到四个数据片上

  • 用户首先请求 cephfs 拉取文件信息

  • cephfs 会根据 crush 算法找计算文件散落到那几个数据片上

  • cephfs 会拉取文件所属的数据片然后聚合起来

  • cephfs 文件拉取后返回给用户

  • 用户拉取完整个文件,开始做过滤关键字操作

2.3 实战

//检索2.7G文件为例$ ll -lh nginx/logs/access.log.2018102911-rw-rw-r-- 1 root root 2.7G Oct 29 12:07 nginx/logs/access.log.2018102911
//grep 模拟花费12s$ time grep "xxxyyyzzzqqq" nginx/logs/access.log.2018102911
real 0m12.355suser 0m0.302ssys 0m0.823s
复制代码

2.4 优缺点

优点


  • 简单方便

  • 开发成本低


缺点


  • 用户端检索延迟大,影响用户体验

  • 客户端集群网卡带宽波动较大,带宽有限,每次都需要把大日志文件拉取到客户端

  • 对 ceph 集群负载也有波动影响

2.5 总结

用户拉取文件,必须先通过 cephfs 拉取文件到本地,然后根据关键字检索这行数据。如果用户检索量比较大的时候,并且文件大小都不统一,拉取文件越大网络延迟越高,并且在大文件中过滤关键字效率非常低,严重影响用户的体验。

3. 优化方案

3.1 流程图

3.2 说明

  • 用户发起请求输入文件名和 key 关键字到达索引层

  • 索引层根据 key 找到对应的 offset 信息,然后传给 dss-readline

  • dss-readline 根据 cephfs cursh 算法找到对应的 object 信息和 offset 信息

  • 根据 dss-readline 用户输入的 offset 找到对应的 object 块信息

  • dss-readline 直接获取需要块的 offset 该行的信息

3.3 实战

//查找2.8G文件offset对应的信息$ ll  nginx/logs/access.log.2018110216 -lh-rw-rw-r-- 1 root root 2.8G Nov  2 17:08 nginx/logs/access.log.2018110216
//sed的方式模拟,花费12s$ time sed -n "1024p" nginx/logs/access.log.2018110216
real 0m12.042suser 0m1.191ssys 0m0.929s
//dss_readfile 自研工具, 输入参数:poolname, filename, offset 可以看出来花费91ms//usage: dss_readfile <poolname> <filename> <offset>time ./dss_readfile data nginx/logs/access.log.2018110216 1024
real 0m0.091suser 0m0.042ssys 0m0.011s
复制代码

3.4 优缺点

缺点


  • 需要额外开发成本


优点


  • 提升用户体验,从以前检索单个 2.8G 文件耗时 10s 左右, 优化后控制在 100ms 左右

  • 客户端网络网卡带宽可用率得到提升

  • 减少对 ceph 集群的冲击影响

3.5 总结

思路:


由于文件信息是放到服务端,进行切片存储到数据节点。


我们能不能只拉取我需要的块信息,不用全量拉取到本地,答案是肯定的。


  • 根据文件信息查找所有的 object、offset 信息

  • 根据 offset 找到需要检索的 object 信息

  • 找到对应的 object,读取该 object 对应的 offset 位置的信息(一行数据可能会拆分多个 object)


优点:


  • 提升用户体验,从以前检索单个 2.8G 文件耗时 10s 左右, 优化后控制在 100ms 左右

  • 客户端网络网卡带宽可用率得到提升

  • 减少对 ceph 集群的冲击影响

4. 深入分析

4.1 文件对应 object 信息

4.1.1 Jewel 版本

//Ceph Jewel版本里,有个cephfs的工具,可以获取file的location信息//根据offset查找object信息$ cephfs /mnt/kernel_log_push.log show_location -l 4194304WARNING: This tool is deprecated.  Use the layout.* xattrs to query and modify layouts.location.file_offset:  4194304location.object_offset:0location.object_no:    1location.object_size:  4194304location.object_name:  10002b63282.00000001location.block_offset: 0location.block_size:   4194304location.osd:          67
//file object map 信息$ cephfs /mnt/kernel_log_push.log mapWARNING: This tool is deprecated. Use the layout.* xattrs to query and modify layouts. FILE OFFSET OBJECT OFFSET LENGTH OSD 0 10002b63282.00000000 0 4194304 61 4194304 10002b63282.00000001 0 4194304 67 8388608 10002b63282.00000002 0 4194304 70 12582912 10002b63282.00000003 0 4194304 68
复制代码

4.1.2 源码跟踪

ceph jewel 版本,cephfs 代码


[](https://github.com/ceph/ceph/blob/v10.2.9/src/cephfs.cc#L117)
复制代码


    struct ceph_ioctl_layout layout;    memset(&layout, 0, sizeof(layout));    //获取layout信息    err = ioctl(fd, CEPH_IOC_GET_LAYOUT, (unsigned long)&layout);    if (err) {      cerr << "Error getting layout: " << cpp_strerror(errno) << endl;      return 1;    }
printf("%15s %24s %12s %12s %s\n", "FILE OFFSET", "OBJECT", "OFFSET", "LENGTH", "OSD"); for (long long off = 0; off < st.st_size; off += layout.stripe_unit) { struct ceph_ioctl_dataloc location; location.file_offset = off; //获取location 信息 err = ioctl(fd, CEPH_IOC_GET_DATALOC, (unsigned long)&location); if (err) { cerr << "Error getting location: " << cpp_strerror(errno) << endl; return 1; } printf("%15lld %24s %12lld %12lld %d\n", off, location.object_name, (long long)location.object_offset, (long long)location.block_size, (int)location.osd); }
复制代码


CEPH_IOC_GET_DATALOC 代码


//定义/src/client/ioctl.h//https://github.com/ceph/ceph/blob/d038e1da7a6c9b31ba4463b8ebedb9908981a55e/src/client/ioctl.h#L46#define CEPH_IOC_GET_DATALOC _IOWR(CEPH_IOCTL_MAGIC, 3,  \           struct ceph_ioctl_dataloc)
//fuse 代码跟踪, 发现只支持layout//https://github.com/ceph/ceph/blob/d038e1da7a6c9b31ba4463b8ebedb9908981a55e/src/client/fuse_ll.cc#L631static void fuse_ll_ioctl(fuse_req_t req, fuse_ino_t ino, int cmd, void *arg, struct fuse_file_info *fi, unsigned flags, const void *in_buf, size_t in_bufsz, size_t out_bufsz){ CephFuse::Handle *cfuse = fuse_ll_req_prepare(req);
if (flags & FUSE_IOCTL_COMPAT) { fuse_reply_err(req, ENOSYS); return; }
switch (static_cast<unsigned>(cmd)) { case CEPH_IOC_GET_LAYOUT: { file_layout_t layout; struct ceph_ioctl_layout l; Fh *fh = (Fh*)fi->fh; cfuse->client->ll_file_layout(fh, &layout); l.stripe_unit = layout.stripe_unit; l.stripe_count = layout.stripe_count; l.object_size = layout.object_size; l.data_pool = layout.pool_id; fuse_reply_ioctl(req, 0, &l, sizeof(struct ceph_ioctl_layout)); } break; default: fuse_reply_err(req, EINVAL); }}
//kernel cephfs代码, 支持layout, 支持dataloc// /usr/src/debug/kernel-3.10.0-693.17.1.el7/linux-3.10.0-693.17.1.el7.x86_64/fs/ceph/ioctl.clong ceph_ioctl(struct file *file, unsigned int cmd, unsigned long arg){ dout("ioctl file %p cmd %u arg %lu\n", file, cmd, arg); switch (cmd) { case CEPH_IOC_GET_LAYOUT: return ceph_ioctl_get_layout(file, (void __user *)arg);
case CEPH_IOC_SET_LAYOUT: return ceph_ioctl_set_layout(file, (void __user *)arg);
case CEPH_IOC_SET_LAYOUT_POLICY: return ceph_ioctl_set_layout_policy(file, (void __user *)arg);
case CEPH_IOC_GET_DATALOC: return ceph_ioctl_get_dataloc(file, (void __user *)arg);
case CEPH_IOC_LAZYIO: return ceph_ioctl_lazyio(file);
case CEPH_IOC_SYNCIO: return ceph_ioctl_syncio(file); }
return -ENOTTY;}
static long ceph_ioctl_get_dataloc(struct file *file, void __user *arg){ ... r = ceph_calc_file_object_mapping(&ci->i_layout, dl.file_offset, len, &dl.object_no, &dl.object_offset, &olen); if (r < 0) { up_read(&osdc->lock); return -EIO; } dl.file_offset -= dl.object_offset; dl.object_size = ceph_file_layout_object_size(ci->i_layout); dl.block_size = ceph_file_layout_su(ci->i_layout);
/* block_offset = object_offset % block_size */ tmp = dl.object_offset; dl.block_offset = do_div(tmp, dl.block_size);
snprintf(dl.object_name, sizeof(dl.object_name), "%llx.%08llx", ceph_ino(inode), dl.object_no);
...
复制代码

4.1.2 Luminous 版本

Luminous 版本里,没有 src/cephfs.cc 文件, 发现 test_ioctls.c 其实有相关的测试代码。


https://github.com/ceph/ceph/blob/master/src/client/test_ioctls.c


/src/client/test_ioctls.c


int main(int argc, char **argv){  ...  fd = open(fn, O_CREAT|O_RDWR, 0644);  if (fd < 0) {    perror("couldn't open file");    return 1;  }
/* get layout */ err = ioctl(fd, CEPH_IOC_GET_LAYOUT, (unsigned long)&l); if (err < 0) { perror("ioctl IOC_GET_LAYOUT error"); return 1; } printf("layout:\n stripe_unit %lld\n stripe_count %lld\n object_size %lld\n data_pool %lld\n", (long long)l.stripe_unit, (long long)l.stripe_count, (long long)l.object_size, (long long)l.data_pool);
/* dataloc */ dl.file_offset = atoll(argv[2]); err = ioctl(fd, CEPH_IOC_GET_DATALOC, (unsigned long)&dl); if (err < 0) { perror("ioctl IOC_GET_DATALOC error"); return 1; }
printf("dataloc:\n"); printf(" file_offset %lld (of object start)\n", (long long)dl.file_offset); printf(" object '%s'\n object_offset %lld\n object_size %lld object_no %lld\n", dl.object_name, (long long)dl.object_offset, (long long)dl.object_size, (long long)dl.object_no); printf(" block_offset %lld\n block_size %lld\n", (long long)dl.block_offset, (long long)dl.block_size); ...
复制代码

4.2 总结

  • 目前只有 kernel 版本支持 CEPH_IOC_GET_DATALOC

  • 根据文件以及 offset 可以获取对应的 object 信息。 目前只支持内核 kernel 版本。

4.3 获取这个对象 offset 对应行的信息

问题点:


  • 一行数据可能会拆分为两个对象

  • 一行数据结尾符是否存在\n

  • 一行数据超大等问题


解决方案:


  • 用户给的 offset 属于这一行的开头, 只需要读取当前读取数据是否存在\n。

  • a. 如果存在\n 证明该行,属于完整的行。

  • b. 否则不存在\n 证明该行,被拆分为两个对象,读取当前 offset 对应的 object 信息以及下一个对象的信息,直到遇到\n 结束,然后合并两个对象读取的数据为完整的行。

  • 超大行或者不存在结尾符\n 自动截取 1024 字节数。

4.4 通过 librados 库,读取 object 的信息

 /* Declare the cluster handle and required arguments. */    int                                 err;    char                                cluster_name[] = "ceph";    char                                user_name[] = "client.admin";    uint64_t                            flags = 0;    rados_t                             cluster;    rados_ioctx_t                       io;    rados_completion_t                  comp;


/* Initialize the cluster handle with the "ceph" cluster name and the "client.admin" user */ err = rados_create2(&cluster, cluster_name, user_name, flags); if (err < 0) { fprintf(stderr, "error: couldn't create the cluster handle poolname=[%s] object_name=[%s] offset=[%d] error=[%s]\n", poolname, object_name, offset, strerror(-err)); return 1; }
/* Read a Ceph configuration file to configure the cluster handle. */ err = rados_conf_read_file(cluster, "/etc/ceph/ceph.conf"); if (err < 0) { fprintf(stderr, "error: cannot read config file poolname=[%s] object_name=[%s] offset=[%d] error=[%s]\n", poolname, object_name, offset, strerror(-err)); return 1; }
/* Connect to the cluster */ err = rados_connect(cluster); if (err < 0) { fprintf(stderr, "error: cannot connect to cluster poolname=[%s] object_name=[%s] offset=[%d] error=[%s]\n", poolname, object_name, offset, strerror(-err)); return 1; }
//create io err = rados_ioctx_create(cluster, poolname, &io); if (err < 0) { fprintf(stderr, "error: cannot open rados pool poolname=[%s] object_name=[%s] offset=[%d] error=[%s]\n", poolname, object_name, offset, strerror(-err)); rados_shutdown(cluster); return 1; }
/* * Read data from the cluster asynchronously. * First, set up asynchronous I/O completion. */ err = rados_aio_create_completion(NULL, NULL, NULL, &comp); if (err < 0) { fprintf(stderr, "error: could not create aio completion poolname=[%s] object_name=[%s] offset=[%d] error=[%s]\n", poolname, object_name, offset, strerror(-err)); rados_ioctx_destroy(io); rados_shutdown(cluster); return 1; }
/* Next, read data using rados_aio_read. */ err = rados_aio_read(io, object_name, comp, line, line_size, offset); if (err < 0) { fprintf(stderr, "error: cannot read object poolname=[%s] object_name=[%s] offset=[%d] error=[%s]\n", poolname, object_name, offset, strerror(-err)); rados_ioctx_destroy(io); rados_shutdown(cluster); return 1; } /* Wait for the operation to complete */ rados_aio_wait_for_complete(comp); /* Release the asynchronous I/O complete handle to avoid memory leaks. */ rados_aio_release(comp);
rados_ioctx_destroy(io); rados_shutdown(cluster);
复制代码

4.5 项目工具

1. 源码地址



2. dss_readfile 工具


  • 根据存储池、文件信息、offset 获取对应的信息


//usage: dss_readfile <poolname> <filename> <offset>./dss_readfile data nginx/logs/access.log.2018110216 1024
复制代码


3. ngx_cephfs_readline


  • 为了提升性能以及用户体验,基于 ceph module + librados 开发,充分利用 nginx 优秀的高并发性能。


//接口
http://127.0.0.1 :8088/v1/dss-cephfs/readfile

//请求body{ "poolname":"data", "filename":"/mnt/business.log.2018101708", "offset":1024}
//响应body{ "code":1, "cost": 50, "data":"[INFO][2018-10-17T08:59:49.018+0800] xxxxxx"}
复制代码

4.7 资料


作者介绍:李航, 多年的底层开发经验,在高性能 nginx 开发和分布式缓存 redis cluster 有着丰富的经验,目前从事分布式存储 Ceph 工作。先后在 58 同城、汽车之家、优酷土豆集团工作。


目前供职于滴滴基础平台运维部-技术专家岗位,主要负责分布式 Ceph 系统。个人主要关注的技术领域:高性能 Nginx 开发、分布式缓存、分布式存储。


2018-12-05 15:562680

评论 1 条评论

发布
暂无评论
发现更多内容

阿里云代码托管平台,不限容量,免费使用

阿里云云效

云计算 阿里云 代码管理 代码托管 阿里云代码托管

大咖说·智篆商业|颜杰华:数字经济时代,不确定性中的确定性

大咖说

阿里巴巴 数字经济数字时代 数智化 存量时代

《数字经济全景白皮书》Z世代用户洞察篇(2)重磅发布!

易观分析

Z世代

什么时候需要使用CRM系统?

低代码小观

CRM 客户关系管理 低代码开发 CRM系统 客户关系管理系统

Android C/C++层hook和java层hook原理以及比较

云智慧AIOps社区

Java android 开发技能 hook

TASKCTL 变量的函数表达式运算

敏捷调度TASKCTL

批量任务 调度引擎 ETL 自动化运维 调度任务

NFT数字藏品交易平台系统开发搭建

薇電13242772558

NFT 数字藏品

GPU底层技术、全球市场格局分析(中)

Finovy Cloud

人工智能 云计算 云服务器 GPU服务器 GPU算力

2022年中国茶饮产业洞察

易观分析

茶饮 产业洞察

小程序运行时+SAAS级服务,提升研发降本增效作用

Speedoooo

flutter APP开发 SaaS平台 小程序容器 小程序运行时

项目中如何优雅的消除if-else

Rubble

4月日更 4月月更

增长270%!PWA 在 Chrome 平台迎来井喷式增长

鼎道智联

图数据库|正反向边的最终一致性——TOSS 介绍

NebulaGraph

图数据库 知识图谱

组合式应用新利器?SaaS新时代事件网格如何解决集成标准化问题

华为云开发者联盟

Serverless 无服务器 事件网格 组合式应用

火山引擎 MARS X 今日头条 | 大型App高效协同开发,实现研发流程自动化

字节跳动终端技术

今日头条 字节跳动 研发 火山引擎MARS

TASKCTL 调度设计器作业属性的应用

敏捷调度TASKCTL

分布式 元数据区 ETL 自动化运维 任务调度器

轻量级兼顾本地体验,PWA应用到底有多卷?

鼎道智联

书单 | “阿里云数字新基建”系列丛书全家福来啦!

博文视点Broadview

【云管平台】三大云管平台厂商详细介绍

行云管家

云计算 云管平台 云堡垒机 云厂商

华为云物联网高级攻城狮的4年配置中心实践分享

华为云开发者联盟

开源 DevOps 配置中心 Apollo 业务配置

基于LAXCUS开发分布式视频转码应用

LAXCUS分布式操作系统

分布式计算 分布式应用 视频转码

当 API 成为服务,我们将连接一切!

鼎道智联

MySQL事务并发带来的问题以及其解决方案分析

乌龟哥哥

4月月更

java高级用法之:JNA中的Structure

程序那些事

Java 程序那些事 JNA 4月月更

人人都可以学会的产品手册制作方法

小炮

产品宣传手册

TiDB 在连锁快餐企业丨海量交易与实时分析的应用探索

TiDB 社区干货传送门

LSM-Tree - LevelDb了解和实现

懒时小窝

LSM树 LSM-Tree

豆瓣评分9.0,时隔6年,“Linux命令行圣经”新版终于来了!

图灵教育

Linux shell脚本编程

【数据安全与流通专题】星环科技春季新品发布周

星环科技

CrashSight异常崩溃管理解决方案

WeTest

桌面运维工程师需要会哪些技能?主要是做什么的?

行云管家

云计算 运维 运维工程师 IT运维 云运维

分布式存储cephfs读取优化方案_大数据_李航_InfoQ精选文章