1.cephfs:用户态客户端预读
2.GlusterFSåCephåªä¸ªå¥½ï¼
3.ceph-fuse原理小记
4.ceph原理 Ceph简单介绍
cephfs:用户态客户端预读
用户态客户端读取文件数据通常通过Client::_read_async函数实现,原理源码源码这是原理源码源码read操作的核心函数。根据名称,原理源码源码“异步读”意味着存在与之对应的原理源码源码“同步读”,即Client::_read_sync。原理源码源码理解了Client::_read_async,原理源码源码坏坏猫朗读引擎url源码Client::_read_sync也就容易掌握了。原理源码源码这两者之间的原理源码源码主要区别在于,Client::_read_async负责读取缓存并具有预读功能,原理源码源码而Client::_read_sync则是原理源码源码直接从osd读取数据。
Client::_read_async可以简单分为read和readahead两部分。原理源码源码read对应于读流,原理源码源码而readahead对应于预读流。原理源码源码以下是原理源码源码这两个流的简单图示。可以看出,原理源码源码readahead stream不会阻塞等待。
结合代码来解释Client::_read_async。首先,解释一下预读。假设我们读取一个M的文件,每次读取k,如果没有预读且最初没有缓存文件内容,总共需要读取次,建站源码网站每次都需要与osd交互。如果有预读,同样需要读取次,但每次发送k的读请求给osd后,预读机制会再发送一个k的读请求,后面的k的读请求不阻塞等待。这相当于将用户的一次读请求在后端分成两个请求,共读取k,其中后一个k的请求是异步的。因此,下一次读请求来时,增加了命中缓存的几率,即使没有命中缓存,这也相当于提前将这个k的请求发送给osd,争取了一点时间效率。
预读Readahead在ceph中可以作为一个单独的模块,其代码位置在src/common/Readahead.h和src/common/Readahead.cc。这个模块的功能很简单,即通过简单的机制或算法来计算每次预读的偏移(offset)和长度(length)。
在Readahead类中,有两个参数m_readahead_min_bytes和m_readahead_max_bytes,表示预读请求的源码分析公式最小和最大预读长度。这两个值是通过配置参数赋值的,在src/common/options.cc中有三个关于预读的配置参数。m_readahead_min_bytes和m_readahead_max_bytes的赋值位置在Client::_create_fh处。
从上面的代码中可以看出,m_readahead_min_bytes的值为 * = KB,m_readahead_max_bytes的值为in->layout.get_period() * (uint_t)conf->client_readahead_max_periods。get_period()函数来自结构体file_layout_t。目前ceph中使用的是简单条带策略,stripe_unit = object_size = 4M,stripe_count = 1,因此m_readahead_max_bytes的值为4M * 4 = M。综上所述,预读的范围是KB ~ M(在不考虑预读对齐的情况下)。
Readahead类实例是作为struct Fh的成员来使用的,struct Fh可以看作是文件的描述结构,Fh即File handle的简写。
读取文件时,需要先打开文件,在打开文件时,会建立Fh,并将其加入fd_map中。然后在关闭文件时,投资软件源码销毁Fh,并将其从fd_map中移除。代码如下:先是Client::open,然后再Client::_open中调用Client::_create_fh创建Fh。可以看出,如果一个文件已经打开,修改的预读范围对正在读取的文件是没有影响的。
在Client::_read_async中,在进行预读之前,需要计算预读的偏移值和长度。readahead_extent.first是偏移值,readahead_extent.second是长度,通过Readahead::update函数得到。
Readahead::update代码如下。在研究Readahead::_observe_read代码之前,先研究Readahead中7个比较重要的参数。Readahead::_observe_read代码如下。从代码中可以看出,_observe_read函数的作用是判断这一次的读请求是否是顺序的,如果不是顺序读,将其中的5个成员置0(置0的作用在后面可以看到,最后算出的keeper源码解析预读偏移和长度都为0)。
Readahead::_compute_readahead是预读最重要的函数,代码如下。单纯看代码,会有点看不懂预读对齐,参考下面的图,更容易理解。上面的原理图只画了一种情况,即offset和offset+len分别落在不同的对象区间。如果readahead_end靠近align_prev时,如果需要对齐到4n,那么对齐的代价就是减少的对齐长度不能大于对齐前的预读长度的一半;如果readahead_end更靠近align_next时,如果需要对齐到4(n+1),那么对齐的代价就是增加的对齐长度不能大于对齐前的预读长度的一半。
还有第二种情况,即offset和offset+len分别落在相同的对象区间。这个时候就不能往align_prev对齐,如果需要补全到align_next,同样的道理,补全的对齐长度不能大于对齐前的预读长度的一半。所以通过预读对齐,预读的大小范围在K到M。
预读的原理图如下。预读的时候会判断是否是顺序读,这里就涉及到读的方式,一般都是顺序读,除非特意指定为随机读,当然这种情况暂且不论。在正常的顺序读中,也会出现非顺序读的情况。以ceph-fuse为例,比如读取M的test文件,一个io是K,总共会发个读请求。简单示意图如下。由于存储后端osd回复读请求的时间不一致,所以预读不一定按照①②③④顺序执行请求,假设①请求处理完后,③请求②请求比先读到后端数据,即顺序为①③②④。那么在处理③时,预读Readahead实例中记录的m_last_pos = K,而此时的读流的offset = K,m_last_pos != offset,这就没有按照顺序去读,就不会去预读。
GlusterFSåCephåªä¸ªå¥½ï¼
GlusterFSåCephé½æ¯Red Hatæä¸çæççå¼æºåå¨äº§åï¼Cephä¸GlusterFSå¨åçä¸æçæ¬è´¨ä¸çä¸åãç°å¨ç½ä¸æå¾å¤GlusterFSåCephç对æ¯æç« ï¼å¯ä»¥åèãæ»çæ¥è¯´ï¼å¦æåªä½¿ç¨æ件åè®®ï¼æè§GlusterFS好ä¸äºï¼å¦æè¦ä½¿ç¨å¯¹è±¡ãåãæ件ç»ä¸åå¨ï¼é£Cephæ¯ä¸äºéæ©ãè¦ä½¿ç¨å¥½è¿ä¸¤ä¸ªå¼æºè½¯ä»¶ï¼é½éè¦æä¸å®çITå®ååæå ¥ï¼å¦æå®é ä¸ç产使ç¨ï¼å»ºè®®éæ©åä¸åç产åï¼GlusterFSåä¸äº§ååå¾æ¯è¾å¥½çå ¬å¸æ大éãå¯ç¿ï¼Cephåä¸äº§ååå¾æ¯è¾å¥½çå ¬å¸æXSKYãå æ ¸äºãæ岩ã
ceph-fuse原理小记
ceph-fuse是Ceph分布式文件系统的FUSE(用户空间文件系统)客户端。基于FUSE,通过实现具体的文件操作(如read、write等)接口,即可实现Ceph用户态文件系统。Ceph定义的文件操作函数都封装在Client类中,Client类的定义如下。
Client实例指针是CephFuse::Handle类的成员变量,而CephFuse::Handle实例指针则是CephFuse类的成员变量。这样,CephFuse实例可以通过client来调用文件系统操作函数进行文件操作。
在Ceph-fuse进程启动时,会实例化StandaloneClient和CephFuse,并执行Client::mount和CephFuse::start。关于Client::mount的操作,这次不详细讨论。
然后是CephFuse::start -> CephFuse::Handle::start。CephFuse::Handle::start代码如下。在start中,将fuse_ll_oper传参给fuse_lowlevel_new。而fuse_ll_opers是struct fuse_lowlevel_ops的实例,fuse_ll_oper中定义了fuse的文件操作函数。
在Ceph-fuse的main函数中,接下来就是CephFuse::loop -> CephFuse::Handle::loop。在fuse_session_loop_mt中会生成多个线程去监听IO操作,具体的流程图如下。
FUSE框架由两部分组成:内核态部分(即fuse.ko)和用户态部分(即libfuse.so)。在Ceph-fuse启动时,监听用户请求接口是fuse_session_loop_mt,该函数属于libfuse.so的接口,监听的媒介是/dev/fuse杂项设备,即通过read /dev/fuse来监听;访问FUSE文件系统,就得实现file_operations(定义在fuse.ko中)。
在mount fuse挂载点时,先要加载fuse.ko,主要做两件事:注册杂项设备/dev/fuse和注册FUSE文件系统。
在FUSE文件系统中,定义了文件的操作fuse_file_operations,这些接口并不是真正实现read/write操作。
通过对杂项设备/dev/fuse进行读写,来实现用户态部分和内核态部分的通信,这就需要对杂项设备注册file_operations实现对应的读写操作。
fuse_dev_init函数如下。fuse_miscdevice中用&fuse_dev_operations给fops赋值。
fuse_dev_operations结构体的定义如下。即read /dev/fuse在fuse中的实现为fuse_dev_read。
...
举个例子,lookup /fuse,从lookup /fuse到ceph-fuse的流程图如下。在fuse_do_work线程中获取到req后,通过fuse_session_process_buf -> fuse_ll_process_buf,执行fuse_ll_ops[in->opcode].func(req, in->nodeid, inarg)。fuse_ll_ops中定义了函数操作码对应的具体的函数。
在此例中in->opcode = FUSE_LOOKUP,所以就会执行do_lookup。而req->f->op就是fuse_ll_oper,所以就走到fuse_ll_lookup,fuse_ll_lookup定义如下。
在Client::ll_lookup中会发送请求给MDS。MDS返回结果给Ceph-fuse进程。从Ceph-fuse到lookup /fuse的流程图如下。
ceph原理 Ceph简单介绍
1、Ceph是一个可靠地、自动重均衡、自动恢复的分布式存储系统,根据场景划分可以将Ceph分为三大块,分别是对象存储、块设备存储和文件系统服务。在虚拟化领域里,比较常用到的是Ceph的块设备存储,比如在OpenStack项目里,Ceph的块设备存储可以对接OpenStack的cinder后端存储、Glance的镜像存储和虚拟机的数据存储,比较直观的是Ceph集群可以提供一个raw格式的块存储来作为虚拟机实例的硬盘。
2、Ceph相比其它存储的优势点在于它不单单是存储,同时还充分利用了存储节点上的计算能力,在存储每一个数据时,都会通过计算得出该数据存储的位置,尽量将数据分布均衡,同时由于Ceph的良好设计,采用了CRUSH算法、HASH环等方法,使得它不存在传统的单点故障的问题,且随着规模的扩大性能并不会受到影响。