导读:本次分享主要包括四个方面:存储现状存储加速存储服务化未来规划 分享嘉宾丁天宝孙颢宁ShopeeDataInfra 编辑整理冯蕾蕾我爱我家 内容来源大数据存储架构峰会(3月26日) 出品社区DataFun 01hr存储现状 1。存储结构 目前虾皮的存储结构从上到下主要分为存储层、调度层、计算引擎层和平台管理层,在引擎层有Spark、Flink、presto;调度层有Yarn;存储主要是HDFS和Ozone,对接存储层的也有一些APP,例如推荐和搜索等等。 2。存储规模 我们的存储集群规模有几千台,存储规模约数百PB,文件数量约几十亿,最大QPS约几十万。 02hr存储加速 1。Presto优化加速查询 存储加速部分,主要主要是针对Presto,它是我们存储系统的一个使用大户。目前Presto的集群规模大概数千实例,TP90大概两分钟,每天读取文件大概有几十PB,查询量大概每天数十万。 经过长时间的使用发现,HDFS性能经常不稳定,Presto的查询也会有抖动现象。所以就产生了既提升查询速度又保证查询稳定性的需求。目前比较主流的改进方式是添加加cache,cache中比较主流的方式是使用Alluxio。Alluxio方案中相对经典的方式是PrestoWorker和AlluxioWorker部署在一起,HDFS挂载在Alluxio目录上,Presto通过Alluxio访问HDFS,缓存Alluxio自己管理。 在这种AlluxioPresto经典解决方案中也存在一些问题:虾皮的存储容量是PB级别,缓存容量是TB级别,大概有千分之一的差距;这样就必须定制化缓存容量去符合Presto运行的一些要求;如果数据不在Alluxio中,需要先将数据导入到Alluxio中,经过尝试,我们发现第一次数据导入都会消耗比较长时间。 解决方案:对HMS:设置标志,告诉Presto缓存在Presto还是在Alluxio中;对AlluxioWorker:设计CacheManger,自定义缓存策略,提前加载缓存;有了这个标志以后Presto就可以直接去查HDFS,不需要通过Alluxio去中转。 2。CacheManager架构和实现细节 为了解决以上问题,我们设计了一个CacheManager系统。首先来看一下CacheManager的整体架构: CacheManager通过loadunloadmount发给Alluxio,Alluxio从HDFS加载数据;根据一些缓存策略去加载一些热表;提供了一些API接口,可以进行一些输入和输出;通过Kafka的HDFS对已经加载的缓存进行一些修改;在HMS上打一些标志,这样计算引擎就可以从HMS得到并从Alluxio去加载数据。 接下来来看一下CacheManager的一些实现细节。热表:通过Presto的查询日志,每天生成的Hive表,按日期分区,统计每个表每一天的热度,即访问次数。缓存策略 从热表中得到最近七天加权访问最频繁的表,取每个表最近的m个分区,把这些分区从HDFS加载到Alluxio中,把这些关系存储到数据库中,然后在HMS设置标志。注意的是,这是一个比较初级的版本,后续会不断迭代优化。 HMS标志:采用key的方式 Key是Cach,Value是数据中心,然后是Alluxio以及Alluxio的NameService,我们会有多种Alluxio的服务。如果分区存在,会设置在partition属性上,如果分区不存在,则设置在table属性上。右图表示的是Presto去HMS查询,如果在Alluxio上就去Alluxio查询,如果不在就去HDFS查询。 举个例子,示例中,打上tag标志后,我们看到分区属性上有个Cache属性,标识这个缓存是在哪个Alluxio上。 接口:有多种接口 我们提供了一些接口,有根据路径的接口和表的接口,还有一些管理员接口。路径和表的接口都是一些mount,unmount,load,query,表的load接口会比较细化一些。 3。效果展现 目前Alluxio正在上线中,数据采集不完全,但现有的测试数据可以看出全部从Alluxio读比全部从HDFS查询最高可以达到55。51的提升。 4。Alluxio社区贡献 03hr存储服务化 1。业务痛点问题大多数业务数据存储到HDFS不同业务使用的开发语言各异目前HDFS的非JAVA客户端不完善 2。解决方案 为了给业务人员提供更成熟便捷的访问方式,从存储服务化的角度出发,结合我们对Alluxio的调研,提供了以下两种解决方案。 (1)FuseforHDFS:在Fuse中可以像在本地访问数据一样来访问HDFS的数据,我们提供了两种部署模式:物理机部署AlluxioFuse服务,以及Kubernetes部署AlluxioFuse服务。 (2)S3forHDFS:通过S3API访问Alluxio服务。S3对多种语言支持,可以解决开发语言差异的问题,同时Alluxio对S3接口兼容,使用S3接口访问HDFS中数据非常便捷,我们最终决定采用这种方式来提升用户体验。 3。了解Fuse Fuse属于一个用户态的软件系统,由两部分组成:内核模块以及用户空间守护进程。Fuse给用户和开发者带来了极大的便利。在内核模块的支持下,开发者只需要实现标准的POSIX协议接口就可以拥有一个自定义的文件系统。 右边这幅图是一个Fuse服务的架构图,当用户在被挂载的目录执行文件操作时,就会触发系统调用,VFS将这些操作路由至Fusedriver,Fusedriver创建请求将其放入到请求队列中,Fusedaemon通过块设备从内核队列中读取请求,进而执行自定义的逻辑操作。 4。物理机部署AlluxioFuse 先看一下右边的这幅图,AlluxioFuse的服务就相当于前面讲的Fusedeamon,在启动AlluxioFuse的时候,相当于进行了一个挂载操作,它会调用libfuse的方法,向kernel去注册挂载点以及回调函数。在挂载目录下执行的操作就是执行的回调函数逻辑,像图中描述的ls指令最后得到的结果就是对挂载的Alluxio目录执行的list操作的结果。 在使用AlluxioFuse之前,我们需要安装libfuse。目前社区的版本是支持libfuse2的,libfuse3应该也在开发过程当中。实现方式现在有两种,一个是JNRFuse一个是JNIFuse。JNRFuse是一个个人维护的项目,所以出现问题的话不一定能够及时地的解决。而JNIFuse是由Alluxio社区来维护的,并且在并发场景下JNIFuse性能更佳。于是我们选择了JNIFuse作为我们的实现方式。 AlluxioFuse有两种部署模式,一种是集成到worker进程,这样能够省去rpc调用,另一种是单独部署在一个客户机上,目前我们使用的是单独部署的模式,因为我们的用户的应用客户端不一定和worker在同一个节点,所以选择更为灵活的独立部署模式。 虽然AlluxioFuse支持标准的POSIX协议,但是它的重点是提供读服务,因为目前的主要使用场景是加速AI训练,这是一个典型的读的场景。对于随机写的支持目前还不够好。而我们的服务化需求可能不单单是读请求,这也是我们后面需要改进的点,以更好地支持用户需求。 5。K8sCSI部署AlluxioFuse 在介绍完物理机部署之后,我们再来看一下如何在K8s集群部署。利用K8s的CSI可以将AlluxioFuse服务部署到K8s上,CSI是一个容器的标准存储接口。借助CSI的容器编排能力,我们可以将任意的存储系统暴露给容器,从而使用这些存储服务。右边这幅图就是Alluxio如何使用CSI的原理图。我们可以看到上面部分主要是CSI部分,其中一部分是由K8S社区维护的组件(右边部分),而我们更需要关注的是左边需要开发者去定义的部分,主要包括CSIController和CSINodeServer。Controller作为控制端,实现了创建、删除、挂载、卸载等功能。而NodeServer端是进行具体的mount操作的节点。在Alluxio的CSI当中,NodeServer以daemonset模式部署到每个Node节点上。 要使用定义好的AlluxioFuse的服务,只需用户在定义PV时,指定使用AlluxioCSI这种服务来提供数据挂载服务就可以,并且需要指定好Alluxio服务系统中以业务的目录作为挂载点。然后在创建业务POD的时候,它就会在NodeServer上去启动一个AlluxioFuse服务,同时业务POD就可以访问挂载在Alluxio当中的目录了。这种模式下,一个NodeServer上可能会有多个Fuse进程,这样能够节约资源。但是它有一个弊端就是一旦这个NodeServer出现了异常,那么其中运行的多个Fuse服务都会受到影响,与其对应使用这个Fuse服务的Container都会受到影响,我们必须重启所有的业务POD才能正常访问文件。 6。K8ssidecar模式部署AlluxioFuse 为了避免NodeServer挂掉之后产生的影响,我们又引入另外一种模式。就是K8s的sidecar模式,sidecar模式就是在用户配置业务Container的YAML文件中,会配置一个AlluxioFuse的服务。相当于在启动一个业务Container的时候,会额外地提供一个AlluxioFuseContainer。这个AlluxioFuseContainer主要就是用来挂载Alluxio目录用的,并且这两个Container可以共享存储网络等资源的。这样业务Container就可以访问AlluxioFuse挂载的目录。这种模式下每个POD都可以有一个Container,部署配置比较灵活,而且每个容器之间互不影响。但是因为每个Fuse进程都会占用一个容器,这样会额外消耗一部分资源。 部署模式总结: 简单回顾一下这三种模式的特点: 对于物理机部署,因为是要部署到用户的每个客户机上,客户机是比较分散的,所以它的部署成本要高一些。而对于K8S模式,因为它是部署在每个node上,所以运维成本相对来说会稍低一些。而对于资源的使用,因为物理机部署就是启动一个进程,而K8S的模式会开启一个NodeServer或者开启一个单独的Container去部署这个服务,所以会占用一些额外的资源。sidecar模式,因为对每个业务Container都会启用一个AlluxioFuse的服务,这样占用资源相较于CSI模式会更高一些。 独立性方面,物理机部署因为采用的是独立部署,不会受worker的影响,也不会影响worker。每个业务使用的Fuse服务也是互相不影响的。K8SCSI模式因为是在NodeServer上可以部署多个Fuse进程,所以可能会受到NodeServer的影响。K8Ssidecar模式也是独立部署的,不会产生任何影响。 7。了解S3 除了挂载操作的方式之外,我们还提供另外一种服务化的方式,就是使用S3SDK。S3是亚马逊的一个公开的云存储服务系统,是存储对象用的。其特点是提供了丰富的客户端SDK,我们就是要借助这些丰富的SDK来实现对Alluxio当中文件的访问。 在此也介绍一下S3的一些基本概念。Bucket是S3中用于存储对象的容器;object是S3中存储的基本实体;Key是存储桶中对象的唯一标识符;region在S3的服务中可以选择一个区域供S3存储创建的桶。下面看一下我们是如何利用S3的SDK来提供存储服务的。 8。S3forHDFS 利用S3的SDK来访问数据主要是依赖于几点: 首先Alluxio可以挂载HDFS数据。Alluxio提供了Proxy的服务,Proxy服务是兼容S3API的,所以可以支持更多的用户通过更多的语言,使用S3SDK来通过发送请求到AlluxioProxy,解析成对Alluxio的请求,从而来访问数据。 9。Proxy映射关系 左边这幅图执行的是一个mount指令。将HDFS当中的projects目录挂载到Alluxio当中的projects目录。下边分别是HDFS中的路径以及Alluxio当中的路径。它们是一一对应的关系。 Proxy服务在做解析的时候,把Alluxio的一级目录作为bucket来进行映射,其子目录和文件构成key,相当于通过这个bucket和key我们就可以得到一个object对象。右边的图就是一个S3的JavaSDK请求Proxy服务的demo,可以看到,其bucket设置为首级目录,目录的其余部分作为key可以获取到这个对象。 10。实现ProxyAuthentication 现在社区提供的Proxy服务并没有提供S3所具有的认证功能,于是我们自己为Proxy服务添加了认证功能。 S3的SDK发动请求时,会将请求转换为REST请求,并且在客户端根据拿到用户的ID以及secret,再加上请求当中的请求信息,生成一个签名,然后把这个签名放到请求当中。 我们在Proxy服务中添加了用于解析认证请求和校验认证的方法。因为在请求中带有ID信息,我们可以拿着ID去secretmanager取出它的secret信息,重新在Proxy服务端生成新的签名,与请求中带来的签名进行比较,从而判断这个认证是否通过。 右图是亚马逊官网给出的计算步骤,我们可以看到它就是解析request请求和计算签名的一个过程。使用了加密算法,多次加密之后得到了三个字段,然后进行最后的编码以及加密编码,才得到的这个签名。 11。服务架构 再来看一下我们整个的服务架构。图的右半部分是一个集群,它的后端是HDFS的数据。我们把所有的刚才介绍到的服务模式都在这个图上进行了展示。可以看到有三个橘黄色客户端,上面是一个使用S3的SDK的客户端,它通过负载均衡,将请求发送到某个Proxy服务,经网络发送到Alluxio集群进行解析之后,数据就会返回到客户端。 下面这个客户端使用的是在物理机部署的模式,在本地物理机去部署一个AlluxioFuse,用户通过访问AlluxioFuse挂载的目录,进而获取到Alluxio当中的数据。 最下边是一个K8s集群。右边是集群里的一个pod。这是一个sidecar模式,一个客户端和AlluxioFuse的Container,它们之间共享存储。因为K8S是有自己的网络服务定义的,通过这个网络连接到外边的网络服务,进而可以拿到Alluxio中的数据。 12。Alluxio社区贡献 我们在使用Alluxio服务的过程中也发现了一些问题,并积极地反馈给了社区,主要是proxy与fuse相关的问题。 04hr未来规划 未来规划主要在以下两大方面:在存储加速方面,我们还会将Spark和Hive接入Alluxio;CacheManager添加自适应的缓存策略,达到更优的缓存使用;在存储服务化方面,我们也会对CSI进行优化,将Fuse独立于nodeserver服务;对于Fuse服务,因为我们提供的场景不单单是读,所以,我们需要根据业务需求完善对POSIX接口支持。 今天的分享就到这里,谢谢大家。 分享嘉宾 丁天宝Shopeeengineer 多年大数据经验,涉及存储、引擎、调度、大数据开发等等。也有丰富的后端开发经验,前端也能做一点。 孙颢宁Shopeeengineer 多年大数据存储研发经验,目前就职于Shopee,负责Alluxio的研发工作。 关于DataFun 专注于大数据、人工智能技术应用的分享与交流。发起于2017年,在北京、上海、深圳、杭州等城市举办超过100线下和100线上沙龙、论坛及峰会,已邀请超过2000位专家和学者参与分享。其公众号DataFunTalk累计生产原创文章800,百万阅读,15万精准粉丝。