Hbase是一个分布式的列式存储的数据库
行键:是hbase表自带的,每个行键对应一条数据。
列族:是创建表时指定的,为列的集合,每个列族作为一个文件单独存储,存储的数据都是字节数组,其中数据可以有很多,通过时间戳来区分。
物理模型:整个hbase表会拆分成多个region,每个region记录着行键的起始点保存在不同的节点上,查询时就是对各个节点的并行查询,当region很大时使用.META表存储各个region的起始点,-ROOT又可以存储.META的起始点。
Rowkey的设计原则:各个列族数据平衡,长度原则、相邻原则,创建表的时候设置表放入regionserver缓存中,避免自动增长和时间,使用字节数组代替string,最大长度64kb,最好16字节以内,按天分表,两个字节散列,四个字节存储时分毫秒。
列族的设计原则:尽可能少(按照列族进行存储,按照region进行读取,不必要的io操作),经常和不经常使用的两类数据放入不同列族中,列族名字尽可能短
Region过小会发生多次compaction,将数据读一遍并写一遍到hdfs上,占用io,region过大会造成多次split,region会下线,影响访问服务,调整hbase.heregion.max.filesize为256m。
1、列族的数量及列族的势
建议将HBase列族的数量设置的越少越好。当前,对于两个或两个以上的列族HBase并不能处理的很好。这是由于HBase的Flushing和压缩是基于Region的。当一个列族所存储的数据达到Flushing的阈值时,该表中所有列族将同时进行Flushing操作。这将带来不必要的I/O开销,列族越多,该特性带来的影响越大。
此外,还要考虑到同一个表中不同列族所存储的记录数量的差别,即列族的势(Cardinality)。当两个列族数量差别过大时会使包含记录数量较少列族的数据分散在多个Region上,而Region有可能存储在不同的RegionServer上。这样,当进行查询或scan操作的时候,系统效率将会受到影响。
2、行键(RowKey)的设计
首先应该避免使用时序或单调(递减/递增)行键。因为当数据到来的时候,HBase首先需要根据记录的行键来确定存储的位置,即Region的位置,如果使用时序或单调行键,那么连续到来的数据将被分配到同一个Region中,而此时系统的其他Region/RegionServer处于空闲状态,这是分布式最不希望看到的状态。
3、尽量最小化行键和列族的大小
在HBase中,一个具体的值由存储该值的行键、对应的列(列族:列)以及该值的时间戳决定。HBase中索引是为了加速随即访问的速度,索引的创建是基于“行键+列族:列+时间戳+值”的,如果行键和列族的大小过大,甚至超过值本身的大小,那么将会增加索引的大小。并且在HBase中数据记录往往非常之多,重复的行键、列将不但使索引的大小过大,也将加重系统的负担
4、版本的数量
默认情况下为3个,可以通过HColumnDescriptor进行设置,建议不要设置的过大
使用 MapReduce Job 方式,根据 Hbase API 编写 java 脚本,将文本文件用文件流的方式截取,然后存储到多个字符串数组中,在 put 方法下,通过对表中的列族进行 for 循环遍历列名,用 if 判断列名后进行 for 循环调用 put.add 的方法对列族下每一个列进行设值,每个列族下有几个列就赋值几次,没有表就先创建表。
Hbase 中的每张表都通过行键(rowkey)按照一定的范围被分割成多个子表(HRegion),默认一个 HRegion 超过 256M 就要被分割成两个,由 HRegionServer 管理,管理哪些 HRegion由 Hmaster 分配。 HRegion 存取一个子表时,会创建一个 HRegion 对象,然后对表的每个列族(Column Family)创建一个 store 实例,每个 store 都会有 0 个或多个 StoreFile 与之对应,每个 StoreFile 都会对应一个 HFile, HFile 就是实际的存储文件,因此,一个 HRegion 还拥有一个 MemStore 实例。
实时查询,可以认为是从内存中查询,一般响应时间在 1 秒内。 HBase 的机制是数据先写入到内存中,当数据量达到一定的量(如 128M),再写入磁盘中, 在内存中,是不进行数据的更新或合并操作的,只增加数据,这使得用户的写操作只要进入内存中就可以立即返回,保证了 HBase I/O 的高性能
rowKey 最好要创建有规则的 rowKey,即最好是有序的。 HBase 中一张表最好只创建一到两个列族比较好,因为 HBase 不能很好的处理多个列族
1.按指定RowKey 获取唯一一条记录, get方法(org.apache.hadoop.hbase.client.Get)Get 的方法处理分两种 : 设置了 ClosestRowBefore 和没有设置的 rowlock .主要是用来保证行的事务性,即每个 get 是以一个 row 来标记的.一个 row 中可以有很多 family 和 column.
2.按指定的条件获取一批记录, scan 方法(org.apache.Hadoop.hbase.client.Scan)实现条件查询功能使用的就是 scan 方式.1)scan 可以通过 setCaching 与 setBatch 方法提高速度(以空间换时间); 2)scan 可以通过 setStartRow 与 setEndRow 来限定范围([start, end]start 是闭区间, end 是开区间)。范围越小,性能越高。3)scan 可以通过 setFilter 方法添加过滤器,这也是分页、多条件查询的基础。
3.全表扫描,即直接扫描整张表中所有行记录
HBase 中通过 row 和 columns 确定的为一个存贮单元称为 cell。Cell:由{row key, column(= + ), version}是唯一确定的单元 cell中的数据是没有类型的,全部是字节码形式存储
cache:
batch:
在 hbase 中每当有 memstore 数据 flush 到磁盘之后,就形成一个 storefile,当 storeFile 的数量达到一定程度后,就需要将 storefile 文件来进行 compaction 操作。
Compact 的作用:
1>.合并文件
2>.清除过期,多余版本的数据
3>.提高读写数据的效率
HBase 中实现了两种 compaction 的方式:
minor and major. 这两种 compaction 方式的区别是:
HBase 为筛选数据提供了一组过滤器,通过这个过滤器可以在 HBase 中的数据的多个维度(行,列,数据版本)上进行对数据的筛选操作,也就是说过滤器最终能够筛选的数据能够细化到具体的一个存储单元格上(由行键,列名,时间戳定位)。 RowFilter、 PrefixFilter。。。hbase的filter是通过scan设置的,所以是基于scan的查询结果进行过滤.过滤器的类型很多,但是可以分为两大类——比较过滤器,专用过滤器过滤器的作用是在服务端判断数据是否满足条件,然后只将满足条件的数据返回给客户端;如在进行订单开发的时候,我们使用rowkeyfilter过滤出某个用户的所有订单
在 HBase 中无论是增加新行还是修改已有行,其内部流程都是相同的。 HBase 接到命令后存下变化信息,或者写入失败抛出异常。默认情况下,执行写入时会写到两个地方:预写式日志(write-ahead log,也称 HLog)和 MemStore。 HBase 的默认方式是把写入动作记录在这两个地方,以保证数据持久化。只有当这两个地方的变化信息都写入并确认后,才认为写动作完成。MemStore 是内存里的写入缓冲区, HBase 中数据在永久写入硬盘之前在这里累积。当MemStore 填满后,其中的数据会刷写到硬盘,生成一个 HFile。 HFile 是 HBase 使用的底层存储格式。 HFile 对应于列族,一个列族可以有多个 HFile,但一个 HFile 不能存储多个列族的数据。在集群的每个节点上,每个列族有一个 MemStore。大型分布式系统中硬件故障很常见, HBase 也不例外。设想一下,如果 MemStore 还没有刷写,服务器就崩溃了,内存中没有写入硬盘的数据就会丢失。 HBase 的应对办法是在写动作完成之前先写入 WAL。 HBase 集群中每台服务器维护一个 WAL 来记录发生的变化。WAL 是底层文件系统上的一个文件。直到 WAL 新记录成功写入后,写动作才被认为成功完成。这可以保证 HBase 和支撑它的文件系统满足持久性。大多数情况下, HBase 使用Hadoop 分布式文件系统(HDFS)来作为底层文件系统。如果 HBase 服务器宕机,没有从 MemStore 里刷写到 HFile 的数据将可以通过回放WAL 来恢复。你不需要手工执行。 Hbase 的内部机制中有恢复流程部分来处理。每台HBase 服务器有一个 WAL,这台服务器上的所有表(和它们的列族)共享这个 WAL。你可能想到,写入时跳过 WAL 应该会提升写性能。但我们不建议禁用 WAL,除非你愿意在出问题时丢失数据。如果你想测试一下,如下代码可以禁用 WAL: 注意:不写入 WAL 会在 RegionServer 故障时增加丢失数据的风险。关闭 WAL,出现故障时 HBase 可能无法恢复数据,没有刷写到硬盘的所有写入数据都会丢失。
宕机分为 HMaster 宕机和 HRegisoner 宕机,如果是 HRegisoner 宕机, HMaster 会将其所管理的 region 重新分布到其他活动的 RegionServer 上,由于数据和日志都持久在 HDFS中,该操作不会导致数据丢失。所以数据的一致性和安全性是有保障的。如果是 HMaster 宕机, HMaster 没有单点问题, HBase 中可以启动多个 HMaster,通过Zookeeper 的 Master Election 机制保证总有一个 Master 运行。即 ZooKeeper 会保证总会有一个 HMaster 在对外提供服务
导致Hbase挂掉的场景
HMaster
HMaster会出现异常(执行abort())停止的场景如下:
1.zk异常导致的master停止服务是最常见的场景,涉及操作包含但不限于以下:
a)Zk链接超时,超时时间通过zookeeper.session.timeout配置,默认为3分钟, 如果fail.fast.expired.active.master配置的值为false(默认为false),则不会立即abort,而是会尝试恢复zk的过期session;
b)在打开region后,需要从zk中删除opened节点,如果zk有该节点,但是删除失败;
c)在split region过程中,从zk删除split节点时;
d)Master节点改变时;
e)从zk中创建unassigned节点时;
f)在下线disabled的regoin时,从zk中删除disabled的region如果发生zk异常;
g)还有很多操作zk的节点时如果出现异常。
2.在assign时,如果设置region为offlined状态,但是region之前的状态不是closed或者offlined;
3.在assign时,如果无法从.META.表中读取region信息;
4.把新的hbase集群加入到正在运行的hbase集群时,如果zk的/hbase/unassigned节点没有数据;
5.使用线程池批量分配region时,如果出现未被捕获的异常,实现方式如下:
6.在启动master的服务线程时,出现了异常;
7.在hdfs中检查hbase日志路径时,发现了dead的server时,需从hdfs中读出log,如果出现io异常需要检查hdfs文件系统,如果fsOk状态为true,但是通过FSUtils工具类进行检查时出现io异常;
8.在校验并且分配-ROOT-的region时,如果zk异常,或者其它异常(其它异常会重试10次),比如:“-ROOT- is onlined on the dead server”。
HRegionServer
HRegionServer会出现异常停止(执行abort())服务的场景如下:
1.在读写hdfs时如果出现IOException异常,此时会发起hdfs的文件系统检查(checkFileSystem)1.
2.Regionserver的服务线程出现了未捕获异常;
3.在启动HRegionServer时出现异常;
4.在进行HLog回滚时,出现异常;
5.在flush memstore时,如果持久化失败,会重启RS,在重启中把hlog的内容重新加载到memstore;
6.出现zk异常,包括但不限于以下场景:
a)Zk链接超时,超时时间通过zookeeper.session.timeout配置,默认为3分钟,与master不同,如果zk操作不会重试;
b)启动HRegionServer时出现KeeperException异常;
c)在进行split操作时,如果出现异常会进行回滚操作,在回滚过程中需要从zk中删除region的spliting状态,如果删除时出现KeeperException或者回滚的其它操作出现异常;
d)在打开region时,出现了KeeperException异常;
e)在进行hbase集群复制时,很多与zk交互的操作出现KeeperException异常时均会导致abort;
7.在close region时,如果出现异常,比如:不能成功的flush memstore;
8.Flush memstore时,如果HLog发现该region已经在flush则会强制终止JVM,采用的是Runtime.getRuntime().halt(1)方法,该方法不会执行正常退出的关闭钩子,从而不会flush RS的所有region,也不会迁移region,只有等待ZK的session超时后master才会发现该RS不可用,做迁移工作。
总结
Hbase挂掉的可能性有很多,主要由zk或者hdfs的问题导致,因此zk、hdfs的可用对于hbase极其重要,关于zk:
1.zk如果停止了服务则在很多时候会导致master、rs挂掉,hbase集群基本上就失去了服务的能力,因此zk一定要是稳定可靠的,当client已经于rs建立了链接,这时zk挂掉,如果不进行split等小数与zk交互失败会导致触发rs的abort()的操作时rs还是可以提供服务的;
2.如果rs/master进行了长时间的gc或者改动了服务器时间,导致出现zk的session超时会导致rs/master停止服务,目前已经出现了2次因为服务器时间变化导致hbase停止服务的事故;
3.别轻易人为改变zk的hbase节点数据,master/rs在进行很多操作时会比较依赖zk的数据,如果发现不符合预期可能会导致master/rs停止服务,尤其是master。
Master通过ZK知道RS是否可用,一般情况下RS在停止服务时均会正常退出,在正常退出时会从ZK中删除/hbase/rs/$regionserver的节点,Master会监听该节点的被删除,从而较快的(速度取决于所有region关闭时间)对该RS负责的region进行重新分配,如果是强制退出,比如 kill -9或者出现HRegionServer挂掉的第8条时则只有等待ZK的session超时时才会删除RS在ZK的节点(RS在ZK中添加节点时采用的是CreateMode.EPHEMERAL模式,该模式创建的节点会在session关闭时自动删除),那时Master才会进行重新assign。
Kill RS的进程也是正常退出(不能使用kill -9强制退出),RS使用Runtime的addShutdownHook方法注册了jvm关闭钩子,在关闭钩子中会执行RS的退出逻辑,实际上hbase-daemon.sh的停止RS就是采用kill。
引起RegionServer宕机的原因各种各样,有因为Full GC导致、网络异常导致、官方Bug导致(close wait端口未关闭)以及DataNode异常导致等等
HBase检测宕机是通过Zookeeper实现的, 正常情况下RegionServer会周期性向Zookeeper发送心跳,一旦发生宕机,心跳就会停止,超过一定时间(SessionTimeout)Zookeeper就会认为RegionServer宕机离线,并将该消息通知给Master
一旦RegionServer发生宕机,HBase都会马上检测到这种宕机,并且在检测到宕机之后会将宕机RegionServer上的所有Region重新分配到集群中其他正常RegionServer上去,再根据HLog进行丢失数据恢复,恢复完成之后就可以对外提供服务,整个过程都是自动完成的,并不需要人工介入.
默认情况下,Hbase只支持rowkey的查询,对于多条件的组合查询的应用场景,不够给力。
如果将多条件组合查询的字段都拼接在RowKey中显然又不太可能
全表扫描再结合过滤器筛选出目标数据(太低效),所以通过设计HBase的二级索引来解决这个问题。
这里所谓的二级索引其实就是创建新的表,并建立各列值(family:column)与行键(rowkey)之间的映射关系。这种方式需要额外的存储空间,属于一种以空间换时间的方式
内存优化
Ø 垃圾回收优化:CMS, G1(Region)
Ø JVM启动:-Xms(1/64) –Xmx(1/4)
Region优化
Ø 预分区
Ø 禁用major合并,手动合并
客户端优化
Hbase配置优化
设置RPC监听数量
hbase-site.xml
属性:hbase.regionserver.handler.count
解释:默认值为 30,用于指定 RPC 监听的数量,可以根据客户端的请求数进行调整,读写请求较多时,增加此值。
优化 HStore 文件大小
hbase-site.xml
属性:hbase.hregion.max.filesize
解释:默认值 10737418240(10GB),如果需要运行 HBase 的 MR 任务,可以减小此值, 因为一个 region 对应一个 map 任务,
如果单个 region 过大,会导致 map 任务执行时间过长。该值的意思就是,如果 HFile 的大小达到这个数值,则这个 region 会被切分为两个 Hfile
优化 hbase 客户端缓存
hbase-site.xml
属性:hbase.client.write.buffer
解释:用于指定 HBase 客户端缓存,增大该值可以减少 RPC 调用次数,但是会消耗更多内存,反之则反之。一般我们需要设定一定的缓存大小,以达到减少 RPC 次数的目的
指定 scan.next 扫描 HBase 所获取的行数
hbase-site.xml
属性:hbase.client.scanner.caching
解释:用于指定 scan.next 方法获取的默认行数,值越大,消耗内存越大。
flush、compact、split 机制
当 MemStore 达到阈值,将 Memstore 中的数据 Flush 进 Storefile;compact 机制则是把 flush 出来的小文件合并成大的 Storefile 文件。split 则是当 Region 达到阈值,会把过大的 Region 一分为二。
涉及属性:
即:128M 就是 Memstore 的默认阈值
hbase.hregion.memstore.flush.size:134217728
即:这个参数的作用是当单个 HRegion 内所有的 Memstore 大小总和超过指定值时,flush
该 HRegion 的所有 memstore。RegionServer 的 flush 是通过将请求添加一个队列,模拟生产消费模型来异步处理的。那这里就有一个问题,当队列来不及消费,产生大量积压请求时,可能会导致内存陡增,最坏的情况是触发 OOM。
hbase.regionserver.global.memstore.upperLimit:0.4
hbase.regionserver.global.memstore.lowerLimit:0.38
即:当 MemStore 使用内存总量达到 hbase.regionserver.global.memstore.upperLimit 指定值时,将会有多个 MemStores flush 到文件中,MemStore flush 顺序是按照大小降序执行的,直到刷新到 MemStore 使用内存略小于 lowerLimit
HDFS优化
NameNode 元数据备份使用 SSD
定时备份 NameNode 上的元数据
每小时或者每天备份,如果数据极其重要,可以 5~10 分钟备份一次。备份可以通过定时任务复制元数据目录即可。
为 NameNode 指定多个元数据目录
使用 dfs.name.dir 或者 dfs.namenode.name.dir 指定。这样可以提供元数据的冗余和健壮性, 以免发生故障。
NameNode 的 dir 自恢复
设置 dfs.namenode.name.dir.restore 为 true,允许尝试恢复之前失败的 dfs.namenode.name.dir
目录,在创建 checkpoint 时做此尝试,如果设置了多个磁盘,建议允许。
HDFS 保证 RPC 调用会有较多的线程数
属性:dfs.namenode.handler.count
解释:该属性是 NameNode 服务默认线程数,默认值是 10,根据机器的可用内存可以调整为 50~100
属性:dfs.datanode.handler.count
解释:该属性默认值为 10,是 DataNode 的处理线程数,如果 HDFS 客户端程序读写请求比较多,可以调高到 15~20,设置的值越大,内存消耗越多,不要调整的过高,一般业务中,
5~10 即可。
hdfs-site.xml
HDFS 副本数的调整
属性:dfs.replication
解释:如果数据量巨大,且不是非常之重要,可以调整为 2~3,如果数据非常之重要,可以调整为 3~5。
hdfs-site.xml
HDFS 文件块大小的调整
属性:dfs.blocksize
解释:块大小定义,该属性应该根据存储的大量的单个文件大小来设置,如果大量的单个文件都小于 100M,
建议设置成 64M 块大小,对于大于 100M 或者达到 GB 的这种情况,建议设置成 256M,一般设置范围波动在 64M~256M 之间。
hdfs-site.xml
MapReduce Job 任务服务线程数调整
属性:mapreduce.jobtracker.handler.count
解释:该属性是 Job 任务线程数,默认值是 10,根据机器的可用内存可以调整为 50~100
mapred-site.xml
Http 服务器工作线程数
mapred-site.xml
属性:mapreduce.tasktracker.http.threads
解释:定义 HTTP 服务器工作线程数,默认值为 40,对于大集群可以调整到 80~100
文件排序合并优化
mapred-site.xml
属性:mapreduce.task.io.sort.factor
解释:文件排序时同时合并的数据流的数量,这也定义了同时打开文件的个数,默认值为
10,如果调高该参数,可以明显减少磁盘 IO,即减少文件读取的次数。
设置任务并发
mapred-site.xml
属性:mapreduce.map.speculative
解释:该属性可以设置任务是否可以并发执行,如果任务多而小,该属性设置为 true 可以明显加快任务执行效率,但是对于延迟非常高的任务,建议改为 false,这就类似于迅雷下载。
MR 输出数据的压缩
mapred-site.xml
属性:mapreduce.map.output.compress、mapreduce.output.fileoutputformat.compress
解释:对于大集群而言,建议设置 Map-Reduce 的输出为压缩的数据,而对于小集群,则不需要。
优化 Mapper 和 Reducer 的个数
mapred-site.xml
属性:mapreduce.tasktracker.map.tasks.maximum mapreduce.tasktracker.reduce.tasks.maximum
解释:以上两个属性分别为一个单独的 Job 任务可以同时运行的 Map 和 Reduce 的数量。
设置上面两个参数时,需要考虑 CPU 核数、磁盘和内存容量。假设一个 8 核的 CPU,业务内容非常消耗 CPU,那么可以设置 map 数量为 4,如果该业务不是特别消耗 CPU 类型的,
那么可以设置 map 数量为 40,reduce 数量为 20。这些参数的值修改完成之后,一定要观察是否有较长等待的任务,如果有的话,可以减少数量以加快任务执行,
如果设置一个很大的值,会引起大量的上下文切换,以及内存与磁盘之间的数据交换,这里没有标准的配置数值,
需要根据业务和硬件配置以及经验来做出选择。
在同一时刻,不要同时运行太多的 MapReduce,这样会消耗过多的内存,任务会执行的非常缓慢,我们需要根据 CPU 核数,内存容量设置一个 MR 任务并发的最大值,
使固定数据量的任务完全加载到内存中,避免频繁的内存和磁盘数据交换,从而降低磁盘 IO,提高性能。
大概估算公式:
map = 2 + ⅔cpu_core, reduce = 2 + ⅓cpu_core
优化 DataNode 允许的最大文件打开数
hdfs-site.xml
属性:dfs.datanode.max.transfer.threads
解释:HBase 一般都会同一时间操作大量的文件,根据集群的数量和规模以及数据动作,设置为 4096 或者更高。默认值:4096
优化延迟高的数据操作的等待时间
hdfs-site.xml
属性:dfs.image.transfer.timeout
解释:如果对于某一次数据操作来讲,延迟非常高,socket 需要等待更长的时间,建议把该值设置为更大的值(默认 60000 毫秒),以确保 socket 不会被 timeout 掉。
优化数据的写入效率
mapred-site.xml
属性:
mapreduce.map.output.compress mapreduce.map.output.compress.codec
解释:开启这两个数据可以大大提高文件的写入效率,减少写入时间。第一个属性值修改为true,第二个属性值修改为:org.apache.hadoop.io.compress.GzipCodec 或者其他压缩方式
优化 DataNode 存储
属性:dfs.datanode.failed.volumes.tolerated
解释:默认为 0,意思是当 DataNode 中有一个磁盘出现故障,则会认为该 DataNode shutdown 了。
如果修改为 1,则一个磁盘出现故障时,数据会被复制到其他正常的 DataNode 上,当前的 DataNode 继续工作。