从以下三个方面对分布式存储系统进行简单介绍:
1.首先,什么是分布式存储系统呢?
简单的说,就是将文件存储到多个服务器中。
2.其次,为什么需要分布式存储系统?
因为单机存储资源和计算资源已经不能满足用户的需求。
3.最后,如何实现一个分布式存储系统或者说实现一个分布式存储系统需要做哪些工作?
(1)既然是将文件存储到多个服务器中那就需要确定将文件具体存储到哪些服务器里,两种方式,一种是通过控制服务器,由这个控制服务器负责统一调度,客户端请求存储一个文件时,首先与控制服务器交互,控制服务器返回需要保存到服务器的地址,读取文件时也需要与控制服务器交互,获取存储位置信息,其中HDFS、GFS等分布式存储使用此种技术,namenode就类似于控制服务器角色。另外一个方式是,不需要控制服务器,客户端自己计算需要存储到哪里,最简单的方式是直接取hash,比如有8台存储服务器,只需要把文件内容或者文件名取hash模8即可计算出应该存储到哪台存储服务器。但有个问题是,当服务器数量增减时,hash就失效了,几乎需要重排迁移所有数据,根本没有办法实现水平扩展,这在分布式系统中是无法忍受的。为了避免出现这种情况,引入了一致性hash算法,又称为环哈希,其中OpenStack Swift、华为FusionStorage就是使用的该方法。除了环hash,当然还有其他的类hash算法,比如CRUSH算法,其中开源分布式存储系统Ceph就是使用的该方法。需要注意的是虽然基于hash的文件分布映射方法不需要控制节点计算需要存储的位置,但仍然需要控制服务器保存一些集群元数据,比如集群的成员信息、映射规则、监控等等,如Ceph的mon服务。
(2)但是,如果只有一个控制服务,则存在单点故障,挂掉了就会导致服务不可用。为了避免单点故障,具备高可用特点,必然需要同时启动多个控制服务,有多个控制服务就必须区分谁是leader,谁是slave,因此需要分布式一致性来协调选主,可以基于现有的分布式协调系统实现,如Zookeeper、Etcd服务等,也可以直接基于Paxos、Raft算法实现。
(3)确定了存储路径,接下来需要解决以什么样的方式存储文件,如果文件直接映射到物理主机或者物理硬盘,粒度太粗略,容易导致数据分布不均匀。如果踢掉一台服务器或者一块硬盘,需要把这台服务器的数据迁移到重映射的另一台主机,迁移数据的IO都集中在这两台主机之间,其它主机帮不上忙。于是引入了虚拟主机概念,OpenStack Swift中叫做partition以及Ceph中PG等都是类似的概念。原理就是在物理主机上面加一层逻辑主机,比如有8台物理主机,可以创建128个虚拟主机,然后把这8台物理主机映射到这128台逻辑主机上,这样相当于每一台主机都虚拟成16台虚拟主机,当然实际上不一定是按照平均分,可以根据磁盘容量映射,磁盘空间大可以映射较多的虚拟主机。当然虚拟主机数量通常都会设置成2的幂,这样很多计算都可以使用位运算进行优化,比如取模运算等。这样文件块会先根据虚拟主机计算存储位置,然后再从表中查找虚拟主机映射的物理主机,文件块分布更均匀,当踢掉一台主机时会重映射到多台主机中,数据迁移效率提升。
(4)如果文件很大怎么办,可能在一台服务器根本存不下,即使存下了,也会导致各个服务器的磁盘利用率不均衡,甚至可能出现大量存储碎片。于是我们自然想到的是把文件分块,然后基于块存储,比如按照64MB大小分块,如果存储一个2GB的文件,则需要把文件分割成32个块,然后逐块存储,存储位置仍然使用前面提到的hash算法。分块是存储密度更大、更紧凑,几乎所有的分布式存储系统都会使用分块技术。
(5)数据存储完成后需要考虑如何进行数据保护,也就是可靠性,当集群中的出现节点异常或磁盘异常时数据依然可以进行正常读取,为了解决这个问题,最容易想到的方法是使用冗余技术,即每一个块,我都存储多份,并分布到不同的服务器中,这样即使其中一个服务器宕机了,也能从其他服务器中读取块,这个和RAID 1技术原理是一样的。存储多少份呢,这个需要权衡成本以及数据可靠性要求,通常来说存储三份就够了。有人会说,存储三份,相当于使用了三倍的存储空间,这样存储资源是不是有点太浪费了,而又不想牺牲数据可靠性。我们学习算法时经常使用时间换空间的思想,计算换存储,这个仍然可以从RAID实现中获取灵感,以RAID 5为例,通过奇偶校验恢复数据,存储利用率为(n-1)/n,相比RAID 1的1/2提高了存储利用率,并且具有RAID 1一样的可靠性,但需要耗费CPU计算奇偶位。奇偶校验只能缺一位,自然可以想到进一步泛化,于是引入纠删码技术,原理其实就是类似解线性方程,关于纠删码技术介绍可以参考Erasure Code - EC纠删码原理。几乎所有的分布式存储系统都使用了冗余副本技术,大多数都会支持纠删码,比如Ceph、Swift。
无论使用纯副本技术还是结合纠删码,必然还是需要把一个块复制多份存储,写入多份,这里假设副本数为3份。这些副本如何写入呢?当拿到三个副本的位置后,客户端可以同时写入三个副本,这种方式称为直接复制(direct replication),这样的问题是客户端会同时占用3倍的业务网络带宽,吞吐量也只有1/3,glusterfs采用的是这种复制策略。另一种方式是客户端只选择其中一个主节点写入数据,当写完第一个节点的数据后,由第一个节点复制到第二个节点,再由第二个节点复制到第三个节点,以此类推直到写完所有的副本,这种方式称为链式复制(chain replication),Ceph、HDFS都是采用的该种策略,这样由于客户端其实只是写了一份数据,不占用额外的业务网络,而存储节点之间的复制可以是一个专门的存储网,不影响业务网络。
(6)写入多份数据,如何保证这些副本数据都是一样的,如何保证三个数据同步呢,万一哪台服务器挂了写不进去怎么办。于是引入了一致性策略。最简单的方法,就是等所有的副本都完成时才返回结果,这样保证写入的三个副本肯定没有问题,这就是强一致性,其中Ceph就是使用的强一致性模型,强一致性能够保证多副本完全一致,并且不会读取脏数据,但是性能不好,万一有一台服务器巨慢则会拖垮整个集群,典型的木桶效应,因此强一致性天生难以支持跨区域部署,因为跨区域的远端时延太长了,导致存储系统性能低。为了避免这种情况,我们可以适当放宽条件,即只要保证一半以上的服务器写入成功即返回,这样即使其中有少数服务器拖后腿也没有关系,不用等,让他自个慢慢同步,最终一致即可。这就是典型的最终一致性模型,OpenStack Swift即采用该种策略,这种模型能够提高读写性能,但可能读取脏数据,比如刚好读到还没有来得及同步的服务器的数据块。事实上高性能和强一致性是两者不可兼得的,这就是著名的CAP理论,这里的C代表一致性,A代表可用性(在一定时间内,用户的请求都会得到正确的应答),P代表分区容错。正常情况下,存储系统的所有节点都是互通的,处在一个网络连通区域中,如果有些节点之间不连通了(节点挂了或者网络故障),这就相当于把一个网络连通区域割裂了几个区域,彼此不能通信了,因此叫做分区。分布式存储系统要系统出现分区时数据不丢(可靠性),数据可访问(可用性),避免脑裂,因此P是100%需要满足的,否则稍微一个网络抖动,数据就损坏了。剩下的就是C和A之间的权衡,这个就看你要设计成什么存储系统了,如果一致性不那么重要,比如对象存储,上传了一个新文件,即使马上读不到数据也无所谓,但是可能需要支持大规模的对象写入,因此更关注A,设计为AP存储系统。而对于一些实时性要求高的系统,必须保证写入后数据一定能够读到正确的数据(而不是脏数据),就必须牺牲吞吐量,因此设计为CP存储系统。
(7)为了节省存储空间,可能会用到压缩技术,压缩大家都很熟悉了,这里不多介绍。
(8)如果是一个海量分布式存储系统,尤其是提供公有云服务,比如网盘服务,肯定会有用户上传一模一样的文件,为了节省成本,自然想到避免存储重复的文件,这就是重删技术(Data deduplication),可参考int32bit:百度云的「极速秒传」使用的是什么技术?,简单理解就是客户端上传文件时,先在本地计算下hash指纹,然后上传到服务器比对,如果指纹一样,说明文件已经存在,此时不需要上传文件内容,直接链接下即可,不仅节省了存储空间(比压缩更省),还节省了上传时间,实现秒传。我了解的Fusion Storage是实现了重删技术,OpenStack Swift、Ceph貌似都没有。
(9)另一个问题是,如果集群彻底瘫了,数据就彻底没了,这可不能忍。为了解决这个问题,你自然会想到使用复制手段,即备份技术,把文件复制存储到其它廉价存储服务器中,比如S3。当用户执行save操作时,复制这个文件并重命名为xxx-20180312233020(时间戳),这样非常容易就能恢复到备份的任意版本,由于每次都要拷贝整个文件,因此称为全量备份(full backup)。每次都复制显然耗时耗空间,自然想到只复制上一次备份后改变的内容,这样就可以节省存储空间,即增量(incremental backup)备份。注意,备份一定要拷贝到其它存储系统,如果仅仅是拷贝到当前存储系统,不叫备份,只能叫副本,集群瘫了,数据仍然不能恢复。副本主要用于防故障,即一块硬盘坏了数据不丢且还能读,备份还用于防人祸,比如误删操作,能回滚到前面的一个备份点上。
以上备份技术需要用户自己手动执行,如果没有实时备份,集群突然挂了,数据还是会丢。因此需要采取容灾策略,其中一个容灾策略就是异地同步技术,或者叫做复制技术(geo-replication/mirror),这个类似于mysql的主从同步,即在异地建立一个一模一样的集群,这个集群正常情况下不向用户提供存储服务,仅仅同步本地的集群数据,当本地的集群挂了,能够自动切换到异地集群,服务依然可用。注意这个和副本之间完全同步不一样,复制技术通常采用异步策略,基于操作日志replay,mysql使用binlog,ceph使用journal日志。ceph的rbd mirror就是采用的此种技术,关于rbd mirro介绍参考Ceph Jewel Preview: Ceph RBD mirroring。
当然即使有如上的副本技术、备份技术、容灾技术,集群瘫了也可能短时间内不可用。这就引入两个指标,一个是RPO,故障后数据可恢复到故障前哪个时间点,当然越小约好,0表示能立即恢复,无穷大表示数据废了恢复不了了,3表示能恢复到故障前3秒,这其实和备份策略有关,比如每天0点备份,那备份点就是当天0点数据。另一个指标为RTO,即故障后多长时间可以恢复,0表示服务连续毫无影响,无穷大表示服务再也恢复不了了,60表示一个分钟能恢复。
参考文档:https://www.zhihu.com/question/25834847/answer/348271275