工业互联网平台需要对海量的工业数据进行处理,因此必须具有能够有效管理海量数据的数据管理技术,可以在规模巨大的数据集里快速找到特定的数据。海量数据的管理通常采用NoSQL的数据库,以保证海量数据的存储和分析功能。
海量数据的数据管理技术中最著名的是Google的BigTable数据管理技术[16]。Hadoop开发团队根据BigTable开发了建立在HDFS之上,提供高可靠性、高性能、列存储、可伸缩、实时读写的数据库系统HBase。
HBase通过主键(Key)和主键的范围(Range)来检索数据,仅支持单行事务。多表连接等复杂操作可以通过Hive支持来实现。HBase主要用来存储非结构化和半结构化的松散数据。
HBase以表的形式存储数据,表由行和列组成,列划分为若干个列族,如图5-7所示。
图5-7 HBase的结构
HBase中的表一般有以下特点。
1)大。一个表可以有上亿行。
2)面向列。面向列(族)的存储和权限控制,列(族)独立检索。
3)稀疏:对于为空(Null)的列,并不占用存储空间,因此表可以设计得非常稀疏。
在HBase中,行键(Row Key)是用来检索记录的主键。访问HBase表中的行有以下三种方式:
1)通过单个行键访问。
2)通过行键的范围访问。
3)全表扫描。
行键可以是任意字符串,其最大长度是64KB,实际应用中长度一般为10~100B。在HBase内部,行键保存为字节数组。
存储时,数据按照行键的字典序(Byte Order)排序存储。设计键时,要充分利用排序存储这个特性,将经常一起读取的行存储到一起。
无论一次读写多少列,行的一次读写都是原子操作。这个设计决策能够使用户很容易地理解程序在对同一个行进行并发更新操作时的行为。
HBase表中的每个列都属于某个列族。列族是表的模式的一部分,必须在使用表之前定义。列名都以列族作为前缀。例如,courses:history和courses:math都属于courses这个列族。
访问控制、磁盘和内存的使用统计都是在列族层面进行的。实际应用中,列族上的控制权限能帮助人们管理不同类型的应用。一些应用可以添加新的基本数据;一些应用可以读取基本数据并创建继承的列族;一些应用则只允许浏览数据,甚至可能因为隐私的原因而只能浏览部分数据。
HBase中通过行和列确定的一个存储单元称为单元格(Cell)。每个单元格都保存着同一份数据的多个版本。版本通过时间戳来索引。时间戳的类型是64位整型。时间戳可以由HBase在数据写入时自动赋值,该时间戳是精确到毫秒的当前系统时间。时间戳也可以由客户显式赋值。如果应用程序要避免数据版本冲突,就必须自己生成具有唯一性的时间戳。每个单元格中,不同版本的数据按照时间倒序排序,即最新的数据排在最前面。
为了避免数据存在过多版本造成的诸如存贮和索引等的管理负担,HBase提供了两种数据版本回收方式。一是保存数据的最后n个版本,二是保存最近一段时间内的版本,如最近10天。用户可以针对每个列族进行设置。
HBase表中的所有行都按照行键的字典序排列。表在行的方向上分割为多个区(Region),如图5-8所示。
图5-8 HBase的存储架构
区按大小分割,每个表一开始只有一个区,随着数据不断插入表,区不断增大,当增大到一个阈值的时候,区就会分为两个新的区,如图5-9所示。表中的行不断增多,就会有越来越多的区。
图5-9 区的分离
区是HBase中分布式存储和负荷均衡的最小单元。不同的区可以分布在不同的区服务器上,但同一个区总是存储在同一个服务器上,如图5-10所示。
图5-10 HBase的存储
区虽然是分布式存储的最小单元,但并不是存储的最小单元。事实上,区由一个或者多个存(Store)组成,每个存保存一个列族(Column Family)。
每个存又由一个忆存(MemStore)和零至多个存文件(StoreFile)组成,如图5-11所示。
图5-11 区的存储
当区服务器(Region Server,RS)处理写请求时,数据先写入到忆存,然后当到达一定的阈值时,忆存中的数据会被存储到存文件中。
存文件以H文件(HFile)格式保存在HDFS上。H文件的格式如图5-12所示。
图5-12 H文件的格式
H文件分为以下六个部分:
1)DataBlocks。保存表中的数据,这部分可以被压缩。
2)MetaBlocks。保存用户自定义的键值对,可以被压缩。
3)Fileinfo。H文件的元信息,不可压缩,用户也可以在这一部分添加自己的元信息。
4)Dataindex。数据块索引。每条索引的键是被索引的块的第一条记录的键。
5)Metablock。Meta Block的索引。
6)Trailer。这一段是定长的,其格式如图5-13所示。Trailer(拖)保存了每一段的偏移量,读取一个H文件时,会首先读取拖。拖保存了每个段的起始位置(段的Magic数用来做安全检查)。然后,数据块索引(DataBlock Index)会被读取到内存中。这样,当检索某个键时,不需要扫描整个H文件,而只须从内存中找到键所在的块,通过一次磁盘I/O将整个块读取到内存中,再找到需要的键。数据块索引采用LRU机制淘汰。
图5-13 Trailer的格式
H文件的DataBlocks,MetaBlocks通常采用压缩方式存储,压缩之后可以大大减少网络I/O和磁盘I/O,随之而来的当然是需要CPU进行压缩和解压缩。目标H文件的压缩支持两种方式:Gzip和Lzo。
HBase在写入数据之前会先写入忆存,成功了再写入H日志(HLog),当忆存的数据丢失时,还可以用H日志的数据来进行恢复。H日志记录数据的所有变更,一旦数据修改,就可以从H日志中进行恢复。
每个区服务器维护一个H日志,而不是每个区一个H日志。这样不同区的日志会混在一起,目的是不断追加单个文件。这相对于同时写多个文件而言,可以减少磁盘寻址次数,因此可以提高对表的写性能。与此同时带来的麻烦是,如果一台区服务器下线,为了恢复其上的区,需要对区服务器上的H日志进行拆分,然后分发到其他区服务器上进行恢复。
H日志序文件就是一个普通的Hadoop序文件。序文件的键是H日志键(HLogKey)对象,H日志键中记录了写入数据的归属信息,除了表和区名字外,同时还包括序号和时间戳,时间戳是“写入时间”,序号的起始值为0,或者是最近一次存入文件系统中的序号。H日志序文件的值是HBase的键值对象,即对应H文件中的键值,可参见上文描述。
HBase的系统架构如图5-14所示。
图5-14 HBase的系统架构
HBase中各组件的功能如下。
客户(Client):包含访问HBase的接口,客户维护着一些快速缓冲存储区来加快对HBase的访问,如区的位置信息。
ZooKeeper:(www.daowen.com)
1)保证任何时候,集群中只有一个主服务器(Master)。
2)存储所有区的寻址入口。
3)实时监控区服务器的状态,将区服务器的上线和下线信息实时通知给主服务器。
4)存储HBase的模式,包括有哪些表、每个表有哪些列族。
主服务器(Master):
1)为区服务器分配区。
2)负责区服务器的负荷均衡。
3)发现失效的区服务器并重新分配其上的区。
4)回收垃圾文件。
5)处理模式更新请求。
区服务器Region Server:
1)维护主服务器分配给它的区,处理对这些区的I/O请求。
2)负责切分在运行过程中变得过大的区。
可以看到,客户访问HBase上的数据的过程并不需要主服务器参与,当寻址时访问ZooKeeper和区服务器,数据读写时访问区服务器,主服务器仅仅维护表和区的元数据信息,负荷很小。
(1)区定位(见图5-15)
HBase使用三层类似B+树的结构来保存区位置。
第一层保存ZooKeeper里面的文件,它持有根区(Root Region)的位置。
第二层根区是META表的第一个区,其中保存了.META.表中其他区的位置。通过根区可以访问.META.表的数据。
第三层.META.是一个特殊的表,保存了HBase中所有数据表的区位置信息。
图5-15 HBase的区定位
需要说明几点:
1)根区永远不会被分割,从而保证了最多需要三次跳转就能定位到任意区。
2).META.表每行保存一个区的位置信息,行键采用表名+表的最后一行编码而成。
3)为了加快访问,.META.表的全部区都保存在内存中。
4)客户会将查询过的位置信息缓存起来,缓存不会主动失效,因此如果客户的缓存全部失效,则需要进行六次网络来回,才能定位到正确的区,其中三次用来发现缓存失效,另外三次用来获取位置信息。
(2)HBase读写过程
HBase使用忆存和存文件存储对表的更新。数据在更新时首先写入预写日志(WAL Log)和忆存中,忆存中的数据是排序的,当忆存累积到一定阈值时,就会创建一个新的忆存,并且将旧的忆存添加到清空队列,由单独的线程储存到磁盘上,成为一个存文件。与此同时,系统会在ZooKeeper中记录一个重做点,表示这个时刻之前的变更已经持久化了。
当系统出现意外时,可能导致忆存中的数据丢失,此时使用预写日志来恢复检测点之后的数据。
前面提到过存文件是只读的,一旦创建后就不可以再修改,因此HBase的更新其实是不断追加的操作。当一个存中的存文件达到一定的阈值后,就会进行一次合并,将对同一个键的修改合并到一起,形成一个大的存文件。当存文件的大小达到一定阈值后,又会对存文件进行分割,等分为两个存文件。
由于对表的更新是不断追加的,因此处理读请求时,需要访问存中全部的存文件和忆存,并将它们按照行键进行合并。由于存文件和忆存都是经过排序的,并且存文件带有内存中的索引,因此合并的过程比较快。
(3)写请求处理过程
HBase写请求的处理过程如下:
1)客户向区服务器提交写请求。
2)区服务器找到目标区。
3)区检查数据是否与模式一致。
4)如果客户没有指定版本,则获取当前的系统时间作为数据版本。
5)将更新写入预写日志。
6)将更新写入忆存。
7)判断忆存是否需要清空为存文件。
(4)区分配与上下线
任何时刻,一个区只能分配给一个区服务器。主服务器记录了当前有哪些可用的区服务器,以及当前哪些区分配给了哪些区服务器,哪些区还没有分配。当存在未分配的区,并且有一个区服务器上有可用空间时,主服务器就给这个区服务器发送一个装载请求,把区分配给这个区服务器。区服务器收到请求后,就开始为该区提供服务。
主服务器使用ZooKeeper来跟踪区服务器状态。当某个区服务器启动时,会先在ZooKeeper上的服务器目录下建立代表自己的文件,并获得该文件的独占锁。由于主服务器订阅了服务器目录上的变更消息,因此当服务器目录下的文件出现新增或删除操作时,主服务器可以得到来自ZooKeeper的实时通知。因此一旦区服务器上线,主服务器能马上得到消息。
当区服务器下线时,它和ZooKeeper的会话断开,ZooKeeper会自动释放代表这台服务器的文件上的独占锁,而主服务器不断轮询服务器目录下文件的锁状态。如果主服务器发现某个区服务器丢失了它自己的独占锁,或者主服务器连续几次和区服务器通信都无法成功,则主服务器就会尝试去获取代表这个区服务器的读写锁,一旦获取成功,就可以确定下列情况中的一种发生了:
1)区服务器和ZooKeeper之间的网络断开了。
2)区服务器停机了。
无论发生哪种情况,区服务器都无法继续为它的区提供服务了,此时主服务器会删除服务器目录下代表这台区服务器的文件,并将这台区服务器的区分配给其他还工作着的区服务器。
如果网络短暂出现问题导致区服务器丢失了它的锁,那么区服务器重新连接到ZooKeeper之后,只要代表它的文件还在,它就会不断尝试获取这个文件上的锁,一旦获取到了,就可以继续提供服务。
(5)主服务器上下线
主服务器启动时进行以下操作:
1)从ZooKeeper上获取唯一一个代表主服务器的锁,用来阻止其他服务器成为主服务器。
2)扫描ZooKeeper上的服务器目录,获得当前可用的区服务器列表。
3)和当前可用的每个区服务器通信,获得当前已分配的区和区服务器的对应关系。
4)扫描.META.Region的集合,计算得到当前还未分配的区,将它们放入待分配区列表。
由于主服务器只维护表和区的元数据,而不参与表数据I/O的过程,主服务器下线仅导致所有元数据的修改被冻结,包括无法创建删除表、无法修改表的模式、无法进行区的负荷均衡、无法处理区上下线、无法进行区的合并,但表的数据读写还可以正常进行,因此主服务器下线在短时间内对整个HBase集群没有影响。从上线过程可以看到,主服务器保存的信息全是冗余信息,因此一般HBase集群中总是有一个主服务器在提供服务,还有一个以上的“主服务器”在等待时机抢占它的位置。
免责声明:以上内容源自网络,版权归原作者所有,如有侵犯您的原创版权请告知,我们将尽快删除相关内容。