ice通信原理_Goofys+s3g+Ozone通信原理及性能调优

整体架构

通过Goofys作为fuse程序,可以创建一个fuse类型的挂载点,将这个挂载点上的文件系统请求,发送到Goofys中进行处理,而Goofys可以把接收到的文件系统请求通过s3api,转发到aws s3对象存储中,也可以转发到实现了基于http协议s3api标准的Ozone s3gateway(简称s3g)中。s3g作为s3api的实现者,因此可以融入s3生态,与此同时,s3g作为Ozone的client,作为Ozone集群的接入层,代理对象请求到Ozone集群,将结果返回给请求者。整体架构如下图所示。

cc3fc190f66a0df36ed10a29f7d0f046.png

其中,用户Application程序和Goofys是部署到一台机器上的,用户程序访问文件系统的方式,是POSIX方式,因此,用户程序无需任何改动,就可以把原本访问本地文件系统的应用程序转换到通过Ozone存取文件了。

什么是Goofys

Goofys 是开源,使用 Go 编写,基于 S3 接口的 Filey 系统。

Goofys 允许你挂载一个 s3 bucket 作为一个 Filey 系统。为什么是 Filey 系统而不是 File 系统?因为 goofys 优先考虑性能而不是 POSIX。Goofys 在设计理念上为了性能而牺牲了 POSIX 兼容性,所支持的文件操作极大地受限于 S3 等对象存储本身。

Goofys快的原因,是因为实现了POSIX文件系统语义的子集

Goofys 和S3FS 类似,都是s3 的本地fuse实现,但比s3fs有更好的性能。S3FS 尽管名为文件系统,但实际上更接近于用文件系统视图管理 S3 bucket 中对象的一种方法。尽管S3FS 支持了 POSIX 的一个较大子集,但只是将系统调用一一映射为对象存储请求,并不支持常规文件系统的语义及一致性(例如目录的原子重命名,独占模式打开时的互斥,附加文件内容会导致重写整个文件以及不支持硬连接等等)。这些缺陷导致 S3FS 并不能用于替代常规文件系统(即便不考虑性能问题),因为当应用访问文件系统时,预期的行为应该是符合 POSIX 规范的,而 S3FS 远远不能满足这一点。

启动流程

先启动一个main.go,进行参数解析。默认情况下,Goofys会运行在后台。启动外壳,再次调用Goofys启动挂载,然后外壳程序退出。-f 参数,意思为foreground,可以直接将Goofys进行在前端,直接mount,而不启动外壳程序。

mount挂载好之后。jacobsa/fuse 模块会接收一个fuse.connection,每个connection都会有一个op。对于每一个op,都使用后台线程处理。

接受到的Op可能是如下这些OP,都会被goofys一一处理。

StatFSOp,LookUpInodeOp,GetInodeAttributesOp,SetInodeAttributesOp,ForgetInodeOp,MkDirOp,MkNodeOp,CreateFileOp,CreateLinkOp,CreateSymlinkOp,RenameOp,RmDirOp,UnlinkOp,OpenDirOp,ReadDirOp,ReleaseDirHandleOp,OpenFileOp,ReadFileOp,WriteFileOp,SyncFileOp,FlushFileOp,ReleaseFileHandleOp,ReadSymlinkOp,RemoveXattrOp,GetXattrOp,ListXattrOp,SetXattrOp,FallocateOp

常用参数

通过执行不带参数的goofys可执行程序,即可获取帮助信息。使用方法为

goofys [global options] bucket[:prefix] mountpoint

参数说明特殊说明
bucket无需参数标志,直接填写bucket名称
mountpoint无需参数标志,直接填写mountpoint位置
--cheap默认关闭,如果开启,则禁止readAhead功能

--readahead-chunk VALUE

是预先读取的chunkSize,单位为字节。默认配置为20MB

开源版本master还未accept

https://github.com/kahing/goofys/pull/535

--max-readahead VALUE是预先读取的最大size,单位为字节。默认配置400MB,这个根据自己的机器内存设定

开源版本master还未accept

https://github.com/kahing/goofys/pull/535

--MPUPartSize VALUE是multiPartUpload的分片大小,单位为字节。默认为5MB,建议配置为4MB的整数倍,比如128MB,64MB,8MB。

开源版本master还未accept

https://github.com/kahing/goofys/pull/537

--cache VALUE使用catfs程序进行缓存。默认关闭。例如: --cache "--free:10%:$HOME/cache"
--profile VALUE指定$HOME/.aws/credentials 中的profile
--stat-cache-ttl VALUE缓存StatObject结果以及inode属性的时间默认1min
--type-cache-ttl VALUE缓存文件夹中name->file/dir的映射默认1min

