3.2.4 反向时间戳
反向扫描API
HBASE-4811(https://issues.apache.org/jira/browse/HBASE-4811)实现了一个API来扫描一个表或范围内的一个范围
表反向,减少了对正向或反向扫描优化模式的需求。此功能在HBase 0.98和更高版本中可用。有关更多信息,请参阅Scan.setReversed()(https://hbase.apache.org/apidocs/org/apache/hadoop/hbase/client/Scan.html#setReversed-boolean-)。
数据库处理中的一个常见问题是快速找到最新版本的值。使用反向时间戳作为密钥的一部分的技术可以帮助解决这个问题的一个特例。在Tom White的书Hadoop:The Definitive Guide(O'Reilly)的HBase章节中也发现,该技术涉及在任何密钥的末尾附加(Long.MAX_VALUE - 时间戳)。 [键] [reverse_timestamp]。
通过执行扫描[键]并获取第一条记录,可以找到表格中[键]的最新值。由于HBase密钥的排序顺序不同,因此该密钥在[key]的任何较旧的行键之前排序,因此是第一个。
(或很长一段时间),同时通过使用相同的扫描技术可以快速访问任何其他版本。
3.2.5 Rowkeys和ColumnFamilies
行键的范围为ColumnFamilies。 因此,相同的rowkey可以存在于没有碰撞的表中存在的每个ColumnFamily中。
3.2.6 Rowkeys的不变性
行键无法更改。 他们可以在表格中“更改”的唯一方法是该行被删除然后重新插入。 这是HBase dist-list上的一个相当常见的问题,所以在第一次(和/或在插入大量数据之前)获得rowkeys是值得的。
3.2.7 RowKeys与区域分割之间的关系
如果您预先拆分表格,了解您的rowkey如何在区域边界上分布是非常重要的。作为重要的一个例子,考虑使用可显示的十六进制字符作为键的前导位置(例如,“0000000000000000”到“ffffffffffffffff”)的示例。通过Bytes.split(这是在创建Admin.createTable(byte [] startKey,byte [] endKey,numRegions)中的区域时使用的分割策略来运行这些关键范围)将生成以下分割...
48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 48 // 0
54 -10 -10 -10 -10 -10 -10 -10 -10 -10 -10 -10 -10 -10 -10 // 6
61 = 67 -67 -67 -67 -67 -67 -67 -67 -67 -67 -67 -67 -67 -68 // =
68 -124 -124 -124 -124 -124 -124 -124 -124 -124 -124 -124 -124 -124 -124 -126 // D
75 75 75 75 75 75 75 75 75 75 75 75 75 75 75 72 // K
82 18 18 18 18 18 18 18 18 18 18 18 18 18 18 14 // R
88 -40 -40 -40 -40 -40 -40 -40 -40 -40 -40 -40 -40 -40 -40 -44 // X
95 -97 -97 -97 -97 -97 -97 -97 -97 -97 -97 -97 -97 -97 -97 -102 // _
102 102 102 102 102 102 102 102 102 102 102 102 102 102 102 102 // f
(注意:前导字节作为注释列在右侧。)鉴于第一个分割是'0'而最后一个分割是'f',一切都很好,对吧?没那么快。
问题是所有的数据都会堆积在前两个区域和最后一个区域,从而产生一个“块状”(也可能是“热”)区域问题。要理解为什么,请参阅ASCII表(http://www.asciitable.com)。 '0'是字节48,'f'是字节102,但字节值(字节58到96)间存在巨大的差距,永远不会出现在这个密钥空间中,因为唯一的值是[0-9]和[af ]。因此,中间地区将永远不会被使用。为了使用该示例密钥空间进行预分割工作,需要分割的自定义定义(即,并且不依赖于内置分割方法)。
第1课:预分割表通常是最佳做法,但您需要预先拆分它们,以便可以在键空间中访问所有区域。虽然此示例演示了十六进制密钥空间的问题,但任何密钥空间都会出现同样的问题。了解你的数据。
第2课:虽然通常不可取,但只要所有创建的区域都可在键空间中访问,则使用十六进制键(更一般而言,可显示的数据)仍可用于预分割表。
总结这个例子,以下是如何为十六进制密钥预先创建合适的分割的例子:
public static boolean createTable(Admin admin, HTableDescriptor table, byte[][] splits) throws IOException {
try {
admin.createTable( table, splits ); return true;
} catch (TableExistsException e) {
logger.info("table " + table.getNameAsString() + " already exists");
}
}
public static byte[][] getHexSplits(String startKey, String endKey, int numRegions) { byte[][] splits = new byte[numRegions-1][];
BigInteger lowestKey = new BigInteger(startKey, 16); BigInteger highestKey = new BigInteger(endKey, 16); BigInteger range = highestKey.subtract(lowestKey);
BigInteger regionIncrement = range.divide(BigInteger.valueOf(numRegions)); lowestKey = lowestKey.add(regionIncrement);
for(int i=0; i < numRegions-1;i++) {
BigInteger key = lowestKey.add(regionIncrement.multiply(BigInteger.valueOf(i))); byte[] b = String.format("%016x", key).getBytes();
splits[i] = b;
}
return splits;
}
3.2.8 最大版本数
要存储的行版本的最大数量是通过HColumnDescriptor(https://hbase.apache.org/apidocs/org/apache/hadoop/hbase/HColumnDescriptor.html)为每个列族配置的。 最大版本的缺省值为1.这是一个重要的参数,因为如数据模型部分所述,HBase不会覆盖行值,而是每次按时间(和限定符)存储不同的值。 在重要的压缩过程中删除多余的版本。 最大版本的数量可能需要根据应用需求增加或减少。
建议不要将最高版本数设置为极高的级别(例如,数百或更多),除非这些旧值对您非常重要,因为这会大大增加StoreFile大小。
3.2.9 最小版本数
像行版本的最大版本数一样,每个列族通过配置保留的最小行数版本
HColumnDescriptor(https://hbase.apache.org/apidocs/org/apache/hadoop/hbase/HColumnDescriptor.html)。 min版本的默认值为0,这意味着该功能被禁用。行版本参数的最小数量与生存时间参数一起使用,并且可以与行版本数量参数组合,以允许诸如“保留最多T分钟数据值,最多N个版本,但是至少保留M个版本“(其中M是最小行数的值,M <N)。此参数只应在为列族启用生存时间时设置,并且必须小于行版本的数量。
HBase通过Put(https://hbase.apache.org/apidocs/org/apache/hadoop/hbase/client/Put.html)和Result(https:// hbase)支持“bytes-in / bytes-out” .apache.org / apidocs / org / apache / hadoop / hbase / client / Result.html),因此可以将任何可以转换为字节数组的值存储为值。输入可以是字符串,数字,复杂对象,甚至图像,只要它们可以呈现为字节。
值的大小有实际的限制(例如,在HBase中存储10-50MB的对象可能太多了)。在邮件列表中搜索关于此主题的对话。 HBase中的所有行都符合数据模型,并包含版本控制。在进行设计时考虑到这一点,以及ColumnFamily的块大小。
3.2.10 计数器
值得特别提及的一种支持的数据类型是“计数器”(即能够执行数字的原子增量)。请参阅表中的增量(https://hbase.apache.org/apidocs/org/apache/hadoop/hbase/client/Table.html#increment%28org.apache.hadoop.hbase.client.Increment%29)。
计数器上的同步在RegionServer上完成,而不是在客户端上完成。
如果您有多个表,请不要忘记考虑连接模式设计的可能性。
ColumnFamilies可以以秒为单位设置TTL长度,一旦达到到期时间,HBase将自动删除行。这适用于所有版本的行 - 即使是当前版本。在该行的HBase中编码的TTL时间以UTC指定。
存储仅包含过期行的文件将在小型压缩中删除。将hbase.store.delete.expired.storefile设置为false会禁用此功能。将最小版本数设置为0以外的值也会禁用此功能。
有关更多信息,请参阅HColumnDescriptor(https://hbase.apache.org/apidocs/org/apache/hadoop/hbase/HColumnDescriptor.html)。 HBase的最新版本也支持设置时间以每个单元为基础生存。参见HBASE-10560
(https://issues.apache.org/jira/browse/HBASE-10560)以获取更多信息。单元TTL作为突变属性提交
使用突变#setTTL请求(追加,增加,放置等)。如果设置了TTL属性,则该操作将应用于服务器上更新的所有单元。单元TTL处理和ColumnFamily TTL之间有两个显着的区别:
单元TTL以毫秒为单位而不是秒。
单元TTL不能将一个单元的有效生命周期延长超过ColumnFamily级TTL设置。
默认情况下,删除标记延伸回到开始时间。因此,获取(https://hbase.apache.org/apidocs/org/apache/hadoop/hbase/client/Get.html)或扫描(https://hbase.apache.org/apidocs/org/apache/hadoop /hbase/client/Scan.html)操作将不会看到已删除的单元格(行或列),即使获取或扫描操作指示删除标记放置之前的时间范围。
ColumnFamilies可以选择保留已删除的单元格。在这种情况下,只要这些操作指定的时间范围在影响单元格的任何删除的时间戳之前结束,仍然可以检索已删除的单元格。这允许甚至在存在删除的情况下进行时间点查询。
Example 17. Change the Value of KEEP_DELETED_CELLS Using HBase Shell
Example 18. Change the Value of KEEP_DELETED_CELLS Using the API
让我们来说明在表上设置KEEP_DELETED_CELLS属性的基本效果。 首先,without:
create 'test', {NAME=>'e', VERSIONS=>2147483647}
put 'test', 'r1', 'e:c1', 'value', 10
put 'test', 'r1', 'e:c1', 'value', 12
put 'test', 'r1', 'e:c1', 'value', 14
delete 'test', 'r1', 'e:c1', 11
hbase(main):017:0> scan 'test', {RAW=>true, VERSIONS=>1000}
ROW COLUMN+CELL
r1 column=e:c1, timestamp=14, value=value
r1 column=e:c1, timestamp=12, value=value
r1 column=e:c1, timestamp=11, type=DeleteColumn
r1 column=e:c1, timestamp=10, value=value
1 row(s) in 0.0120 seconds
hbase(main):018:0> flush 'test'
0 row(s) in 0.0350 seconds
hbase(main):019:0> scan 'test', {RAW=>true, VERSIONS=>1000}
ROW COLUMN+CELL
r1 column=e:c1, timestamp=14, value=value
r1 column=e:c1, timestamp=12, value=value
r1 column=e:c1, timestamp=11, type=DeleteColumn
1 row(s) in 0.0120 seconds
hbase(main):020:0> major_compact 'test'
0 row(s) in 0.0260 seconds
hbase(main):021:0> scan 'test', {RAW=>true, VERSIONS=>1000}
ROW COLUMN+CELL
r1 column=e:c1, timestamp=14, value=value
r1 column=e:c1, timestamp=12, value=value
1 row(s) in 0.0120 seconds
JAVA
注意删除单元格是如何放开的。
现在让我们仅使用在表上设置的KEEP_DELETED_CELLS运行相同的测试(您可以执行表或每列家族):
hbase(main):005:0> create 'test',
{NAME=>'e', VERSIONS=>2147483647, KEEP_DELETED_CELLS => true}
0 row(s) in 0.2160
seconds
=> Hbase::Table - test
hbase(main):006:0> put 'test',
'r1', 'e:c1',
'value', 10
0 row(s)
in 0.1070 seconds
hbase(main):007:0> put 'test',
'r1', 'e:c1',
'value', 12
0 row(s) in 0.0140
seconds
hbase(main):008:0> put 'test',
'r1', 'e:c1',
'value', 14
0 row(s) in 0.0160
seconds
hbase(main):009:0> delete 'test',
'r1', 'e:c1', 11
0 row(s) in 0.0290 seconds
hbase(main):010:0> scan 'test',
{RAW=>true, VERSIONS=>1000}
ROW COLUMN+CELL
r1 column=e:c1, timestamp=14,
value=value
r1 column=e:c1, timestamp=12,
value=value
r1 column=e:c1, timestamp=11,
type=DeleteColumn
r1 column=e:c1, timestamp=10,
value=value
1 row(s) in 0.0550 seconds
hbase(main):011:0> flush 'test'
0 row(s) in 0.2780 seconds
hbase(main):012:0> scan 'test',
{RAW=>true, VERSIONS=>1000}
ROW COLUMN+CELL
r1 column=e:c1, timestamp=14,
value=value
r1 column=e:c1, timestamp=12,
value=value
r1 column=e:c1, timestamp=11,
type=DeleteColumn
r1 column=e:c1, timestamp=10,
value=value
1 row(s) in 0.0620 seconds
hbase(main):013:0> major_compact 'test'
0 row(s) in 0.0530 seconds
hbase(main):014:0> scan 'test',
{RAW=>true, VERSIONS=>1000}
ROW COLUMN+CELL
r1 column=e:c1, timestamp=14,
value=value
r1 column=e:c1, timestamp=12,
value=value
r1 column=e:c1, timestamp=11,
type=DeleteColumn
r1 column=e:c1, timestamp=10,
value=value
1 row(s) in 0.0650 seconds
KEEP_DELETED_CELLS是为了避免从HBase中删除单元格时,删除它们的唯一原因是删除标记。 因此,如果使用KEEP_DELETED_CELLS启用已删除的单元格,如果您编写的版本多于配置的最大版本,或者您有TTL且单元格超过配置的超时等,则删除的单元格将被删除。
这部分也可以标题为“如果我的表rowkey看起来像这样,但我也想要像这样查询我的表。” dist-list上的一个常见示例是row-key格式为“user-timestamp”的格式,但对于特定时间范围内的用户活动有报告要求。因此,用户选择容易,因为它处于密钥的主导位置,但时间不是。
没有一个最好的方法来解决这个问题的答案,因为它取决于...
用户数量
数据大小和数据到达率
报告要求的灵活性(例如,完全特定的日期选择与预先配置的范围)
期望的查询执行速度(例如,对于临时报告来说90秒可能是合理的,而对于其他方面可能太长)
解决方案也受集群规模和解决方案所需的处理能力的影响。常见的技巧在下面的小节中介绍。这是一个全面但并非详尽的方法清单。
二级索引需要额外的集群空间和处理并不令人惊讶。这正是RDBMS中发生的情况,因为创建备用索引的操作需要更新空间和处理周期。
RDBMS产品在这方面更加先进,可以开箱即用地处理替代索引管理。但是,HBase在更大的数据量下可以更好地扩展,所以这是一项功能交换。
在实施任何这些方法时,请注意Apache HBase性能调整。另外,请参阅David Butler在这个dist-list线程HBase,mail#user - Stargate + hbase中的响应
(http://search-hadoop.com/m/nvbiBp2TDP/Stargate%252Bhbase&subj=Stargate+hbase)
3.2.11
过滤查询
根据具体情况,可能适合使用客户端请求过滤器。
在这种情况下,不会创建二级索引。 但是,请勿尝试在应用程序(如单线程客户端)上对此类大型表进行全面扫描。
3.2.12
定期更新二级索引
二级索引可以在另一个通过MapReduce作业定期更新的表中创建。 这项工作可以在一天内执行,但根据加载策略,它仍然可能与主数据表不同步。
有关更多信息,请参阅mapreduce.example.readwrite。
3.2.12双写二次索引
另一种策略是在将数据发布到集群时构建二级索引(例如写入数据表,写入索引表)。
如果这是在数据表已经存在之后采取的方法,那么对于具有MapReduce作业的二级索引将需要引导(请参阅secondary.indexes.periodic)。
3.2.13汇总表
在时间范围非常广泛(例如,长达一年的报告)以及数据量很大的情况下,汇总表是常用的方法。 这些将通过MapReduce作业生成到另一个表中。
有关更多信息,请参阅mapreduce.example.summary。
3.2.14协处理器二级索引
协处理器就像RDBMS触发器一样。这些增加了0.92。有关更多信息,请参阅协处理器
HBase目前支持传统(SQL)数据库术语中的“约束”。 Constraints的建议用法是强制执行表中属性的业务规则(例如,确保值在1-10范围内)。也可以使用约束来强制引用完整性,但是强烈建议不要使用约束,因为它会显着降低启用完整性检查的表的写入吞吐量。从版本0.94开始,可以在Constraint(https://hbase.apache.org/devapidocs/org/apache/hadoop/hbase/constraint/Constraint.html)上找到有关使用约束的大量文档。
以下将描述HBase的一些典型数据提取用例,以及如何处理rowkey设计和构造。注意:这只是潜在方法的一个例证,而不是详尽的清单。了解您的数据,并了解您的处理要求。
强烈建议您在阅读这些案例研究之前,首先阅读HBase和Schema Design的其余部分。描述了以下案例研究:
记录数据/时间序列数据
日志数据/时间序列上的类固醇
客户订单
高/宽/中图式设计
列表数据
3.2.15案例研究 - 日志数据和时间序列数据
假设正在收集以下数据元素。
主机名
时间戳
记录事件
值/消息
我们可以将它们存储在名为LOG_DATA的HBase表中,但rowkey会是什么? 从这些属性中,rowkey将是主机名,时间戳和日志事件的一些组合 - 但具体是什么?
3.2.16案例研究 -行密钥主导位置中的时间戳
44.1.1。行密钥主导位置中的时间戳
rowkey
[timestamp] [hostname] [log-event]受单调递增行键/时间序列数据中描述的单调递增rowkey问题的影响。
通过在时间戳上执行mod操作,在dist-lists中经常提到关于“分组”时间戳的另一种模式。如果时间扫描很重要,这可能是一个有用的方法。必须注意桶的数量,因为这将需要相同数量的扫描来返回结果。
long bucket
= timestamp%numBuckets;
JAVA
构建:
[桶] [时间戳] [主机名] [登录事件]
JAVA
如上所述,要选择特定时间范围的数据,需要为每个存储桶执行扫描。例如,100个存储桶将在密钥空间中提供广泛的分布,但它需要100次扫描才能获得单个时间戳的数据,因此存在权衡。
44.1.2。主持人在Rowkey主导地位
如果存在大量的主机来通过密钥空间来传播写入和读取,则rowkey [主机名] [日志事件] [时间戳]是候选者。如果按主机名扫描是优先事项,则此方法非常有用。
44.1.3。时间戳或反向时间戳?
如果最重要的访问路径是拉取最近的事件,则将时间戳存储为反向时间戳(例如timestamp = Long.MAX_VALUE - timestamp)将创建能够对[hostname] [log-事件]来获取最近捕获的事件。
这两种方法都不是错的,只是取决于最适合这种情况的东西。
反向扫描API
表反向,减少了对正向或反向扫描优化模式的需求。此功能在HBase 0.98和更高版本中可用。有关更多信息,请参阅Scan.setReversed()(https://hbase.apache.org/apidocs/org/apache/hadoop/hbase/client/Scan.html#setReversed-boolean-)。
44.1.4。可变长度或固定长度的行键?
记住在HBase的每一列上加盖行密码是非常重要的。如果主机名是a并且事件类型是e1,那么生成的rowkey将会很小。但是,如果所摄入的主机名是myserver1.mycompany.com,并且事件类型是com.package1.subpackage2.subsubpackage3.ImportantService,该怎么办?
在rowkey中使用一些替换可能是有意义的。至少有两种方法:散列和数字。在Rowkey Lead Position示例中的主机名中,它可能如下所示:
带有哈希的复合Rowkey:
[主机名的MD5散列] = 16个字节
[事件类型的MD5散列] = 16个字节
[时间戳] = 8个字节
带数字替换的复合行列码:
对于这种方法,除了LOG_DATA之外,还需要另一个查找表,称为LOG_TYPES。 LOG_TYPES的rowkey将是:
[类型](例如,指示主机名与事件类型的字节)
原始主机名或事件类型的[bytes]可变长度字节。
此rowkey的列可能是一个具有指定编号的长整数,可通过使用HBase计数器获得
(https://hbase.apache.org/apidocs/org/apache/hadoop/hbase/client/Table.html#incrementColumnValue-byte:A-byte:A-byte:A-long-)
所以得到的复合rowkey将是:
[代替主机名长] = 8个字节
[长时间取代事件类型] = 8个字节
[时间戳] = 8个字节
在Hash或Numeric替换方法中,主机名和事件类型的原始值可以存储为列。
3.2.17案例研究 –关于日志数据和时间序列数据
这实际上是OpenTSDB的方法。 OpenTSDB做的是重写数据并在特定的时间段内将行打包成列。 有关详细说明,请参阅:http://opentsdb.net/schema.html,以及HBaseCon2012从OpenTSDB获得的经验教训(https://www.slideshare.net/cloudera/4-opentsdb-hbasecon)。
但是,这是一般概念的工作原理:例如,以这种方式摄入数据...
[主机名] [日志事件] [时间戳1] [主机名] [日志事件] [时间戳2] [主机名] [日志事件] [时间戳3]
每个细节事件都有独立的rowkeys,但是会被重写成这样...
[主机名] [登录事件] [TIMERANGE]
并且每个上述事件被转换成相对于开始时间范围(例如每5分钟)以时间偏移存储的列。 这显然是一种非常先进的处理技术,但HBase使这成为可能。
3.2.18案例研究 –客户/订单
假设HBase用于存储客户和订单信息。 有两种核心记录类型被摄取:客户记录类型和订单记录类型。
顾客号码
顾客姓名
地址(例如,城市,州,邮编)
电话号码等
订单记录类型将包含如下内容:
顾客号码
订单号
销售日期
一系列用于装运位置和订单项的嵌套对象(有关详细信息,请参阅订单对象设计)
假设客户编号和销售订单的组合唯一地标识订单,这两个属性将组成rowkey,特别是组合键,例如:
[customer number][order number]
为一个ORDER表。 但是,还有更多的设计决策需要:原始值是rowkeys的最佳选择?
Log Data用例中的相同设计问题在这里面对我们。
客户编号的密钥空间是什么,以及格式是什么(例如,数字?字母数字?)由于在HBase中使用固定长度的密钥以及可以在密钥空间中支持合理分布的密钥是有利的,因此类似 选项出现:
带有哈希的复合Rowkey:
[客户号码的MD5] = 16字节
[订单号的MD5] = 16字节组合数字/哈希组合Rowkey:
[代替客户号码] = 8个字节
[订单号的MD5] = 16字节
单表? 多表?
传统的设计方法会为CUSTOMER和SALES分开表格。
另一种选择是将多个记录类型打包到一个表中(例如,CUSTOMER ++)。
客户记录类型Rowkey:
[客户ID]
[type] =表示客户记录类型的类型'1'订单记录类型Rowkey:
[客户ID]
[type] =指示订单记录类型为'2'的类型
[订购]
这种特定的CUSTOMER ++方法的优点是通过客户ID组织许多不同的记录类型(例如,单次扫描可以让你得到关于该客户的所有信息)。 缺点是扫描特定记录类型并不容易。
订单对象设计
现在我们需要解决如何建模Order对象。假设类结构如下:
订购
(订单可以有多个ShippingLocations
(一个ShippingLocation可以有多个LineItems,在存储这些数据时有多个选项,完全标准化
通过这种方法,ORDER,SHIPPING_LOCATION和LINE_ITEM将会有单独的表格。
ORDER表的rowkey如上所述:schema.casestudies.custorder
SHIPPING_LOCATION的复合rowkey如下所示:
[订单rowkey]
[出货地点编号](例如,第一地点,第二地点等)LINE_ITEM表的复合行键将是这样的:
[订单rowkey]
[出货地点编号](例如,第一地点,第二地点等)
[订单项编号](例如,第一个线性项,第二个等)
这样的标准化模型很可能是RDBMS的方法,但这不是HBase唯一的选择。这种做法的缺点是要检索任何订单的信息,您需要:
获取订单的订单表
在SHIPPING_LOCATION表上扫描该订单以获取ShippingLocation实例
在每个ShippingLocation的LINE_ITEM上扫描
这是一个RDBMS无论如何都会在封面下做的事情,但由于HBase中没有连接,所以您只是更加意识到这一点。
单一表与记录类型
采用这种方法,将会存在一个包含单个表的ORDER
Order rowkey如上所述:schema.casestudies.custorder
[订单rowkey]
[订单记录类型]
ShippingLocation复合rowkey将如下所示:
[订单rowkey]
[SHIPPING记录类型]
[出货地点编号](例如,第一地点,第二地点等)LineItem复合行键将是这样的:
[订单rowkey]
[LINE记录类型]
[出货地点编号](例如,第一地点,第二地点等)
[订单项编号](例如,第一个线性项,第二个等)
反规范化
具有记录类型的单一表格的一种变体是对一些对象层次结构进行非规范化和扁平化,比如将ShippingLocation属性折叠到每个LineItem实例上。
LineItem复合rowkey将如下所示:
[订单rowkey]
[LINE记录类型]
[订单项编号](例如,第一个lineitem,第二个等等,必须注意整个订单中都有唯一),LineItem列将如下所示:
项目编号
数量
价钱
shipToLine1(从ShippingLocation非正规化)
shipToLine2(从ShippingLocation非正规化)
shipToCity(从ShippingLocation非正规化)
shipToState(从ShippingLocation非正规化)
shipToZip(从ShippingLocation非正规化)
这种方法的优点包括不太复杂的对象层次结构,但其中一个缺点是,如果这些信息发生变化,更新会变得更加复杂。
对象BLOB
通过这种方法,整个Order对象图都以某种方式作为BLOB进行处理。例如,上面描述了ORDER表的rowkey:schema.casestudies.custorder,而一个名为“order”的列将包含一个可以反序列化的对象,该对象包含一个容器Order,ShippingLocations和LineItems。
这里有很多选项:JSON,XML,Java序列化,Avro,Hadoop Writable等等。所有这些都是相同方法的变体:将对象图编码为字节数组。应该注意这种方法,以确保向后兼容,以防对象模型发生变化,使旧的持久结构仍能从HBase中读出。
优点是能够以最少的I / O管理复杂的对象图(例如,在本例中为单个HBase Get Order),但缺点包括前面提到的关于序列化的向后兼容性,序列化的语言依赖性(例如Java序列化只适用于Java客户端),事实上你必须反序列化整个对象才能获得BLOB中的任何信息,以及像Hive这样的框架难以使用像这样的自定义对象。
3.2.19案例研究 – “高/宽/中”架构设计Smackdown
本节将介绍出现在远程列表中的其他模式设计问题,特别是高和宽表。这些是一般准则而不是法律 - 每个应用程序都必须考虑自己的需求。
行与版本
一个常见的问题是应该更喜欢行还是HBase的内置版本。上下文通常是保留行的“很多”版本的地方(例如,它明显高于1个最大版本的HBase默认值)。 rows-approach需要在rowkey的某些部分存储一个时间戳,以便在每次连续更新时不会覆盖它们。
首选项:行(一般来说)。
行与列
另一个常见问题是,应该更喜欢行还是列。上下文通常在宽表格的极端情况下,例如具有1行100万个属性,或100万行,每列1列。
首选项:行(一般来说)。需要说明的是,本指南在上下文中是非常宽泛的情况,而不是标准的用例,其中需要存储几十或者一百列。但是这两个选项之间也有一条中间路径,那就是“行列为列”。
行作为列
行与列之间的中间路径将打包数据,对于某些行,这些数据将成为单独的行。在这种情况下,OpenTSDB就是最好的例子,其中一行表示一个定义的时间范围,然后将离散事件视为列。这种方法通常更加复杂,并且可能需要重写数据的额外复杂性,但具有I / O高效的优点。有关此方法的概述,请参阅schema.casestudies.log-steroids。
3.2.20案例研究 –列表数据
在Apache HBase中。
题 *
我们正在研究如何在HBase中存储大量(每用户)列表数据,并且我们试图弄清楚哪种访问模式最有意义。一种选择是将大部分数据存储在一个密钥中,所以我们可以有这样的内容:
<FixedWidthUserName>
<FixedWidthValueId1>:“”(无值)
<FixedWidthUserName>
<FixedWidthValueId2>:“”(无值)
<FixedWidthUserName>
<FixedWidthValueId3>:“”(无值)
JAVA
我们的另一个选择是完全使用:
<FixedWidthUserName>
<FixedWidthPageNum0>:<FixedWidthLength> <FixedIdNextPageNum> <ValueId1>
<ValueId2> <ValueId3> ...
<FixedWidthUserName>
<FixedWidthPageNum1>:<FixedWidthLength> <FixedIdNextPageNum> <ValueId1>
<ValueId2> <ValueId3> ...
XML
每行将包含多个值。所以在一种情况下,读取前三十个值将是:
扫描{STARTROW
=>'FixedWidthUsername'LIMIT => 30} JAVA
而在第二种情况下会是这样
得到'FixedWidthUserName
\ x00 \ x00 \ x00 \ x00'JAVA
一般的使用模式是仅读取这些列表的前30个值,并且不经常访问更深入地阅读列表。一些用户在这些列表中的总值将达到30%,而一些用户将拥有数百万(即幂律分布)
单值格式似乎会占用HBase更多的空间,但会提供一些改进的检索/分页灵活性。是否有任何显着的性能优势能够通过获取与分页扫描分页?
我最初的理解是,如果我们的分页大小未知(并且缓存设置恰当),那么执行扫描应该会更快,但如果我们始终需要相同的页面大小,则扫描速度应该更快。我听到不同的人告诉了我关于表演的相反事情。我假设页面大小会相对一致,所以对于大多数用例,我们可以保证我们只需要固定页面长度的情况下的一页数据。我还会假设我们将不经常更新,但可能会插入这些列表的中间(这意味着我们需要更新所有后续行)。
感谢您的帮助/建议/后续问题。
答案*
如果我正确理解你,你最终试图以“user,valueid,value”的形式存储三元组,对吗?例如,类似于:
“user123,firstname,Paul”,“user234,lastname,Smith”
JAVA
(但用户名是固定宽度,而valueids是固定宽度)。
而且,您的访问模式符合以下要求:“对于用户X,列出接下来的30个值,以valueid Y开头”。是对的吗?这些值应该返回按valueid排序?
tl; dr版本是,你可能应该为每个用户+值添加一行,除非你确定需要,否则不要自行构建复杂的行内分页方案。
您的两个选项反映了人们在设计HBase模式时常见的问题:我应该选择“高”还是“宽”?您的第一个模式是“高”:每行代表一个用户的一个值,因此每个用户的表中有很多行;行键是user +
valueid,并且会有(可能)单个列限定符,意思是“值”。这是
正确)。你可以在任何用户+
valueid开始扫描,阅读下一个30,并完成。你放弃的是能够在一个用户的所有行周围提供事务保证,但它听起来并不像你需要的那样。通常建议这样做(参见https://hbase.apache.org/book.html#schema.smackdown)。
第二个选项是“宽”:使用不同的限定符(其中限定符是valueid)将一堆值存储在一行中。简单的做法是将一个用户的所有值存储在一行中。我猜你跳到了“分页”版本,因为你认为在单行中存储数百万列会对性能造成影响,这可能是也可能不是真的;只要您不想在单个请求中做太多事情,或者执行诸如扫描并返回行中的所有单元格之类的内容,它不应该基本上变得更糟。客户端具有允许您获取特定的列的片段的方法。
请注意,这两种情况都不会从根本上占用更多的磁盘空间;您只是将部分识别信息“移动”到左侧(在行键中,在选项一中)或向右(在选项2中的列限定符中)。在封面下,每个键/值仍然存储整个行键和列名称。 (如果这有点令人困惑,请花一个小时观看Lars George关于理解HBase架构设计的优秀视频:http://www.youtube.com/watch?v = _HLoH_PgrLk)。
正如你注意到的那样,手动分页版本有很多复杂性,比如必须跟踪每个页面中有多少内容,如果插入新值,则重新洗牌等。这看起来要复杂得多。在分机上它可能有一些轻微的速度优势(或缺点!)
3.2.21调试HBase服务器RPC处理
将hbase.regionserver.handler.count(在hbase-site.xml中)设置为cores x spindles以实现并发。
可选地,将呼叫队列分成单独的读取和写入队列以用于区别服务。参数
hbase.ipc.server.callqueue.handler.factor指定呼叫队列的数量:
0表示单个共享队列
1表示每个处理程序有一个队列。
0到1之间的值与处理程序的数量成比例地分配队列数量。例如,.5的值在每个处理程序之间共享一个队列。
使用hbase.ipc.server.callqueue.read.ratio(0.98中的hbase.ipc.server.callqueue.read.share)将调用队列分为读写队列:
0.5意味着将有相同数量的读写队列
读取比写入少0.5
> 0.5以上的写入比读取
设置hbase.ipc.server.callqueue.scan.ratio(HBase 1.0+)将读取调用队列分为小读和长读队列:
0.5意味着将有相同数量的短读取和长读取队列
更短的读数<0.5
> 0.5为更长的阅读
3.2.22禁用RPC的Nagle
禁用Nagle的算法。 延迟的ACK可以将RPC往返时间加起来大约200ms。 设置以下参数:
在Hadoop的core-site.xml中:
ipc.server.tcpnodelay = true
ipc.client.tcpnodelay = true
在HBase的hbase-site.xml中:
hbase.ipc.client.tcpnodelay = true
hbase.ipc.server.tcpnodelay = true
3.2.23限制服务器故障影响
尽可能快地检测区域服务器故障。 设置以下参数:
在hbase-site.xml中,将zookeeper.session.timeout设置为30秒或更短时间来绑定故障检测(20-30秒是一个好的开始)。
检测并避免不健康或失败的HDFS数据节点:在hdfs-site.xml和hbase-site.xml中,设置以下参数:
dfs.namenode.avoid.read.stale.datanode = true
dfs.namenode.avoid.write.stale.datanode =
true
3.2.24针对低延迟优化服务器端
跳过本地块的网络。 在hbase-site.xml中,设置以下参数:
dfs.client.read.shortcircuit = true
dfs.client.read.shortcircuit.buffer.size =
131072(重要避免OOME)
确保数据的局部性。 在hbase-site.xml中,设置hbase.hstore.min.locality.to.skip.major.compact
= 0.7(意思是0.7 <=
n <= 1)
确保DataNode有足够的处理程序进行块传输。 在hdfs-site.xml中,设置以下参数:
dfs.datanode.max.xcievers> = 8192
3.2.25
JVM调优
调整JVM GC以获取低收集延迟
使用CMS收集器:-XX:+ UseConcMarkSweepGC
保持伊甸园空间尽可能小,以减少平均收集时间。例:
-XX:CMSInitiatingOccupancyFraction = 70
优化低收集延迟而不是吞吐量:-Xmn512m
并行收集伊甸园:-XX:+ UseParNewGC
避免在压力下收集:-XX:+ UseCMSInitiatingOccupancyOnly
限制每个请求扫描器的结果大小,所以一切都适合幸存者空间,但没有任职期限。在hbase-site.xml中,将hbase.client.scanner.max.result.size设置为eden空间的1/8(使用-Xmn512m这是〜51MB)
设置max.result.size
x handler.count小于生存者空间
OS级调整
关闭透明大页面(THP):
echo never> / sys / kernel / mm /
transparent_hugepage / enabled echo never> / sys / kernel / mm /
transparent_hugepage / defrag
设置vm.swappiness
= 0
将vm.min_free_kbytes设置为至少1GB(较大内存系统上为8GB)
使用vm.zone_reclaim_mode
= 0禁用NUMA区域回收
3.2.26对于快速失败优于等待的应用程序
在客户端的hbase-site.xml中,设置以下参数:
设置hbase.client.pause
= 1000
设置hbase.client.retries.number
= 3
如果你想骑跨分割和区域移动,大幅增加hbase.client.retries.number(> = 20)
设置RecoverableZookeeper重试计数:zookeeper.recovery.retry = 1(不重试)
在服务器端的hbase-site.xml中,设置Zookeeper会话超时以检测服务器故障:
zookeeper.session.timeout⇐30秒(20-30是比较好的)
3.2.27对于可以容忍稍微过时的信息的应用程序
HBase时间线一致性(HBASE-10070)启用只读副本后,区域(副本)的只读副本将分布在群集中。 One RegionServer为默认或主副本提供服务,这是唯一可以服务写入的副本。
其他Region Server服务于辅助副本,请遵循主要RegionServer,并仅查看提交的更新。 辅助副本是只读的,但可以在主服务器故障时立即提供读取操作,从而将读取可用性从几秒钟减少到几毫秒。 Phoenix支持时间线一致性4.4.0提示:
部署HBase 1.0.0或更高版本。
在服务器端启用时间线一致副本。
使用以下方法之一设置时间线一致性:
使用ALTER
SESSION SET CONSISTENCY ='TIMELINE'
在JDBC连接字符串中将连接属性Consistency设置为时间轴
3.3 Hbase Mapreduce和其他一些建议与示例
3.3.1默认的HBase MapReduce Splitter
TableInputFormat(https://hbase.apache.org/apidocs/org/apache/hadoop/hbase/mapreduce/TableInputFormat.html)用于源MapReduce作业中的HBase表格时,其拆分器将为每个映射任务创建一个映射任务表的区域。 因此,如果表格中有100个区域,则无论在“扫描”中选择多少个列族,该作业都会有100个地图任务。
3.3.2自定义的Splitter
对于那些有兴趣实现自定义拆分器的人,请参阅TableInputFormatBase中的getSplits方法(https://hbase.apache.org/apidocs/org/apache/hadoop/hbase/mapreduce/TableInputFormatBase.html)。 这是map-task分配的逻辑所在。
}
。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。。关于此部分暂时不填入。后续将继续补充
3.4 Hbase 与安全有关的一些配置
3.4.1先决条件
Hadoop认证配置
要使用强身份验证运行HBase
RPC,您必须将hbase.security.authentication设置为kerberos。 在这种情况下,您还必须将hadoop.security.authentication设置为core-site.xml中的kerberos。 否则,您将对HBase使用强身份验证,但不会对底层HDFS使用强身份验证,否则会取消任何收益。
Kerberos KDC
您需要有一个可用的Kerberos
KDC。
3.4.2用于安全操作的服务器端配置
首先,参考security.prerequisites并确保您的基础HDFS配置是安全的。
将以下内容添加到集群中每台服务器计算机上的hbase-site.xml文件中:
<property>
<name>hbase.security.authentication</name>
<value>kerberos</value>
</property>
<property>
<name>hbase.security.authorization</name>
<value>true</value>
</property>
<property>
<name>hbase.coprocessor.region.classes</name>
<value>org.apache.hadoop.hbase.security.token.TokenProvider</value>
</property>
部署这些配置更改时,需要完全关闭并重新启动HBase服务。
3.4.3用于安全操作的客户端配置
首先,请参阅先决条件并确保您的基础HDFS配置是安全的。
将以下内容添加到每个客户端上的hbase-site.xml文件中:
<property>
<name>hbase.security.authentication</name>
<value>kerberos</value>
</property>
客户端环境必须通过kinit命令从KDC或keytab登录到Kerberos,然后才能与HBase群集通信。
请注意,如果客户端和服务器端站点文件中的hbase.security.authentication不匹配,客户端将无法与群集进行通信。
一旦将HBase配置为安全RPC,就可以选择配置加密通信。 为此,请将以下内容添加到每个客户端上的hbase-site.xml文件中:
<property>
<name>hbase.rpc.protection</name>
<value>privacy</value>
</property>
此配置属性也可以在每个连接的基础上进行设置。 在提供给表的配置中进行设置:
Connection connection =
ConnectionFactory.createConnection(conf);
conf.set("hbase.rpc.protection", "privacy");
try
(Connection
connection = ConnectionFactory.createConnection(conf); Table table =
connection.getTable(TableName.valueOf(tablename))) {
.... do your stuff
}
对于加密通信,预计会有大约10%的性能损失。
3.4.4用于安全操作的客户端配置 - Thrift Gateway
将以下内容添加到每个Thrift网关的hbase-site.xml文件中
<property>
<name>hbase.thrift.keytab.file</name>
<value>/etc/hbase/conf/hbase.keytab</value>
</property>
<property>
<name>hbase.thrift.kerberos.principal</name>
<value>$USER/_HOST@HADOOP.LOCALDOMAIN</value>
<!-- TODO: This may need to be HTTP/_HOST@<REALM> and
_HOST may not work. You may have to put the concrete full hostname.
-->
</property>
<!-- Add these if you need to configure a different DNS
interface from the default -->
<property>
<name>hbase.thrift.dns.interface</name>
<value>default</value>
</property>
<property>
<name>hbase.thrift.dns.nameserver</name>
<value>default</value>
</property>
分别替换$ USER和$ KEYTAB的相应凭证和密钥表。
为了使用Thrift API主体与HBase进行交互,还需要将hbase.thrift.kerberos.principal添加到acl表。
例如,为了给予Thrift API主体thrift_server管理访问权限,像这样的命令就足够了。
grant 'thrift_server', 'RWCA'
有关ACL的更多信息,请参阅访问控制标签(ACL)部分
Thrift网关将使用提供的凭证向HBase进行身份验证。 Thrift网关本身不会执行认证。 所有通过Thrift网关访问的客户端都将使用Thrift网关的凭证并拥有其权限。
3.4.5代表客户端配置Thrift网关进行身份验证
用于安全操作的客户端配置 -
Thrift Gateway描述了如何使用固定用户向HBase验证Thrift客户端。作为替代,您可以将Thrift网关配置为代表客户端向HBase进行身份验证,并使用代理用户访问HBase。这在HBASE-11349(https://issues.apache.org/jira/browse/HBASE-11349)中为Thrift 1和HBASE-11474(https://issues.apache.org/jira/browse/HBASE)实施-11474)为Thrift 2。
节俭框架运输的局限性
如果您使用框架式运输,您还不能利用此功能,因为此时SASL不适用于Thrift框架式运输。
要启用它,请执行以下操作。
1.按照安全操作客户端配置 - Thrift Gateway中所述的步骤,确保Thrift以安全模式运行。
2.确保HBase配置为允许代理用户,如REST网关模拟配置中所述。
3.在运行Thrift网关的每个群集节点的hbase-site.xml中,将属性hbase.thrift.security.qop设置为以下三个值之一:
integrity
- authentication and integrity checking
authentication
- authentication checking only
4.重新启动Thrift网关进程以使更改生效。如果节点正在运行Thrift,则jps命令的输出将列出ThriftServer进程。要停止节点上的Thrift,请运行命令bin / hbase-daemon.sh stop
thrift。要在节点上启动Thrift,请运行命令bin / hbase-daemon.sh start
thrift。
3.4.6配置Thrift网关以使用doAs功能
代表客户端配置Thrift网关进行身份验证描述如何配置Thrift网关以代表客户端对HBase进行身份验证,以及如何使用代理用户访问HBase。 这种方法的局限性在于客户端使用特定的凭证集进行初始化后,在会话期间它不能更改这些凭证。 doAs功能提供了使用相同客户端模拟多个主体的灵活方式。 此功能在H BASE-12640(https://issues.apache.org/jira/browse/HBASE-12640)中为Thrift 1实施,但目前不适用于Thrift 2。
要启用doAs功能,请将以下内容添加到每个Thrift网关的hbase-site.xml文件中:
<property>
<name>hbase.regionserver.thrift.http</name>
<value>true</value>
</property>
<property>
<name>hbase.thrift.support.proxyuser</name>
<value>true/value>
</property>
要在使用doAs模拟时允许代理用户,请将以下内容添加到每个HBase节点的hbase-site.xml文件中:
<property>
<name>hadoop.security.authorization</name>
<value>true</value>
</property>
<property>
<name>hadoop.proxyuser.$USER.groups</name>
<value>$GROUPS</value>
</property>
<property>
<name>hadoop.proxyuser.$USER.hosts</name>
<value>$GROUPS</value>
</property>
看看演示客户端
(https://github.com/apache/hbase/blob/master/hbase-examples/src/main/java/org/apache/hadoop/hbase/thrift/HttpDoAsClient.java)以获得关于如何使用的总体思路 这个功能在你的客户端。
3.4.7用于安全操作的客户端配置 - REST网关
将以下内容添加到每个REST网关的hbase-site.xml文件中:
<property>
<name>hbase.rest.keytab.file</name>
<value>$KEYTAB</value>
</property>
<property>
<name>hbase.rest.kerberos.principal</name>
<value>$USER/_HOST@HADOOP.LOCALDOMAIN</value>
</property>
分别替换$ USER和$ KEYTAB的相应凭证和密钥表。 REST网关将使用提供的凭证对HBase进行身份验证。
为了使用REST API主体与HBase进行交互,还需要将hbase.rest.kerberos.principal添加到acl表。
例如,要赋予REST API主体rest_server管理访问权限,像这样的命令就足够了:
有关ACL的更多信息,请参阅访问控制标签(ACL)部分
HBase REST网关支持客户端访问网关的SPNEGO HTTP身份验证(https://hadoop.apache.org/docs/stable/hadoop-auth/index.html)。 要为客户端访问启用REST网关Kerberos身份验证,请将以下内容添加到每个REST网关的hbase-site.xml文件中。
<property>
<name>hbase.rest.support.proxyuser</name>
<value>true</value>
</property>
<property>
<name>hbase.rest.authentication.type</name>
<value>kerberos</value>
</property>
<property>
<name>hbase.rest.authentication.kerberos.principal</name>
<value>HTTP/_HOST@HADOOP.LOCALDOMAIN</value>
</property>
<property>
<name>hbase.rest.authentication.kerberos.keytab</name>
<value>$KEYTAB</value>
</property>
<!-- Add these if you need to configure a different DNS
interface from the default -->
<property>
<name>hbase.rest.dns.interface</name>
<value>default</value>
</property>
<property>
<name>hbase.rest.dns.nameserver</name>
<value>default</value>
</property>
用$ KEYTAB替代HTTP的keytab。
HBase REST网关支持不同的'hbase.rest.authentication.type':简单,kerberos。 您也可以通过实现Hadoop AuthenticationHandler来实现自定义身份验证,然后将完整的类名称指定为'hbase.rest.authentication.type'值。 有关更多信息,请参阅SPNEGO HTTP身份验证(https://hadoop.apache.org/docs/stable/hadoop-auth/index.html)。
3.4.8 REST网关模拟配置
默认情况下,REST网关不支持模拟。 它以用户的身份访问HBase,按照上一节中的配置。 对于HBase服务器,所有请求都来自REST网关用户。 实际用户不详。 您可以打开模拟支持。 通过模拟,REST网关用户是代理用户。
HBase服务器知道每个请求的实际/真实用户。 因此它可以应用适当的授权。
要打开REST网关模拟,我们需要配置HBase服务器(主服务器和区域服务器)以允许代理用户; 配置REST网关以启用模拟。
要允许代理用户,请将以下内容添加到每个HBase服务器的hbase-site.xml文件中:
<property>
<name>hadoop.security.authorization</name>
<value>true</value>
</property>
<property>
<name>hadoop.proxyuser.$USER.groups</name>
<value>$GROUPS</value>
</property>
<property>
<name>hadoop.proxyuser.$USER.hosts</name>
<value>$GROUPS</value>
</property>
将REST网关代理用户替换为$ USER,并将允许的组列表替换为$ GROUPS。
要启用REST网关模拟,请将以下内容添加到每个REST网关的hbase-site.xml文件中。
<name>hbase.rest.authentication.type</name>
<value>kerberos</value>
</property>
<property>
<name>hbase.rest.authentication.kerberos.principal</name>
<value>HTTP/_HOST@HADOOP.LOCALDOMAIN</value>
</property>
<property>
<name>hbase.rest.authentication.kerberos.keytab</name>
<value>$KEYTAB</value>
</property>
用$ KEYTAB替代HTTP的keytab。
较新版本的Apache
HBase(> = 0.92)支持客户端的可选SASL身份验证。 另请参阅Matteo Bertozzi有关了解Apache HBase中的用户身份验证和授权的文章(https://blog.cloudera.com/blog/2012/09/understanding-user-authentication-and-authorization-in-apache-hbase/)。
本节介绍如何设置Apache
HBase和客户端,以便用户访问HBase资源。
3.4.9 简单与安全访问
以下部分介绍如何设置简单的用户访问。 简单的用户访问不是一种运行HBase的安全方法。 此方法用于防止用户犯错。 它可用于在开发系统上模拟访问控制,而无需设置Kerberos。
此方法不用于防止恶意或黑客入侵。 为了使HBase能够抵御这些类型的攻击,您必须配置HBase进行安全操作。 请参阅安全客户端访问Apache HBase部分并完成此处描述的所有步骤。
3.4.10
简单用户访问操作的客户端配置
将以下内容添加到集群中每台服务器计算机上的hbase-site.xml文件中:
<property>
<name>hbase.security.authentication</name>
<value>simple</value>
</property>
<property>
<name>hbase.security.authorization</name>
<value>true</value>
</property>
<property>
<name>hbase.coprocessor.master.classes</name>
<value>org.apache.hadoop.hbase.security.access.AccessController</value>
</property>
<property>
<name>hbase.coprocessor.region.classes</name>
<value>org.apache.hadoop.hbase.security.access.AccessController</value>
</property>
<property>
<name>hbase.coprocessor.regionserver.classes</name>
<value>org.apache.hadoop.hbase.security.access.AccessController</value>
</property>
0.94版本以下不适用上述xml配置。
3.4.11保护ZooKeeper数据
ZooKeeper具有可插入的身份验证机制,可以使用不同的方法访问客户端。 ZooKeeper甚至允许同时验证和未验证的客户端。通过为每个znode提供访问控制列表(ACL)来限制对znodes的访问。 ACL包含两个组件,即身份验证方法和主体。 ACL不是分层强制执行的。有关详细信息,请参阅ZooKeeper程序员指南(https://zookeeper.apache.org/doc/r3.3.6/zookeeperProgrammers.html#sc_ZooKeeperPluggableAuthentication)。
HBase守护进程通过SASL和Kerberos向ZooKeeper进行身份验证(请参阅使用ZooKeeper进行SASL身份验证)。 HBase设置znode ACL,以便只有HBase用户和配置的hbase超级用户(hbase.superuser)才能访问和修改数据。在ZooKeeper用于服务发现或与客户端共享状态的情况下,由HBase创建的znodes也将允许任何人(不管身份验证)读取这些znode(clusterId,主地址,元位置等),但只有HBase用户可以修改它们。
3.4.12保护文件系统(HDFS)数据
所有管理的数据都保存在文件系统(hbase.rootdir)的根目录下。访问文件系统中的数据和WAL文件应受到限制,以便用户不能绕过HBase层,并从文件系统中查看底层数据文件。 HBase假定使用的文件系统(HDFS或其他)分层次地强制执行权限。如果没有提供足够的文件系统保护(授权和身份验证),HBase级授权控制(ACL,可见性标签等)就没有意义,因为用户可以随时访问文件系统中的数据。
HBase对其根目录强制执行posix-like权限700(rwx ------)。这意味着只有HBase用户可以读写FS中的文件。通过在hbase-site.xml中配置hbase.rootdir.perms可以更改默认设置。需要重新启动活动主服务器,以便更改使用的权限。对于1.2.0之前的版本,您可以检查是否提交了HBASE-13780,如果没有,您可以根据需要手动设置根目录的权限。使用HDFS,该命令将是:
sudo -u hdfs hadoop fs -chmod 700
/hbase
如果您使用不同的hbase.rootdir,则应该更改/ hbase。
在安全模式下,应配置SecureBulkLoadEndpoint并将其用于将用户作业从MR作业创建到HBase守护程序和HBase用户的正确处理。分布式文件系统中的临时目录用于批量加载
(hbase.bulkload.staging.dir,默认为/ tmp / hbase-staging)应具有(模式711或rwx-x-x),以便用户可以访问在该父目录下创建的暂存目录,但不能执行任何其他操作操作。有关如何配置SecureBulkLoadEndPoint,请参阅安全批量加载。
在HBase客户端和服务器进程和网关之间配置安全身份验证后,您需要考虑数据本身的安全性。 HBase提供了几种保护数据的策略:
基于角色的访问控制(RBAC)控制哪些用户或组可以使用熟悉的角色范例来读取和写入给定的HBase资源或执行协处理器端点。
可见性标签,允许您标记单元格并控制对标记单元格的访问,以进一步限制谁可以读取或写入数据的某些子集。可见性标签存储为标签。有关更多信息,请参阅hbase.tags。
在HFile和WAL中对底层文件系统的静态数据进行透明加密。这可以保护您的数据免受可以访问底层文件系统的攻击者的影响,而无需更改客户端的实现。它还可以防止不正确放置的磁盘泄漏数据,这对法律和法规遵从性可能非常重要。
下面讨论这些功能中的每个功能的服务器端配置,管理和实现细节,以及任何性能折衷。最后给出一个示例安全配置,以显示所有这些功能一起使用,因为它们可能在真实世界的场景中。
HBase安全的各个方面都在积极发展和快速发展。您为了保护您的数据而采取的任何策略都应该进行彻底的测试。另外,其中一些功能还处于实验阶段。要利用其中的许多功能,您必须运行HBase 0.98+并使用HFile v3文件格式。
保护敏感文件
本节中的几个过程要求您在群集节点之间复制文件。复制包含敏感字符串的密钥,配置文件或其他文件时,请使用安全方法(如ssh)来避免泄露敏感数据。
过程:基本的服务器端配置
- 通过在hbase-site.xml中将hfile.format.version设置为3来启用HFile v3。 这是HBase 1.0和更高版本的默认值。
<property>
<name>hfile.format.version</name>
<value>3</value>
</property>
- 为RPC和ZooKeeper启用SASL和Kerberos认证,如security.prerequisites和ZooKeeper的SASL认证中所述。
3.4.13标签
标签是HFile v3的一项功能。 标签是作为单元的一部分的元数据,与密钥,值和版本分开。 标签是实现细节,为其他安全相关功能(如单元级ACL和可见性标签)提供基础。 标签存储在HFiles自身中。 将来可能会使用标签来实现其他HBase功能。 您无需了解很多关于标签的信息,以便使用它们启用的安全功能。
实施细节
每个单元可以有零个或多个标签。
每个标签都有一个类型和实际的标签字节数组。
就像行键,列族,限定符和值可以被编码一样(参见data.block.encoding.types),标签也可以被编码。
您可以在列族级别启用或禁用标签编码,并且默认情况下启用。 使用HColumnDescriptor#setCompressionTags(boolean compressTags)方法来管理列族的编码设置。
您还需要为列族启用DataBlockEncoder,以使标记的编码生效。
如果启用WAL压缩,则可以通过设置WAL的值来启用WAL中每个标记的压缩
在hbase-site.xml中将hbase.regionserver.wal.tags.enablecompression设置为true。 标记压缩使用字典编码。
使用WAL加密时,不支持标记压缩。
3.4.14访问控制标签(ACL)
HBase中的ACL基于用户对组的访问权限,以及给定组访问给定资源的权限。 ACL被实现为称为AccessController的协处理器。
HBase不维护私有组映射,但依赖于Hadoop组映射器,它映射LDAP或Active Directory等目录中的实体和HBase用户。任何支持的Hadoop组映射器都可以工作。然后,针对资源(全局,名称空间,表格,单元或端点)授予用户特定的权限(读取,写入,执行,创建,管理)。
启用Kerberos和访问控制后,客户端对HBase的访问将得到验证,并且用户数据是专用的,除非明确授予访问权限。
与关系数据库相比,HBase具有更简单的安全模型,特别是在客户端操作方面。例如,插入(新记录)和更新(现有记录)之间没有区别,因为两者都折叠成投入。
了解访问级别
HBase访问级别是相互独立授予的,并允许在给定范围内进行不同类型的操作。
读取(R) - 可以读取给定范围的数据
写入(W) - 可以在给定范围写入数据
执行(X) - 可以在给定范围内执行协处理器端点
创建(C) - 可以在给定范围内创建表或删除表(甚至不创建它们)
管理员(A) - 可以执行群集操作,例如平衡群集或在给定范围内分配区域可能的范围包括:
超级用户 - 超级用户可以执行HBase中可用的任何操作,以访问任何资源。在群集上运行HBase的用户是超级用户,分配给HMaster上的hbase-site.xml中的配置属性hbase.superuser的任何主体都是如此。
全局 - 在全局范围授予的权限允许管理员对集群的所有表进行操作。
名称空间 - 在命名空间范围授予的权限适用于给定名称空间内的所有表。
表 - 在表范围授予的权限适用于给定表中的数据或元数据。
ColumnFamily - 在ColumnFamily范围内授予的权限适用于该ColumnFamily内的单元格。
单元格 - 在单元格范围内授予的权限适用于该确切的单元格坐标(键,值,时间戳)。这允许政策与数据一起发展。
要更改特定单元格上的ACL,请使用新ACL写入更新后的单元格,以获得原始坐标的精确坐标。
如果您有多版本模式并且想要更新所有可见版本的ACL,则需要为所有可见版本编写新的单元格。应用程序可以完全控制策略演变。
上述规则的例外是附加和增量处理。追加和增量可以在操作中携带ACL。如果包含在操作中,那么它将应用于追加或增量的结果。否则,保存您正在追加或增加的现有单元的ACL。
访问级别和范围的组合创建了可授予用户的可能访问级别的矩阵。在生产环境中,根据执行特定工作所需的内容来考虑访问级别是有用的。以下列表描述了一些常见类型的HBase用户的适当访问级别。重要的是不要授予给定用户执行其所需任务所需的更多访问权限。
超级用户 - 在生产系统中,只有HBase用户应具有超级用户访问权限。在开发环境中,管理员可能需要超级用户访问才能快速控制和管理群集。但是,这种类型的管理员通常应该是全局管理员而不是超级用户。
全局管理员 - 全局管理员可以执行任务并访问HBase中的每个表。在典型的生产环境中,管理员不应具有对表内数据的读取或写入权限。
具有管理员权限的全局管理员可以在群集上执行群集范围的操作,例如平衡,分配或取消分配区域或调用明确的主要压缩。这是一个操作角色。
具有创建权限的全局管理员可以创建或删除HBase中的任何表。这更像是一个DBA类型的角色。
在当前实施中,具有管理员权限的全局管理员可以授予他自己对表的读写权限并获得对该表数据的访问权限。因此,只需将全局管理员权限授予实际需要它们的可信用户。
还要注意,具有创建权限的全局管理员可以在ACL表上执行Put操作,模拟授予或撤销并绕过Global Admin权限的授权检查。
由于这些问题,请谨慎授予全局管理员权限。
命名空间管理员 - 具有创建权限的命名空间管理员可以在该命名空间内创建或删除表,并获取和恢复快照。具有管理员权限的名称空间管理员可以对该名称空间内的表执行操作,例如拆分或主要压缩。
表管理员 - 表管理员只能在该表上执行管理操作。具有创建权限的表管理员可以从该表创建快照或从快照中恢复该表。具有管理员权限的表管理员可以在该表上执行操作,例如拆分或主要压缩。
用户 - 用户可以读取或写入数据,或两者兼有。如果给定可执行文件,用户也可以执行协处理器端点
权限。
Table
8. Real-World Example of Access Levels
Job |
Scope |
Permissions |
Description |
Senior |
Global |
Access, |
Manages |
Junior |
Global |
Create |
Creates |
Table |
Table |
Access |
Maintains |
Data |
Table |
Read |
Creates |
Web |
Table |
Read, |
Puts |
ACL矩阵
有关ACL如何映射到特定HBase操作和任务的更多详细信息,请参阅附录acl矩阵。
实施细节
单元级ACL使用标签实现(请参阅标签)。为了使用单元级别的ACL,您必须使用HFile v3和HBase 0.98或更高版本。
1.由HBase创建的文件由运行HBase进程的操作系统用户拥有。要与HBase文件交互,您应该使用API或批量加载功能。
2. HBase不在HBase内部为“角色”建模。相反,组名可以被授予权限。这允许通过组成员身份对角色进行外部建模。组通过Hadoop组映射服务在HBase外部创建和操作。
服务器端配置
1.作为先决条件,执行过程:基本服务器端配置中的步骤。
2.通过在hbase-site.xml中设置以下属性来安装和配置AccessController协处理器。这些属性包含一个类的列表。
如果您使用AccessController以及VisibilityController,则AccessController必须首先出现在列表中,因为在这两个组件都处于活动状态时,VisibilityController将委派访问
安全配置示例。
<property>
<name>hbase.coprocessor.region.classes</name>
<value>org.apache.hadoop.hbase.security.access.AccessController,
org.apache.hadoop.hbase.security.token.TokenProvider</value>
</property>
<property>
<name>hbase.coprocessor.master.classes</name>
<value>org.apache.hadoop.hbase.security.access.AccessController</value>
</property>
<property>
<name>hbase.coprocessor.regionserver.classes</name>
<value>org.apache.hadoop.hbase.security.access.AccessController</value>
</property>
<property>
<name>hbase.security.exec.permission.checks</name>
<value>true</value>
</property>
或者,您可以通过将hbase.rpc.protection设置为隐私来启用传输安全性。
这需要HBase
0.98.4或更新版本
3.4.15可见性标签
可见性标签控件可用于仅允许与给定标签关联的用户或主体读取或访问具有该标签的单元格。例如,您可能会标记单元格绝密,并且只能将该标签的访问权授予管理员组。可见性标签是使用标签实现的,标签是HFile v3的一个功能,并允许您以每个单元为基础存储元数据。标签是一个字符串,可以使用逻辑运算符(&,|或!)将标签组合为表达式,并使用圆括号进行分组。 HBase不能对表达式进行任何形式的验证,除了基本的良构。可见性标签本身没有意义,可以用来表示敏感级别,特权级别或任何其他任意的语义含义。
在HBase 0.98.6及更高版本中,可见性标签和表达式支持UTF-8编码。当使用org.apache.hadoop.hbase.security.visibility.VisibilityClient类提供的addLabels(conf,labels)方法创建标签并通过扫描或获取在授权中传递标签时,标签可以包含UTF-8字符以及通常在可见性标签中使用的逻辑运算符,以及普通的Java符号,而不需要任何转义方法。但是,当您通过Mutation传递CellVisibility表达式时,如果使用UTF-8字符或逻辑运算符,则必须用CellVisibility.quote()方法将表达式括起来。请参阅TestExpressionParser和源文件hbase-client / src / test / java
/ org / apache / hadoop / hbase / client / TestScan.java。
用户在Put操作期间向单元添加可见性表达式。在默认配置中,用户不需要访问标签就可以用它来标记单元格。此行为由配置选项hbase.security.visibility.mutations.checkauths控制。如果将此选项设置为true,则用户正在修改的标签作为突变的一部分必须与用户关联,否则突变将失败。在获取或扫描期间确定用户是否有权读取标记的单元格,并且不允许用户读取的结果被过滤掉。这会导致相同的I / O处罚,如同返回结果一样,但会减少网络负载。
可见性标签也可以在删除操作中指定。有关可见性标签和删除的详细信息,请参阅HBASE-10885(https://issues.apache.org/jira/browse/HBASE-10885)。
当RegionServer首次收到请求时,用户的有效标签集建立在RPC上下文中。用户与标签关联的方式是可插入的。默认插件通过添加到获取或扫描的授权中指定的标签,并检查针对主叫用户的已认证标签列表的标签。当客户端传递用户未通过认证的标签时,默认插件会丢弃它们。您可以通过Get#setAuthorizations(Authorizations(String,...))和Scan#setAuthorizations(Authorizations(String,...))传递一部分用户认证标签;方法。
可以按照与用户相同的方式向组授予可见性标签。组前缀为@符号。当检查用户的可见性标签时,服务器将包括用户所属的组的可见性标签以及用户自己的标签。当使用API VisibilityClient#getAuths或Shell命令get_auths为用户检索可见性标签时,我们将返回专门为该用户添加的标签,而不是组级标签。
可见性标签访问检查由VisibilityController协处理器执行。您可以使用界面VisibilityLabelService来提供自定义实现和/或控制可见性标签与单元格一起存储的方式。例如,查看源文件hbase-server / src / test / java / org / apache / hadoop / hbase /
security / visibility / TestVisibilityLabelsWithCustomVisLabService.java。
可见性标签可以与ACL一起使用。
标签必须在可见标签中使用之前明确定义。请参阅下面的示例来了解如何完成此操作。
目前没有办法确定哪些标签已应用于单元格。参见HBASE-12470
(https://issues.apache.org/jira/browse/HBASE-12470)以获取详细信息。
可见性标签目前不适用于超级用户。
Table
9. Examples of Visibility Expressions
Expression |
Interpretation |
fulltime |
Allow |
!public |
Allow |
( secret | topsecret ) |
Allow access |
3.4.16服务器端配置
1.作为先决条件,执行过程:基本服务器端配置中的步骤。
2.通过在hbase-site.xml中设置以下属性来安装和配置VisibilityController协处理器。 这些属性包含类名的列表。
<property>
<name>hbase.coprocessor.region.classes</name>
<value>org.apache.hadoop.hbase.security.visibility.VisibilityController</value>
</property>
<property>
<name>hbase.coprocessor.master.classes</name>
<value>org.apache.hadoop.hbase.security.visibility.VisibilityController</value>
</property>
如果一起使用AccessController和VisibilityController协处理器,AccessController
必须首先出现在列表中,因为在这两个组件都处于活动状态时,VisibilityController会将其系统表上的访问控制委托给AccessController。
3.调整配置
默认情况下,用户可以使用任何标签来标记单元格,包括它们没有关联的标签,这意味着用户可以放入他无法阅读的数据。
例如,即使用户没有与该标签相关联,用户也可以用(假设的)'topsecret'标签来标记单元格。 如果您只希望用户能够标记与其关联的标签的单元格,请将hbase.security.visibility.mutations.checkauths设置为true。 在这种情况下,如果使用用户未关联的标签,则突变将失败。
分发您的配置并重新启动群集以使更改生效。
3.4.17透明的数据静止加密
HBase提供了一种保护静态数据的机制,位于HDFS或其他分布式文件系统中的HFiles和WAL。
双层体系结构用于灵活和非侵入式密钥旋转。 “透明”意味着客户端不需要实施变更。
数据写入时,它被加密。 当它被读取时,它被按需解密。
它是怎么运行的
管理员为集群提供主密钥,该主密钥存储在管理工作站上的每个受信任的HBase进程都可访问的密钥提供程序中,包括HMaster,RegionServers和客户端(如HBase Shell)。默认密钥提供程序与Java KeyStore API和任何支持它的密钥管理系统集成在一起。其他自定义密钥提供者实现是可能的密钥检索机制在hbase-site.xml中配置
外部密钥服务器或硬件安全模块中。此主密钥由HBase进程通过配置的密钥提供程序根据需要进行解析。
接下来,通过创建或修改列描述符以包含两个附加属性,可以在模式中的每列系列中指定加密用途:要使用的加密算法的名称(当前仅支持“AES”),以及可选的数据密钥用集群主密钥打包(加密)。如果没有为ColumnFamily显式配置数据密钥,则HBase将为每个HFile创建一个随机数据密钥。与替代方案相比,这提供了安全性的渐进式改进。除非需要提供明确的数据密钥,例如在使用给定数据密钥生成用于批量导入的加密HFile的情况下,只需在ColumnFamily模式元数据中指定加密算法,并让HBase按需创建数据密钥。每列系列按键便于低影响增量式按键旋转,并减少关键材料的任何外部泄漏的范围。包装后的数据密钥存储在ColumnFamily模式元数据中,并存储在列族的每个HFile中,并使用群集主密钥进行加密。 Column Family配置为加密后,任何新的HFile都将被加密写入。为了确保所有HFile的加密,在启用此功能后触发主要压缩。
当HFile打开时,数据密钥从HFile中提取,用集群主密钥解密,并用于解密HFile的其余部分。如果主密钥不可用,HFile将不可读。如果远程用户以某种方式获取对HDile数据的访问权限,或者由于某些HDFS权限失效或丢弃了不适当的媒体,将无法解密数据密钥或文件数据。
也可以加密WAL。尽管WAL是暂时的,但是如果底层文件系统受到威胁,则有必要对WALEdits进行加密,以避免对加密列族的HFile进行保护。当启用WAL加密时,无论相关的HFile是否加密,所有WAL都会被加密。
服务器端配置
此过程假定您正在使用默认的Java密钥库实现。 如果您正在使用自定义实现,请检查其文档并进行相应调整。
- 使用keytool实用程序为AES加密创建适当长度的密钥。
$ keytool -keystore /path/to/hbase/conf/hbase.jks \
-storetype jceks -storepass **** \
-genseckey -keyalg AES -keysize 128 \
-alias <alias>
- 将****替换为keystore文件的密码,将<alias>替换为HBase服务帐户的用户名或任意字符串。
如果您使用任意字符串,则需要配置HBase才能使用它,如下所述。指定适当的密钥大小。
不要为密钥指定单独的密码,而是按 - .在密钥文件上设置适当的权限并将其分发到所有HBase服务器。
上一个命令在HBase conf /目录中创建了一个名为hbase.jks的文件。 设置此文件的权限和所有权,以便只有HBase服务帐户用户可以读取该文件,并将密钥安全地分发给所有HBase服务器。
配置HBase守护进程。
在区域服务器上的hbase-site.xml中设置以下属性,将HBase守护程序配置为使用由KeyStore文件支持的密钥提供程序或检索集群主密钥。 在下面的示例中,将****替换为密码。
<property>
<name>hbase.crypto.keyprovider</name>
<value>org.apache.hadoop.hbase.io.crypto.KeyStoreKeyProvider</value>
</property>
<property>
<name>hbase.crypto.keyprovider.parameters</name>
<value>jceks:///path/to/hbase/conf/hbase.jks?password=****</value>
</property>
默认情况下,将使用HBase服务帐户名称来解析群集主密钥。 但是,您可以使用任意别名(在keytool命令中)存储它。 在这种情况下,请将以下属性设置为您使用的别名。
<property>
<name>hbase.crypto.master.key.name</name>
<value>my-alias</value>
</property>
配置HBase 1.0以后。 对于以前的版本,请在hbase-site.xml文件中设置以下属性
<property>
<name>hfile.format.version</name>
<value>3</value>
</property>
或者,您可以使用不同的密码提供者,可以是Java密码加密(JCE)算法提供者,也可以是自定义HBase密码实现。
JCE:
安装签名的JCE提供程序(支持128位密钥的AES / CTR / NoPadding模式)
将其最高优先级添加到JCE站点配置文件$
JAVA_HOME / lib / security / java.security。
在hbase-site.xml中更新hbase.crypto.algorithm.aes.provider和hbase.crypto.algorithm.rng.provider选项。
定制HBase密码:
实现org.apache.hadoop.hbase.io.crypto.CipherProvider。
将实现添加到服务器类路径。
更新hbase-site.xml中的hbase.crypto.cipherprovider。
4.配置WAL加密。
通过设置以下属性,在每个RegionServer的hbase-site.xml中配置WAL加密。 您也可以将这些包含在HMaster的hbase-site.xml中,但HMaster没有WAL并且不会使用它们。
<property>
<name>hbase.regionserver.hlog.reader.impl</name>
<value>org.apache.hadoop.hbase.regionserver.wal.SecureProtobufLogReader</value>
</property>
<property>
<name>hbase.regionserver.hlog.writer.impl</name>
<value>org.apache.hadoop.hbase.regionserver.wal.SecureProtobufLogWriter</value>
</property>
<property>
<name>hbase.regionserver.wal.encryption</name>
<value>true</value>
</property>
5.在hbase-site.xml文件上配置权限。
由于密钥库密码存储在hbase-site.xml中,因此您需要确保只有HBase用户可以使用文件所有权和权限来读取hbase-site.xml文件。
6.重新启动群集。
将新的配置文件分发到所有节点并重新启动群集。
行政
管理任务可以在HBase Shell或Java API中执行。
Java API
本节中的Java API示例取自源文件hbase-
服务器/ SRC /测试/ JAVA /组织/阿帕奇/ hadoop的/ HBase的/ UTIL / TestHBaseFsckEncryption.java。
。
这些例子以及它们的源文件都不属于公共HBase API的一部分,仅供参考。有关使用说明,请参阅官方API。
在列系列上启用加密
要在列系列上启用加密,您可以使用HBase Shell或Java API。启用加密后,触发重大压缩。当主要压缩完成时,HFiles将被加密。
旋转数据键
要旋转数据键,首先更改列描述符中的ColumnFamily键,然后触发一个主要压缩。压缩完成后,所有HFile将使用新数据密钥重新加密。在压实完成之前,旧的HFile仍然可以使用旧密钥进行读取。
在使用随机数据键和指定键之间切换
如果将某个列族配置为使用特定的键,并且您希望返回为该列族使用随机生成的键的默认行为,请使用Java API来更改HColumnDescriptor,以便不会使用键ENCRYPTION_KEY发送值。
旋转主密钥
要旋转主密钥,请首先生成并分发新密钥。然后更新KeyStore以包含新的主密钥,并使用不同的别名将旧的主密钥保存在KeyStore中。接下来,将备用配置配置到hbase-site.xml文件中的旧主密钥。
3.4.18
安全批量加载
由于客户端必须将从MapReduce作业生成的文件的所有权转移给HBase,所以在安全模式下的批量加载比正常设置涉及更多。 安全批量加载由名为的协处理器实现
SecureBulkLoadEndpoint(https://hbase.apache.org/devapidocs/org/apache/hadoop/hbase/security/access/SecureBulkLoadEndpoint.html),它使用配置属性hbase.bulkload.staging.dir配置的登台目录,它 默认为
的/ tmp/ HBase的分段/。
安全批量加载算法
只有一次,创建一个世界可穿越的并由运行HBase(模式711或rwx-x-x)的用户所拥有的临时目录。 该目录的列表将如下所示:
$ ls -ld /tmp/hbase-staging
drwx--x--x 2 hbase hbase 68 3 Sep 14:54
/tmp/hbase-staging
用户将数据写入该用户拥有的安全输出目录。
例如,/ user / foo / data。
在内部,HBase创建一个全局可读写的秘密分段目录(-rwxrwxrwx,777)。 例如,/ tmp / hbase-staging / averylongandrandomdirectoryname。 该目录的名称和位置不会公开给用户。 HBase管理这个目录的创建和删除。
用户使数据具有世界可读性和世界可写性,将数据移入随机登台目录,然后调用
SecureBulkLoadClient#bulkLoadHFiles方法。
安全的优势在于秘密目录的长度和随机性。
要启用安全批量加载,请将以下属性添加到hbase-site.xml。