HBase
-
NOSQL(Not Only SQL)
高并发的更新(插入、修改、删除)、多表关联后的复杂查询(order by、group by)
-
CAP 定理:
背景: 解决数据库压力的一个好方法是分摊压力,即扩展为分布式的数据库。但是,可能会带来一些原子性的问题。没有原子性,事务就无从谈起了,关系型数据库也就失去了存在的意义。
20世纪90年代初期Berkerly大学有位Eric Brewer教授提出了一个CAP理论。
全称是Consistency Availability and Partition tolerance。
- Consistency(强一致性):数据更新操作的一致性,所有数据变动都是同步的。
- Availability(高可用性):良好的响应性能。
- Partition tolerance(高分区容错性):可靠性。
教授说只能满足其中的两点, 没法让三者全部满足。架构师们应该适当的进行取舍
-
-
NOSQL
数据库的最终一致性: 即数据的操作存在延迟(有时候是不被允许的) 因此推出了NOSQL(非关系型数据库)的概念。
-
HBase
基于BigTable论文研发了BigTable的Java开源版本, 即HBase.
-
分布式、可扩展、支持海量数据存储的NOSQL数据库。面向列存储(即列族,列族下可以有很多的列,需要在建表的时候指定), 构建于Hadoop之上,提供对10亿级别表数据的快速随机实时读写。
-
逻辑架构
- 物理存储架构
- HBase架构
1. Region Server
RegionServer是一个服务,负责多个Region的管理。其实现类为**HRegionServer**
主要作用如下:
对于**数据**的操作:**get, put, delete**;
对于**Region**的操作:**splitRegion**、**compactRegion**。
**客户端**从**ZooKeeper**获取**RegionServer**的地址,从而调用相应的服务,获取数据。
2. Master
Master是所有Region Server的管理者,其实现类为**HMaster**
主要作用如下:
对于**表**的操作:**create, delete, alter**,这些操作可能需要跨**多个ReginServer**,因此需要Master来进行协调!
对于**RegionServer**的操作:分配regions到每个RegionServer,监控每个RegionServer的状态,**负载均衡**和**故障转移**。
> 即使Master进程宕机,集群依然可以执行数据的读写,只是不能进行表的创建和修改等操作!
>
> 当然Master也不能宕机太久,有很多必要的操作,比如创建表、修改列族配置,以及更重要的分割和合并都需要它的操作。
3. ZooKeeper
RegionServer非常依赖ZooKeeper服务,ZooKeeper管理了HBase所有RegionServer的信息,包括具体的数据段存放在哪个RegionServer上。
客户端每次与HBase连接,其实都是先与ZooKeeper通信,查询出哪个RegionServer需要连接,然后再连接RegionServer。Zookeeper中记录了读取数据所需要的**元数据表**hbase:meata,因此关闭Zookeeper后,客户端是无法实现读操作的!
> HBase通过Zookeeper来做master的高可用、RegionServer的监控、元数据的入口以及集群配置的维护等工作。
4. HDFS
**HDFS**为Hbase提供最终的底层**数据存储服务**,同时为HBase提供高可用的支持。
-
HBase 安装、启动
-
启动Zookeeper、Hadoop、解压HBase、配置
-
启动HBase // 需要同步时间服务
bin/HBase-daemon.sh start regionserver
-
Hbase 页面: http://hadoop101:16010
-
Shell 操作和其他操作
bin/HBase shell status // 查看集群状态 version // 查看版本 whoami // 查看操作用户及组信息 table_help // 查看操作信息 help // 查看帮助信息 help 'get' // 查看帮助命令
-
表操作
-
list
-
create:
create ‘表名’, { NAME => ‘列族名1’, 属性名 => 属性值}, {NAME => ‘列族名2’, 属性名 => 属性值}, …
create ‘student’,‘info’
-
desc/describe: 描述表
-
disable: 对表进行维护或者修改的前必须停用
-
enable: 启用表
-
exits: 判断是否存在
-
count
-
drop: 删除表 删除之前必须停用
-
truncate: 截断表
-
get_split: 获取表对应的Region数。刚开始只有一个region
-
alter: 修改表的属性(通常是某个列的属性)
-
-
数据操作
-
scan: 可以按照rowkey的字典顺序来遍历指定的表的数据.
scan 'student' scan 'student',{STARTROW => '1001', STOPROW => '1001'} scan 'student',{STARTROW => '1001'}
-
put: 可以新增记录还可以为记录设置属性。
put 'student','1001','info:name','Nick' put 'student','1001','info:sex','male' put 'student','1001','info:age','18' put 'student','1002','info:name','Janna' put 'student','1002','info:sex','female' put 'student','1002','info:age','20'
-
get: 支持scan所支持的大部分属性。
-
delete: 删除rowkey 的全部(某一列) 数据
-
-
-
HBase 进阶
-
RegionServer架构
-
写流程
1)Client先访问zookeeper,获取hbase:meta表位于哪个Region Server。
2)访问对应的Region Server,获取hbase:meta表,根据读请求的namespace:table/rowkey,查询出目标数据位于哪个Region Server中的哪个Region中。并将该table的region信息以及meta表的位置信息缓存在客户端的meta cache,方便下次访问。
3)与目标Region Server进行通讯;
4)将数据顺序写入(追加)到WAL;
5)将数据写入对应的MemStore,数据会在MemStore进行排序;
6)向客户端发送ack;
7)等达到MemStore的刷写时机后,将数据刷写到HFile。
-
写流程
1)Client先访问zookeeper,获取hbase:meta表位于哪个Region Server。
2)访问对应的Region Server,获取hbase:meta表,根据读请求的namespace:table/rowkey,查询出目标数据位于哪个Region Server中的哪个Region中。并将该table的region信息以及meta表的位置信息缓存在客户端的meta cache,方便下次访问。
3)与目标Region Server进行通讯;
4)分别在Block Cache(读缓存),MemStore和Store File(HFile)中查询目标数据,并将查到的所有数据进行合并。此处所有数据是指同一条数据的不同版本(time stamp)或者不同的类型(Put/Delete)。
5)将查询到的数据块(Block,HFile数据存储单元,默认大小为64KB)缓存到Block Cache。
6)将合并后的最终结果返回给客户端。
-
-
- MemStore Flush
作用是在写入HDFS之前, 将其中的数据整理有序.
-
StoreFile Compaction
由于memstore每次刷写都会生成一个新的HFile,且同一个字段的不同版本(timestamp)和不同类型(Put/Delete)有可能会分布在不同的HFile中,因此查询时需要遍历所有的HFile。
为了减少HFile的个数,以及清理掉过期和删除的数据,会进行StoreFile Compaction。
- Compaction分为两种:分别是Minor Compaction和Major Compaction。
-
Minor Compaction会将临近的若干个较小的HFile合并成一个较大的HFile,但不会清理过期和删除的数据。
-
Major Compaction会将一个Store下的所有的HFile合并成一个大HFile,并且会清理掉过期和删除的数据。
-
Region Split
-
HBase API
-
获取Configuration对象
public static Configuration conf; static{ //使用HBaseConfiguration的单例方法实例化 conf = HBaseConfiguration.create(); conf.set("HBase.zookeeper.quorum", "192.166.9.102"); conf.set("HBase.zookeeper.property.clientPort", "2181"); }
-
判断表是否存在
public static boolean isTableExist(String tableName) throws MasterNotRunningException, ZooKeeperConnectionException, IOException{ //在HBase中管理、访问表需要先创建HBaseAdmin对象 //Connection connection = ConnectionFactory.createConnection(conf); //HBaseAdmin admin = (HBaseAdmin) connection.getAdmin(); HBaseAdmin admin = new HBaseAdmin(conf); return admin.tableExists(tableName); }
-
创建表
public static void createTable(String tableName, String... columnFamily) throws MasterNotRunningException, ZooKeeperConnectionException, IOException{ HBaseAdmin admin = new HBaseAdmin(conf); //判断表是否存在 if(isTableExist(tableName)){ System.out.println("表" + tableName + "已存在"); //System.exit(0); }else{ //创建表属性对象,表名需要转字节 HTableDescriptor descriptor = new HTableDescriptor(TableName.valueOf(tableName)); //创建多个列族 for(String cf : columnFamily){ descriptor.addFamily(new HColumnDescriptor(cf)); } //根据对表的配置,创建表 admin.createTable(descriptor); System.out.println("表" + tableName + "创建成功!"); } }
-
删除表
public static void dropTable(String tableName) throws MasterNotRunningException, ZooKeeperConnectionException, IOException{ HBaseAdmin admin = new HBaseAdmin(conf); if(isTableExist(tableName)){ admin.disableTable(tableName); admin.deleteTable(tableName); System.out.println("表" + tableName + "删除成功!"); }else{ System.out.println("表" + tableName + "不存在!"); } }
-
向表中添加数据
-
获得某一行数据
-
获得所有数据
-
删除某一行数据
-
删除全部数据
-
删除指定的“列族:列”的值
-
-
HBase 优化
高可用
在HBase中Hmaster负责监控RegionServer的生命周期,均衡RegionServer的负载,如果Hmaster挂掉了,那么整个HBase集群将陷入不健康的状态,并且此时的工作状态并不会维持太久。所以HBase支持对Hmaster的高可用配置。
1.关闭HBase集群(如果没有开启则跳过此步)
[atguigu@hadoop102 HBase]$ bin/stop-HBase.sh
2.在conf目录下创建backup-masters文件
[atguigu@hadoop102 HBase]$ touch conf/backup-masters
3.在backup-masters文件中配置高可用HMaster节点
[atguigu@hadoop102 HBase]$ echo hadoop103 > conf/backup-masters
4.将整个conf目录scp到其他节点
[atguigu@hadoop102 HBase]$ scp -r conf/ hadoop103:/opt/module/HBase/
[atguigu@hadoop102 HBase]$ scp -r conf/ hadoop104:/opt/module/HBase/
5.打开页面测试查看
预分区
每一个region维护着startRow与endRowKey,如果加入的数据符合某个region维护的rowKey范围,则该数据交给这个region维护。那么依照这个原则,我们可以将数据所要投放的分区提前大致的规划好,以提高HBase性能。
1.手动设定预分区
HBase> create ‘staff1’,‘info’,‘partition1’,SPLITS => [‘1000’,‘2000’,‘3000’,‘4000’]
2.生成16进制序列预分区
create ‘staff2’,‘info’,‘partition2’,{NUMREGIONS => 15, SPLITALGO => ‘HexStringSplit’}
3.按照文件中设置的规则预分区
创建splits.txt文件内容如下:
aaaa bbbb cccc dddd
然后执行:
create ‘staff3’,‘partition3’,SPLITS_FILE => ‘splits.txt’
4.使用JavaAPI创建预分区
//自定义算法,产生一系列Hash散列值存储在二维数组中 byte[][] splitKeys = 某个散列值函数 //创建HBaseAdmin实例 HBaseAdmin hAdmin = new HBaseAdmin(HBaseConfiguration.create()); //创建HTableDescriptor实例 HTableDescriptor tableDesc = new HTableDescriptor(tableName); //通过HTableDescriptor实例和散列值二维数组创建带有预分区的HBase表 hAdmin.createTable(tableDesc, splitKeys);
RowKey设计
一条数据的唯一标识就是rowkey,那么这条数据存储于哪个分区,取决于rowkey处于哪个一个预分区的区间内,设计rowkey的主要目的 ,就是让数据均匀的分布于所有的region中,在一定程度上防止数据倾斜。接下来我们就谈一谈rowkey常用的设计方案。
1.生成随机数、hash、散列值
原本rowKey为1001的,SHA1后变成:dd01903921ea24941c26a48f2cec24e0bb0e8cc7 原本rowKey为3001的,SHA1后变成:49042c54de64a1e9bf0b33e00245660ef92dc7bd 原本rowKey为5001的,SHA1后变成:7b61dec07e02c188790670af43e717f0f46e8913 在做此操作之前,一般我们会选择从数据集中抽取样本,来决定什么样的rowKey来Hash后作为每个分区的临界值。
2.字符串反转
20170524000001转成10000042507102 20170524000002转成20000042507102
这样也可以在一定程度上散列逐步put进来的数据。
3.字符串拼接
20170524000001_a12e 20170524000001_93i7
内存优化
HBase操作过程中需要大量的内存开销,毕竟Table是可以缓存在内存中的,一般会分配整个可用内存的70%给HBase的Java堆。但是不建议分配非常大的堆内存,因为GC过程持续太久会导致RegionServer处于长期不可用状态,一般16~48G内存就可以了,如果因为框架占用内存过高导致系统内存不足,框架一样会被系统服务拖死。
基础优化
1.允许在HDFS的文件中追加内容
hdfs-site.xml、HBase-site.xml
属性:dfs.support.append 解释:开启HDFS追加同步,可以优秀的配合HBase的数据同步和持久化。默认值为true。
2.优化DataNode允许的最大文件打开数
hdfs-site.xml
属性:dfs.datanode.max.transfer.threads 解释:HBase一般都会同一时间操作大量的文件,根据集群的数量和规模以及数据动作,设置为4096或者更高。默认值:4096
3.优化延迟高的数据操作的等待时间
hdfs-site.xml
属性:dfs.image.transfer.timeout 解释:如果对于某一次数据操作来讲,延迟非常高,socket需要等待更长的时间,建议把该值设置为更大的值(默认60000毫秒),以确保socket不会被timeout掉。
4.优化数据的写入效率
mapred-site.xml
属性: mapreduce.map.output.compress mapreduce.map.output.compress.codec 解释:开启这两个数据可以大大提高文件的写入效率,减少写入时间。第一个属性值修改为true,第二个属性值修改为:org.apache.hadoop.io.compress.GzipCodec或者其他压缩方式。
5.设置RPC监听数量
HBase-site.xml
属性:HBase.regionserver.handler.count 解释:默认值为30,用于指定RPC监听的数量,可以根据客户端的请求数进行调整,读写请求较多时,增加此值。
6.优化HStore文件大小
HBase-site.xml
属性:HBase.hregion.max.filesize 解释:默认值10737418240(10GB),如果需要运行HBase的MR任务,可以减小此值,因为一个region对应一个map任务,如果单个region过大,会导致map任务执行时间过长。该值的意思就是,如果HFile的大小达到这个数值,则这个region会被切分为两个Hfile。
7.优化HBase客户端缓存
HBase-site.xml
属性:HBase.client.write.buffer 解释:用于指定HBase客户端缓存,增大该值可以减少RPC调用次数,但是会消耗更多内存,反之则反之。一般我们需要设定一定的缓存大小,以达到减少RPC次数的目的。
8.指定scan.next扫描HBase所获取的行数
HBase-site.xml
属性:HBase.client.scanner.caching 解释:用于指定scan.next方法获取的默认行数,值越大,消耗内存越大。
9.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
-
扩展 HBase在商业项目中的能力
-
消息量:发送和接收的消息数超过60亿
-
将近1000亿条数据的读写
-
高峰期每秒150万左右操作
-
整体读取数据占有约55%,写入占有45%
-
超过2PB的数据,涉及冗余共6PB数据
-
数据每月大概增长300千兆字节。
布隆过滤器
在日常生活中,包括在设计计算机软件时,我们经常要判断一个元素是否在一个集合中。比如在字处理软件中,需要检查一个英语单词是否拼写正确(也就是要判断它是否在已知的字典中);在 FBI,一个嫌疑人的名字是否已经在嫌疑名单上;在网络爬虫里,一个网址是否被访问过等等。
最直接的方法就是将集合中全部的元素存在计算机中,遇到一个新元素时,将它和集合中的元素直接比较即可。一般来讲,计算机中的集合是用哈希表(hash table)来存储的。它的好处是快速准确,缺点是费存储空间。当集合比较小时,这个问题不显著,但是当集合巨大时,哈希表存储效率低的问题就显现出来了。比
如说,一个像 Yahoo,Hotmail 和 Gmai 那样的公众电子邮件(email)提供商,总是需要过滤来自发送垃圾邮件的人(spamer)的垃圾邮件。一个办法就是记录下那些发垃圾邮件的 email 地址。由于那些发送者不停地在注册新的地址,全世界少说也有几十亿个发垃圾邮件的地址,将他们都存起来则需要大量的网络服务器。如果用哈希表,每存储一亿个 email 地址, 就需要 1.6GB 的内存(用哈希表实现的具体办法是将每一个 email 地址对应成一个八字节的信息指纹googlechinablog.com/2006/08/blog-post.html,然后将这些信息指纹存入哈希表,由于哈希表的存储效率一般只有 50%,因此一个 email 地址需要占用十六个字节。一亿个地址大约要 1.6GB, 即十六亿字节的内存)。
因此存贮几十亿个邮件地址可能需要上百 GB 的内存。除非是超级计算机,一般服务器是无法存储的。
布隆过滤器只需要哈希表 1/8 到 1/4 的大小就能解决同样的问题。
Bloom Filter是一种空间效率很高的随机数据结构,它利用位数组很简洁地表示一个集合,并能判断一个元素是否属于这个集合。Bloom Filter的这种高效是有一定代价的:在判断一个元素是否属于某个集合时,有可能会把不属于这个集合的元素误认为属于这个集合(false positive)。因此,Bloom Filter不适合那些“零错误”的应用场合。而在能容忍低错误率的应用场合下,Bloom Filter通过极少的错误换取了存储空间的极大节省。
下面我们具体来看Bloom Filter是如何用位数组表示集合的。初始状态时,Bloom Filter是一个包含m位的位数组,每一位都置为0,如图9-5所示。
-
为了表达S={x1, x2,…,xn}这样一个n个元素的集合,Bloom Filter使用k个相互独立的哈希函数(Hash Function),它们分别将集合中的每个元素映射到{1,…,m}的范围中。对任意一个元素x,第i个哈希函数映射的位置hi(x)就会被置为1(1≤i≤k)。注意,如果一个位置多次被置为1,那么只有第一次会起作用,后面几次将没有任何效果。如图9-6所示,k=3,且有两个哈希函数选中同一个位置(从左边数第五位)。
在判断y是否属于这个集合时,我们对y应用k次哈希函数,如果所有hi(y)的位置都是1(1≤i≤k),那么我们就认为y是集合中的元素,否则就认为y不是集合中的元素。如图9-7所示y1就不是集合中的元素。y2或者属于这个集合,或者刚好是一个false positive。
· 为了add一个元素,用k个hash function将它hash得到bloom filter中k个bit位,将这k个bit位置1。
· 为了query一个元素,即判断它是否在集合中,用k个hash function将它hash得到k个bit位。若这k bits全为1,则此元素在集合中;若其中任一位不为1,则此元素比不在集合中(因为如果在,则在add时已经把对应的k个bits位置为1)。
· 不允许remove元素,因为那样的话会把相应的k个bits位置为0,而其中很有可能有其他元素对应的位。因此remove会引入false negative,这是绝对不被允许的。
布隆过滤器决不会漏掉任何一个在黑名单中的可疑地址。但是,它有一条不足之处,也就是它有极小的可能将一个不在黑名单中的电子邮件地址判定为在黑名单中,因为有可能某个好的邮件地址正巧对应一个八个都被设置成一的二进制位。好在这种可能性很小,我们把它称为误识概率。
布隆过滤器的好处在于快速,省空间,但是有一定的误识别率,常见的补救办法是在建立一个小的白名单,存储那些可能个别误判的邮件地址。
布隆过滤器具体算法高级内容,如错误率估计,最优哈希函数个数计算,位数组大小计算,请参见http://blog.csdn.net/jiaomeng/article/details/1495500。