Goofys的结构

Goofys继承fuseutil.NotImplementedFileSystem,而fuseutil.NotImplementedFileSystem继承FileSystem接口,是一个FileSystem的默认空实现,这样Goofys只需要实现部分就可以了。

type Goofys struct {  fuseutil.NotImplementedFileSystem  bucket string  flags *FlagStorage  umask uint32  gcs       bool  rootAttrs InodeAttributes  bufferPool *BufferPool  mu sync.RWMutex  nextInodeID fuseops.InodeID  inodes map[fuseops.InodeID]*Inode  nextHandleID fuseops.HandleID  dirHandles   map[fuseops.HandleID]*DirHandle  fileHandles map[fuseops.HandleID]*FileHandle  replicators *Ticket  restorers   *Ticket  forgotCnt uint32}

Filesystem的内容是

type FileSystem interface {  StatFS(context.Context, *fuseops.StatFSOp) error  LookUpInode(context.Context, *fuseops.LookUpInodeOp) error  GetInodeAttributes(context.Context, *fuseops.GetInodeAttributesOp) error  SetInodeAttributes(context.Context, *fuseops.SetInodeAttributesOp) error  ForgetInode(context.Context, *fuseops.ForgetInodeOp) error  MkDir(context.Context, *fuseops.MkDirOp) error  MkNode(context.Context, *fuseops.MkNodeOp) error  CreateFile(context.Context, *fuseops.CreateFileOp) error  CreateLink(context.Context, *fuseops.CreateLinkOp) error  CreateSymlink(context.Context, *fuseops.CreateSymlinkOp) error  Rename(context.Context, *fuseops.RenameOp) error  RmDir(context.Context, *fuseops.RmDirOp) error  Unlink(context.Context, *fuseops.UnlinkOp) error  OpenDir(context.Context, *fuseops.OpenDirOp) error  ReadDir(context.Context, *fuseops.ReadDirOp) error  ReleaseDirHandle(context.Context, *fuseops.ReleaseDirHandleOp) error  OpenFile(context.Context, *fuseops.OpenFileOp) error  ReadFile(context.Context, *fuseops.ReadFileOp) error  WriteFile(context.Context, *fuseops.WriteFileOp) error  SyncFile(context.Context, *fuseops.SyncFileOp) error  FlushFile(context.Context, *fuseops.FlushFileOp) error  ReleaseFileHandle(context.Context, *fuseops.ReleaseFileHandleOp) error  ReadSymlink(context.Context, *fuseops.ReadSymlinkOp) error  RemoveXattr(context.Context, *fuseops.RemoveXattrOp) error  GetXattr(context.Context, *fuseops.GetXattrOp) error  ListXattr(context.Context, *fuseops.ListXattrOp) error  SetXattr(context.Context, *fuseops.SetXattrOp) error  Fallocate(context.Context, *fuseops.FallocateOp) error  Destroy()}

Goofys函数的实现情况如下图所示,可以看出除了Fallocate,ReadSymlink,CreateSymlink,CreateLink,MkNode之外的函数,都实现了。

46686ad0df1cec2096e0a38c89b43b96.png

优化方向

元数据缓存

Goofys 通过 Inode map, 缓存 stat结果和inode的attribute,构造了一个active工作集的文件系统树状结构,这使得,元数据请求命中缓存的情况下,性能提升巨大。但是副作用则是缓存一致性问题,存在一个默认1分钟的时间窗口可能会有不一致的情况发生。可以通过配置stat-cache-ttl 和type-cache-ttl参数,根据业务场景进行性能调优。

写优化原理(MPU)

Goofys的写操作,采用了s3的MPU(MultipartUpload)方式,MPU这种写文件方式的原理如下图所示。

ce925f15d821a94a382291053c600837.png

写一个大文件的步骤如下:

  • 发送initializeMultipartUpload请求到server端,s3g端收到该请求后,会分配一个uploadId,返回给请求者。

  • Client端将文件切分为若干个分片,然后可以并行的将文件分片的数据和uploadId发送到s3g。

  • Client端当把所有分片的数据全部发送成功后,会发送一个completeMultipartUpload请求给server端,s3g收到这个请求后,将这个大文件标记完成。

Goofys写s3的MPU的partSize,是hardcode。比如,MaxMultipartSize: 5 * 1024 * 1024 * 1024,也就是5GB。一个part的大小,在1000个part之内,为5MB,第1000到2000的part 是25MB。第2000个以上的是125MB。

9af0c9de52f5b9ee695216691bf6e337.png

