1.分布式文件系统
1.1 简介
分布式文件系统是指文件系统管理的物理存储资源不直接与本地节点相连,而是分布于计算机网络中的一个或者多个节点的计算机上。目前意义上的分布式文件系统大多都是由多个计算机构成,结构上是典型的客户机/服务器模式。流行的模式是当客户机需要存储数据时,服务器指引其将数据分散存储到多个存储节点上,已提供更快的速度,更大的容量,更好的冗余特性。
[流程图]
如上图所示,一个简易的分布式文件系统。
用户看到是文件系统服务,里面包含很多个文件系统。
实际上对应的底层文件存储是分散在多个节点上。
分布式文件系统可以有效解决数据的存储和管理难题,将固定于某个节点的某个文件系统,扩展到任意多个地点、多个文件系统,众多的节点组成一个文件系统网络。每个节点可以分布在不同的机房、地点。通过网络进行节点间的通信和数据传输。用户在使用文件系统时,无须关心存储,只需要像本地文件系统一样管理和存储文件系统中的数据即可。
1.2 发展史
[流程图]
可以看到随着技术的演进,分布式文件系统也在不断迭代,以适应业务的发展需要。
2000s面向对象并行文件系统。随着计算机技术发展,尤其是网络技术的发展,对于存储系统的扩展性提出了更高的需求,相应的研究重点主要集中在对象存储技术,进行高效的元数据管理、提高数据访问的并发性。
2010s云文件系统。随着云计算和大数据技术发展,数据呈现爆炸式增长趋势。云存储要求实现弹性扩展、高可用、高性能、多租户和Qos保证。
1.3 代表性产品
1.3.1 GFS
(图片来源:The Google File System论文)
GoogleFileSystem (GFS) 是谷歌2003年基于linux的专有分布式文件系统。GFS主要组件可以分为三块
GFS Client :客户端。
GFS Master:中心元数据节点。维护文件目录、属性、权限、链接等信息
GFS chunkserver:数据实际存储的节点。每一个chunkserver都会落地到本地的文件系统。
文件读取流程:
客户端请求master,从master获取到当前文件的元数据信息。
客户端请求chunkserver,获取实际的数据。
需要注意的是GFS系统中文件是以chunk分块存储。比如一个1G的文件,GFS会按照一个固定的大小对这个文件进行分块,分块之后会分布到不同的chunkserver上,所以对于一个文件的赌球会涉及到不同的chunkserver通信。
同时为了满足数据的可靠性,文件块会存储到不同的chunkserver中。
1.3.2 HDFS
[流程图]
Hadoop Distributed File System (HDFS) 是Hadoop项目的子项目。是Hadoop的核心组件之一。Hadoop非常适合存储大型数据(比如TB和PB)。HDFS使用多台计算机存储文件,提供统一的访问接口。
整个HDFS系统包含两大模块。Client、Server部分。
Client是发起操作的一方,可以使HDFS自带的工具,也可以是第三方通过API调用的工具。
Server包含两部分:NameNode、DataNode。NameNode负责维护文件目录树,Block映射关系等元信息。DataNode负责具体的数据存储。
三个组件的通信如下:
Client向NameNode、DataNode发起通信。Client发起的文件操作主要为读写操作,这些操作跟GFS一样先跟NameNode通信,拿到返回信息之后,再跟DataNode通信。
DataNode、NameNode通信。DataNode会定期向NameNode发送headrt、block report信息,NameNode给出相应的指令。
DataNode之间的通信。遇到文件的写时复制、节点均衡时的数据移动。
1.3.3 Ceph
Ceph(赛福)是一个可靠的、自动重均衡、自动恢复的分布式存储系统。根据场景可以将Ceph分为三大模块,分别为对象存储、块设备存储、文件系统服务。
图片来自网络
Ceph Io流程
client创建cluster handler。
client读取配置文件。
client连上monitor,获取集群map信息
client读写ip根据crshmap算法请求对应的主osd数据节点。
主osd数据节点同时写入另外两个副本节点数据。
等待主节点以及另外两个副本节点写完数据状态。
主节点及副本写入状态都成功,返回给client,io写入完成。
1.3.4 GlusterFs
GlusterFS 是由美国的 Gluster 公司开发的 POSIX 分布式文件系统(以 GPL 开源),2007 年发布第一个公开版本,2011 年被 Redhat 收购。
它的基本思路就是通过一个无状态的中间件把多个单机文件系统融合成统一的名字空间(namespace)提供给用户。这个中间件是由一系列可叠加的转换器(Translator)实现,每个转换器解决一个问题,比如数据分布、复制、拆分、缓存、锁等等,用户可以根据具体的应用场景需要灵活配置。
Gluster主要应用在集群系统,具有很好的可扩展性。软件结构的设计良好,易于扩展和配置,通过各个模块的灵活搭配以得到针对性的解决方案。GlusterFs适合大文件,小文件性能较差。
2.GlusterFs
由于本部门使用的分布式文件系统是GlusterFs这里就以Gluster进行详细的介绍。
2.1 存储基础
GlusterFs系统是一个可扩展的网络文件系统,相比于其他分布式文件系统,GlusterFS具有高扩展性、高可用性、高性能、可横向扩展的特点,由于其没有元数据服务器的设计,让整个服务没有单点故障的隐患。GlusterFS是一个横向扩展的文件系统,把多台异构的存储服务器的存储空间整合起来给用户提供统一的命名空间。用户访问存储资源的方式有很多,可以通过NFS、SMB、HTTP协议等访问,也可以通过Gluster本身提供的客户端访问。
GlusterFS是Scale-Out存储解决方案Gluster的核心,它是一个开源的分布式文件系统,具有强大的横向扩展能力,通过扩展能够支持数PB存储容量和处理数千客户端。GlusterFS借助TCP/IP或InfiniBand RDMA网络将物理分布的存储资源聚集在一起,使用单一全局命名空间来管理数据。GlusterFS基于可堆叠的用户空间设计,可为各种不同的数据负载提供优异的性能。
2.2 应用场景
GlusferFs目前主要用户大文件存储场景,对于小文件尤其是海量小文件,存储效率和访问性能都表现不佳。海量小文件的LOSF问题是工业界和学术界公认的难题,Glusterfs作为通用的分布式文件系统并没有对小文件做额外的优化措施。
LOSF问题概述如下:
衡量存储系统性能主要有两个关键指标,即IOPS和数据吞吐量。
IOPS (Input/Output Per Second) 即每秒的输入输出量 (或读写次数) ,是衡量存储系统性能的主要指标之一。IOPS是指单位时间内系统能处理的I/O请求数量,一般以每秒处理的I/O请求数量为单位,I/O请求通常为读或写数据操作请求。随机读写频繁的应用,如OLTP(OnlineTransaction Processing),IOPS是关键衡量指标。
另一个重要指标是数据吞吐量(Throughput),指单位时间内可以成功传输的数据数量。对于大量顺序读写的应用,如VOD(VideoOn Demand),则更关注吞吐量指标。
我们的存储磁盘最适合顺序的大文件I/O读写模式,非常不适合随机的小文件I/O读写模式,这是磁盘文件系统在海量小文件应用下性能表现不佳的根本原因。磁盘文件系统的设计大多都侧重于大文件,包括元数据管理、数据布局和I/O访问流程,另外VFS系统调用机制也非常不利于海量小文件,这些软件层面的机制和实现加剧了小文件读写的性能问题。
对于LOSF而言,IOPS/OPS是关键性能衡量指标,造成性能和存储效率低下的主要原因包括元数据管理、数据布局和I/O管理、Cache管理、网络开销等方面。从理论分析以及上面LOSF优化实践来看,优化应该从元数据管理、缓存机制、合并小文件等方面展开,而且优化是一个系统工程,结合硬件、软件,从多个层面同时着手,优化效果会更显著。
2.3 GlusterFs通信流程
GlusterFs是通信的全互联架构,因为Glusterfs没有主节点,也即是没有大家常见的master/slave概念,每一个客户端都会和不同的节点进行通信。如下图所示。
接下来会为介绍Gluster一些核心的组件和术语。
2.4 部署集群
在着重介绍gluster之前。我们需要搭建一个glusterfs集群环境。笔者这里采用docker的方式搭建glustered集群。
需要宿主家安装docker、docker-compose
docker-composer.yaml如下
version: '3'
services:
glusterfs1:
container_name: pcx-server1
image: gluster/gluster-centos
privileged: true
networks:
network_glusterfs:
ipv4_address: 172.20.0.101
volumes:
- alex_etc_volume1:/etc/glusterfs
- alex_lib_volume1:/var/lib/glusterd
- alex_log_volume1:/var/log/glusterfs
- alex_data_volume1:/data/glusterfs
glusterfs2:
container_name: pcx-server2
image: gluster/gluster-centos
privileged: true
networks:
network_glusterfs:
ipv4_address: 172.20.0.102
volumes:
- alex_etc_volume2:/etc/glusterfs
- alex_lib_volume2:/var/lib/glusterd
- alex_log_volume2:/var/log/glusterfs
- alex_data_volume2:/data/glusterfs
glusterfs3:
container_name: pcx-server3
image: gluster/gluster-centos
privileged: true
networks:
network_glusterfs:
ipv4_address: 172.20.0.103
volumes:
- alex_etc_volume3:/etc/glusterfs
- alex_lib_volume3:/var/lib/glusterd
- alex_log_volume3:/var/log/glusterfs
- alex_data_volume3:/data/glusterf
networks:
network_glusterfs:
# driver: bridge
ipam:
driver: default
config:
- subnet: 172.20.0.0/16
volumes:
alex_etc_volume1:
alex_lib_volume1:
alex_log_volume1:
alex_data_volume1:
alex_etc_volume2:
alex_lib_volume2:
alex_log_volume2:
alex_data_volume2:
alex_etc_volume3:
alex_lib_volume3:
alex_log_volume3:
alex_data_volume3:
注意需要再AMD架构下执行。docker版本暂不支持ARM(mac m系列)执行。
编辑好docker-compose.yaml 文件之后通过执行如下命令可以启动、停止
docker-compose up -d (启动)
docker-compose down (停止)
当集群成功启动后,通过docker ps -a 可以看到如下容器。
其中name:pcx-server1、pcx-server2、pcx-server3 就是容器的hostname,可以直接在内部直接ping pcx-server* 判断网络是否联通。
2.5 组件术语
Bricks:GlusterFs中的存储单元,通过一个受信任存储池的服务器的一个到处目录。可以通过主机名和目录名来标识。如“SERVER_EXPORT”。
Clinet:挂载GlusterFs卷的设备。
GFID:GlusterFs卷中每个文件和目录都有一个128位的数据相关联,其用于模拟inode。
NameSpace:每个Gluster卷都到处单个ns作为POSIX的挂载点。
Node:拥有若干brick的设备。
RDMA:远程直接内存方伟。
RRDNS:通过DNS轮转返回不同的设备以进行负载均衡的方法。
Self-Heal:用于后台运行监测副本卷中文件的不一致并解决这些不一致。
Split-brain:脑裂。
Vofile:GlusterFs进程中的配置文件。/var/lib/glusterd/vols/volname
Volume:一组bricks的逻辑集合。
2.5.1 trusted pool
trusted storage pool 是一堆存储节点的集合。
通过一个节点邀请其他节点创建,
成员可以动态加入,动态删除。
添加命令如下
Gluster peer probe pcx-server2 pcx-server3 # (注意 node2 node3 一定要是通过hostname访问到)
[流程图]
此时pcx-server1、pcx-server2、pcx-server3 已经在一个受信任的存储池中。
2.5.2 Bricks
brick是一个节点和导出目录的集合。
brick是底层的RAID或磁盘经XFS或ext4文件系统格式化而来,继承了文件系统的限制。
每个节点上的bricks是不限制的。
理想的状况是,一个集群的所有Brick大小都一样。
(图片来自网络)
2.5.3 volumes
volume 是brick的逻辑集合。
volume是可挂载的目录
一个节点上的不同brick可以属于不同的卷。
挂载命令如下
mount -t glusterfs pcx-server1:/alex_data /data/mnt/opt
glusterfs支持不同的卷种类。
分布式卷。目录在每个brick都可见,单个brick失效会带来数据丢失,无需额外元数据服务器。
条带卷。文件切分成不同的chunk,存放不同brick,brick故障会导致数据丢失。
复制卷。同步复制所有目录和文件,节点故障保证高可用。
分布式复制卷。最常见的一种模式,读操作可以做到负载均衡。
条带复制卷。
分布式条带复制卷。
2.5.4 分布式卷(Distributed volume)
默认模式DHT又称哈希卷,文件没有分片,文件根据hash算法写入各个节点的硬盘上,优点是容量大,缺点是没有冗余。
观察文件存储的容器
ls /data/glusterfs/test-distributed/
可以看到新创建的文件只在pcx-server1上存储。
2.5.5 复制卷(Replicated Volume)
复制模式,复制的份数决定集群的大小,通常和分布式卷或者条带卷组使用。
缺点是磁盘利用率低。副本卷在创建时可以指定副本的数量,通常为2或者3,副本会存储在卷的不同brick上。因此有几个副本就必须提供至少多个brick。当其中一台失效后可以从另一台服务器读取数据。因此复制GlusterFs提高数据可靠性,提供数据冗余能力。
创建两个副本的复制卷容易出现脑裂现象,在后面ariiter会进行详细表述。
mkdir -p /mnt/test-distribute0 &&
mount -t glusterfs pcx-server1:test-distribute0/mnt/test0distrubute0 &&
touch 1.pdf
观察两个节点brick情况
2.5.6 分布式复制卷
分布式存储,数据被分散到不同的brick中
mkdir -p /mnt/test-distribute2 && mount -t glusterfs pcx-server1:test-distribute2 /mnt/test-distribute2 &&
dd if=/dev/zero of=/mnt/test-distribute2/1.txt bs=64k count=1000
观察brick挂载情况
du -sh /data/glusterfs/test-distribute2/
观察数据被分布复制到后三个节点
2.5.7 分散卷 (gluster最新版本已弃用stripe模式)
文件是均匀写在各个硬盘上,优点是分布式读写,性能整体良好。缺点是没有冗余,分片随机读写可能会导致硬盘IOPS饱和 。
示例:分布式分散卷 disperse必须大于2 (disperse 为 3 后面的文件系统必须是6个)
mkdir -p /mnt/test-disperse && mount -t glusterfs pcx-server1:test-disperse /mnt/test-disperse &&
dd if=/dev/zero of=/mnt/test-disperse/1.txt bs=64k count=1000
此时观察文件在glusterfs服务器brick中的存储情况。
可以发现文件在brick是分散存储的。
2.5.8 分布式分散卷
示例:分布式分散卷 disperse必须大于2 (disperse 为 3 后面的文件系统必须是6个)
由于机器不足或者磁盘不足,用根目录的/k8s目录 作为两个文件系统,生产上请使用新增机器,或者新数据盘(副本集不能再同一个磁盘中)
mkdir -p /mnt/test-disperse-3 && mount -t glusterfs pcx-server1:test-disperse-3 /mnt/test-disperse-3 &&
dd if=/dev/zero of=/mnt/test-disperse-3/1.txt bs=64k count=1000
此时观察brick的挂载情况
du -sh /data/glusterfs/test-disperse-3/
数据被分布保留在三个节点上。
2.5.5 arbiter仲裁卷
引入仲裁节点,最大的原因是为了减少脑裂的发生,所谓的脑裂现象是副本中的元数据信息在机器产生故障或者反复宕机等复杂环境下,元数据信息不一致时产生的,在分布式系统中属于非常致命且危险的现象。
对于仲裁节点来说,不直接存放数据。因此磁盘使用量会比其他数据节点少很多。
为了进一步感受仲裁节点的作用,下面通过简单的命令进行测试
gluster volume create test-arbiter replica 2 arbiter 1 pcx-server{1,2,3}:/data/glusterfs/test-arbiter
gluster volume start test-arbiter
gluster volume info test-arbiter
可以发现已经成功创建了带有仲裁节点集群。
进行目录挂载
mkdir -p /mnt/test-arbiter && mount -t glusterfs pcx-server1:test-arbiter /mnt/test-arbiter &&
dd if=/dev/zero of=/mnt/test-arbiter/1.txt bs=64k count=1000
可以看到在仲裁节点是不保存数据的。
2.6 工作流程
首先是在客户端, 用户通过glusterfs的mount point 来读写数据, 对于用户来说,集群系统的存在对用户是完全透明的,用户感觉不到是操作本地系统还是远端的集群系统
用户的这个操作被递交给 本地linux系统的VFS来处理。
VFS 将数据递交给FUSE 内核文件系统:在启动 glusterfs 客户端以前,需要想系统注册一个实际的文件系统FUSE,如上图所示,该文件系统与ext3在同一个层次上面, ext3 是对实际的磁盘进行处理, 而fuse 文件系统则是将数据通过/dev/fuse 这个设备文件递交给了glusterfs client端。所以, 我们可以将 fuse文件系统理解为一个代理。
数据被fuse 递交给Glusterfs client 后, client 对数据进行一些指定的处理(所谓的指定,是按照client 配置文件据来进行的一系列处理, 我们在启动glusterfs client 时需要指定这个文件。
在glusterfs client的处理末端,通过网络将数据递交给 Glusterfs Server,并且将数据写入到服务器所控制的存储设备上
客户端访问流程如下
当客户端访问GlusterFS存储时,首先程序通过访问挂载点的形式读写数据,对于用户和程序而言,集群文件系统是透明的,用户和程序根本感觉不到文件系统是本地还是在远程服务器上。读写操作将会被交给VFS(Virtual File System)来处理,VFS会将请求交给FUSE内核模块,而FUSE又会通过设备/dev/fuse将数据交给GlusterFS Client。最后经过GlusterFS Client的计算,并最终经过网络将请求或数据发送到GlusterFS Server上。
3.结语
以上是笔者基于gluster的实践。受限于本人的机器环境,采用docker-compose的方式进行开发。在生产环境建议还是以物理机进行搭建,挂载目录和系统目录最好是两个磁盘。