一种将对象存储系统实现为本地文件系统的方法与流程

文档序号:11545215阅读:281来源:国知局
本发明属于计算机存储
技术领域
:,更具体地,涉及一种将对象存储系统实现为本地文件系统的方法。
背景技术
::用户空间文件系统(filesysteminuserspace,fuse)是一种unix/linux操作系统提供的机制,这种机制使得非特权用户无需理解unix/linux内核的文件系统实现、无需编辑和编译内核的源代码就可以实现自己的文件系统。目前,fuse已经被广泛用于编写开发各种文件系统,与传统的文件系统不同,fuse并不一定真正地读写磁盘,它提供到已存在的文件系统或存储设备的转换,使得已存在的文件系统或存储设备上的数据以文件和目录的形式提供给用户或应用。对象存储系统swift是开源云计算项目openstack的子项目之一。swift是一个无单点故障问题的分布式的对象存储系统,提供数据的高可用性和最终一致性,swift存储系统具有高的水平扩展性,无论是性能还是容量的扩展,都可以通过增加物理节点服务器来获得。swift存储系统提供http/https、restapi的对象访问接口,可用来高效、安全、廉价地存储大量的数据。目前,借助fuse文件系统框架将对象存储系统实现为本地文件系统已经有一些成熟稳定的实现案例,如s3fs、expandriv,s3fs是一个基于fuse的文件系统,后端由amazons3提供存储支撑,用户可以将amazons3的bucket挂载为本地的文件系统。expandrive是一个针对macosx和microsoftwindows用fuse实现的商业版本的网络文件系统客户端,它能将本地卷映射到ftp、amazons3、swift、webdav、dropbox等存储服务器。cloudfuse是一个借助fuse将swift中一个account实施为本地文件系统的开源实现。然而,像s3fs、expandrive和cloudfuse等这些基于fuse实现的文件系统客户端,都是针对pc机上个人用户做了充分的考虑和优化,但是它们并不适合于作为应用服务器上的存储支撑。将诸如swift、amazons3等对象存储系统借助fuse实现为本地文件系统,在处理对一个几百兆或gb级别的大文件进行读或写,或是对一个有几千上万个子目录的大目录进行遍历的应用场景时,文件系统表现出来的性能和该文件系统实现的算法是密切相关的。特别地,如果是该客户端的应用同时操作多个大文件和大目录,性能和稳定性问题就尤为突出。技术实现要素:针对现有技术的以上缺陷或改进需求,本发明提供了一种将对象存储系统实现为本地文件系统的方法,通过高效的文件元数据缓存算法、遍历目录时的内存使用算法、读文件的缓存算法、分块的文件写入算法等减少了内存使用量,提高了该文件系统的读和写性能,减少了fuse文件系统和后端swift存储系统的网络交互,为将swift对象存储系统挂载到应用服务器本地作为本地文件系统访问提供了可行性。由此解决现有技术中应用同时操作多个大文件和大目录时出现的性能和稳定性的问题。为实现上述目的,按照本发明的一个方面,提供了一种将对象存储系统实现为本地文件系统的方法,包括:(1)文件系统元数据缓存:将本地文件系统文件的绝对路径名作为缓存项的关键字,计算得到一个哈希值,以哈希值作为元数据缓存项的关键字,将缓存项散列到哈希表中,其中,缓存项结构包括类型、修改时间、缓存时间、是否目录、状态、互斥锁、反向指针、哈希冲突双向链表以及最近最少使用双向链表;(2)文件系统目录遍历:当应用程序通过可移植操作系统接口posix兼容的c库接口readdir访问本地文件系统的特定目录时,实现readdir接口所采用算法核心是:预先分配内存池,初始化池中描述目录结构的描述体dir_info,在每次遍历文件系统目录时,都向池中请求dir_info描述体,当池中的dir_info描述体耗尽或达到某个阀值时,启动回收算法,释放未被占用的dir_info描述体,归还到内存池中,应用程序重新从内存池中获取请求dir_info描述体;(3)文件的打开:当应用程序读或者写文件时,都会执行打开文件操作,对于每个打开文件操作,给所打开的文件分配一个描述打开操作的打开文件句柄内存结构,其中,该打开文件句柄记录了:(a)描述打开文件的标记,打开文件标记分为读、写、读和写三种情况;(b)以字节为单位的文件对象的大小;(c)读缓冲区的内存描述体,但实际的读缓冲区滞后到具体第一次读操作请求时才分配;(d)描述从swift对象存储系统下载对象的下载上下文结构,该结构体是在打开文件时预分配的,与之关联的读缓冲区是滞后分配的;(e)描述将文件作为对象上传到swift对象存储系统的上传上下文结构,该结构体是在打开文件时预分配的,但与之关联的上传缓冲区无需额外分配,复用应用程序写操作的write调用缓冲区即可;(f)描述在写文件操作时,实际上传文件到swift对象存储系统过程的内存描述符上传任务描述结构,该描述结构体中记录了上传状态、线程id、互斥访问锁、下载计数器、能写信号量、写完成信号量、能上传信号量以及任务结束信号量;整个上传过程中,用户的write系统调用与上传用户数据到swift对象存储系统之间的同步逻辑,由这四个信号量进行控制;(4)读文件:对于应用程序读文件的操作,采用的是预读策略,所谓预读是指从当前请求偏移处开始,读出比当前请求数据量更大的一块到内存缓存起来,每次读操作时,首先检查所请求的数据是否在缓冲区,如果在,则直接从缓冲区中读出,无需向后端的swift对象存储系统请求数据,如果不在缓冲区,则向后端的swift对象存储系统请求比当前请求更大的一段数据到内存缓存起来;当应用程序读文件之前,必须先以读或者读和写方式打开文件,并建立打开文件句柄的内存结构,当应用程序第一次调用read系统调用读文件时,首先分配一个缓冲区,缓冲区的大小通过配置文件进行调节,但最小为1mbytes,然后,采取“预读”策略,预先尽可能从swift对象存储后端将文件对象的内容一次读满缓冲区,应用程序后续的读文件系统调用,将先检查所读数据是否已经在缓冲区中,如果不在,则再触发一次对swift对象存储后端的读操作,如此反复进行,直到应用程序关闭文件,当文件被关闭时,释放打开文件句柄所占用的内存和信号量资源;(5)文件写入:对于应用程序的写文件操作,采用的是零次拷贝,分块写入的策略,当应用程序打开文件准备写入数据时,建立打开文件句柄的内存结构,每次应用程序写文件操作的write系统调用的用户数据缓冲区将被复用为“上传缓冲区”,上传线程直接从该缓冲区中读取数据上传到swift对象存储后端,当该缓冲区中的所有数据都上传完毕时,视该次write系统调用成功执行,下一次write系统调用都重复该过程,直到该文件被关闭,当文件被关闭时,释放打开文件句柄所占用的内存和信号量资源;在整个写文件过程中,应用程序的write系统调用与上传数据到swift对象存储后端这两个动作之间的同步,由打开文件句柄所引用的上传任务描述结构中的四个信号量进行精确控制,而且在用户态空间没有任何的数据拷贝操作。优选地,在(1)所述的文件系统元数据缓存中,文件系统中每个文件的元数据组织为一个元数据缓存项,当出现哈希冲突时,采用哈希冲突链的方式来解决,将出现哈希冲突的缓存项链接到一个双向链表中,根据文件绝对路径名在哈希表中查找缓存项时,首先根据文件绝对路径名计算该缓存项的哈希值,以该哈希值作为关键字得到元数据缓存项的哈希表入口,每个哈希表入口对应一个冲突链,然后在对应的冲突链中使用文件绝对路径名顺序匹配缓存项。优选地,在(1)所述的文件系统元数据缓存中,在初始化时预先分配一个缓存项池,缓存项池的大小根据物理机器的内存大小配置,每次需要缓存项时,从该缓存项池中取一个,当池中的缓存项耗尽时,启动缓存项回收例程,使用最近最少使用算法从缓存系统中回收若干个缓存项到池中,每次释放缓存项时,将缓冲项设置为空闲状态并将其归还到缓存项池中。优选地,在(1)所述的文件系统元数据缓存中,根数据结构是一个名为cache-manager的结构体,cache-manager结构体在挂载该文件系统时完成初始化操作,其中cache-manager结构体的字段包括关键字比较函数指针、哈希函数指针、缓存项计数器、缓存目录项计数器互斥锁、哈希表指针以及最近最少使用缓存项管理器。优选地,检查确定名称的文件对象是否已经被缓存的过程名为check_cache(path,len),实现步骤如下:(1)参数合法性检查,对于非法参数不做任何后续处理,直接空指针给调用者,调用者必须检查该过程的返回结果;(2)根据文件绝对路径名参数及其长度,计算哈希值,以该哈希值作为数组下标,在哈希表中得到哈希冲突链的入口;(3)获取哈希冲突链的“读”锁,如果获取不到,则进行超时等待,在超时范围内获取不到“读”锁,则向调用者返回空指针;(4)从冲突链首项开始,以文件绝对路径名及其长度为关键字,依次匹配冲突链中的每一项中保持的文件绝对路径名和长度,如果精确匹配,则将返回值设置为该缓存项的地址值,否则设置返回值为空指针;(5)释放步骤(3)中所获取哈希冲突链的“读”锁;(6)向调用者返回查找结果。优选地,文件系统在运行时,缓存系统的工作步骤为:s1:以文件绝对路径名及其长度为参数,调用check_cache函数检查文件元数据是否已经被缓存,如果没被缓存,则执行步骤s3;s2:文件元数据被缓存,检查该缓存项是否已经过期,如果没过期,则更新该缓存项时间戳,并返回该缓存项的地址给调用者,否则,跳转到步骤s5;s3:从缓存项池中申请获取一个空闲状态的缓存项,如果获取到,则转至步骤s5;s4:从缓存项池中没有获取到空闲状态的缓存项,启动缓存项回收过程,将最近最少使用的缓存项目回收到池中,转到步骤s3;s5:从swift对象存储系统中检索该文件对象信息,使用其元数据填充该缓存项,并将该缓存项的状态由空闲状态设置为占用状态;s6:将该缓存项插入到缓存系统中;s7:将该缓存项的地址给调用者。优选地,文件系统目录遍历具体包括以下操作:(2.1)初始化参数ret_dir_info_list链表为空值,将文件系统的目录名映射为swift对象存储系统中对应的容器名;(2.2)以容器名为参数,向swift对象存储系统服务器发送gethttp请求,以获取该容器下所有对象名的一份xml文档;(2.3)如果gethttp请求的返回状态码大于预设值,则表示获取不到对象列表,这种情况下向调用者返回空值链表,否则,执行步骤(2.4);(2.4)将当前节点元素指向xml文档的根节点元素的第一个子节点,访问当前节点所描述对象属性的数据;(2.5)从内存池中申请获取一个描述目录结构的内存结构dir_info,如果获取失败,则启动内存回收算法,并执行步骤(2.4),否则执行步骤(2.6);(2.6)将当前节点元素信息填写入所获取到的目录结构dir_info中,将dir_info结构体加入到ret_dir_info_list链表;(2.7)如果遍历完了xml文档的根节点元素的所有子节点,则向调用者返回ret_dir_info_list链表,否则转向步骤(2.2)递归执行。优选地,所述打开文件句柄的内存结构包括:文件打开描述结构体,具体包括:打开标志,用于标识该文件是否已经被打开;文件对象大小,用于表示字节单位的文件大小;读缓存上下文结构,包括缓存使能标志、计数器、缓冲区长度、当前读缓存指针的偏移、读缓存指针;下载操作的上下文指针,用于指向下载上下文结构体;上传操作的上下文指针,用于指向上传上下文结构体;下载上下文结构,具体包括:缓存区大小字段,用于描述该缓冲区的大小;当前缓存区下载偏移量,用于记录以字节为单位,相对于缓存区首字节的偏移量;下载缓冲区指针,表示指向数据存储区的指针;下载计数器,表示打开该文件后,直到当前所下载数据量的统计值;上传上下文结构,具体包括:打开文件标志,记录用户打开文件的选项,为以后兼容posix语义时扩展;当前缓存区数据是否上传完成标志,标记该缓冲区中的文件是否上传完毕;文件名指针,指向文件名字符串的指针;缓存区大小,记录写缓存区的大小;当前缓存区偏移,该缓存区中已经写入的数据量,也是下次写数据到缓存区时相对于缓存区首字节的偏移量;上传缓存区指针,指向数据缓存区当前写入位置的指针;上传任务指针,指向上传任务描述结构体的指针;上传任务描述结构,具体包括:上传状态,分为正在进行、传输完毕、传输错误共三种状态;线程id,上传线程的句柄;互斥访问锁,访问该描述结构的互斥量,只有持有该锁的线程才能进行读写操作;下载计数器,本次上传任务所下载数据量的总和;“能写”信号量,是二值信号量,用于两次向缓存区写请求之间的同步;“写完成”信号量,用于两次缓存区写操作之间的同步,只有上一次写缓存区完成,下一次写才能开始;“能上传”信号量,是二值信号量,用于两次网络上传操作之间的同步;任务结束信号量,用户写操作线程和后台传输线程之间的同步信号量。优选地,读文件具体包括以下操作:(4.1)文件路径名path、读偏移offset、本次读请求的数据量size、用户数据缓存区buff、返回值retval的初始化;(4.2)调用打开文件例程,获取打开文件句柄openfile,调用swift对象存储接口api,获取对应文件的大小,并记录打开文件标志、清空用户数据缓存区;(4.3)检查偏移量offset和size的合法性,如果不合法,则返回当前实际所读数据的数量;(4.4)检查本次用户读请求的数据是否存在或部分存在于缓存区中,如果是,则直接从缓存区中获取数据,将其拷贝到用户数据缓存区buff中,同时修正读偏移量和已读数据长度等变量值,否则,如果数据完全不在缓存区,则执行步骤(4.5);(4.5)清空“读缓冲区”,重置“文件打开描述结构体”、“下载上下文结构”中的计数器和偏移字段值为0;(4.6)调用swift对象存储接口api,从swift对象存储系统中,将特定对象的指定偏移处的数据读到“读缓冲区”,一次尽量读满缓冲区,除非读完整个对象;设置“下载上下文结构”中的“下载计数器”、“当前缓冲下载偏移”字段值;(4.7)将缓冲区中特定偏移处特定长度的数据拷贝到用户缓冲区buff,调整“文件打开描述结构体”中的“当前读偏移”字段值,将本次实际读到数据长度累计到retsize变量中;(4.8)检查是否读完请求长度的数据量,文件是否未结束,只要满足这二者之一,则返回retsize给调用者,结束本次读文件操作,否则,修正offset和size分别到下一次读请求的偏移和长度,转至步骤(4.3)。优选地,文件写入具体包括以下操作:(5.1)初始化文件路径名path、写偏移offset、本次写请求的数据量length、用户数据缓存区buff、返回值retval;(5.2)调用打开文件例程,获取打开文件句柄openfile,初始化“上传上下文结构”中的“能写信号量”为能写状态,can_write=1;初始化“上传上下文结构”中的“写完成信号量”为未完成状态,write_can_return=0;初始化“上传上下文结构”中的“能上传信号量”为不能上传状态,can_cloud=0;初始化“上传上下文结构”中的“任务结束信号量”为未结束状态,can_release=0;(5.3)启动上传异步io线程octet_upload();(5.4)octet_upload()调用sem_wait等待在信号量can_cloud上;(5.5)检查上传上下文结构中当前缓冲区是否有数据,如果没有,则octet_upload()依次对信号量write_can_return、can_cloud、can_write、can_release调用sem_post操作,表示整个上传过程结束,同时octet_upload()线程向swift对象存储系统提交本次写操作,本次写操作完成;否则,继续执行步骤(5.5);(5.6)检查当前缓冲区是否有未上传完的数据;(5.7)如果当前缓冲区是有未上传完的数据,则octet_upload()依次对信号量write_can_return、can_write调用sem_post操作,表示本次write调用的数据上传完成,等待下一次write调用,转至步骤(5.3);(5.8)如果当前缓冲区中没有尚未上传完的数据,则octet_upload()将上传缓存区中当前偏移处确定大小的数据通过switf客户端api写入swift对象存储系统,同时修改上传偏移和计数器,并对信号量can_cloud调用sem_post操作,表示可继续上传数据,转至步骤(5.3)。总体而言,通过本发明所构思的以上技术方案与现有技术相比,主要有以下的技术优点:(1)通过用户空间文件系统fuse将对象存储系统swift实施为应用服务器的本地文件系统的方案中,所采用的文件系统元数据缓存算法和内存描述结构。该算法和内存描述结构极大减少了应用程序与swift存储系统后台的交互次数,提高了应用程序访问swift存储系统的性能,使得通过fuse将swift存储系统实施为本地文件系统作为应用服务器的存储支撑系统成为可行的方案。(2)通过用户空间文件系统fuse将对象存储系统swift实施为应用服务器的本地文件系统的方案中,所采用的文件系统目录遍历算法。该算法采用预分配内存池和延迟批量回收空闲内存块的策略,提高了遍历一个包含大量子目录和文件的目录时的效率。(3)通过用户空间文件系统fuse将对象存储系统swift实施为应用服务器的本地文件系统的方案中,在执行打开文件操作时,本方案所采用的“打开文件句柄”内存描述结构。应用程序通过打开文件获得该文件句柄,能高效的进行文件读写操作。(4)通过用户空间文件系统fuse将对象存储系统swift实施为应用服务器的本地文件系统的方案中,在读文件时所采用“预读”策略。通过实施预读策略,能有效减少应用服务器和swift存储后端之间的网络交互次数,提供文件系统的“读”性能。(5)通过用户空间文件系统fuse将对象存储系统swift实施为应用服务器的本地文件系统的方案中,在写文件时所采用“零次拷贝,分块写入”策略。通过实施该策略,写文件的过程中没有任何的数据拷贝和缓存,每次write系统调用都是一个完整的“块”写入操作,从而提高了文件写入的效率。附图说明图1为fuse文件系统的模块交互示意图;图2为swift对象存储系统的逻辑示意图;图3为一种将对象存储系统实现为本地文件系统的方法的流程示意图;图4为缓存项结构示意图;图5为缓存系统内存映像结构示意图;图6为检查文件系统元数据是否被缓存的流程示意图;图7为文件系统运行时的缓存系统工作流程示意图;图8为文件系统目录遍历的流程示意图;图9为打开文件句柄的内存结构示意图;图10为文件读操作的流程示意图;图11为写文件操作的流程示意图。具体实施方式为了使本发明的目的、技术方案及优点更加清楚明白,以下结合附图及实施例,对本发明进行进一步详细说明。应当理解,此处所描述的具体实施例仅仅用以解释本发明,并不用于限定本发明。此外,下面所描述的本发明各个实施方式中所涉及到的技术特征只要彼此之间未构成冲突就可以相互组合。通过用户空间文件系统fuse将对象存储系统swift实施为应用服务器的本地文件系统的方法中,将对象存储系统swift中的若干个账户映射为linux系统中高性能的若干个本地文件系统,对运行于这些本地文件系统之上的应用提供:创建文件、打开文件、读文件、写文件、关闭文件、删除文件、创建目录、删除目录、遍历目录等编程和命令接口。该技术可应用于互联网应用的日志归档备份、云计算环境中虚拟机镜像文件和快照的存储和备份、音视频存储等需要海量存储能力和水平扩容能力的场景。该技术有两大优点,第一、简化了应用开发者使用swift对象存储系统的复杂性;第二、对于以本地文件系统方式使用存储的应用,可以无缝地迁移到swift对象存储系统。基于
背景技术
:的介绍,用fuse实现的文件系统运行在用户态,通过libfuse库提供的api与名为fuse的内核模块交互,使得用户可以完全在用户态开发自己的文件系统。在用户空间实现文件系统能够大幅提高生产率,它简化了为操作系统提供新的文件系统的工作量,特别适用于各种虚拟文件系统和网络文件系统。如图1所示,展示了fuse文件系统的主要模块和模块之间的交互。位于内核空间中的fuse模块通过打开设备文件/dev/fuse获得的文件描述符,与位于用户空间的libfuse库通信,设备文件/dev/fuse可以被打开多次,所获得的文件描述符传递给mount系统调用。基于fuse开发的文件系统有很多,例如:(1)transmit:在macosx上用fuse实现的商业版的ftp客户端,它能够将webdav、sftp、ftp和amazons3服务器挂在为本地finder中的文件存储系统。(2)encfs:使用fuse实现的加密虚拟文件系统。(3)volatilefs:使用fuse实现的商业版的ramdisk。(4)glusterfs:借助于fuse实现的集群分布式文件系统,它提供pb级别的扩展能力。(5)sshfs:使用fuse实现的通过ssh访问远程文件系统的存储系统。(6)cassandrafs:借助fuse实现的位于cassandra(一种nosql存储系统)之上的文件系统。如图2所示给出了一个swift系统的实际部署拓扑。swift有三种形式的节点:代理节点(也称作proxynode)、存储节点(也称作storagenode)、认证节点(也称作authnode),有四种类型的服务器:代理服务器(也称作proxyserver)、存储服务器(也称作storageserver)、一致性服务器(也称作consistencyserver)、认证服务器(也称作authserver)。storageserver和consistencyserver运行在storagenode上,proxyserver运行在proxynode上,authserver可以作为一个独立的服务运行在一个集群或一个节点上,也可以和proxyserver一起运行在proxynode上。proxyserver是负责提供swiftapi服务的进程。对于每个客户端的请求,它将在一个称为ring的内部映射表中查询该用户请求的账户、容器或对象的实际物理位置信息,根据这些信息,proxyserver执行请求的操作并响应用户。proxy提供了rest-fullapi,并且符合标准的http/https协议规范。storageserver提供物理磁盘上的存储服务,有三类存储服务器:accountserver、containerserver、objectserver。其中containerserver负责处理“对象”的列表,containerserver不知道“对象”的实际物理存放位置,只知道指定“容器”里存的哪些“对象”。container服务器也做一些跟踪统计,例如某个container中object的总数、container的使用情况。accountserver与containerserver的工作原理类似,只是accountserver负责处理的是“容器”的列表信息。objectserver以二进制文件的形式将“对象”存储在本地存储设备中,对象的元数据以文件的扩展属性的形式保持在本地文件系统中。objectserver提供数据对象的存储、检索、删除等服务。swift存储系统中的复制服务器(也称作raplicator)、更新服务器(也称作updater)、审计服务器(也称作auditor)可以统称为consistencyservers。consistencyservers查找并解决由数据损坏和硬件故障引起的错误。auditor在每个sotrageserver的后台持续地扫描磁盘来检测对象、容器和账户的完整性。如果发现数据文件损坏,auditor就会将该文件移动到隔离区。然后,replicator负责用一个完好的拷贝来替代该数据。在系统高负荷或者发生故障的情况下,container或account中的数据也许不会被立即更新。如果更新失败,该次更新会加入到本地文件系统中的队列中,随后,updater会继续处理这些失败的更新工作。相应地,accountupdater负责account的列表更新,containerupdater负责object列表的更新。replicator的功能是保证数据最终存放在正确地位置,并保持数据的合理拷贝数。replicator的设计目的是在面临诸如网络中断或驱动器故障等临时性故障情况下,swift还可以保持数据的一致性。swift存储系统中的“环(也称为ring)”维护着存储在磁盘上的数据项的名字与它的物理存储位置之间的映射信息。对于三种不同的数据项:账户、容器、对象,都有分别对应的rings。当swift中的其它组件需要对账户、容器或对象执行某种操作时,这些组件都需要和相对应的ring交互,以得到这些被操作的accounts、containers或objects在集群中的物理位置信息。本发明提出高效的文件元数据缓存算法、遍历目录时的内存使用算法、读文件的缓存算法、分块的文件写入算法等。这些技术减少了内存使用量,提高了该文件系统的读和写性能,减少了fuse文件系统和后端swift存储系统的网络交互。通过解决这些问题,为将swift对象存储系统挂载到应用服务器本地作为本地文件系统访问提供了可行性。如图3所示,为本发明实施例公开的一种将对象存储系统实现为本地文件系统的方法的流程示意图,在图3所示的方法中,包括:(1)文件系统元数据缓存:将本地文件系统文件的绝对路径名作为缓存项的关键字,计算得到一个哈希值,以哈希值作为元数据缓存项的关键字,将缓存项散列到哈希表中,其中,缓存项结构包括类型、修改时间、缓存时间、是否目录、状态、互斥锁、反向指针、哈希冲突双向链表以及最近最少使用双向链表;其中,在将swift存储系统通过fuse实施为linux应用服务器的本地文件系统方案中,引入缓存系统的目的是减少应用服务访问本地文件系统时,通过swift存储系统的客户端与swift存储系统服务器交互的次数。下面以一个具体的实施例介绍如何将本地文件系统文件(包括目录)的绝对路径名作为缓存项的关键字,计算得到一个哈希值,以哈希值作为元数据缓存项的关键字,将缓存项散列到哈希表中。例如,将swift的账户foo挂载到本地/mnt/foofs目录下,swift的foo账户有容器container-1、container-2,容器container-1下有对象b1、folder-1、folder-1/b1、folder-1/b2、folder-1/folder-1-1/b1,容器containter-2下有对象folder-1、folder-1/b1,则下面九个文件/目录路径名都可作为缓存项的关键字:/mnt/foofs/container-1;/mnt/foofs/container-2;/mnt/foofs/container-1/b1;/mnt/foofs/container-1/folder-1;/mnt/foofs/container-1/folder-1/b2;/mnt/foofs/container-1/folder-1/folder-1-1;/mnt/foofs/container-1/folder-1/folder-1-1/b1;/mnt/foofs/container-2/folder-1/;/mnt/foofs/container-2/folder-1/b1。其中,容器container-1和container-2对应本地文件系统的文件夹,容器containter-1下的对象folder-1、folder-1/folder-1-1对应本地文件系统下的文件夹,container-1下的对象folder-1/b1、folder-1/folder-1-1/b1对应本地文件系统下的文件等。缓存项的结构见图4。图4所示的缓存项结构的各个字段描述如下:类型:该字段对应swift存储系统中的对象的content_type属性;修改时间:该字段表示该对象(包括对象的属性)的修改的时间;缓存时间:该字段表示对象被该缓存系统所缓存的起始时间,当访问某一个对象时,如果从该缓存系统中得到了该对象的信息,则用当前时间减去“缓存时间”,如果其差值大于某一规定的实际(比如60秒),则认为该缓存项所描述的对象信息已经过期,这种情况下,缓存系统将从swift存储系统重新检索该对象的信息,并将新的对象信息缓存起来,如果该对象在swift存储系统中已经不存在了,则从缓存系统中删除该缓存项;是否目录:该字段表示该缓存对象是否对应本地文件系统中的一个目录,在该例子中,容器container-1、container-2是目录,容器container-1下的对象folder-1、folder-1/folder-1-1是目录,而容器container-1下的对象b1、folder-1/b2不是目录;状态:该字段指示该缓存项目前的状态,有三种不同的状态:free、unlocked、locked。缓存项结构体在初始化后,置该字段为free状态。一般而言,正在被使用的缓存项处于“unlocked”状态。但是,当打开一个文件执行写操作时,该缓存项处于locked状态,写文件操作执行完成并执行文件的fflush或fsync族函数后,该字段置为unlocked状态;互斥锁:该字段用于多线程环境中控制对缓存项结构体的并发访问;反向指针:该字段指向“哈希冲突双向链表”的表头;哈希冲突双向链表:该字段用来将产生了哈希冲突的缓存项链接成一个双向链表;最近最少使用双向链表:该字段用来将缓存项链接到一个lru算法的链表中,最近被使用的缓存项位于lru链表的表首第一项,次最近被使用缓存项位于表首第二项,依此类推。当需要回收缓存项时,从lru链表的末尾开始,逆向依次释放。文件系统中每个文件的元数据组织为一个元数据缓存项,当出现哈希冲突时,采用哈希冲突链的方式来解决。将出现哈希冲突的缓存项链接到一个双向链表中,根据文件绝对路径名在哈希表中查找某个缓存项时,首先根据文件绝对路径名计算其哈希值,以该哈希值作为关键字得到元数据缓存项的哈希表入口,每个哈希表入口对应一个冲突链,然后在对应的冲突链中使用文件绝对路径名顺序匹配缓存项。为了提高系统的查找效率,缓存系统在初始化时预先分配一个“缓存项池”,池的大小根据物理机器的内存大小配置。每次需要缓存项时,从该池中取一个,当池中的缓存项耗尽时,启动缓存项回收例程,使用“最近最少使用”算法从缓存系统中回收若干个缓存项到池中。每次释放缓存项时,将缓冲项设置为“空闲状态”并将其归还到池中。本缓存系统实施方案中,数据结构及其之间的关系见图5。如图5所示,缓存系统的“根”数据结构是一个名为cache-manager的结构体,cache-manager结构体在挂载该文件系统时完成初始化操作。cache-manager结构体的字段如下:关键字比较函数指针:在哈希冲突链中根据文件绝对路径名查找缓存项的函数,返回值是false或true,分别表示查找成功和失败;哈希函数指针:将文件绝对路径名作为该函数的输入参数,计算得到一个整数值h并返回。返回值h作为哈希表的索引,根据该索引可得到缓存项链表;缓存项计数器:该缓存系统目前所缓存的文件(对象)的总数。向缓存系统增加一个新的缓存项时递增该计数器,从缓存系统删除一个缓存项时,递减该计数器。该计数器用于缓存系统的缓存项淘汰算法;缓存目录项计数器互斥锁:在多线程环境中同步对缓存目录项计数器的并发访问;哈希表指针:指向哈希表的指针。哈希表是一个一唯数组,每个数组项是一个结构体,该结构体由哈希冲突链表头节点和控制该链表并发访问的读写锁组成。当使用文件绝对路径名查找缓存项是否在已经被缓存时,获得对应冲突链的“读”锁;当向冲突链中插入(插在链表首部)缓存项时,获得冲突链的“写”锁;最近最少使用缓存项管理器:由lru双向链表及控制该链表并发访问的互斥锁组成的结构体。该管理器用于缓存项的管理,新加入哈希表(冲突)链的缓存项,同时也加入lru链表的表首,当内存不够需要回收缓存项时,从lru链表的表尾开始,逆序依次回收。互斥锁用于保护lru链表在多线程环境中的并发修改。检查确定名称的文件对象是否已经被缓存的过程名为check_cache(path,len),其算法如图6所示,实现步骤如下:第一步:参数合法性检查,对于非法参数不做任何后续处理,直接空指针(0)给调用者,调用者必须检查该过程的返回结果;第二步:根据文件绝对路径名参数及其长度,计算哈希值,以该哈希值作为数组下标,在哈希表(数组)中得到哈希冲突链的入口。第三步:获取哈希冲突链的“读”锁,如果获取不到,则进行超时等待,在超时范围内获取不到“读”锁,则向调用者返回空指针;第四步:从冲突链首项开始,以文件绝对路径名及其长度为关键字,依次匹配冲突链中的每一项中保持的文件绝对路径名和长度,如果精确匹配,则将返回值设置为该缓存项的地址值,否则设置返回值为空指针;第五步:释放第三步中所获取哈希冲突链的“读”锁;第六步:向调用者返回查找结果。当以文件的绝对路径名及其长度调用check_cache函数,该函数返回null时,表示该文件元数据没有被缓存,当返回一个“缓存项”指针时,表示该文件元数据已经存在于缓存系统。文件系统在运行时,缓存系统的工作流程如图7描述,实现步骤如下:第一步:以文件绝对路径名及其长度为参数,调用check_cache函数检查文件元数据是否已经被缓存,如果没被缓存,则转第三步;第二步:文件元数据被缓存,检查该缓存项是否已经过期,如果没过期,则更新该缓存项时间戳,并返回该缓存项的地址给调用者,否则,跳转到第五步开始执行;第三步:从“缓存项池”中申请获取一个空闲状态的缓存项,如果获取到,则转第五步执行;第四步:从“缓存项池”中没有获取到空闲状态的缓存项,启动缓存项回收过程,将最近最少使用的缓存项目回收到池中,转到第三步执行;第五步:从swift存储系统中检索该文件对象信息,使用其元数据填充该缓存项,并将该缓存项的状态由空闲状态设置为占用状态;第六步:将该缓存项插入到缓存系统中;第七步:将该缓存项的地址给调用者。(2)文件系统目录遍历:当应用程序通过可移植操作系统接口(portableoperatingsysteminterface,posix)兼容的c库接口readdir访问本地文件系统的某个特定目录时,实现readdir接口所采用算法核心是:预先分配内存池,初始化池中描述目录结构的描述体dir_info,在每次遍历文件系统目录时,都向池中请求dir_info描述体,当池中的dir_info描述体耗尽或达到某个阀值时,启动回收算法,释放未被占用的dir_info描述体,归还到内存池中,应用程序重新从内存池中获取请求dir_info描述体;其中,考虑到应用服务器工作过程中,文件系统目录遍历的操作很频繁,所以采用(2)中描述的文件系统目录遍历方法能显著减少文件系统目录遍历的时间。其算法描述如图8,实现步骤如下:第一步:初始化参数ret_dir_info_list链表为空值,将文件系统的目录名映射为swift存储系统中对应的容器名;第二步:以容器名为参数,向swift存储系统服务器发送gethttp请求,以获取该容器下所有对象名的一份xml文档;第三步:如果gethttp请求的返回状态码大于预设值(例如200),则表示获取不到对象列表,这种情况下向调用者返回空值链表,否则,执行下面第四步;第四步:将当前节点元素指向xml文档的根节点元素的第一个子节点,访问当前节点所描述对象属性的数据;第五步:从内存池中申请获取一个描述目录结构的内存结构dir_info,如果获取失败,则启动内存回收算法,并转第四步执行,否则执行下面第六步;第六步:将当前节点元素信息填写入所获取到的目录结构dir_info中,将dir_info结构体加入到ret_dir_info_list链表;第七步:如果遍历完了xml文档的根节点元素的所有子节点,则向调用者返回ret_dir_info_list链表,否则转第二步递归执行。(3)文件的打开:当应用程序读或者写某个文件时,都会执行打开文件操作,对于每个打开文件操作,给所打开的文件分配一个描述打开操作的打开文件句柄内存结构,其中,该打开文件句柄记录了:(a)描述打开文件的标记,打开文件标记分为读、写、读和写三种情况;(b)以字节为单位的文件对象的大小;(c)读缓冲区的内存描述体,但实际的读缓冲区滞后到具体第一次读操作请求时才分配;(d)描述从swift对象存储系统下载对象的下载上下文结构,该结构体是在打开文件时预分配的,与之关联的读缓冲区是滞后分配的;(e)描述将文件作为对象上传到swift对象存储系统的上传上下文结构,该结构体是在打开文件时预分配的,但与之关联的上传缓冲区无需额外分配,复用应用程序写操作的write调用缓冲区即可;(f)描述在写文件操作时,实际上传文件到swift对象存储系统过程的内存描述符上传任务描述结构,该描述结构体中记录了上传状态、线程id、互斥访问锁、下载计数器、能写信号量、写完成信号量、能上传信号量以及任务结束信号量;整个上传过程中,用户的write系统调用与上传用户数据到swift对象存储系统之间的同步逻辑,由这四个信号量进行控制;其中,打开文件句柄内存结构如图9所示,具体数据结构体各个字段说明如下:(一)文件打开描述结构体(1)打开标志:标识该文件是否已经被打开;(2)文件对象大小:字节单位的文件大小表示;(3)读缓存上下文结构:包括缓存使能标志、计数器、缓冲区长度、当前读缓存指针的偏移、读缓存指针;(4)下载操作的上下文指针:指向下载上下文结构体;(5)上传操作的上下文指针:指向上传上下文结构体;(二)下载上下文结构(1)缓存区大小字段:描述该缓冲区的大小;(2)当前缓存区下载偏移量:以字节为单位,相对于缓存区首字节的偏移量。(3)下载缓冲区指针:指向数据存储区的指针;(4)下载计数器:打开该文件后,直到当前所下载数据量的统计值;(三)上传上下文结构(1)打开文件标志:记录用户打开文件的选项,本实施中暂时未用到该字段,保留该字段的目的是为以后兼容posix语义时扩展;(2)当前缓存区数据是否上传完成标志:标记该缓冲区中的文件是否上传完毕;(3)文件名指针:指向文件名字符串的指针;(4)缓存区大小:记录写缓存区的大小;(5)当前缓存区偏移:该缓存区中已经写入的数据量,也是下次写数据到缓存区时相对于缓存区首字节的偏移量;(6)上传缓存区指针:指向数据缓存区当前写入位置的指针;(7)上传任务指针:指向上传任务描述结构体的指针;(四)上传任务描述结构(1)上传状态:分为正在进行、传输完毕、传输错误共三种状态;(2)线程id:上传线程的句柄;(3)互斥访问锁:访问该描述结构的互斥量,只有持有该锁的线程才能进行读写操作;(4)下载计数器:本次上传任务所下载数据量的总和;(5)“能写”信号量:是二值信号量,用于两次向缓存区写请求之间的同步;(6)“写完成”信号量:用于两次缓存区写操作之间的同步,只有上一次写缓存区完成,下一次写才能开始;(7)“能上传”信号量:是二值信号量,用于两次网络上传操作之间的同步;(8)任务结束信号量:用户写操作线程和后台传输线程之间的同步信号量。(4)读文件:对于应用程序读文件的操作,采用的是预读策略,所谓预读是指从当前请求偏移处开始,读出比当前请求数据量更大的一块到内存缓存起来,每次读操作时,首先检查所请求的数据是否在缓冲区,如果在,则直接从缓冲区中读出,无需向后端的swift对象存储系统请求数据,如果不在缓冲区,则向后端的swift对象存储系统请求比当前请求更大的一段数据到内存缓存起来;当应用程序读某个文件之前,必须先以读或者读和写方式打开文件,并建立打开文件句柄的内存结构,当应用程序第一次调用read系统调用读文件时,首先分配一个合适大小的缓冲区,缓冲区的默认大小是1mbytes,其大小可以通过配置文件进行调节,但最小为1mbytes,然后,采取“预读”策略,预先尽可能从swift对象存储后端将文件对象的内容一次读满缓冲区,应用程序后续的读文件系统调用,将先检查所读数据是否已经在缓冲区中,如果不在,则再触发一次对swift对象存储后端的读操作,如此反复进行,直到应用程序关闭文件,当文件被关闭时,释放打开文件句柄所占用的内存和信号量资源;其中,读文件具体算法描述见图10,实现步骤如下:第一步:文件路径名path、读偏移offset、本次读请求的数据量size、用户数据缓存区buff、返回值retval等参数的初始化;第二步:调用打开文件例程,获取打开文件句柄openfile,调用swift存储接口api,获取对应文件的大小,并记录打开文件标志、清空用户数据缓存区;第三步:检查偏移量offset和size的合法性,如果不合法,则返回当前实际所读数据的数量;第四步:检查本次用户读请求的数据是否存在或部分存在于缓存区中,如果是,则直接从缓存区中获取数据,将其拷贝到用户数据缓存区buff中,同时修正读偏移量和已读数据长度等变量值,否则,如果数据完全不在缓存区;第五步:清空“读缓冲区”,重置“文件打开描述结构体”、“下载上下文结构”中的计数器和偏移字段值为0;第六步:调用swift存储接口api,从swift存储系统中,将特定对象的指定偏移处的数据读到“读缓冲区”,一次尽量读满缓冲区,除非读完整个对象;设置“下载上下文结构”中的“下载计数器”、“当前缓冲下载偏移”字段值;第七步:将缓冲区中特定偏移处特定长度的数据拷贝到用户缓冲区buff,调整“文件打开描述结构体”中的“当前读偏移”字段值;将本次实际读到数据长度累计到retsize变量中;第八步:检查是否读完请求长度的数据量,文件是否未结束,只要满足这二者之一,则返回retsize给调用者,结束本次读文件操作,否则,修正offset和size分别到下一次读请求的偏移和长度,转第三步执行。(5)文件写入:对于应用程序的写文件操作,采用的是零次拷贝,分块写入的策略,当应用程序打开文件准备写入数据时,建立打开文件句柄的内存结构,每次应用程序写文件操作的write系统调用的用户数据缓冲区将被复用为“上传缓冲区”,上传线程直接从该缓冲区中读取数据上传到swift对象存储后端,当该缓冲区中的所有数据都上传完毕时,视该次write系统调用成功执行,下一次write系统调用都重复该过程,直到该文件被关闭,当文件被关闭时,释放打开文件句柄所占用的内存和信号量资源;在整个写文件过程中,应用程序的write系统调用与上传数据到swift对象存储后端这两个动作之间的同步,由打开文件句柄所引用的上传任务描述结构中的四个信号量进行精确控制,而且在用户态空间没有任何的数据拷贝操作。其中,文件写入的具体算法描述如图11所示,实现步骤如下:第一步:初始化文件路径名path、写偏移offset、本次写请求的数据量length、用户数据缓存区buff、返回值retval等参数;第二步:调用打开文件例程,获取打开文件句柄openfile,初始化“上传上下文结构”中的“能写信号量”为能写状态,can_write=1;初始化“上传上下文结构”中的“写完成信号量”为未完成状态,write_can_return=0;初始化“上传上下文结构”中的“能上传信号量”为不能上传状态,can_cloud=0;初始化“上传上下文结构”中的“任务结束信号量”为未结束状态,can_release=0;第三步:启动上传异步io线程octet_upload();第四步:octet_upload()调用sem_wait等待在信号量can_cloud上;第五步:检查上传上下文结构中当前缓冲区是否有数据,如果没有,则octet_upload()依次对信号量write_can_return、can_cloud、can_write、can_release调用sem_post操作,表示整个上传过程结束,同时octet_upload()线程向swift存储系统提交本次写操作,本次写操作完成,否则,继续执行第五步;第五步:检查当前缓冲区是否有未上传完的数据;第六步:如果当前缓冲区是有未上传完的数据,则octet_upload()依次对信号量write_can_return、can_write调用sem_post操作,表示本次write调用的数据上传完成,等待下一次write调用,转到第三步执行;第七步:如果当前缓冲区中没有尚未上传完的数据,则octet_upload()将上传缓存区中当前偏移处确定大小的数据通过switf客户端api写入swift存储系统,同时修改上传偏移和计数器,并对信号量can_cloud调用sem_post操作,表示可继续上传数据,转到第三步执行。本领域的技术人员容易理解,以上所述仅为本发明的较佳实施例而已,并不用以限制本发明,凡在本发明的精神和原则之内所作的任何修改、等同替换和改进等,均应包含在本发明的保护范围之内。当前第1页12当前第1页12
当前第1页1 2 
网友询问留言 已有0条留言
  • 还没有人留言评论。精彩留言会获得点赞!
1