对于Ozone来说,默认的chunkSize为4MB,而goofys的默认5MB的分片大小,会在写block的时候,产生4MB的完整Chunk和1MB的不完整chunk,这使得数据之外的传输量增大,有效数据传输比降低。此外将一个大文件,分隔成这么小的分片,会使得Ozone的datanode上产生很多5MB大小的小block文件,给Datanode管理block增加了压力。因此,可以调整分片大小,得到性能和存储系统压力之间的平衡。此外,可以确定的是,当分片大小是Ozone的chunkSize的整数倍时,性能会好于未对齐的分片大小。

读优化原理 (ReadAhead)

Goofys读取大文件时,也会获得很好的性能,其原因也是因为Goofys默认开启了预读功能。预读功能的原理就是预先把你接下来可能会读取的数据读回来到本地缓存,后续的读请求则可以从缓存中读取文件的内容。此外,Goofys的预读也是分为若干个分片并发请求分片数据的。

但是遗憾的是,目前Goofys的预读ChunkSize和预读最大容量都是hardCode。

const MAX_READAHEAD = uint32(400 * 1024 * 1024)  // 400MBconst READAHEAD_CHUNK = uint32(20 * 1024 * 1024) // 20MB

目前Goofys的预读chunkSize为20MB,那么20MB以后的数据,才会进行预读,以20MB为一个分片,并发读取分片数据,直到预读达到上限400MB,(或则期间发现用户读文件不是顺序读文件,而是发现有3次以上seek后乱序读文件这种行为),则不再进行预读。

当"--cheap" 开关关闭的时候(默认关闭), readAhead才可能会工作。

同时,还必须保证已经读过的文件大于一个READAHEAD_CHUNK,也就是读1个文件20MB以后,才有机会readAhead。此外,还需要保证numOOORead<3, 也就是一个文件的乱序的read必须小于3个。

!fs.flags.Cheap && fh.seqReadAmount >= uint64(READAHEAD_CHUNK) && fh.numOOORead 

使用tcpdump抓包后,用wireshark分析抓包结果,也可以看到10线程并发读。有10个GET请求。可以看出预读是并发读取每个readAhead的chunk的。

85b5d1b792d73f23d23bf97df104fbe4.png

下图是读数据量未达到readAheadChunk大小或者已经达到了最大readAhead大小的时候,会通过s3api向远端s3或Ozone的s3g请求数据。

2d08778b645b7e94af575f9debd6587c.png

当读数据量在可预读的范围内,运行readAhead逻辑,直到读到的数据已经超过了最大预读量。

0040139a05baf0cd53d5231ee0107bd8.png

ReadAhead逻辑是并发执行的,以一个readAhead chunk为一个并发单位,分别通过s3api向远端s3或Ozone的s3g请求该chunk的数据内容,并缓存到buffer中,供后续读取操作快速读取数据,达到读性能优化目的。

c89fb75b574ab5ffd3116b9fef061d24.png

可以把READAHEAD_CHUNK 和 MAX_READAHEAD修改为配置项,根据业务场景需求,进行调优,找到最适合的配置值。

分析全链路优化瓶颈点

s3gKeyInputStream#copyLarge 使用hardCode 4096作为buffer大小。扩大buffer可以获取更好的性能。

KeyInputStream会维护了一组blockStreams,在初始化阶段,会为key对应的所有block创造对应的BlockStream

BlockInputStream维护着对应的ChunkInputStream。读操作是以一个chunk为单位的。读同一个文件只会落到同一个datanode上去。sortDatanode的逻辑,目前是rpc请求到SCM进行排序,可以做成策略式,按负载,或Random选取。

s3g作为client向Datanode获取Chunk的全部内容, 供后续的ChunkInputStream进行读取,这里如果缓存Chunk内容,可以获得为后续的seek读提升性能。DataNode 的 KeyValueHandler#handleReadChunk会处理来自于Client的请求, 读取chunk内容,返回给datanode,因为目前默认的ChunkLayOutVersion为一个Block一个文件,因此其实一个block就包含若干个逻辑chunk,但这些chunk都在一个block文件中,并没有单独存储。

确认了Datanode是可以无限并发响应ReadChunk请求的。因此DataNode提供读服务的能力,受限于Datanode的磁盘、CPU、网络带宽等因素,应用本身的服务能力不是瓶颈。

通过Goofys写入的文件,由于采用MPU方式进行写入,分片大小为hardcode 5MB,因此一个2GB的文件会被切割成400多个分片进行存储,当读取文件的时候,是以4MB为一个chunk,与datanode请求chunk数据内容,因此一个5MB的block,需要发送两次GET_CHUNK请求,第2次请求的数据包并没有占满,因此整体的请求传输中的有效数据占比就会降低,请求量也会增加近一倍,这是读写性能没有发挥正常水平的根本原因。

