哈希Meta数藏多久可以转赠?

HBase 涉及的知识点如下图所示,本文将逐一讲解:

本文档参考了关于 HBase 的官网及其他众多资料整理而成,为了整洁的排版及舒适的阅读,对于模糊不清晰的图片及黑白图片进行重新绘制成了高清彩图。

HBase 是 BigTable 的开源 Java 版本。是建立在 HDFS 之上,提供高可靠性、高性能、列存储、可伸缩、实时读写 NoSql 的数据库系统。

它介于 NoSql 和 RDBMS 之间,仅能通过主键(row key)和主键的 range 来检索数据,仅支持单行事务(可通过 hive 支持来实现多表 join 等复杂操作)。

主要用来存储结构化和半结构化的松散数据。

Hbase 查询数据功能很简单,不支持 join 等复杂操作,不支持复杂的事务(行级的事务)Hbase 中支持的数据类型:byte[]与 hadoop 一样,Hbase 目标主要依靠横向扩展,通过不断增加廉价的商用服务器,来增加计算和存储能力。

HBase 中的表一般有这样的特点:

大:一个表可以有上十亿行,上百万列面向列:面向列(族)的存储和权限控制,列(族)独立检索。稀疏:对于为空(null)的列,并不占用存储空间,因此,表可以设计的非常稀疏。

HBase 的原型是 Google 的 BigTable 论文,受到了该论文思想的启发,目前作为 Hadoop 的子项目来开发维护,用于支持结构化的数据存储。

为分布式存储提供文件系统针对存储大尺寸的文件进行优化,不需要对 HDFS 上的文件进行随机读写直接使用文件数据模型不灵活使用文件系统和处理框架优化一次写入,多次读取的方式

提供表状的面向列的数据存储针对表状数据的随机读写进行优化使用 key-value 操作数据提供灵活的数据模型使用表状存储,支持 MapReduce,依赖 HDFS优化了多次读,以及多次写3. RDBMS 与 HBase 的对比

数据库以表的形式存在支持 FAT、NTFS、EXT、文件系统使用 Commit log 存储日志参考系统是坐标系统使用主键(PK)支持分区使用行、列、单元格

支持向上扩展使用 SQL 查询面向行,即每一行都是一个连续单元数据总量依赖于服务器配置具有 ACID 支持适合结构化数据传统关系型数据库一般都是中心化的支持事务支持 Join

数据库以 region 的形式存在支持 HDFS 文件系统使用 WAL(Write-Ahead Logs)存储日志参考系统是 Zookeeper使用行键(row key)支持分片使用行、列、列族和单元格

支持向外扩展使用 API 和 MapReduce 来访问 HBase 表数据面向列,即每一列都是一个连续的单元数据总量不依赖具体某台机器,而取决于机器数量HBase 不支持 ACID(Atomicity、Consistency、Isolation、Durability)适合结构化数据和非结构化数据一般都是分布式的HBase 不支持事务不支持 Join4. HBase 特征简要海量存储

Hbase 适合存储 PB 级别的海量数据,在 PB 级别的数据以及采用廉价 PC 存储的情况下,能在几十到百毫秒内返回数据。这与 Hbase 的极易扩展性息息相关。正式因为 Hbase 良好的扩展性,才为海量数据的存储提供了便利。

这里的列式存储其实说的是列族存储,Hbase 是根据列族来存储数据的。列族下面可以有非常多的列,列族在创建表的时候就必须指定。

Hbase 的扩展性主要体现在两个方面,一个是基于上层处理能力(RegionServer)的扩展,一个是基于存储的扩展(HDFS)。通过横向添加 RegionSever 的机器,进行水平扩展,提升 Hbase 上层的处理能力,提升 Hbsae 服务更多 Region 的能力。备注:RegionServer 的作用是管理 region、承接业务的访问,这个后面会详细的介绍通过横向添加 Datanode 的机器,进行存储层扩容,提升 Hbase 的数据存储能力和提升后端存储的读写能力。

由于目前大部分使用 Hbase 的架构,都是采用的廉价 PC,因此单个 IO 的延迟其实并不小,一般在几十到上百 ms 之间。这里说的高并发,主要是在并发的情况下,Hbase 的单个 IO 延迟下降并不多。能获得高并发、低延迟的服务。

稀疏主要是针对 Hbase 列的灵活性,在列族中,你可以指定任意多的列,在列数据为空的情况下,是不会占用存储空间的。

负责存储 HBase 的实际数据处理分配给它的 Region刷新缓存到 HDFS维护 HLog执行压缩负责处理 Region 分片

HBase 的修改记录,当对 HBase 读写数据的时候,数据不是直接写进磁盘,它会在内存中保留一段时间(时间以及数据量阈值可以设定)。但把数据保存在内存中可能有更高的概率引起数据丢失,为了解决这个问题,数据会先写在一个叫做 Write-Ahead logfile 的文件中,然后再写入内存中。所以在系统出现故障的时候,数据可以通过这个日志文件重建。

这是在磁盘上保存原始数据的实际的物理文件,是实际的存储文件。

顾名思义,就是内存存储,位于内存中,用来保存当前的数据操作,所以当数据保存在 WAL 中之后,RegsionServer 会在内存中存储键值对。

2) 查询操作通过 rowkey 进行查询

查看 rowkey 下面的某个列族的信息

查看 rowkey 指定列族指定字段的值

查看 rowkey 指定多个列族的信息

指定 rowkey 与列值模糊查询

查询 user 表中的所有信息

指定列族与某个列名查询

指定列族与列名以及限定版本查询

查询 user 表中列族为 info、列标示符为 name 的信息,并且版本最新的 5 个

指定多个列族与按照数据值模糊查询

查询 user 表中指定范围的数据

统计一张表有多少行数据count 'user'

3) 更新操作更新数据值

更新操作同插入操作一模一样,只不过有数据就更新,没数据就添加。

4) 删除操作指定 rowkey 以及列名进行删除

指定 rowkey,列名以及字段值进行删除

首先需要先让该表为 disable 状态,使用命令:

