13.凤凰架构:构建可靠的大型分布式系统 --- 持久化存储

第13章 持久化存储
	容器是镜像的运行时实例,为了保证镜像能够重复的产出具备一致性的运行时实例,必须要求镜像本身是持久且稳定的,这决定了在容器中发生的一切数据
变动操作都不能真正写入镜像当中,否则必然会破坏镜像不变的性质。为此,容器中的数据修改操作大多是基于写时复制(Copy-on-Write)策略来实现的。
容器会利用叠加式文件系统(OverlayFS)的特性,在用户意图修改数据的时候,自动将变更的内容写入独立区域,再与原有的数据叠加到一起,使其从外观上
看像是"覆盖"了原有内容。这种改动通常是临时的,一旦容器终止运行,这些存储于独立区域中的变动信息也会一并删除,不复存在。由此可见,如果不额外处理,
容器默认是不具备持久化存储能力的。
	
	而另外一方面,容器作为信息系统的运行载体,必定会产出有价值、应该被持久化保存的信息,譬如扮演数据库角色的容器;多个容器之间有时候也需要通过
共享存储来实现某些交互操作。


13.1 Kubernetes存储设计
		k8s 提出了如此多关于存储的概念,最重要的原因是存储技术本来就种类繁多,为了尽可能兼容各种存储技术,k8s不得不预置了很多In-Tree插件来
	对接,让用户根据自己的需要来选择。同时,为了兼容那些不在预置范围内的需求场景,支持用户使用 FlexVolume或者CSI 来定制Out-of-Tree插件,
	实现更加丰富的存储能力。

	13.1.1 Mount和Volume
		Mount和Volume 都是源自操作系统的常用术语。Mount是动词,表示将某个外部存储挂载到系统中;Volumn是名词,表示物理存储的逻辑抽象,目的是
	为物理存储提供有弹性的分割方式。容器源于对操作系统层的虚拟化,为了满足容器内 生成数据的外部存储需求,很自然的会将Mount和Volume的概念延伸到
	容器中。

		目前,Docker 内置了三种挂在类型,分别是Bind(--mount type=bind)、Volume(--mount type=volume)和tmpfs(--mount type=tmpfs)。

		Bind Mount 是docker 最好提供的挂载类型,作用是把宿主机的某个目录(或文件)挂载到容器的指定目录下,譬如下面命令参数 -v 表达的意思就是将
	外部的 HTML 文档挂载到nginx容器的默认网站根目录下:
		docker run -v /xxx/html:/usr/share/nginx/html nginx:latest

		请注意,虽然命令中的-v参数是 --volume 的缩写,但-v最初只是用来创建 Bind Mount 而不是创建 Volume Mount的,是docker发布的是没考虑周全。
	从docker 17.06开始,它在docker Swarm中借用了 --mount 参数,该参数默认创建的是Volume Mount,可以通过明确的type 子参数来指定另外两种类型。
	如下:
		docker run --mount type=bind,source=/xxx/html,destination=/usr/share/nginx/html nginx:latest

		从Bind Mount 到Volume Mount,实质是容器发展过程中对存储抽象能力提升的外在表现。从Bind这个名字以及Bind Mount的实际功能可以推断出,docker
	最初认为Volume就只是一种"外部宿主机的磁盘存储到内部容器的映射关系",但后来发现事情并没有那么简单:存储的位置并不局限于外部宿主机,存储的介质并不局限于
	物理磁盘,存储的管理也不局限于映射关系。

		譬如,Bind Mount 只允许容器与本地宿主机之间建立某个目录的映射,如果想要在不同宿主机上的容器共享同一份存储,就必须先把共享存储挂载到每一台宿主机
	操作系统的某个目录下,然才能逐个挂载到容器内使用。

		这种存储范围超越了宿主机的共享存储,配置过程却要涉及大量与宿主机环境相关的操作,只能由管理员人工去完成,不仅烦琐,而且很难自动化。

		又譬如,即便只考虑单台宿主机的情况,基于可管理性的需求,docker也完全有支持Volume Mount的必要。在Bind Mount里面,docker只有容器的控制权,
	而存放在容器产生的数据的主机目录是完全独立的,与docker没有任何关系,既不受docker保护,也不受docker管理。数据很容易被其他进程访问到,甚至被修改和
	删除。如果用户想对挂载的目录进行备份、迁移等管理运维操作,也只能在docker之外靠管理员人工进行,增加了数据安全域操作意外的风险。因此,docker希望
	有一种抽象的资源来代表宿主机或网络存储的区域,以便让docker管理这些资源,由此很自然的想到了操作系统里的Volume概念。

		提出Volume的核心是 提升docker对不同存储介质的支撑能力,这同时也可以减轻docker本身的工作量。存储并不是仅有挂载在宿主机上的物理这一种介质,云计算
	时代,网络存储逐渐成为数据中心的主流选择,不同的网络存储有各自的协议和交互接口,而且并非所有存储系统都适合先挂载到操作系统,再挂载到容器上,如果docker
	想要越过操作系统去支持挂载某种存储系统,首先必须知道该如何访问它,然后才能将容器中的读写操作自动转移到该位置。docker把解决如何访问存储系统的功能模块
	称为存储驱动(Storage Driver)。通过docker info命令,你能查看到当前docker所支持的存储驱动。虽然docker已经内置了市面上主流的OverlayFS驱动,如
	Overlay,Overlay2,AUFS等,但面对云时代的快速迭代,仅靠docker自身来支持全部云厂商的存储系统是完全不现实的,为此,docker提出了与Storage Driver
	相对应的Volume Driver(卷驱动)的概念。用户可以通过 docker plugin install 命令来安装外部的卷驱动,并在创建Volume时指定一个与其存储系统相匹配的
	卷驱动,譬如希望数据存储在aws Elastic Block Store上,就找一个AWS EBS的驱动,如果想存储在 Azure File Storage上,就找一个对应的Azure File
	Storage驱动。如果创建的Volume时不指定卷驱动,将默认local类型,在Volume中存放的数据会存储在宿主机的 /var/lib/docker/volumes目录中。


	13.1.2 静态存储分配
		k8s 将Volume 分为持久化的 PersistentVolume和非持久化的普通Volume两类。

		普通Volume的设计目标不是为了持久化保存数据,而是为了在同一个Pod中多个容器提供可共享的存储资源,因此Volume具有十分明确的生命周期---与挂载它的pod
	相同的生命周期,这意味着尽管普通Volume不具备持久化的存储能力,但至少比pod中运行的任何容器的存活期都长。pod中不同的容器能共享相同的普通Volume,当容器
	重新启动时,普通Volume中的数据也能够保留。当然,一旦整个pod被销毁,普通Volume也将不复存在,数据在逻辑上也会被销毁,至于实质上是否会被真正的删除,就
	取决于存储驱动具体是如何实现 Unmount,Detach,Delete接口的。

		从Persistent这个单词就可以看出,PersistentVolume是指能够持久化存储数据的一种资源对象,它可以独立于Pod存在,且生命周期与pod无关,因此也决定了
	PersistentVolume不应该依附于任何一个宿主机节点。

		Local PersistentVolume:
			对于部署在云端的数据中心系统,通过网络访问同一个可用区中的远程存储,速度是完全可以接受的。但对于私有部署的系统,基于性能考虑,使用本地存储往往
		更为常见。

			考虑到这样的实际需求,从1.10版本开始,k8s开始支持 Local PersistentVolume,这是一种将一整块本地磁盘作为 PersistentVolume供容器使用
		的专用方案。专用方面字面的意思就是,Local PersistentVolume 并不适用于全部应用,只是针对磁盘IO为瓶颈的特定场景的解决方案,副作用十分明显:由于
		不能保证这种磁盘在每一个节点都存在,所以k8s在调度时就必须考虑到PersistentVolume的分布情况,只能把使用了Local PersistentVolume的pod调度到
		这种PersistentVolume的节点上。调度器中专门有个Volume Binding模式来支持这项处理,但一旦使用了Local PersistentVolume,无疑会限制pod的
		可调度范围。

		将 PersistentVolume 与pod分离后,便需要专门考虑PersistentVolume该如何被pod引用的问题。原本在pod中引用其他资源是常有的事,要么通过资源名称
	直接引用,要么通过标签选择器(Selector)间接引用。但类似的方法在这里都不妥,至于原因,你想下"Pod该使用何种存储"这件事情应该是由系统管理员(运维)说了算,
	还是由用户(开发)说的算。最合理的是,他们一起说的算,因为只有开发知道Pod需要消耗多少存储空间,只有运维知道当前系统可以使用的存储设备情况。为了让他们得以
	提供各自擅长的信息,k8s 又额外设计了PersistentVolumeClaim资源。k8s官方给出的定义特别强调了PersistentVolume由运维人员维护,由开发通过
	PersistentVolumeClaim来匹配到合乎需求的PersistentVolume。

		PersistentVolume 是由管理员负责提供的集群存储。
		PersistentVolumeClaim 是由用户负责提供的存储请求。

		PersistentVolume 是Volume这个抽象概念的具体化表现,通俗地说,它是已经被管理员分配好的具体存储,这里的具体是说有明确的存储系统地址,有明确的容量,
	访问模式,存储位置等信息;而PersistentVolumeClaim 则是pod对齐所需存储能力的声明,通俗地说就是满足这个pod正常运行要什么条件,譬如要消耗多大的存储空间,
	要支持怎样的访问方式。因此,两者并不是谁引用谁的问题,而是根据实际情况动态匹配的,两者匹配的过程如下:
		1.管理员准备好要使用的存储系统,它应是某种网络文件系统(NFS)或者云存储系统,一般来说具备跨主机共享的能力;
		2.管理员根据存储系统的实际情况手工预先分配好若干个PersistentVolume,并定义好每个PersistentVolume可以提供的具体能力,如下:
		apiVersion:v1
		kind:PersistentVolume
		metadata:
			name:nginx-html
		spec:
			capacity:
				storage:5Gi  #最大容量为5Gi
			accessModes:
				- ReadWriteOnce  #访问模式为 RXO
			persistentVolumeReclaimPolicy:Retain #回收策略为 Retain
			nfs:  #存储驱动是nfs
				path:/html 
				server:172.17.0.2

			以上yaml中定义的存储能力具体为:
				a) 存储的最大容量为5Gi
				b) 存储的访问模式为"只能被一个节点读写挂载"(ReadWriteOnce,RWO),另外两种可选的模式为"可以被多个节点以只读方式挂载"
				(ReadOnlyMany,ROX),和"可以被多个节点读写挂载"(ReadWriteMany,RWX)
				c) 存储的回收策略是Retain,即在pod被销毁时并不会删除数据。另外两种可选的回收策略分别是Recycle和Delete。Recycle策略下的pod
				被销毁时,由k8s自动执行 rm -rf /volume/* 这样的命令来删除数据。Delete策略下,k8s会自动调用 AWS EBS 等这些云存储的删除指令。
				d) 存储的驱动是NFS,其他常见的存储驱动有 AWS EBS 等。
		3.根据业务的实际情况创建PersistentVolumeClaim,声明pod运行所需的存储能力,譬如:
		kind:PersistentVolumeClaim
		apiVersion:v1
		metadata:
			name:nginx-html-claim
		spec:
			accessModes:
				- ReadWriteOnce
			resources:	
				requests:
					storage:5Gi

			以上yaml中声明了容量不得小于5Gi,必须支持 RWO 的访问模式
		4.k8s在创建pod的过程中,会根据系统的PersistentVolume与PersistentVolumeClaim的供需关系对两者进行撮合。如果系统中存在满足
		PersistentVolumeClaim声明中要求能力的PersistentVolume,则撮合成功,将它们绑定;如果撮合不成功,pod就不会被继续创建,直到
		系统中出现新的或者让出空闲的PersistentVolume资源。
		5.以上步骤都顺利完成的话,意味着pod的存储得到了满足,可以继续pod的创建过程。

		k8s 对PersistentVolumeClaim于PersistentVolume的撮合结果是产生一对一的绑定关系,一对一意味着PersistentVolume一旦绑定某个
	PersistentVolumeClaim,直到释放之前都会被这个PersistentVolumeClaim所独占,不能再与其他PersistentVolumeClaim进行绑定,意味着
	可能有这样的浪费。


	13.1.3 动态存储分配
		对于中小规模的k8s集群,PersistentVolume已经能满足有状态应用的存储需求,它依靠人工介入来分配空间的设计,简单直观,却算不上先进,
	一旦应用规模增大,其很难被自动化的问题就会凸显出来。这是由于Pod创建过程中去挂载某个Volume时,要求该Volume必须是真实存在的,否则pod
	启动可能依赖的数据(如配置,数据,外部资源等)都将无从读取。k8s有能力随着压力和硬件资源的情况,自动扩缩pod的数量,但是当k8s自动扩展出一个
	pod时,并没有办法让Pod去自动挂载一个还未分配资源的PersistentVolume。要解决这个问题,要么允许多个不同的pod公用相同的PersistentVolumeClaim,
	这种方案确实只能靠PersistentVolume就能解决,却失去了隔离性,难以通用;要么要求每个pod用到的PersistentVolume都是已经被预先建立并分配
	好的,这种方案靠管理员提前手工分配好是可以实现的,却损失了自动化能力。

		动态存储分配(Dynamic Provisioning)
		静态存储分配(Static Provisioning)

		所谓的动态存储分配方案,是指在用户声明存储能力的需求时,不是通过k8s撮合来获得一个管理员预置的PersistentVolume,而是由特定的资源分配器
	(Provisioner)自动的在存储资源池或云存储系统中分配符合用户存储需求的PersistentVolume,然后挂载到pod中使用。完成这项工作的资源被命名为
	StorageClass,它的具体工作过程如下:
		1.管理员根据存储系统的实际情况,先准备好对应的资源分配器。k8s官方已经提供了一系列预置的In-Tree资源分配器,放置在k8s的API组之下。其中部分
		资源分配器已经有了官方的CSI驱动,譬如vSphere的k8s自动驱动为 kubernetes.io/vsphere-volume。
		2.管理员不再手工分配PersistentVolume,而是根据存储配置StorageClass。pod是可以动态伸缩的,而存储则是相对固定的,哪怕用的是云存储,也会
		将它们视为 存储容量、IOPS等参数可变的固定存储来看待。譬如你可以将来自不同云存储提供商、不同性能、支持不同访问模式的存储配置为各种类型的
		StorageClass,例子如下:
		
		apiVersion:storage.k8s.io/v1
		kind:StorageClass
		metadata:
			name:standard
		provisioner:kubernetes.io/aws-ebs
		parameters:
			type:gp2
		reclaimPolicy:Retain

		3.用户依然通过PersistentVolumeClaim声明所需的存储,但是应在声明中明确指出该由哪个StorageClass来代替k8s处理该PersistentVolumeClaim
		的请求,如下:

		apiVersion:v1
		kind:PersistentVolumeClaim
		metadata:
			name:standard-claim
		spec:
			accessModes:
			- ReadWriteOnce
			storageClassName:standard  #明确指出该由哪个StorageClass来处理该PersistentVolumeClaim请求
			resource:
				requests:
					storage:5Gi


		4.如果PersistentVolumeClaim中要求的StorageClass及它用到的资源分配器均可用,那这个StorageClass就会接管原本由k8s撮合
		PersistentVolume与PersistentVolumeClaim的操作,按照PersistentVolumeClaim中声明的存储需求,自动生成满足该需求的PersistentVolume
		描述信息,并发送给资源分配器处理。
		5.资源分配器接收到StorageClass发来的创建PersistentVolume的请求后,会操作其背后的存储系统去分配空间,如果分配成功,就生成并返回符合要求
		的PersistentVolume提供pod使用。
		6.以上步骤都顺利完成的话,意味着pod的存储需求得到了满足,可以继续pod的创建过程。

		Dynamic Provisioning 和 Static Provisioning 并不是各有用途的互补设计,而是针对同一个问题先后提出的两种解决方案。完全可以用 
	Dynamic Provisioning 来满足所有 Static Provisioning 能满足的需求,包括那些不需要动态分配的场景。甚至之前例子里面使用HostPath在
	本地静态分配存储的操作,都可以指定 no-provisioner作为资源分配器StorageClass,以Local Persistent Volume 代替。

		使用 Dynamic Provisioning 来分配存储无疑是更合理的设计,不仅省去了管理员的人工操作的中间层,而且不再需要将PersistentVolume这样的
	概念暴露给最终用户,因为Dynamic Provisioning 里的PersistentVolume只是处理过程的中间产物,用户不需要接触和理解它,只需要知道由
	PersistentVolumeClaim去描述存储需求,由StorageClass去满足存储需求即可。只描述意图而不关心中间具体的处理过程是声明式编程的精髓,也是流程
	自动化的必要基础。

		由Dynamic Provisioning来分配存储还能获得更高的可管理性。


13.2 容器存储与生态
	13.2.1 Kubernetes存储架构
		k8s参考了传统操作系统接入或者移除新存储设备的做法,把接入和移除外部存储分解为下面三步:
			1.首先,决定应准备(Provision)哪种存储设备,Provision可以类比为给操作系统扩容而购买的新的存储设备。这步确定了存储设备的来源、容量、
			性能以及其他技术参数,它的逆操作是(Delete)存储。
			2.然后,将准备好的存储设备附加(Attach)到系统中,Attach可类比为将存储设备接入操作系统,此时尽管设备还不能用,但已经可以用操作系统的
			fdisk -l 命令查看到设备了。这步确定了存储的设备名称、驱动方式等面向系统侧的信息,它的逆操作是分离(Detach)存储设备
			3.最后,将附加好的存储挂载(Mount)到系统中,Mount可类比为将设备挂载到系统的指定位置,也就是起到操作系统的mount命令的作用。这步确定了
			存储设备的访问目录,文件系统格式等面向应用侧的信息,它的逆操作是卸载(Unmount)存储设备。

			上面提到的Provision,Delete,Attach,Detach,Mount,Unmount 六种操作,并不是直接由k8s来实现的,而是在存储插件中完成的,它们分别会
		被k8s通过两个控制器和一个管理器来调用,作用如下:
			1.PV控制器(PersistentVolume Controller)
				k8s 里所有的控制器都遵循相同的工作模式 --- 让实际状态尽可能的接近期望状态。PV控制器的期望状态有2个,分别是"所有未绑定的
			PersistentVolume都处于可用的状态" 以及 "所有处于等待状态的PersistentVolumeClaim都能匹配到与之绑定的PersistentVolume"。PV
			控制器内部也有两个相对独立的核心逻辑(ClaimWorker和VolumeWorker)来分别跟踪这两种期望状态,可以简单的理解为PV控制器实现了
			PersistentVolume和PersistentVolumeClaim的生命周期管理职能,在这个过程中,它会根据需要调用层次驱动插件的Privision/Delete操作。

			2.AD控制器(Attach/Detach Controller)
				AD控制器的期望状态是"所有被调度到的准备创建新pod的节点都附加了要使用的存储设备;当pod被销毁后,原本运行的pod节点都分离了不再
			使用的存储",如果实际状态不符合该期望,会根据需要调用存储驱动插件的Attach/Detach操作。

			3.Volume管理器(Volume Controller)
				Volume管理器实际上是kubelet的一部分,是kubelet的众多管理器之一,主要用来支持本节点中Volume执行Attach/Detach/Mount/Unmount
			的操作。这里不仅有 Mount/Unmont操作,还有 Attach/Detach操作,这是历史原因,由于最初k8s的版本中并没有AD控制器,Attach/Detach也
			在kubelet中完成。现在kubelet默认情况下,已经不再执行Attach/Detach操作了,但有少量旧程序已经依赖了由kubelet来执行Attach/Detach
			操作的内部逻辑,所以kubelet不得不设计一个 --enable-controller-attach-detach 参数,如果将其设置为false,就会重新回到旧的兼容
			模式上,由kubelet代替AD控制器完成 Attach/Detach 操作。


		后端的真实存储经过 Provision,Attach,Mount操作之后,就形成了可以在容器中挂在的Volume,当存储插件的生命周期完结,依次经过Unmount,Detach,
	Delete操作之后,Volume便能够被存储系统回收。

	13.2.2 FlexVolume与CSI
		k8s 目前同时支持 FlexVolume和CSI(Container Storage Interface,容器存储接口)两套独立的存储扩展机制。

		由于FlexVolume 是为k8s量身定做的,所以 FlexVolume 的实现逻辑上与k8s的存储架构高度一致。FlexVolume 驱动其实就是一个实现了Attach,
	Detach,Mount,Unmount 操作的可执行文件(甚至仅仅是个shell)而已,该可执行文件应该放在集群每个节点的
	/usr/libexec/kubernetes/kubelet-plugins/volume/exec 目录里,其工作过程就是当AD控制器和Volume管理器需要进行Attach,Detach,Mount,
	Unmount操作时自动调用它的对应的接口。

		如果仅仅考虑支持最基本的 Static Provisioning,那实现一个 FlexVolume 驱动确实非常简单。然而也是因为太简单,导致应用时会有诸多不便:
			1.FlexVolume并不是全功能的驱动
			2.FlexVolume的部署、维护相对繁琐
			3.FlexVolume实现复杂交互时也相对烦琐

		相对来说CSI可以说是一个十分完善的存储扩展规范。CSI 规范可以分为 需要容器系统去实现的组件 以及 需要存储提供商去实现的组件 两大部分。前者
	包括存储整体架构、Volume的生命周期模型、驱动注册、Volume创建、挂载、扩容、快照、度量等内容;目前,通过k8s提供的插件都已经很完整的实现了这些
	内容,其中涉及的主要组件包括:
		1.Driver Register
		2.External Provisioner
		3.External Attacher
		4.External Resizer
		5.External Snapshotter
		6.External Health Monitor

		需要存储提供商实现的组件才是 CSI 的主体部分。这部分着重定义了 外部存储挂载到容器过程中所涉及操作的抽象接口和具体的通信方式,主要包括以下3
	个gRPC接口:
		1.CSI Identify 接口
		2.CSI Controller 接口
		3.CSI Node 接口

		与FlexVolume以单独的可执行程序的存在形式不同,CSI插件本身便是由一组标准的k8s资源所构成的,CSI Controller接口是一个以StatefulSet方式
	部署的gRPC服务,CSI Node 接口则是基于 DaemonSet方式部署的gRPC服务。这意味着虽然CSI实现起来要比FlexVolume复杂的多,但却很容易安装 --- 如同
	安装CNI插件以及其他应用程序那样,直接载入Manifest文件即可,也不会遇到FlexVolume 那样需要人工运维,或者自己编写DaemonSet 来维护集群节点变更
	的问题。此外,通过gRPC协议传递参数比通过命令行参数传递参数更严谨、灵活和可靠。

	13.2.3 从In-Tree到Out-of-Tree
		k8s 曾内置了相当多的 In-Tree 的存储驱动。但是,这种策略也让k8s丧失了随时添加或修改存储驱动的灵活性,只能在更新大版本时才能加入或者修改
	驱动。

	13.2.4 容器插件生态
		现在几乎所有云产商都支持自家的容器通过CSI规范去接入外部存储。

		目前出现的存储系统和设备均可以划分为 块存储,文件存储和对象存储这3种存储类型之中,划分的根本依据其实不是各种存储系统或设备如何存储数据---
	那完全是存储系统的事情,更合理的划分依据应该是 各种存储系统或设备提供何种形式的接口供外部访问数据,不同的外部访问接口将反过来影响存储的内部结构、
	性能与功能表现。虽然这三种可以彼此协同工作,但确实有擅长的领域与优缺点。

		1.块存储
			块存储是数据存储的最古老的形式,数据都存储在固定长度的一个或者多个块(Block)中,想要读写访问数据,就必须使用与存储类型相匹配的协议(
		SCSI,SATA,SAS,FCP,FCoE,iSCSI等)来进行。

			我们最熟悉的硬盘就是最经典的块存储设备,以机械硬盘为例,一个块就是一个扇区,通常在512字节到4098字节。老式机械硬盘用柱面、磁头、扇区号(
		Cylinder-Head-Sector,CHS)组成的编号进行寻址,现代机械硬盘只用一个逻辑块编号(Logical BLock Addressing,LBA)进行寻址。为了方便管理,
		硬盘通常会以多个块来组成一个逻辑分区,将分区进行高级格式化后就形成了卷(Volume)。

			块存储由于最贴近底层硬件,没有文件,目录,访问权限等牵绊,所以性能通常都是最优秀的,吞吐量高,延迟低。多数应用程序是基于文件而不是块来
		读写数据,但是操作系统内核中许多地方都是直接通过块设备接口来访问硬盘的,一些追求IO性能的软件,譬如高性能的数据库,也会直接读写块设备以提升
		磁盘IO。块存储还具有排他性,一旦块设备被某个客户端挂载,其他客户端就无法再访问上面的数据了。

		2.文件存储
			文件存储是最贴近人类用户的数据存储形式,数据存储在长度不固定的文件之中,用户可以针对文件进行增删查改等各种操作,通常文件存储还会提供文件
		查找,目录管理,权限控制等额外的高级功能。文件存储的访问不像块存储那样有五花八门的协议,POSIX接口已经成为事实标准,被各种商用的存储系统和
		操作系统共同支持。

			绝大多数的文件存储都是基于块存储来实现的,"文件"这个概念的出现是因为"块"对人类用户来说实在是太难用了。可以近似的认为文件是由块所组成的
		更高级的存储单位。对于固定不会发生变动的文件,直接让每个文件连续占用若干个块,在文件头尾加入标志区分即可,磁盘,CD-ROM,DVD-ROM就采用了由
		连续块来构成文件的存储方案;但对于可能发生变动的场景,就必须考虑如何跨多个不连续的块来构成文件了。这种需求从数据结构的角度看只需在每个块中
		记录好下一个块的地址,形成链表即可。但是链表的缺点是只能顺序查找,这样访问文件效率太低。真正被广泛运用的解决方案是把形成链表的指针整合起来
		统一存放,这变形成了 文件分配表(File Allocation File,FAT)。既然有了专门组织块结构来构成文件的分配表,那在表中再加入其它控制信息,就能
		很方便的扩展出更多的高级功能,譬如除了文件占用的块地址信息外,加上文件的逻辑位置就形成了目录,加上文件的访问标志就形成了权限,还可以加上文件的
		名称,创建时间,所有者等一系列元数据信息。人们把定义文件分配表应该如何实现,存储哪些信息,提供什么功能的标准称为文件系统(File System),如
		FAT32,NTFS,exFAT,ext2/3/4等。前面介绍的对分区进行高级格式化操作,实际上就是在初始化一套空白的文件系统,供后续用户和应用访问。

			文件存储相对于块存储来说是更高层次的存储类型,加入目录,权限等元素后形成树状结构以及路径访问方式更便于人类理解。文件系统能够提供哪个进程
		打开或者正在读写文件的信息。但在另外一方面,计算机需要对路径进行分解,然后逐级往下查找,最后才能找到需要的文件。要从文件分配表中确定具体数据
		存储的位置,要判断文件的权限,要记录每次修改文件的用户与时间,这些额外的操作对性能产生负面影响也是无可避免的。因此,如果一个系统不用文件系统,
		那磁盘IO一般就是最主要的考虑。

		3.对象存储
			对象存储是较新的数据存储形式,是一种随着云数据中心的兴起而发展起来的存储,是以非结构化数据为目标的存储方案。这里的"对象"可以理解为一个
		元数据及其配对的一个逻辑数据块的组合,元数据提供了对象所包含的上下文信息,譬如数据的类型,大小,权限,创建人,创建时间等,数据块则存储了
		对象的具体内容。你可以简单的认为数据和元数据共同构成了一个对象。每个对象都有自己的全局唯一标识,这个标识会直接开放给最终用户使用,作为访问对象
		的主要凭证,通常会是uuid的形式。对象的存储访问接口就是根据唯一的标识,对逻辑数据块进行读写删除操作,通常接口都十分简单,甚至修改都不会提供。

			对象存储只会在分布式存储系统之上去实现,由于对象存储天生就具有明确的"元数据"概念,不必依靠文件系统来提供数据的描述信息,因此,完全可以
		将一大批对象的元数据集中存放在一台(组)服务器上,再辅以多态OSD(Object Storage Device)服务器来存储对象的数据块部分。当外部要访问对象时,
		多台OSD能够同时对外发送数据,因此对象存储不仅易于共享,容量庞大,还能提供非常高的吞吐。不过,由于需要先经过元数据查询确定OSD存放对象的确切
		位置,该过程可能涉及多次网络传输,延时方面会表现的比较差。

			由于对象的元数据仅描述对象本身的信息,与其他对象没有关联,换言之每个对象都是独立的,自然也不存在目录的概念,所以对象存储天然就是扁平化的,
		与软件系统中常见的kv访问类型,不过许多对象存储会提供Bucket的概念,用户可以在逻辑上把它当做"单层的目录"来使用。由于对象存储天生的分布式特性,
		以及及其低廉的扩展成本,很适合cdn一类的应用,用于存放图片和音视频等媒体内容以及网页和脚本等静态资源。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值