当我们拥有了许多的存储服务器,且通过将数据在网关进行一致性哈希或者哈希桶的分发之后,我们拥有了一个具有基本负载均衡的系统,但是,此时我们又有新的问题产生了:我们所有的数据只有一份,如果这一份数据丢失了,将造成不可逆的后果,因此,我们要考虑将数据冗余。
1. 多副本模式
3副本模式的时候,任何一个交换机下的节点宕机,都不会影响读写操作 。
1.1 基于强一致协议的多副本
学习完raft之后,我们发现,使用强一致性协议可以完美实现三副本模式,本质上就是结点之间同步日志的过程,一致性的展现行为就是数据冗余。
这里不再啰嗦,我们只需要使用raft实现一个具有1主2从的集群即可。这也是我们Dsjourney提供的存储系统中使用的冗余实现原理。
但是,数据冗余并不是直接等价于“一致性协议”的,很多初学者在入门分布式的时候会误解这一点(包括笔者)。使用强一致协议固然实现简单,但是考虑到强一致性的低性能问题,大多数时候,我们不会在实际生产环境选择强一致协议,可能退而且其次选择其他的一致性模型,这一种变化会导致我们的冗余策略发生变化。
这里我们介绍两种比较有名的数据冗余解决方案:
1.2 ceph (了解)
ceph中的事件分发是通过CRUSH算法去做的,也就是通过从master集群中获取的inode我们可以计算出一个pgid,通过pgid可以得到一组OSD的信息,此时就可以通过map得到它们的真实存储信息了。
OSD列表的第一个结点为Primary,剩下的都是Replica。客户端完成所有的写到主OSD上的对象PG中(主host),这些对象和PG被分配新的版本号,然后写到副本OSD上,当每个复制都完成并响应给主节点,主节点完成更新,客户端写完成。这里是一个两阶段的提交,当副本都写到内存中时回复Ack,主节点收到全部的Ack就可以回复,当副本都提交到磁盘时还会回复Commit,主节点收到全部的Commit时还会给Client一个回复,这样大大减小了客户端的响应速度
客户端读就直接在主节点读,这个方法节省了客户端的副本之间的复杂同步和序列化。这种方法可见的W配置的就是全部结点,意味着错误恢复的时候都可可靠的保持副本一致性。当然倘若某个Replica出现故障,操作就会失败了,因为没办法收到所有的回复
此时(超时以后)就可以重新计算OSD,进而重新复制,在OSD恢复时会加入原来的伙伴关系中,同时也会基于PG最近的变化日志去同步日志,这样就保证了一致性
1.3 GFS(了解)
GFS的数据冗余来源于两个方面,一个是Master结点的状态副本,一个ChunkServer的数据副本。这其中又涉及到一个问题,即选主,前者通过Chubby进行可靠的选主(获取一把锁),后者通过Master发放的leases确定主节点,我们看到,两种方法都没有使用一致性协议,但都可以唯一的指定主节点
其实对于master的冗余paper中对于实现并没有太多的描述,但是提到了Master服务器所有的操作日志和checkpoint文件都被复制到多台机器上。且对Master服务器状态的修改操作能够提交成功的前提是,操作日志写入到Master服务器的备节点和本机的磁盘。这样的话当主服务器宕机时一旦通过Chubby选主成功,那么这些数据非常全的副本就可以很快的切换到正常的运行状态,因为其拥有原主节点的全部数据,且已经入盘。
初次之外,GFS中还有一种服务器称为影子服务器,这些影子服务器在“主”Master服务器宕机的时候提供文件系统的只读访问。它们是影子,而不是镜像,所以它们的数据可能比“主”Master服务器更新要慢,通常是不到1秒。这样的操作使得就算是“主”master下线也不会使得服务整体下线,因为影子还可以提供原数据的读取。对于那些不经常改变的文件、或者那些允许获取的数据有少量过期的应用程序,影子服务器能够提高读取的效率,且提升整个系统的可用性
为什么一般影子的数据会慢一些呢?因为Chunk的信息是由影子自己与Chunk通信维护的,但是副本的创建和修改只能由master来完成,如果修改是选择先通知影子,收到回复后再更新可能会导致影子的数据新于master,这是不可忍受的,所以GFS中影子选择读取一份当前正在进行的操作的日志副本,并且依照和主Master服务器完全相同的顺序来更改内部的数据结构,一次保证一致性,不过当然会有一个同步的时间,导致数据稍微旧一点
然后我们说说ChunkServer的数据冗余,以下是更新流程:
GFS使用租约机制来保持多个副本间变更顺序的一致性。Master节点为Chunk的一个副本建立一个租约,这个副本叫做主Chunk。主Chunk对Chunk的所有更改操作进行序列化,所有的副本都遵从这个序列进行修改操作。
可以在paper的3.1节中看出数据的拷贝是一个强一致性的过程,因为任何副本产生的任何错误都会返回给客户机,首先数据肯定在主chunk执行成功了,不然不会产生一个操作顺序,所以倘若有任何一个Replica执行操作序列失败都会把这个消息传递给主chunk,此时客户请求被认为是失败的,且此时这些数据处于不一致的状态,客户端通过重复执行失败的操作来处理这样的错误。
至于为什么使用租约保证多个副本之间的一致性而使用租约,paper中给出的解释是这样的:设计租约机制的目的是为了最小化Master节点的管理负担。这个其实并不太好理解,我的想法是这样的,应该说是为了更为高效的管理chunk。因为在GFS眼中Chunk的管理是一个动态的过程,master对于chunk的管理包括但不局限于如下几点
- master检查当前的副本分布情况,然后移动副本以便更好的利用硬盘空间、更有效的进行负载均衡
- 对于新Chunk的选择选择和创建时类似:平衡硬盘使用率、限制同一台Chunk服务器上的正在进行的克隆操作的数量、在机架间分布副本
- 当Chunk的有效副本数量少于用户指定的复制因数的时候,Master节点会重新复制它
2. 纠删码
2.1 纠删码简介
与多副本将数据完整复制多份进行分布式存储不同,纠删码将一份数据拆分进行分布式存储,这是如何为做到的呢?
删码的英文全称是Erasure Code,所以有时我们也会简称为EC。它可以将 n 份原始数据,增加 m 份数据,并能通过 n+m 份中的任意 n 份数据,还原为原始数据。即如果有任意小于等于 m 份的数据失效,仍然能通过剩下的数据还原出来,在Hadoop3.X中已经支持了该功能
当然以上计算只是一个简化的方案,目的是帮助大家理解,真正存储中用的校验方式会比这个复杂得多,但效果是类似的。
2.2 纠删码原理
纠删码的原理很简单,主要使用线性代数的方法,假设有四块:D1,D2,D3,D4,校验数据两块P1,P2.组成下面方程组:
(1)X1=D1
(2)X2=D2
(3)X3=D3
(4)X4=D4
(5)X1+X2+X3+X4=P1
(6)X1+2X2+4X3+8*X4=P2
根据上面的6个方程任意两个缺失都可以根据其他四个将丢失的两个计算出来。这就是纠删码的基本原理,当然在实现的时候不是直接使用方程组,实际应用是使用矩阵
方程(1),(2),(3),(4)正好对应单位矩阵:
除此之外还有两个校验数据需要构造,一般使用范德蒙矩阵(使用柯西矩阵要快一些),为什么使用范德蒙矩阵是因为涉及到逆矩阵,只有当逆矩阵存在的前提下才可以恢复丢失的数据,而范德蒙矩阵恰好满足这个条件。我们的得到完整的编码矩阵
- 编码
编码就是根据编码矩阵,计算出校验数据,将校验数据和原始数据一起保存。
还是以上面为例,根据编码矩阵可以求得P1和P2
应该注意的编码之后是原始数据块和校验数据块大小相同,因此这里的矩阵乘法是在伽罗华有限域(GF)内进行。简单来说,校验数据就是让P和所有或者部分原始数据都发生关系,这样在有数据块损坏时可以根据方程求解出损坏部分的数据
- 解码
假设有一个数据数据损坏,我们希望能用其他数据将损坏的部分恢复出来。最大允许损坏校验数据P个数这么多数据。
假设D4数据损坏:
-
求解解码矩阵,在原编码矩阵中去掉对应损坏的数据那行,从上到下取n=4行,组成一个新矩阵这个矩阵就是解码矩阵:
-
解码矩阵乘原始数据正好得到剩余完好的数据:
-
根据A*A-1=E,两边同乘解码矩阵的逆矩阵
-
只要求出解码矩阵的逆矩阵即可求得损坏的数据D4
求解逆矩阵首先要判断是否存在逆矩阵,判断逆矩阵是否存在的常用的两种方法:
- 原矩阵的行列式的值是不等于0,这是由逆矩阵定义可知
- 原矩阵的秩等于矩阵阶数。
3. 多副本与纠删码的比较
在可用容量上,纠删码的优势是较大的,比如4+2纠删码的利用率是66%,但3副本只有33%,两者差了2倍,8+2纠删码更可以做到80%,这是多副本远远无法相比的
在读写性能上,多副本往往会更高,因为纠删码在写入时涉及数据校验,而且可能会产生写惩罚,在读取时更会横跨多个节点。比如4+2纠删码在读取1个数据时,需要从4个节点分别读取4个分片再进行拼接,任何1个节点时延过高,都会对性能造成很大影响。而多副本只需要读取1个完整的分片即可,不涉及节点的数据拼接。这两者的性能差异在小块IO时会较为明显,但如果IO块比较大的话,比如1MB,那么两者的性能差距就会逐渐缩小,因为这时候写惩罚较少,纠删码也能很好发挥多个节点并发的优势,这一局多副本略胜一筹
在重构性能上,多副本也会有明显优势,因为不涉及数据校验,只是单纯的数据拷贝,所以速度比较快。而纠删码的重构涉及反向校验的计算过程,所需要的读写数据量和CPU计算消耗都会更大,这一局多副本同样略胜一筹
在可靠性上,多副本和纠删码的故障冗余程度往往差别不大,比如3副本和4+2纠删码都可以允许任意2个节点故障而数据不丢失。但我们也需要注意两点,一是多副本的重构性能往往比纠删码更快,所以硬盘故障恢复也更快,会带来一些可靠性上的优势。二是纠删码可以采用+3、+4的策略来容忍更多节点故障,而且空间利用率并不会太低,但如果多副本采用4副本、5副本的话代价就太大了,所以这一点上纠删码有优势
综合来看,如果用户更关注性能,尤其是小IO的场景,多副本往往是更好的选择,如果用户更关注可用容量,而且是大文件场景的话,纠删码会更合适