然后才能 drop 这个表,使用命令:

显示 HBase 当前用户,例如:

统计指定表的记录数,例如:

检查表是否存在,适用于表量特别多的情况

该命令可以改变表和列族的模式,例如:

禁用一张表/启用一张表

删除一张表,记得在删除表之前必须先禁用

过滤器的类型很多,但是可以分为两大类——比较过滤器,专用过滤器。

过滤器的作用是在服务端判断数据是否满足条件,然后只将满足条件的数据返回给客户端;

hbase 过滤器的比较运算符:

Hbase 过滤器的比较器(指定比较机制):

查询比 f2 列族小的所有的列族内的数据

查询所有列当中包含 8 的数据

六、HBase 底层原理1. 系统架构

根据这幅图,解释下HBase中各个组件

HBase可以使用内置的Zookeeper,也可以使用外置的,在实际生产环境,为了保持统一性,一般使用外置Zookeeper。

与nosql数据库一样,row key是用来检索记录的主键。访问hbase table中的行,只有三种方式:

Row Key 行键可以是任意字符串(最大长度是 64KB,实际应用中长度一般为 10-100bytes),在hbase内部,row key保存为字节数组。

Hbase会对表中的数据按照rowkey排序(字典顺序)

存储时,数据按照Row key的字典序(byte order)排序存储。设计key时,要充分排序存储这个特性,将经常一起读取的行存储放到一起。(位置相关性)。

注意:字典序对int排序的结果是1,10,100,11,12,13,14,15,16,17,18,19,2,20,21 ... 。要保持整形的自然序,行键必须用0作左填充。

行的一次读写是原子操作 (不论一次读写多少列)。这个设计决策能够使用户很容易的理解程序在对同一个行进行并发更新操作时的行为。

HBase表中的每个列,都归属于某个列族。列族是表的schema的一部分(而列不是),必须在使用表之前定义。

访问控制、磁盘和内存的使用统计都是在列族层面进行的。列族越多,在取一行数据时所要参与IO、搜寻的文件就越多,所以,如果没有必要,不要设置太多的列族。

列族下面的具体列,属于某一个ColumnFamily,类似于在mysql当中创建的具体的列。

HBase中通过row和columns确定的为一个存贮单元称为cell。每个 cell都保存着同一份数据的多个版本。版本通过时间戳来索引。时间戳的类型是 64位整型。时间戳可以由hbase(在数据写入时自动 )赋值,此时时间戳是精确到毫秒的当前系统时间。时间戳也可以由客户显式赋值。如果应用程序要避免数据版本冲突,就必须自己生成具有唯一性的时间戳。每个 cell中,不同版本的数据按照时间倒序排序,即最新的数据排在最前面。

为了避免数据存在过多版本造成的的管理 (包括存贮和索引)负担,hbase提供了两种数据版本回收方式:

保存数据的最后n个版本保存最近一段时间内的版本(设置数据的生命周期TTL)。

用户可以针对每个列族进行设置。

由{row key, column( =<family> + <label>), version} 唯一确定的单元。cell中的数据是没有类型的,全部是字节码形式存贮。

数据的版本号,每条数据可以有多个版本号,默认值为系统时间戳,类型为Long。

3. 物理存储1) 整体结构

HRegion按大小分割的(默认10G),每个表一开始只有一 个HRegion,随着数据不断插入表,HRegion不断增大,当增大到一个阀值的时候,HRegion就会等分会两个新的HRegion。当Table 中的行不断增多,就会有越来越多的 HRegion。

首先HFile文件是不定长的,长度固定的只有其中的两块:Trailer和FileInfo。正如图中所示的,Trailer中有指针指向其他数 据块的起始点。

Magic内容就是一些随机数字,目的是防止数据损坏。

HFile里面的每个KeyValue对就是一个简单的byte数组。但是这个byte数组里面包含了很多项,并且有固定的结构。我们来看看里面的具体结构:

开始是两个固定长度的数值,分别表示Key的长度和Value的长度。紧接着是Key,开始是固定长度的数值,表示RowKey的长度,紧接着是 RowKey,然后是固定长度的数值,表示Family的长度,然后是Family,接着是Qualifier,然后是两个固定长度的数值,表示Time Stamp和Key Type(Put/Delete)。Value部分没有这么复杂的结构,就是纯粹的二进制数据了。

HFile分为六个部分:

Data Block 段–保存表中的数据,这部分可以被压缩.

Meta Block 段 (可选的)–保存用户自定义的kv对,可以被压缩。

File Info 段–Hfile的元信息,不被压缩,用户也可以在这一部分添加自己的元信息。

Trailer–这一段是定长的。保存了每一段的偏移量,读取一个HFile时,会首先读取Trailer,Trailer保存了每个段的起始位置(段的Magic Number用来做安全check),然后,DataBlock Index会被读取到内存中,这样,当检索某个key时,不需要扫描整个HFile,而只需从内存中找到key所在的block,通过一次磁盘io将整个

HFile的Data Block,Meta Block通常采用压缩方式存储,压缩之后可以大大减少网络IO和磁盘IO,随之而来的开销当然是需要花费cpu进行压缩和解压缩。目前HFile的压缩支持两种方式:Gzip,Lzo。

当 StoreFile 大小超过一定阈值后,会把当前的 HRegion 分割成两个,并由 HMaster 分配给相应的 HRegion 服务器,实现负载均衡

客户端检索数据时,先在memstore找,找不到再找storefile。

WAL 意为Write ahead log,类似 mysql 中的 binlog,用来 做灾难恢复时用,Hlog记录数据的所有变更,一旦数据修改,就可以从log中进行恢复。

每个Region Server维护一个Hlog,而不是每个Region一个。这样不同region(来自不同table)的日志会混在一起,这样做的目的是不断追加单个文件相对于同时写多个文件而言,可以减少磁盘寻址次数,因此可以提高对table的写性能。带来的麻烦是,如果一台region server下线,为了恢复其上的region,需要将region

Client先把数据写入到HLog,以防止数据丢失。