其它优化

使用catfs进行缓存加速,默认不开启。

性能测试

对Goofys的hardcode做了参数化修改,新增了--MPUPartSize 参数,可以配置partSize,因此对partSize在默认的5MB和配置为128MB之前,进行对比测试。

测试环境

goofys 以及测试程序运行在 同一台机器上 , 连接的s3gOzone集群的40个datanodes的其中一个。

使用如下命名,进行Goofys挂载,分别使用partSize为5MB和128MB,创建2个挂载点。后续的测试,针对这两个挂载点进行测试,测试组为128MB,对照组为5MB,其中5MB为原有的Goofys的partSize大小。

/root/mbl/goofys --max-readahead 2147483648 --readahead-chunk 4194304 --MPUPartSize 134217728 --profile default --endpoint http://192.168.1.101:9878 bucketmbl /data/bucketmbl-128M

/root/mbl/goofys --max-readahead 2147483648 --readahead-chunk 4194304 --MPUPartSize 5242880 --profile default --endpoint http://192.168.1.101:9878 bucketmbl /data/bucketmbl-5M

测试方法

使用python编写的并发测试程序rwtest进行并发测试 https://github.com/opendataio/rwtest/blob/master/rwtest.py

测试流程,使用jenkins作为测试前端,http://192.168.1.102:8080/job/testgoofys ,触发测试,参数化构建,测试结果收集。(人人都可以做测试,无需登录测试机器)          6ee1c0c5badc08109dd4aea9f2616e3a.png

进行了2GB文件和2MB文件的1,10,50,100并发读写。

改进点

  • https://issues.apache.org/jira/browse/HDDS-3994 s3g的重试策略发生变化,导致写遇到异常重试情况性能下降很大

  • https://issues.apache.org/jira/browse/HDDS-3979 读bufferSize可以配置大小,调大bufferSize为4MB可以在读chunk的时候一次读取chunk内容,减少无效数据传输量

  • https://github.com/kahing/goofys/pull/537 分区大小可通过参数指定,是后续调优之根本

  • https://github.com/kahing/goofys/pull/535 ReadAhead的chunkSize和MaxSize可以通过参数指定

总结

本次初衷对Goofys进行调优,延伸到对"Goofys+s3g+Ozone"全链路进行原理分析,性能瓶颈诊断,改进关键瓶颈点,从而得到性能提升。

通过通读Goofys源码,可以感受到Goofys作者对于性能的极致的追求,但是不足之处是一些关键数值,采用了hardcode的方式写死在代码里,尤其是partSize为5MB是本次性能调优的最关键点

在调优s3g的过程中,也遭遇到了retryPolicy的向前不兼容的变化,以及s3g中的读bufferSizehardcode,无法通过配置项进行调整的问题。

遭遇了这些问题,我们后续的设计开发过程中,应该避免hardcode以及向前不兼容的修改,以免给使用者代码不便。

最后,通过实验证明,对于2GB文件样本,当partSize为128MB,相比partSize为5MB,有着3倍左右的性能提升,对于2MB以下的小文件,由于小于一个partSize,不会有性能优化。在一般场景下,给出的默认配置参数为: " --max-readahead 2147483648 --readahead-chunk 4194304 --MPUPartSize 134217728",其中,最大内存可以根据实际情况,自行调整。

引用

  • https://juicefs.com/blog/cn/posts/cloud-file-system-posix-compliant/

  • https://github.com/apache/hadoop/pull/1679

  • https://zhuanlan.zhihu.com/p/109773532

  • https://github.com/opendataio/rwtest/blob/master/rwtest.py

欢迎阅读其他Ozone系列文章

Ozone如何设计实现高可用SCM

对象存储和新型分布式文件系统 - 填补Hadoop存储的空白

Ozone 安全认证简介及实践

Ozone Native ACL的特性及应用

浅谈配置化的Ozone网络拓扑结构

Ozone如何利用Multi-Raft优化写入吞吐量

浅谈Ozone的HA能力

如何用hadoop的用法来玩转ozone

Ozone on K8S

聊一聊Ozone如何高效利用Raft机制

Hadoop原生对象存储Ozone

欢迎关注我们的公众号

787de1ea50341d35ae354900d7410e82.png

腾讯大数据诚招计算,存储,消息中间件,调度,中台等各方向的大数据研发工程师,请私信或联系jerryshao@tencent.com

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值