人工智能系统的技术架构
一、架构图
1.基础层包括:
硬件设施、软件设施、数据资源。其中在硬件设施方面,做深度学习和神经网络训练时候往往会涉及到模型训练是在CPU还是GPU上面,在这个里面GPU就是做计算加速的,第二个是智能芯片,市面上出现的智能语音芯片和图像识别的芯片就是对应这一块。在软件设施方面,智能云平台解决的是硬件资源管理的问题,目前市面上有阿里云,腾讯云、亚马逊云,微软云,百度云等各种云平台,对外输出的是资源的服务能力,第二个是大数据平台,涉及到的是分布式存储,Hadoop等框架,在数据资源方面,把通用数据作为基础层,主要考量的点是通用数据更多的是人工智能类产品当前对外输出的人类相关的数据,往往涉及到人机对话聊天等数据,而专业的行业数据,在会场的智能导航,智能问诊等场景有所应用。
2.技术层包括:
基础框架、算法模型、通用技术,其中基础框架与软件设施有一定的映射关系,算法模型包括机器学习深度学习增强学习等,深度学习包括神经网络,深度神经网络,卷积神经网络等具体的算法,通用技术是算法模型的一个应用,包括自然语言处理、智能语音、机器问答、计算机视觉等,这里需要注意一个点,我们把自然语言处理等归类为通用技术,说明它本身并不是一种算法模型,而是算法模型支撑起来的一种具体的技术形态。
3.底应用层:
包括应用平台和智能产品,需要注意的是智能操作系统,智能音箱、人脸支付等都属于终端,它依赖于音箱和手机等智能设备,这些设备是需要依赖于特定的硬件平台上的,而硬件平台的管理控制则依赖于智能操作系统,这个可以直接对比传统的移动互联网时代,操作系统是安卓、iOS,在PC互联网时代的Windows,Ubuntu。
目前市面上能看到的智能操作系统有百度DuerOS、图灵等。
4.案例:
在这张图对应的是DuerOS的整体技术 架构,从上到下包括三个层次:能力层(小度技术开放平台),包括原生技能、第三方技能的各种开发工具。核心层(小度对话核心系统),对应的是通用技术层,包括语音识别、语音播报、屏幕展示、对话服务等。应用层(小度智能设备开放平台),包括各种API接口、开发套件、麦克风阵列等。在这个里面我们能看到的是它的整体涵盖了对外的开放平台,然后它的技术设备层面对外开放对话核心系统。
分布式文件系统:JuiceFS 技术架构
文章目录@[toc]一、整体架构二、存储文件三、写入流程1.随机写2.客户端写缓存四、读取流程一、整体架构JuiceFS文件系统由三个部分组成:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-90ZtG0tw-1687771442157)(https://juicefs.com/docs/zh/assets/images/juicefs-arch-new-ab6339cb1408945cc9b70dc091c523c5.png)]
JuiceFS客户端(Client):所有文件读写,乃至于碎片合并、回收站文件过期删除等后台任务,均在客户端中发生。可想而知,客户端需要同时与对象存储和元数据引擎打交道。客户端支持众多接入方式:
通过FUSE,JuiceFS文件系统能够以POSIX兼容的方式挂载到服务器,将海量云端存储直接当做本地存储来使用。通过HadoopJavaSDK,JuiceFS文件系统能够直接替代HDFS,为Hadoop提供低成本的海量存储。通过KubernetesCSI驱动,JuiceFS文件系统能够直接为Kubernetes提供海量存储。通过S3网关,使用S3作为存储层的应用可直接接入,同时可使用AWSCLI、s3cmd、MinIOclient等工具访问JuiceFS文件系统。通过WebDAV服务,以HTTP协议,以类似RESTfulAPI的方式接入JuiceFS并直接操作其中的文件。数据存储(DataStorage):文件将会切分上传保存在对象存储服务,既可以使用公有云的对象存储,也可以接入私有部署的自建对象存储。JuiceFS支持几乎所有的公有云对象存储,同时也支持OpenStackSwift、Ceph、MinIO等私有化的对象存储。
元数据引擎(MetadataEngine):用于存储文件元数据(metadata),包含以下内容:
常规文件系统的元数据:文件名、文件大小、权限信息、创建修改时间、目录结构、文件属性、符号链接、文件锁等。JuiceFS独有的元数据:文件的chunk及slice映射关系、客户端session等。JuiceFS采用多引擎设计,目前已支持Redis、TiKV、MySQL/MariaDB、PostgreSQL、SQLite等作为元数据服务引擎,也将陆续实现更多元数据存储引擎。欢迎提交Issue反馈你的需求。
二、存储文件与传统文件系统只能使用本地磁盘存储数据和对应的元数据的模式不同,JuiceFS会将数据格式化以后存储在对象存储(云存储),同时会将文件的元数据存储在专门的元数据服务中,这样的架构让JuiceFS成为一个强一致性的高性能分布式文件系统。
任何存入JuiceFS的文件都会被拆分成一个或多个**「Chunk」(最大64MiB)。而每个Chunk又由一个或多个「Slice」组成。Chunk的存在是为了对文件做切分,优化大文件性能,而Slice则是为了进一步优化各类文件写操作,二者同为文件系统内部的逻辑概念。Slice的长度不固定,取决于文件写入的方式。每个Slice又会被进一步拆分成「Block」**(默认大小上限为4MiB),成为最终上传至对象存储的最小存储单元。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-U2UAt015-1687771442159)(https://juicefs.com/docs/zh/assets/images/juicefs-storage-format-new-fb61716d2baaf23335573504aa5f3bc7.png)]
因此,你会发现在对象存储平台的文件浏览器中找不到存入JuiceFS的源文件,存储桶中只有一个chunks目录和一堆数字编号的目录和文件,不必惊慌,这正是经过JuiceFS拆分存储的数据块。与此同时,文件与Chunks、Slices、Blocks的对应关系等元数据信息存储在元数据引擎中。正是这样的分离设计,让JuiceFS文件系统得以高性能运作。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-utFvu5De-1687771442161)(https://juicefs.com/docs/zh/assets/images/how-juicefs-stores-files-new-a5529b6935c59e4c6f36ac38dd1652f7.png)]
JuiceFS的存储设计,还有着以下技术特点:
对于任意大小的文件,JuiceFS都不进行合并存储,这也是为了性能考虑,避免读放大。提供强一致性保证,但也可以根据场景需要与缓存功能一起调优,比如通过设置出更激进的元数据缓存,牺牲一部分一致性,换取更好的性能。详见「元数据缓存」。支持并默认开启「回收站」功能,删除文件后保留一段时间才彻底清理,最大程度避免误删文件导致事故。三、写入流程JuiceFS对大文件会做多级拆分(JuiceFS如何存储文件),以提高读写效率。在处理写请求时,JuiceFS先将数据写入Client的内存缓冲区,并在其中按Chunk/Slice的形式进行管理。Chunk是根据文件内offset按64MiB大小拆分的连续逻辑单元,不同Chunk之间完全隔离。每个Chunk内会根据应用写请求的实际情况进一步拆分成Slice;当新的写请求与已有的Slice连续或有重叠时,会直接在该Slice上进行更新,否则就创建新的Slice。Slice是启动数据持久化的逻辑单元,其在flush时会先将数据按照默认4MiB大小拆分成一个或多个连续的Block,并作为最小单元上传到对象存储;然后再更新一次元数据,写入新的Slice信息。
显然,在应用顺序写情况下,只需要一个不停增长的Slice,最后仅flush一次即可;此时能最大化发挥出对象存储的写入性能。以一次简单的JuiceFS基准测试为例,使用1MiBIO顺序写1GiB文件,在不考虑压缩和加密的前提下,数据在各个组件中的形式如下图所示:
用juicefsstats命令记录的指标图,可以直观地看到实时性能数据:
图中第1阶段:
对象存储写入的平均IO大小为object.put/object.put_c=4MiB,等于Block的默认大小元数据事务数与对象存储写入数比例大概为meta.txn:object.put_c~=1:16,对应Sliceflush需要的1次元数据修改和16次对象存储上传,同时也说明了每次flush写入的数据量为4MiB*16=64MiB,即Chunk的默认大小FUSE层的平均请求大小为约fuse.write/fuse.ops~=128KiB,与其默认的请求大小限制一致小文件的写入通常是在文件关闭时被上传到对象存储,对应IO大小一般就是文件大小。指标图的第3阶段是创建128KiB小文件,可以发现:
对象存储PUT的大小就是128KiB元数据事务数大致是PUT计数的两倍,对应每个文件的一次Create和一次Write对于这种不足一个BlockSize的对象,JuiceFS在上传的同时还会尝试写入到本地缓存,来提升后续可能的读请求速度。因此从图中第3阶段也可以看到,创建小文件时,本地缓存(blockcache)与对象存储有着同等的写入带宽,而在读取时(第4阶段)大部分均在缓存命中,这使得小文件的读取速度看起来特别快。
由于写请求写入客户端内存缓冲区即可返回,因此通常来说JuiceFS的Write时延非常低(几十微秒级别),真正上传到对象存储的动作由内部自动触发,比如单个Slice过大,Slice数量过多,或者仅仅是在缓冲区停留时间过长等,或应用主动触发,比如关闭文件、调用fsync等。
缓冲区中的数据只有在被持久化后才能释放,因此当写入并发较大时,如果缓冲区大小不足(默认300MiB,通过--buffer-size调节),或者对象存储性能不佳,读写缓冲区将持续被占用而导致写阻塞。缓冲区大小可以在指标图的usage.buf一列中看到。当使用量超过阈值时,JuiceFSClient会主动为Write添加约10ms等待时间以减缓写入速度;若已用量超过阈值两倍,则会导致写入暂停直至缓冲区得到释放。因此,在观察到Write时延上升以及Buffer长时间超过阈值时,通常需要尝试设置更大的--buffer-size。另外,增大上传并发度(--max-uploads,默认20)也能提升写入到对象存储的带宽,从而加快缓冲区的释放。
1.随机写JuiceFS支持随机写,包括通过mmap等进行的随机写。
要知道,Block是一个不可变对象,这也是因为大部分对象存储服务并不支持修改对象,只能重新上传覆盖。因此发生覆盖写、大文件随机写时,并不会将Block重新下载、修改、重新上传(这样会带来严重的读写放大!),而是在新分配或者已有Slice中进行写入,以新Block的形式上传至对象存储,然后修改对应文件的元数据,在Chunk的Slice列表中追加新Slice。后续读取文件时,其实在读取通过合并Slice得到的视图。
因此相较于顺序写来说,大文件随机写的情况更复杂:每个Chunk内可能存在多个不连续的Slice,使得一方面数据对象难以达到4MiB大小,另一方面元数据需要多次更新。因此,JuiceFS在大文件随机写有明显的性能下降。当一个Chunk内已写入的Slice过多时,会触发碎片清理(Compaction)来尝试合并与清理这些Slice,来提升读性能。碎片清理以后台任务形式发生,除了系统自动运行,还能通过juicefsgc命令手动触发。
2.客户端写缓存客户端写缓存,也称为「回写模式」。
如果对数据一致性和可靠性没有极致要求,可以在挂载时添加--writeback以进一步提写性能。客户端缓存开启后,Sliceflush仅需写到本地缓存目录即可返回,数据由后台线程异步上传到对象存储。换个角度理解,此时本地目录就是对象存储的缓存层。
更详细的介绍请见「客户端写缓存」。
四、读取流程JuiceFS支持顺序读和随机读(包括基于mmap的随机读),在处理读请求时会通过对象存储的GetObject接口完整读取Block对应的对象,也有可能仅仅读取对象中一定范围的数据(比如通过S3API的Range参数限定读取范围)。与此同时异步地进行预读(通过--prefetch参数控制预读并发度),预读会将整个对象存储块下载到本地缓存目录,以备后用(如指标图中的第2阶段,blockcache有很高的写入带宽)。显然,在顺序读时,这些提前获取的数据都会被后续的请求访问到,缓存命中率非常高,因此也能充分发挥出对象存储的读取性能。数据流如下图所示:
但是对于大文件随机读场景,预读的用途可能不大,反而容易因为读放大和本地缓存的频繁写入与驱逐使得系统资源的实际利用率降低,此时可以考虑用--prefetch=0禁用预读。考虑到此类场景下,一般的缓存策略很难有足够高的收益,可考虑尽可能提升缓存的整体容量,达到能几乎完全缓存所需数据的效果;或者直接禁用缓存(--cache-size=0),并尽可能提高对象存储的读取性能。
小文件的读取则比较简单,通常就是在一次请求里读取完整个文件。由于小文件写入时会直接被缓存起来,因此类似juicefsbench这种写入后不久就读取的访问模式,基本都会在本地缓存目录命中,性能非常可观。