然后将数据写入到Memstore。

如果HLog和Memstore均写入成功,则这条数据写入成功

当Storefile越来越大,Region也会越来越大,达到阈值后,会触发Split操作,将Region一分为二。

log)和内存(MemStore)中,MemStore中的数据是排序的,当MemStore累计到一定阈值时,就会创建一个新的MemStore,并且将老的MemStore添加到flush队列,由单独的线程flush到磁盘上,成为一个StoreFile。于此同时,系统会在zookeeper中记录一个redo point,表示这个时刻之前的变更已经持久化了。当系统出现意外时,可能导致内存(MemStore)中的数据丢失,此时使用Log(WAL log)来恢复checkpoint之后的数据。

StoreFile是只读的,一旦创建后就不可以再修改。因此HBase的更新其实是不断追加的操作。当一个Store中的StoreFile达到一定的阈值后,就会进行一次合并(minor_compact, major_compact),将对同一个key的修改合并到一起,形成一个大的StoreFile,当StoreFile的大小达到一定阈值后,又会对

由于对表的更新是不断追加的,compact时,需要访问Store中全部的 StoreFile和MemStore,将他们按row key进行合并,由于StoreFile和MemStore都是经过排序的,并且StoreFile带有内存中索引,合并的过程还是比较快。

master启动进行以下步骤:

Server的对应关系。扫描.META.region的集合,计算得到当前还未分配的HRegion,将他们放入待分配HRegion列表。2) master下线

由于HMaster只维护表和region的元数据,而不参与表数据IO的过程,HMaster下线仅导致所有元数据的修改被冻结(无法创建删除表,无法修改表的schema,无法进行HRegion的负载均衡,无法处理HRegion 上下线,无法进行HRegion的合并,唯一例外的是HRegion的split可以正常进行,因为只有HRegion Server参与),表的数据读写还可以正常进行。因此HMaster下线短时间内对整个HBase集群没有影响。

从上线过程可以看到,HMaster保存的信息全是可以冗余信息(都可以从系统其它地方收集到或者计算出来)

因此,一般HBase集群中总是有一个HMaster在提供服务,还有一个以上的‘HMaster’在等待时机抢占它的位置。

4.(hbase.regionserver.global.memstore.size.lower.limit)默认:堆大小 * 0.4 * 0.95有时候集群的“写负载”非常高,写入量一直超过flush的量,这时,我们就希望memstore不要超过一定的安全设置。在这种情况下,写操作就要被阻塞一直到memstore恢复到一个“可管理”的大小, 这个大小就是默认值是堆大小 * 0.4 *

很大的时候,flush  操作会消耗很多时间。"pre-flush" 操作意味着在 region 下线之前,会先把 memstore 清空。这样在最终执行 close 操作的时候,flush 操作会很快。

也即是每个region的每个列族对应的memstore在flush为hfile的时候,默认情况下当超过3个hfile的时候就会对这些文件进行合并重写为一个新文件,设置个数越大可以减少触发合并的时间,但是每次合并的时间就会越长

把小的storeFile文件合并成大的HFile文件。清理过期的数据,包括删除的数据将数据的版本号保存为1个。

当HRegion达到阈值,会把过大的HRegion一分为二。默认一个HFile达到10Gb的时候就会进行切分。

HBase 当中的数据最终都是存储在 HDFS 上面的,HBase 天生的支持 MR 的操作,我们可以通过 MR 直接处理 HBase 当中的数据,并且 MR 可以将处理后的结果直接存储到 HBase 当中去。

需求:读取 HBase 当中一张表的数据,然后将数据写入到 HBase 当中的另外一张表当中去。

需求一:读取 myuser 这张表当中的数据写入到 HBase 的另外一张表当中去:

注意:列族的名字要与 myuser 表的列族名字相同

第二步:开发 MR 的程序

将我们打好的 jar 包放到服务器上执行:

需求二:读取 HDFS 文件,写入到 HBase 表当中去

准备数据文件,并将数据文件上传到 HDFS 上面去。

第二步:开发 MR 程序

需求四:通过 bulkload 的方式批量加载数据到 HBase 当中去

加载数据到 HBase 当中去的方式多种多样,我们可以使用 HBase 的 javaAPI 或者使用 sqoop 将我们的数据写入或者导入到 HBase 当中去,但是这些方式不是慢就是在导入的过程的占用 Region 资料导致效率低下,我们也可以通过 MR 的程序,将我们的数据直接转换成 HBase 的最终存储格式 HFile,然后直接 load 数据到 HBase 当中去即可。

HBase 中每张 Table 在根目录(/HBase)下用一个文件夹存储,Table 名为文件夹名,在 Table 文件夹下每个 Region 同样用一个文件夹存储,每个 Region 文件夹下的每个列族也用文件夹存储,而每个列族下存储的就是一些 HFile 文件,HFile 就是 HBase 数据在 HFDS 下存储格式,所以 HBase 存储文件最终在 hdfs 上面的表现形式就是 HFile,如果我们可以直接将数据转换为 HFile 的格式,那么我们的 HBase 就可以直接读取加载 HFile 格式的文件,就可以直接读取了。

导入过程不占用 Region 资源

第三步:将代码打成 jar 包然后运行

第四步:开发代码,加载数据

将输出路径下面的 HFile 文件,加载到 hbase 表当中去

或者我们也可以通过命令行来进行加载数据。

3) 分区规则创建于文件中

HBase 中 rowkey 可以唯一标识一行记录,在 HBase 查询的时候,有以下几种方式:

通过 get 方式,指定 rowkey 获取唯一一条记录;通过 scan 方式,设置 startRow 和 stopRow 参数进行范围匹配;全表扫描,即直接扫描整张表中所有行记录。1. rowkey 长度原则

rowkey 是一个二进制码流,可以是任意字符串,最大长度 64kb,实际应用中一般为 10-100bytes,以 byte[]形式保存,一般设计成定长。

建议越短越好,不要超过 16 个字节,原因如下:

