三分钟了解 HBase

面向人群:想要初步了解 HBase,并对 Java 有一定了解的同学。

数据模型

在 HBase中,数据存储在具有行和列的表中,这是与关系数据库(RDBMS)类似的模型,但与之不同的是其具备结构松散、多维有序映射的特点,它的索引排序键由行+列+时间戳组成,HBase表可以被看做一个“稀疏的、分布式的、持久的、多维度有序Map”。

相关术语

  • 命名空间(Namespace):对表的逻辑分组,类似于关系型数据库中的Database概念。Namespace可以帮助用户在多租户场景下做到更好的资源和数据隔离。
  • 表(Table):HBase会将数据组织进一张张的表里面,一个HBase 表由多行组成。
  • 行(Row):HBase中的一行包含一个行键和一个或多个与其相关的值的列。在存储行时,行按字母顺序排序。出于这个原因,行键的设计非常重要。目标是以相关行相互靠近的方式存储数据。常用的行键模式是网站域。如果你的行键是域名,则你可能应该将它们存储在相反的位置(org.apache.www,org.apache.mail,org.apache.jira)。这样表中的所有Apache域都彼此靠近,而不是根据子域的第一个字母分布。
  • 列(Column):HBase中的列由一个列族和一个列限定符组成,它们由冒号(:)字符分隔。
  • 列族(Column Family):由于性能原因,列族在物理上共同存在一组列和它们的值。在HBase中每个列族都有一组存储属性,例如其值是否应缓存在内存中,数据如何压缩或其行编码是如何编码的等等。表中的每一行都有相同的列族,但给定的行可能不会在给定的列族中存储任何内容。列族一旦确定后,就不能轻易修改,因为它会影响到HBase真实的物理存储结构,但是列族中的列标识(Column Qualifier)以及其对应的值可以动态增删。
  • 列限定符(Column Qualifier):列限定符被添加到列族中,以提供给定数据段的索引。鉴于列族的content,列限定符可能是content:html,而另一个可能是content:pdf。虽然列族在创建表时是固定的,但列限定符是可变的,并且在行之间可能差别很大。
  • 单元格(Cell):单元格是行、列族和列限定符的组合,并且包含值和时间戳,它表示值的版本。
  • 时间戳(Timestamp):时间戳与每个值一起编写,并且是给定版本的值的标识符。默认情况下,时间戳表示写入数据时RegionServer上的时间,但可以在将数据放入单元格时指定不同的时间戳值。

概念视图

HBase视图

如上图所示,我们可以借助 Java 种的 Map,或者数据结构中的哈希表来了解 HBase 的结构。图中标识的逻辑层视图,表示的是可以通过 String 类型来匹配 Map 种的 KeyValue,而物理层视图则是存储到 HBase 上的格式,以字节存储。
特别声明:上图中描述的并非 HBase 的真实数据结构,具体需要看 HBase 的内部实现,不排除实现的结构出现与图中的结构类似。仅用于帮助初学者直观理解 HBase。图中并没有提出 Timestamp 的概念,并不是不存在。

数据排序

所有数据模型操作HBase以排序顺序返回数据。首先按行,然后按列族(ColumnFamily),然后是列限定符,最后是时间戳(反向排序,因此首先返回最新的记录)。

ACID

ACID,指数据库事务正确执行的四个基本要素的缩写,即:原子性(Atomicity),一致性(Consistency),隔离性(Isolation),持久性(Durability)。

HBase支持单行操作下的ACID,即对同一行的Put操作保证完全的ACID。

schema设计原则

更新

当表或者列簇改变时(包括:编码方式、压力格式、block大小等等),都将会在下次major compaction时或者StoreFile重写时生效。

表模式设计经验

  • 地域最大的阈值取值建议在8GB到50GB之间,不宜过小或过大。
  • 单个cell不超过10MB,如果超过10MB,请使用mob,若再大可以直接存在HDFS中,在HBase内存储HDFS地址。
  • 列簇数量不建议过多,一般1个即可,不建议超过3个。
  • 列簇名应尽量简短,因为存储时每个value都包含列簇名(忽略前缀编码,prefix encoding)。
  • 对于时序场景,建议rowkey设计为设备ID加上时间,如果采用“时间+设备ID”的方案会导致如下:
    • 同一时间点的数据落入同一个地域,导致热点。
    • 较早数据随着时间推移、数据过期会留下大量的空地域,带来不必要的开销。

Rowkey设计

HBase的RowKey设计可以说是使用HBase最为重要的事情,直接影响到HBase的性能,常见的RowKey的设计问题及对应访问。

RowKey的行由行键按字典顺序排序,这样的设计优化了扫描,允许存储相关的行或者那些将被一起读的邻近的行。然而,设计不好的行键是导致 hotspotting 的常见原因。当大量的客户端流量( traffic )被定向在集群上的一个或几个节点时,就会发生 hotspotting。这些流量可能代表着读、写或其他操作。流量超过了承载该地域的单个机器所能负荷的量,这就会导致性能下降并有可能造成地域的不可用。在同一 RegionServer 上的其他地域也可能会受到其不良影响,因为主机无法提供服务所请求的负载。设计使集群能被充分均匀地使用的数据访问模式是至关重要的。

为了防止在写操作时出现hotspotting,设计行键时应该使得数据尽量同时往多个地域上写,而避免只向一个地域写,除非那些行真的有必要写在一个地域里。

