Hbase是一个强一致性数据库,不是“最终一致性”数据库,官网给出的介绍:
“Strongly consistent reads/writes: HBase is not an "eventually consistent" DataStore. This makes it very suitable for tasks such as high-speed counter aggregation.”
这里要先提一下分布式系统的CAP原理:
Consistency(一致性), 数据一致更新,所有数据变动都是同步的
Availability(可用性), 好的响应性能
Partition tolerance(分区容错性) 可靠性
定理:任何分布式系统只可同时满足二点,没法三者兼顾。
忠告:架构师不要将精力浪费在如何设计能满足三者的完美分布式系统,而是应该进行取舍。
1.首先来理解下一致性
对于一致性,可以分为从客户端和服务端两个不同的视角。
从客户端来看,一致性主要指的是多并发访问时更新过的数据如何获取的问题。从服务端来看,则是更新如何复制分布到整个系统,以保证数据最终一致。一致性是因为有并发读写才有的问题,因此在理解一致性的问题时,一定要注意结合考虑并发读写的场景。
从客户端角度,多进程并发访问时,更新过的数据在不同进程如何获取的不同策略,决定了不同的一致性。对于关系型数据库,要求更新过的数据能被后续的访问都能看到,这是强一致性。如果能容忍后续的部分或者全部访问不到,则是弱一致性。如果经过一段时间后要求能访问到更新后的数据,则是最终一致性
从服务端角度,如何尽快将更新后的数据分布到整个系统,降低达到最终一致性的时间窗口,是提高系统的可用度和用户体验非常重要的方面。对于分布式数据系统:
- N — 数据复制的份数
- W — 更新数据时需要保证写完成的节点数
- R — 读取数据的时候需要读取的节点数
如果W+R>N,写的节点和读的节点重叠,则是强一致性。例如对于典型的一主一备同步复制的关系型数据库,N=2,W=2,R=1,则不管读的是主库还是备库的数据,都是一致的。
如果W+R<=N,则是弱一致性。例如对于一主一备异步复制的关系型数据库,N=2,W=1,R=1,则如果读的是备库,就可能无法读取主库已经更新过的数据,所以是弱一致性。
对于分布式系统,为了保证高可用性,一般设置N>=3。不同的N,W,R组合,是在可用性和一致性之间取一个平衡,以适应不同的应用场景。
- 如果N=W,R=1,任何一个写节点失效,都会导致写失败,因此可用性会降低,但是由于数据分布的N个节点是同步写入的,因此可以保证强一致性。
- 如果N=R,W=1,只需要一个节点写入成功即可,写性能和可用性都比较高。但是读取其他节点的进程可能不能获取更新后的数据,因此是弱一致性。这种情况下,如果W<(N+1)/2,并且写入的节点不重叠的话,则会存在写冲突
2.HBase是强一致性系统
Hbase具有以下特点
- 每个值只出现在一个REGION
- 同一时间一个Region只分配给一个Region服务器
- 行内的mutation操作都是原子的(原子性操作是指:如果把一个事务可看作是一个程序,它要么完整的被执行,要么完全不执行)。
- put操作要么成功,要么完全失败。
联系上文提到的一致性特点,可以得出HBase是强一致性系统的结论。
当某台region server fail的时候,它管理的region failover到其他region server时,需要根据WAL log(Write-Ahead Logging)来redo(redolog,有一种日志文件叫做重做日志文件),这时候进行redo的region应该是unavailable的,所以hbase降低了可用性,提高了一致性。设想一下,如果redo的region能够响应请求,那么可用性提高了,则必然返回不一致的数据(因为redo可能还没完成),那么hbase就降低一致性来提高可用性了。
3.HBase的强一致性和HDFS的多副本
一开始非常迷惑于HBase的强一致性和HDFS的多副本是怎么协同的。
这一块儿就需要对HBase和HDFS的读写数据流有个比较透彻的理解。
先假设HDFS的副本存储策略,也就是dfs.replication的值为3(默认值就是3)
这样所有存储在HDFS上的文件都有3个副本。那么,HBase的存储实例,也就是HFile也有3个副本。那么当某一个RegionServer崩溃时,并不用担心数据的丢失,因为数据是存储在HDFS上,哪怕崩溃的RegionServer所在的DataNode上有一个副本,在其他DataNode上也还有2个副本。
那么也许你要问,既然有3个副本,如何保证HBase的强一致性呢?
HFile是已经持久化在磁盘上了,而HFile是不能改变的(这个时候暂时把删除数据这个操作放到一边,相关内容请看下面的Note),一旦在某一个DataNode上生成一个HFile后就会异步更新到其他两个DataNode上,这3个HFile是一模一样的。
那也许你又要问,那我的数据是不断更新当中啊!
更新的数据是放在Memstore,只有当Memstore里的数据达到阈值,或者时间达到阈值,就会flush到磁盘上,生成HFile,而一旦生成HFile就是不可改变的(compaction,split就是后话啦)。
这里再提一下WAL的一致性
WAL是Write-Ahead logging,这个是Memstore里的数据在RegionServer崩溃时得以恢复的保证。WAL的实现是HLog,HLog也是存储在HDFS上的,所以HRegionServer崩溃了也不会导致HLog的丢失,它也有备份。
每一次更新都会调用写日志的sync()方法,这个调用强迫写入日志的更新都会被文件系统确认。
当前的sync()的实现是管道写,也就是HDFS写数据、生成副本的默认方式,这意味着当修改被写入时,它会被发送到第一个DataNode进行存储。一旦成功,第一个DataNode就会把修改发送到另一个DataNode来进行相同的工作。只有3个DataNode都已经确认了写操作,客户端才被允许继续进行; 另一种存储修改的方法是多路写,也就是写入被同时送到3台机器上。当所有主机确认了写操作后,客户端才可以继续。 两种方法的优缺点: 管道写需要时间去完成,所以它有很高的延迟,但是它能更好地利用网络带宽;多路写有着比较低的延迟,因为客户端只需要等待最慢的DataNode确认(假设其余都已成功确认)。但是写入需要共享发送服务器的网络带宽,这对于有着很高负载的系统来说是一个瓶颈。 目前有正在进行的工作能让HDFS支持上面两种方式。 |
Note:当客户端提交删除操作的时候,数据不是真正的删除,只是做了一个删除标记(delete marker,又称母被标记),表明给定航已经被伤处了,在检索过程中,这些删除标记掩盖了实际值,客户端读不到实际值。直到发生compaction的时候数据才会真正被删除。
参考文献
【1】http://kabike.iteye.com/blog/2168852?utm_source=tuicool
【2】http://www.blogjava.net/hello-yun/archive/2012/04/27/376744.html
【3】《HBase权威指南》Lars George著