MemStore 将缓存部分数据到内存,如果 rowkey 字段过长,内存的有效利用率就会降低,系统不能缓存更多的数据,这样会降低检索效率。

如果 rowkey 按照时间戳的方式递增,不要将时间放在二进制码的前面,建议将 rowkey 的高位作为散列字段,由程序随机生成,低位放时间字段,这样将提高数据均衡分布在每个 RegionServer,以实现负载均衡的几率。

如果没有散列字段,首字段直接是时间信息,所有的数据都会集中在一个 RegionServer 上,这样在数据检索的时候负载会集中在个别的 RegionServer 上,造成热点问题,会降低查询效率。

必须在设计上保证其唯一性,rowkey 是按照字典顺序排序存储的,因此,设计 rowkey 的时候,要充分利用这个排序的特点,将经常读取的数据存储到一块,将最近可能会被访问的数据放到一块。

HBase 中的行是按照 rowkey 的字典顺序排序的,这种设计优化了 scan 操作,可以将相关的行以及会被一起读取的行存取在临近位置,便于 scan。然而糟糕的 rowkey 设计是热点的源头。

热点发生在大量的 client 直接访问集群的一个或极少数个节点(访问可能是读,写或者其他操作)。大量访问会使热点 region 所在的单个机器超出自身承受能力,引起性能下降甚至 region 不可用,这也会影响同一个 RegionServer 上的其他 region,由于主机无法服务其他 region 的请求。

设计良好的数据访问模式以使集群被充分,均衡的利用。为了避免写热点,设计 rowkey 使得不同行在同一个 region,但是在更多数据情况下,数据应该被写入集群的多个 region,而不是一个。

下面是一些常见的避免热点的方法以及它们的优缺点:

这里所说的加盐不是密码学中的加盐,而是在 rowkey 的前面增加随机数,具体就是给 rowkey 分配一个随机前缀以使得它和之前的 rowkey 的开头不同。分配的前缀种类数量应该和你想使用数据分散到不同的 region 的数量一致。加盐之后的 rowkey 就会根据随机生成的前缀分散到各个 region 上,以避免热点。

哈希会使同一行永远用一个前缀加盐。哈希也可以使负载分散到整个集群,但是读却是可以预测的。使用确定的哈希可以让客户端重构完整的 rowkey,可以使用 get 操作准确获取某一个行数据。

第三种防止热点的方法时反转固定长度或者数字格式的 rowkey。这样可以使得 rowkey 中经常改变的部分(最没有意义的部分)放在前面。这样可以有效的随机 rowkey,但是牺牲了 rowkey 的有序性。

反转 rowkey 的例子以手机号为 rowkey,可以将手机号反转后的字符串作为 rowkey,这样的就避免了以手机号那样比较固定开头导致热点问题。

HBase 中 rowkey 是有序的,第一条记录是最后录入的数据。

尽量减少行键和列族的大小在 HBase 中,value 永远和它的 key 一起传输的。当具体的值在系统间传输时,它的 rowkey,列名,时间戳也会一起传输。如果你的 rowkey 和列名很大,这个时候它们将会占用大量的存储空间。

列族尽可能越短越好,最好是一个字符。

冗长的属性名虽然可读性好,但是更短的属性名存储在 HBase 中会更好。

十、HBase 的协处理器

Hbase 作为列族数据库最经常被人诟病的特性包括:无法轻易建立“二级索引”,难以执行求和、计数、排序等操作。

比如,在旧版本的(<0.92)Hbase 中,统计数据表的总行数,需要使用 Counter 方法,执行一次 MapReduce Job 才能得到。

虽然 HBase 在数据存储层中集成了 MapReduce,能够有效用于数据表的分布式计算。然而在很多情况下,做一些简单的相加或者聚合计算的时候, 如果直接将计算过程放置在 server 端,能够减少通讯开销,从而获得很好的性能提升。于是, HBase 在 0.92 之后引入了协处理器(coprocessors),实现一些激动人心的新特性:能够轻易建立二次索引、复杂过滤器(谓词下推)以及访问控制等。

Observer 类似于传统数据库中的触发器,当发生某些事件的时候这类协处理器会被 Server 端调用。

以 HBase0.92 版本为例,它提供了三种观察者接口:

Endpoint 协处理器类似传统数据库中的存储过程,客户端可以调用这些 Endpoint 协处理器执行一段 Server 端代码,并将 Server 端代码的结果返回给客户端进一步处理,最常见的用法就是进行聚集操作。

如果没有协处理器,当用户需要找出一张表中的最大数据,即 max 聚合操作,就必须进行全表扫描,在客户端代码内遍历扫描结果,并执行求最大值的操作。这样的方法无法利用底层集群的并发能力,而将所有计算都集中到 Client 端统一执行,势必效率低下。

利用 Coprocessor,用户可以将求最大值的代码部署到 HBase Server 端,HBase 将利用底层 cluster 的多个节点并发执行求最大值的操作。即在每个 Region 范围内 执行求最大值的代码,将每个 Region 的最大值在 Region Server 端计算出,仅仅将该 max 值返回给客户端。在客户端进一步将多个 Region 的最大值进一步处理而找到其中的最大值。这样整体的执行效率就会提高很多。

3. 协处理器加载方式

协处理器的加载方式有两种,我们称之为静态加载方式( Static Load)和动态加载方式( Dynamic Load)。

通过修改 hbase-site.xml 这个文件来实现, 启动全局 aggregation,能过操纵所有的表上的数据。只需要添加如下代码:

十一、HBase当中的二级索引的简要介绍

HBase的一级索引就是rowkey,我们只能通过rowkey进行检索。如果我们相对hbase里面列族的列列进行一些组合查询,就需要采用HBase的二级索引方案来进行多条件的查询。

常见的二级索引我们一般可以借助各种其他的方式来实现,例如Phoenix或者solr或者ES等。

十二、HBase 调优1. 通用优化

定时备份NameNode上的元数据,每小时或者每天备份,如果数据极其重要,可以5~10分钟备份一次。备份可以通过定时任务复制元数据目录即可。