下面介绍了集中常用的避免hotspotting的技巧,它们各有优劣。

Salting

Salting 从某种程度上看与加密无关,它指的是将随机数放在行键的起始处。进一步说,salting给每一行键随机指定了一个前缀来让它与其他行键有着不同的排序。所有可能前缀的数量对应于要分散数据的地域的数量。如果有几个“hot”的行键模式,而这些模式在其他更均匀分布的行里反复出现,salting就能到帮助。下面的例子说明了salting能在多个RegionServer间分散负载,同时也说明了它在读操作时候的负面影响。

假设行键的列表如下,表按照每个字母对应一个地域来分割。前缀‘a’是一个地域,‘b’就是另一个地域。在这张表中,所有以‘f’开头的行都属于同一个地域。这个例子关注的行和键如下:

foo0001
foo0002
foo0003
foo0004

现在,假设想将它们分散到不同的地域上,就需要用到四种不同的salts :a,b,c,d。在这种情况下,每种字母前缀都对应着不同的一个地域。用上这些salts后,便有了下面这样的行键。由于现在想把它们分到四个独立的区域,理论上吞吐量会是之前写到同一地域的情况的吞吐量的四倍。

a-foo0003
b-foo0001
c-foo0004
d-foo0002

如果想新增一行,新增的一行会被随机指定四个可能的salt值中的一个,并放在某条已存在的行的旁边。

a-foo0003
b-foo0001
c-foo0003
c-foo0004
d-foo0002

由于前缀的指派是随机的,因而如果想要按照字典顺序找到这些行,则需要做更多的工作。从这个角度上看,salting增加了写操作的吞吐量,却也增大了读操作的开销。

Hashing

可用一个单向的 hash 散列来取代随机指派前缀。这样能使一个给定的行在“salted”时有相同的前缀,从某种程度上说,这在分散了RegionServer间的负载的同时,也允许在读操作时能够预测。确定性hash( deterministic hash )能让客户端重建完整的行键,以及像正常的一样用Get操作重新获得想要的行。

考虑和上述salting一样的情景,现在可以用单向hash来得到行键foo0003,并可预测得‘a’这个前缀。然后为了重新获得这一行,需要先知道它的键。可以进一步优化这一方法,如使得将特定的键对总是在相同的地域。

Reversing the Key(反转键)

第三种预防hotspotting的方法是反转一段固定长度或者可数的键,来让最常改变的部分(最低显著位, the least significant digit )在第一位,这样有效地打乱了行键,但是却牺牲了行排序的属性。

单调递增行键/时序数据

在一个集群中,一个导入数据的进程锁住不动,所有的client都在等待一个地域(因而也就是一个单个节点),过了一会后,变成了下一个地域。 如果使用了单调递增或者时序的key便会造成这样的问题。使用了顺序的key会将本没有顺序的数据变得有顺序,把负载压在一台机器上。所以要尽量避免时间戳或者序列(比如1, 2, 3)这样的行键。

如果需要导入时间顺序的文件(如log)到HBase中,可以学习OpenTSDB的做法。它有一个页面来描述它的HBase模式。OpenTSDB的Key的格式是[metric_type][event_timestamp],乍一看,这似乎违背了不能将timestamp做key的建议,但是它并没有将timestamp作为key的一个关键位置,有成百上千的metric_type就足够将压力分散到各个地域了。因此,尽管有着连续的数据输入流,Put操作依旧能被分散在表中的各个地域中。

简化行和列

在HBase中,值是作为一个单元保存在系统的中的,要定位一个单元,需要行,列名和时间戳。通常情况下,如果行和列的名字要是太大(甚至比value的大小还要大)的话,可能会遇到一些有趣的情况。在HBase的存储文件(storefiles)中,有一个索引用来方便值的随机访问,但是访问一个单元的坐标要是太大的话,会占用很大的内存,这个索引会被用尽。要想解决这个问题,可以设置一个更大的块大小,也可以使用更小的行和列名 。压缩也能得到更大指数。

大部分时候,细微的低效不会影响很大。但不幸的是,在这里却不能忽略。无论是列族、属性和行键都会在数据中重复上亿次。

列族

尽量使用较小的列族名,最好为一个字符(例如:f)

属性

详细属性名(比如myVeryImportantAttribute)易读,最好还是用短属性名(比如via)保存到HBase。

行键长度

让行键短到可读即可,这样对获取数据有帮助(比如Get vs. Scan)。短键对访问数据无用,并不比长键对get或scan更好。设计行键需要权衡。

倒序时间戳

一个数据库处理的通常问题是找到最近版本的值。采用倒序时间戳作为键的一部分可以对此特定情况有很大帮助。该技术包含追加(Long.MAX_VALUE - timestamp)到key的后面,如[key][reverse_timestamp] 。

表内[key]的最近的值可以用[key]进行Scan,找到并获取第一个记录。由于HBase行键是排序的,该键排在任何比它老的行键的前面,所以是第一个。

该技术可以用于代替版本数,其目的是保存所有版本到“永远”(或一段很长时间) 。同时,采用同样的Scan技术,可以很快获取其他版本。

行键和列族

行键在列族范围内。所以同样的行键可以在同一个表的每个列族中存在而不会冲突。

行键不可改

行键不能改变。唯一可以“改变”的方式是删除然后再插入。这是一个常问问题,所以要注意开始就要让行键正确(且/或在插入很多数据之前)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值