为NameNode指定多个元数据目录,使用dfs.name.dir或者dfs.namenode.name.dir指定。一个指定本地磁盘,一个指定网络磁盘。这样可以提供元数据的冗余和健壮性,以免发生故障。

NameNode节点必须配置为RAID1(镜像盘)结构。

保持NameNode日志目录有足够的空间,这些日志有助于帮助你发现问题。

因为Hadoop是IO密集型框架,所以尽量提升存储的速度和吞吐量(类似位宽)。

2. Linux优化开启文件系统的预读缓存可以提高读取速度

调整ulimit上限,默认值为比较小的数字

$ ulimit -n 查看允许最大进程数

$ ulimit -u 查看允许打开最大文件数

开启集群的时间同步NTP。

更新系统补丁(提示:更新补丁前,请先测试新版本补丁对集群节点的兼容性)

解释:该属性是NameNode服务默认线程数,的默认值是10,根据机器的可用内存可以调整为50~100

解释:该属性默认值为10,是DataNode的处理线程数,如果HDFS客户端程序读写请求比较多,可以调高到15~20,设置的值越大,内存消耗越多,不要调整的过高,一般业务中,5~10即可。

解释:如果数据量巨大,且不是非常之重要,可以调整为2~3,如果数据非常之重要,可以调整为3~5。

解释:块大小定义,该属性应该根据存储的大量的单个文件大小来设置,如果大量的单个文件都小于100M,建议设置成64M块大小,对于大于100M或者达到GB的这种情况,建议设置成256M,一般设置范围波动在64M~256M之间。

该属性是Job任务线程数,默认值是10,根据机器的可用内存可以调整为50~100

Http服务器工作线程数

解释:定义HTTP服务器工作线程数,默认值为40,对于大集群可以调整到80~100

解释:文件排序时同时合并的数据流的数量,这也定义了同时打开文件的个数,默认值为10,如果调高该参数,可以明显减少磁盘IO,即减少文件读取的次数。

解释:该属性可以设置任务是否可以并发执行,如果任务多而小,该属性设置为true可以明显加快任务执行效率,但是对于延迟非常高的任务,建议改为false,这就类似于迅雷下载。

解释:对于大集群而言,建议设置Map-Reduce的输出为压缩的数据,而对于小集群,则不需要。

解释:以上两个属性分别为一个单独的Job任务可以同时运行的Map和Reduce的数量。

设置上面两个参数时,需要考虑CPU核数、磁盘和内存容量。假设一个8核的CPU,业务内容非常消耗CPU,那么可以设置map数量

4,如果该业务不是特别消耗CPU类型的,那么可以设置map数量为40,reduce数量为20。这些参数的值修改完成之后,一定要观察是否有较长等待的任务,如果有的话,可以减少数量以加快任务执行,如果设置一个很大的值,会引起大量的上下文切换,以及内存与磁盘之间的数据交换,这里没有标准的配置数值,需要根据业务和硬件配置以及经验来做出选择。

在同一时刻,不要同时运行太多的MapReduce,这样会消耗过多的内存,任务会执行的非常缓慢,我们需要根据CPU核数,内存容量设置一个MR任务并发的最大值,使固定数据量的任务完全加载到内存中,避免频繁的内存和磁盘数据交换,从而降低磁盘IO,提高性能。

5. HBase优化在HDFS的文件中追加内容

HDFS 不是不允许追加内容么?没错,请看背景故事:

解释:开启HDFS追加同步,可以优秀的配合HBase的数据同步和持久化。默认值为true。

优化DataNode允许的最大文件打开数

解释:HBase一般都会同一时间操作大量的文件,根据集群的数量和规模以及数据动作,设置为4096或者更高。默认值:4096

**优化延迟高的数据操作的等待时间

解释:如果对于某一次数据操作来讲,延迟非常高,socket需要等待更长的时间,建议把该值设置为更大的值(默认60000毫秒),以确保socket不会被timeout掉。

解释:开启这两个数据可以大大提高文件的写入效率,减少写入时间。第一个属性值修改为true,第二个属性值修改为:org.apache.hadoop.io.compress.GzipCodec

解释:默认为0,意思是当DataNode中有一个磁盘出现故障,则会认为该DataNode shutdown了。如果修改为1,则一个磁盘出现故障时,数据会被复制到其他正常的DataNode上,当前的DataNode继续工作。

解释:默认值为30,用于指定RPC监听的数量,可以根据客户端的请求数进行调整,读写请求较多时,增加此值。

解释:默认值(10GB),如果需要运行HBase的MR任务,可以减小此值,因为一个region对应一个map任务,如果单个region过大,会导致map任务执行时间过长。该值的意思就是,如果HFile的大小达到这个数值,则这个region会被切分为两个Hfile。

优化hbase客户端缓存

解释:用于指定HBase客户端缓存,增大该值可以减少RPC调用次数,但是会消耗更多内存,反之则反之。一般我们需要设定一定的缓存大小,以达到减少RPC次数的目的。

解释:用于指定scan.next方法获取的默认行数,值越大,消耗内存越大。

HBase操作过程中需要大量的内存开销,毕竟Table是可以缓存在内存中的,一般会分配整个可用内存的70%给HBase的Java堆。但是不建议分配非常大的堆内存,因为GC过程持续太久会导致RegionServer处于长期不可用状态,一般16~48G内存就可以了,如果因为框架占用内存过高导致系统内存不足,框架一样会被系统服务拖死。

同时处理垃圾回收的线程数

解释:该属性设置了同时处理垃圾回收的线程数。

解释:防止开发人员手动调用GC

该值会直接关系到master发现服务器宕机的最大周期,默认值为30秒,如果该值过小,会在HBase在写入大量数据发生而GC时,导致RegionServer短暂的不可用,从而没有向ZK发送心跳包,最终导致认为从节点shutdown。一般20台左右的集群需要配置5台zookeeper。

十三、HBase 大厂面试题解析1. Hbase是怎么写数据的?

由此过程可知,HBase只是增加数据,没有更新和删除操作,用户的更新和删除都是逻辑层面的,在物理层面,更新只是追加操作,删除只是标记操作。

用户写操作只需要进入到内存即可立即返回,从而保证I/O高性能。

首先一点需要明白:Hbase是基于HDFS来存储的。

一次性写入,多次读取。

主要是可以部署在许多廉价机器中,通过多副本提高可靠性,提供了容错和恢复机制。

瞬间写入量很大,数据库不好支撑或需要很高成本支撑的场景。

数据需要长久保存,且量会持久增长到比较大的场景。

HBase不适用与有 join,多级索引,表关系复杂的数据模型。

大数据量(100s TB级数据)且有快速随机访问的需求。如:淘宝的交易历史记录。数据量巨大无容置疑,面向普通用户的请求必然要即时响应。

业务场景简单,不需要关系数据库中很多特性(例如交叉列、交叉表,事务,连接等等)。

4. 热点现象(数据倾斜)怎么产生的,以及解决方法有哪些

某个小的时段内,对HBase的读写请求集中到极少数的Region上,导致这些region所在的RegionServer处理请求量骤增,负载量明显偏大,而其他的RgionServer明显空闲。

HBase中的行是按照rowkey的字典顺序排序的,这种设计优化了scan操作,可以将相关的行以及会被一起读取的行存取在临近位置,便于scan。然而糟糕的rowkey设计是热点的源头。

热点发生在大量的client直接访问集群的一个或极少数个节点(访问可能是读,写或者其他操作)。大量访问会使热点region所在的单个机器超出自身承受能力,引起性能下降甚至region不可用,这也会影响同一个RegionServer上的其他region,由于主机无法服务其他region的请求。

为了避免写热点,设计rowkey使得不同行在同一个region,但是在更多数据情况下,数据应该被写入集群的多个region,而不是一个。常见的方法有以下这些:

加盐:在rowkey的前面增加随机数,使得它和之前的rowkey的开头不同。分配的前缀种类数量应该和你想使用数据分散到不同的region的数量一致。加盐之后的rowkey就会根据随机生成的前缀分散到各个region上,以避免热点。

哈希:哈希可以使负载分散到整个集群,但是读却是可以预测的。使用确定的哈希可以让客户端重构完整的rowkey,可以使用get操作准确获取某一个行数据

反转:第三种防止热点的方法时反转固定长度或者数字格式的rowkey。这样可以使得rowkey中经常改变的部分(最没有意义的部分)放在前面。这样可以有效的随机rowkey,但是牺牲了rowkey的有序性。反转rowkey的例子以手机号为rowkey,可以将手机号反转后的字符串作为rowkey,这样的就避免了以手机号那样比较固定开头导致热点问题

时间戳反转:一个常见的数据处理问题是快速获取数据的最近版本,使用反转的时间戳作为rowkey的一部分对这个问题十分有用,可以用 Long.Max_Value - timestamp 追加到key的末尾,例如[key][reverse_timestamp],[key]的最新值可以通过scan [key]获得[key]的第一条记录,因为HBase中rowkey是有序的,第一条记录是最后录入的数据。

比如需要保存一个用户的操作记录,按照操作时间倒序排序,在设计rowkey的时候,可以这样设计[userId反转][Long.Max_Value - timestamp],在查询用户的所有操作记录数据的时候,直接指定反转后的userId,startRow是[userId反转][],stopRow是[userId反转][Long.Max_Value -

HBase建表预分区:创建HBase表时,就预先根据可能的RowKey划分出多个region而不是默认的一个,从而可以将后续的读写操作负载均衡到不同的region上,避免热点现象。

长度原则:100字节以内,8的倍数最好,可能的情况下越短越好。因为HFile是按照 keyvalue 存储的,过长的rowkey会影响存储效率;其次,过长的rowkey在memstore中较大,影响缓冲效果,降低检索效率。最后,操作系统大多为64位,8的倍数,充分利用操作系统的最佳性能。

散列原则:高位散列,低位时间字段。避免热点问题。

唯一原则:分利用这个排序的特点,将经常读取的数据存储到一块,将最近可能会被访问 的数据放到一块。

原则:在合理范围内能尽量少的减少列簇就尽量减少列簇,因为列簇是共享region的,每个列簇数据相差太大导致查询效率低下。

最优:将所有相关性很强的 key-value 都放在同一个列簇下,这样既能做到查询效率最高,也能保持尽可能少的访问不同的磁盘文件。以用户信息为例,可以将必须的基本信息存放在一个列族,而一些附加的额外信息可以放在另一列族。

7. HBase 中 compact 用途是什么,什么时候触发,分为哪两种,有什么区别

清除过期,多余版本的数据

Minor 操作只用来做部分文件的合并操作以及包括 minVersion=0 并且设置 ttl 的过期版本清理,不做任何删除数据、多版本数据的清理工作。

}

不过,B20对本章节的最重要意义是告诉我们:NFT也是可以“所有权拆分”的形式变相拆分成FT的

有人提出可拆分NFT(Fractional NFT)概念,设计了ShardingDAO拆分解决方案,用户可将 NFT 进行碎片化,每个 NFT 碎片(Shard)都代表 NFT 资产所有权的一部分。

可是可是,你把NFT拆分了,那不就成了一堆加密货币的集合了吗

这所谓的NFT特征“不可拆分”也有NFT违反了。

甚至有人提出把拆分后的CryptoPunks进一步拆分,这就有点儿行为艺术。

其实,把权益进行拆分公开发行,那就是股票,而股票作为金融是需要严格监管的。

早期股市由于缺乏监管出现过许多乱象,了解香港证券市场历史的人,再看看现在的币圈、NFT圈,难免会感到一丝“亲切”。

夏虫不可语冰,泡沫这种东西,只要时间拉得够长,总是要破的。

也有可能是还没遇到那颗钉子。

这里做个简单介绍,佳士得官网提到这个数字藏品有一个“40913”的Token,这个“40913”的Token代表的哈希值指向一串JSON数据。

而这段数据里包含《Everydays: The First 5000 Days》缩略图和原图的地址和哈希值,他们的关系大概如下图所示。

比起CryptoPunks直接指向文件的哈希值,《Everydays: The First 5000 Days》的这种指向更模糊,这就是像声明你对一张小纸条拥有所有权,而小纸条上写着,你对某张钞票有所有权。

如果我找来一张草稿纸,写上我才是这张钞票的拥有者,其实也是可行的。因为只要随便改动一个像素或标点符号,文件的哈希值就发生了变化。

如果我们更改最后的那个句号为感叹号,哈希值就变了,在区块链上就不会出现“哈希值相同”这样的矛盾,除非有人工干预。

但是从权属上出现了权属矛盾,因为从字段数据来看,它们最终都指向了《Everydays: The First 5000 Days》那张图片呢。

而且,用字段数据来确定权属,目前没有法律来进行相应的保护,也许未来会有,但最核心的问题是:以哈希值作为确权工具并不合适。

一个很明显的原因:哈希值的唯一性由于过于严格,容错性很差,缺乏作为确权工具的价值

如之前所言,做出任何微小的修改,哈希值都会发生改变。

如果钞票有哈希值,那么只要它弯折了或者缺了一个角,它的哈希值就变了。如果房子有哈希值,那么只要随着时间发生了老化或腐蚀,它的哈希值也变了。如果人脸有哈希值,你脸上长了个痘,哈希值就变了,通不过以哈希值为算法的人脸识别。

但现实中我们对唯一性的要求,其实是留有一定的容错性的,即使是保存极佳的艺术品,也会随着时间发生改变,这不否认我们承认其唯一性。

优秀的小说被被抄袭被洗稿,我们也不要求完全一致才能认定抄袭。

2021年12月,数字艺术家Lois van Baarle表示:在全球最大的NFT市场OpenSea上她发现有132件有关她的NFT艺术品正在出售,都是未经许可的。

Lois van Baarle认出自己的作品是靠哈希值吗?当然不是。

别人把Lois van Baarle的原图上传区块链,哈希值独一无二且与原图哈希值相同,这是唯一的不假,但这是经过Lois van Baarle许可的吗?

如果我在Lois van Baarle的作品上改变了一个像素,哈希值改变了且是独一无二的,然后把文件上传区块链,这个作品是唯一的吗?

显然也不是,很明显这是抄袭。

以此类推,我们能发现把哈希值作为唯一性是很荒谬的:在保护商标唯一性时,我们也不会以哈希值这么严苛的标准进行判定。

哈希值这种唯一性,看似严密无懈可击,但无法起到任何实际意义,反而等于告诉大众,它无法保护你的唯一性

那么,如果不用哈希值,而是用网络存储是否能解决这个问题?

这就要提到NFT“去中心化”这个理念的可行性了。

去中心化方面,NFT在web 3.0中并不特殊,别的web 3.0有的问题它也有。

别的web 3.0没有的问题,他也有。

比如NFT所对应的数字资产(比如图片)限于成本和技术往往是不上链的,而是存储在链外,这就导致一些NFT的存储是在中心化的服务器上。

在“DeFi之道”的《浅析不同 NFT 数据存储方法的优缺点以及未来展望》一文中分析了NFT数字资产的存储情况:

总结一下,交易量排名前100的NFT合约中,48%的数字资产存储在去中心化的IPFS上,而39%的数字资产则储存在中心化服务器中。

换言之,这39%的NFT就没有做到所谓的“去中心化”

那存储在IPFS上的数字资产呢?

IPFS是一种点对点的分布式文件系统,有点儿类似于BT下载(BitTorrent),这个技术的确有许多值得研究的地方,也有一些畅想空间。

但目前来看IPFS有安全性差、稳定性差、上传·读取效率低等问题,导致没有大规模应用的商业价值

很多文章列举的IPFS的优点,实际上并不现实。

比如一些文章说IPFS的文件分布在各个电脑里,所以没有丢失的可能,我用人话帮各位捋捋他们的观点:

“那些异地备份的商用服务器很容易关闭,一旦关闭你的数据就没有了,而我们的IPFS数据分散在张三李四王五的电脑里(也可能这三台全是李四的电脑),他们的电脑会永远给我们提供存储空间,欧耶!”

你想想BT上那些陈年老片的下载速度就知道这观点靠不靠谱了。

其他很多问题是也从逻辑上出现了矛盾,比如防范恶意节点和建立核心节点,但这不就是“中心化”吗?

但IPFS提倡又是去中心化,so……

这就好比一个城市的同类产业往往会聚集在一起,就像深圳华强北和义乌小商品城,但如果非要把这些店铺分散开,其实效率是降低的。

就算IPFS是分散的,它同样也有受到51%攻击的风险,无法保证数据的安全。

如果链下存储的数据失效,在链上所保存的 NFT 所有权凭证,只是一张废纸。没错,这就是NFT的另一个核心缺陷:虽然区块链是不可篡改的,但链下的数据是可篡改的,最终导致NFT的不可篡改性遭到质疑

NFT是不可篡改的吗?

链下存储型NFT不可篡改的只有所有权凭证,但不是资产本身,如果资产篡改了,所有权没篡改又有什么意义呢?

2021年3月,一位名叫neitherconfirm的加密艺术家在OpenSea上出售了26种彩色玻璃风格的人物和动物面孔的NFT,然而没过多久购买这些NFT的投资者发现,他们买的这些图片变成了地毯图片。

这里艺术家借用了地毯(rug)在加密货币或DeFi中的特殊含义:“Rug Pull”是指加密货币发起者突然放弃一个项目卷款逃跑的意思。

这个案例也告诉各位,所谓的NFT的“不可篡改性”有多么脆弱。

NFT的其他特点,例如“可追溯性”也往往在现实中无法实现。由于匿名性和交易成本过低,NFT可以转手成千上万次,最后面对浩如烟海的交易数据,其实就是不可追溯的。

从目前大部分的NFT案例来看,说“NFT是骗局”也没什么问题,就跟说“印度人能吃辣”没问题一样。甚至对于NFT的诞生原因,笔者有一个猜测,但这就是诛心论,这里就不讲了。

但是,笔者也从极少数的案例中,发现了NFT的价值,只要你不拘泥于那些已经被笔者证伪的条条框框,NFT是可以创造价值、带动社会发展的。

不过那就是下一篇文章的话题了。

}

在合约内启用元交易是一个强大的补充。要求用户持有ETH来支付Gas一直以来都是而且仍然是新用户进入的最大挑战之一。如果只是简单的点击,谁知道现在会有多少人在使用以太坊?

但有时,解决方案可以在你的合约中加入元交易能力。实现起来可能比你想象的要容易。

元交易是一个普通的以太坊交易,它包含另一个交易,即实际交易。实际交易由用户签署,然后发送给运营商(或类似的操作者),用户不需要Gas和区块链交互。而是由运营商支付费用签署交易,提交给区块链。

合约确保在实际交易上有一个有效的签名,然后执行它。

如果我们想在合约中支持广义的元交易,可以通过几个简单的步骤完成。从高层次上讲,有两个步骤:

第1步:验证元交易的签名。按照标准和ecrecover创建一个哈希值来完成:

第2步:一旦得到验证,我们就可以提取实际的交易数据。通过对当前的合约地址使用delegatecall,执行一个函数(而不做新的合约调用)。请记住,delegatecall 使用当前合约的状态去调用合约的代码。因此,通过执行address(this).delegatecall,可以在当前的合约中执行所有的功能,并且可以传递交易数据。

大致就是这样。但也有一些关键的信息需要验证,也有签名的替代品。

让我们来看看更多细节。

正如我们所看到的,执行的核心是 delegatecall。这是实际交易被执行的地方。但是为了确保正确的执行,我们必须确保一些事情是正确的。

首先让我们看看交易结构中的数据,包含了用户设定的所有相关要求,以及 bytes data 作为将要执行的交易本身。data 也是从用户传递到运营商再到合约的内容:

我们还需要在所有这些数据上计算一个哈希值。这将用于签名 schema和防止同一交易的重复执行。关于这方面的细节,请看最后的签名解释。

这是交易schema的哈希值:

这是EIP712 Schema 的哈希值,可以在合约的构造函数中计算一次。

通过hash所有相关的值,我们可以确保只有原用户签名的交易才会成功执行。例如,即使运营商只是改变了expirationTimeSeconds中的1秒,它也不能成功执行。

这只是哈希值的第一部分,要了解包括安全签名要求在内的全部细节,请阅读下面关于签名的部分。

如果我们只是执行delegatecall,交易的msg.sender仍然是元交易的运营商,而不是原始签名者。

我们可以通过设置一个上下文变量来解决这个问题:

我们要防止的另一件事是执行元-元-交易。(除非你想无缘无故地耍酷)

它没有任何作用,只是浪费了额外的Gas。因此,我们可以在任何交易执行之前添加检查。

我们将进一步确保所有规定的条件得到满足

  • 过期时间是有用的,用户需要知道一个交易在几个月后不会被执行。
  • 一个由用户定义的Gas价格。这在你的系统中可能不需要。因为Gas是由运营商支付的,需要指定gas交易成本的唯一原因是该值在交易中会具有进一步对交易的影响。例如,在0x中,Gas价格会影响费用价格。

当然,我们只想执行有有效签名的交易。一个天真的解决方案可能只处理 transaction.data 并签名。

  • 我们如何确保所有额外的交易参数被正确设置(过期时间、salt、signer...)?
  • 我们如何防止一个已签名的交易被多次使用?

第一部分很简单,我们用前面的_getTransactionTypedHash函数在所有这些值上创建一个哈希值。第二部分通过解决的问题,你可以看到如何从交易数据和额外的EIP-712数据中创建一个哈希值,代码如下:

将额外的信息放入我们的哈希值中,因此,一个已签署的交易只能准确地用于该合约与给定的链Id。所有的细节,请查看EIP或我之前关于的文章。

好了,现在我们有了完整的交易哈希值和用户的签名。我们可以通过一个辅助工具提取byte32值来获得三个值r、s、v,这三个值是签名中的椭圆曲线签名值。uint8的v值只需要一个简单的转换。

使用ecrecover与给定的签名和交易哈希,可计算出一个签名者地址。如果这个地址与transaction.signerAddress相匹配,则签名确实有效。

这就是常规的签名方案。如果你需要用户签署他自己的交易,它就能完美地工作。

但如果你想让智能合约创建有效的签名呢?

一个更高级的使用场景是让智能合约签署元交易,但想象一下,用户把他的资金放在一个多签名的智能合约里面。这对于某些钱包来说已经很常见了。这个用户不能用EIP-712方案签署交易来创建一个有效的v、r、s签名。

这就是的作用,它允许智能合约来验证签名。标准本身没有说明合约如何做到这一点。唯一的定义是函数签名,其定义是:

其中有效签名的返回值为0x1626ba7e。如何实现签名逻辑则取决于智能合约开发者。

那么,我们怎样才能验证这样的签名呢?

你可以在下边看到一个实现的例子。使用staticcall,我们可以确保在调用过程中没有进一步的状态修改发生。如果结果成功并且有一个有效的returnData长度(),我们可以检查返回值是否符合0x1626ba7e

你可能想允许更多的签名方法,比如预签名或拥有可以代表用户签名的运营商。请看0x中的现有类型,以获得一些灵感。

到目前为止,我们已经看到了所有实现的关键部分,这应该让你对如何实现它有一个好的启发。我还建议你看一下:

Openzeppelin EIP-712库仍然是一个草案,但对链ID可能改变的分叉情况有额外支持。也可以看看0x代码,本博文中的很多实现都来自于此。



}

我要回帖

更多关于 meta信标怎么获得 的文章

更多推荐

版权声明:文章内容来源于网络,版权归原作者所有,如有侵权请点击这里与我们联系,我们将及时删除。

点击添加站长微信