声明: 1. 本文为我的个人复习总结, 并非那种从零基础开始普及知识 内容详细全面, 言辞官方的文章
2. 由于是个人总结, 所以用最精简的话语来写文章
3. 若有错误不当之处, 请指出
数据种类:
- 结构化数据 比如MySQL这种关心型数据库里的数据, 操控起来方便半结构化数据 有特定语法格式的数据, 比如json非结构化数据 视频,音频,文本 这种难以处理的数据
关系型数据库 & 非关系型(NoSQL)数据库:
- 数据类型不同: 关系型数据库的数据类型是二维表格, NoSQL数据库的数据类型没有确定形式, 很灵活, 有KV型, 列式, 文档型等等关系型数据库支持SQL语法, 而NoSQL数据库不支持SQL语法
行式数据库 & 列式数据库
存储方式不同:
比如有A列和B列, 数据有A1, A2, A3, B1, B2, B3
行式存储为: [A1, B1, A2, B2, A3, B3]; 列式存储为: [A1, A2, A3, B1, B2, B3]
擅长领域不同: 行式数据库适用于查询, 列式数据库适用于聚合统计
从存储方式上可以看出, 行式存储的某一行的不同列之间紧挨着, 适合查询一整行数据;
列式存储的某一列的不同行之间紧挨着, 适合查询一整列的数据
而大数据场景, 大多数时都是数据的统计。我们经常只是需要其中某一列进行聚合统计,而不关心其他列的数据
压缩效果不同, 列式存储压缩效果更好
OLTP & OLAP
OLTP: 联机事务处理, 一般是那种 后端的关系型数据库(存储数据少, 行式数据库MySQL等)
OLAP: 联机事务分析, 一般是那种 大数据的NoSQL数据库(存储数据多, 列式数据库如Hbase/ClickHouse, Presto)
架构:有RowKey, 列族, 列, Region, StoreFile(HFile), Cell, 命名空间, 表
增改为Put, 删除为Delete(本质也是Put), 关键字段timestap就代表着版本
结构图:
一个Region是多行相邻数据, 按列族(对应StoreFile)存储,
自带的命名空间:
- hbase 存放的是Hbase内置的表default 表是用户默认使用的命名空间
Hbase定义表时只需要声明列族即可, 不需要声明具体的列, 字段可以动态、按需指定。因此适合应对字段变更的场景。
BlockCache是用来优化读操作的缓存, MemCache是为了优化写操作的缓冲(到刷写时机了批量写到磁盘)
WAL预写日志:
Hbase的WAL只是为了备份数据, flush和compact的时候并不用它(flush用的是内存数据, compact用的是HFile)
Hadoop的EditLog除了是用来备份数据, 与FsImage合并时也要用到它
hbase truncate不要乱用, 它会同时删除元数据信息如分区键规则, 协处理器倒是不会删掉
LSM:
Hbase和ClickHouse这种内存+磁盘的架构, 即用的是LSM结构(日志结构的合并树)
思想: 假定内存足够大,因此不需要每次有数据更新就必须将数据写入到磁盘中,而可以先将最新的数据驻留在内存中;
等到积攒到足够多之后,再归并排序将数据合并追加到磁盘
与B+树相比:
- 牺牲读性能(读取时可能需要访问较多的磁盘文件)提高写性能(刷写落盘前有一个内存缓冲区)
补偿读性能的优化:
- Bloom Filter 读HFile时使用布隆过滤器compact 小树合并为大树
架构角色:
HMaster
负责DDL操作: 对于表结构 create, delete, alter
负责管理调度: 分配regions到每个RegionServer(并做负载均衡和故障转移)
监控每个RegionServer的状态
HRegion Server
管理Region, WAL, 负责table数据的实际读写, 执行Flush 和 Compaction
ZooKeeper
Hbase通过ZooKeeper来做Master的高可用
监控RegionServer
Client请求ZooKeeper meta表位于哪个RegionServer
HDFS
写流程:
Client先访问ZooKeeper,获取hbase:meta表位于哪个Region Server
访问对应的Region Server,获取hbase:meta表,
根据namespace:table:rowkey,查询出目标数据位于哪个Region Server中的哪个Region
并将该表的 Region信息&meta表的位置信息 缓存在客户端的meta cache
与目标Region Server进行通讯先在MemStore中查询目标数据,再去Block Cache中查询目标数据,如果找不到的话再去查询Store File(HFile)将查询到的新的数据块(HFile的数据存储单元, 默认大小为64KB)缓存到Block Cache返回这两次查到的 最新版本的数据
MemStore Flush刷写时机:1)Client先访问ZooKeeper,获取hbase:meta表位于哪个Region Server
2)访问对应的Region Server,获取hbase:meta表,
根据namespace:table:rowkey, 查询出目标数据所在表位于哪个Region
并将该表的 Region信息&meta表的位置信息 缓存在客户端的meta cache
3)与目标Region Server进行通讯
4)将数据顺序写入(追加)到HLog(WAL), 做备份
5)将数据写入对应的MemStore,数据会按RowKey在MemStore进行排序, 便于以后的查找缩小范围
6)Region Server向客户端发送ack
7)等达到MemStore的刷写时机后,将数据刷写到HFile
某个MemStore的大小达到了hbase.hregion.memstore.flush.size(默认128M)
什么时候阻止继续往某个MemStore写数据?
当MemStore的大小达到了hbase.hregion.memstore.flush.size(默认125M) * hbase.hregion.memstore.block.multiplier(默认4), 即500M时
某个RegionServer中所有MemStore的总大小达到
堆内存大小 * hbase.regionserver.global.memstore.size(默认0.4)
* hbase.regionserver.global.memstore.size.lower.limit(默认0.95), 即0.38倍堆大小时
则按照所有MemStore由大到小的顺序依次进行刷写, 直到所有memstore的总大小减小到低于上述临界值
这种情况下可见, HFile里的数据timestap未必是最小的, 那查数据时, 即便MemStore中找到了该数据也仍要去访问HFile文件(但会借助于布隆过滤器), 因为timestap大的才是最新数据
什么时候阻止继续往所有MemStore写数据?
当某个RegionServer中MemStore的总大小达到
堆内存大小 * hbase.regionserver.global.memstore.size(默认值0.4), 即0.4倍堆大小时
到达自动刷写的时 hbase.regionserver.optionalcacheflushinterval(默认1小时)
当WAL文件的数量超过hbase.regionserver.max.logs,会按照timestap从小到大顺序依次进行刷写, 直到WAL文件数量减小到低于上述临界值(该属性名已废弃, 现无需手动设置,最大为32)
读写特点:
Hbase的读写类似, 即便是写(增删改)数据也要先查找数据所在表位于哪个RegionServer中的哪个Region
而HDFS的写, 既然是写(增加)数据, 就不会提前位于DataNode中
StoreFile Compaction:StoreFile底层是HFile
是为了减少HFile的个数,以及清理掉 过期和删除 的老数据
Minor Compaction
将临近的若干个较小的HFile合并成一个较大的HFile, 并清理掉 过期和删除 的数据
Major Compaction
将一个Store下的所有的HFile合并成一个大HFile, 并清理掉 过期和删除 的数据
起初表只有一个Region(如果不自己预分区的话),
为了更好的负载均衡 以及增加存储利用率,HMaster有可能会将某个Region转移给其他的Region Server
切分规则:
0.94版本之后, 2.0版本之前:
阈值为min(R^3 * initialSize,hbase.hregion.max.filesize"), 即min(R^3 * 256M, 10G)
R为当前Region Server中属于该表的Region个数
示例:
第一次split:1^3 * 256 = 256MB
第二次split:2^3 * 256 = 2048MB
第三次split:3^3 * 256 = 6912MB
第四次split:4^3 * 256 = 16384M > 10GB,因此取较小的值10GB
后面每次split的size都是10GB了
2.0版本新策略:
如果当前RegionServer上该表只有1个Region,则按照2 * hbase.hregion.memstore.flush.size切分,
否则按照hbase.hregion.max.filesize切分。
目的: 自定义分区后, 根据数据的RowKey可以迅速得知它属于哪一个Region
比如按 100,200,300分区键 去预分区, 则有(-∞, 100], (100,200], (200,300],(300,+∞) 这4个分区,
一条数据是拿Rowkey和分区键 按照字典序比较的
设定预分区的方法:
手动设定预分区
create ‘staff1’,‘info’,‘partition1’,SPLITS => [‘100’,‘200’,‘300’]
生成16进制序列预分区(0-15, 16个分区键)
create ‘staff2’,‘info’,‘partition2’,{NUMREGIONS => 15, SPLITALGO => ‘HexStringSplit’}
按照文件中设置的规则预分区(常用):
vim splits.txt
aaaa
bbbb
cccc
dddd
create ‘staff3’,‘partition3’,SPLITS_FILE => ‘splits.txt’
使用JavaAPI创建预分区
//自定义算法,产生一系列Hash散列值存储在二维数组中, byte[]相当于String, bytre[][]相当于String[] byte[][] splitKeys = 某个散列值函数 //创建HbaseAdmin实例 HbaseAdmin hAdmin = new HbaseAdmin(HbaseConfiguration.create( )); //创建HTableDescriptor实例 HTableDescriptor tableDesc = new HTableDescriptor(tableName); //通过HTableDescriptor实例和散列值二维数组创建带有预分区的Hbase表 hAdmin.createTable(tableDesc, splitKeys);
目的: 让数据均匀的分布, 防止数据倾斜
_小于所有数字
| 是大于很多字符, 只小于}
设计原则:
rowkey长度原则
rowkey散列原则
rowkey唯一原则
方案:
生成随机数、hash、加密
例如原本rowKey为1001的,SHA1后变成:dd01903921ea24941c26a48f2cec24e0bb0e8cc7
日期时间反转(个位变化频率低, 高位变化频率低, 所以原数据分布有规律会数据密集, 而反转后就没规律了)
20170524000001转成10000042507102
20170524000002转成20000042507102
随机字符串拼接
20170524000001_a12e
20170524000001_93i7
vim conf/backup-masters, 里面写上备机ip
内存优化:不建议分配非常大的堆内存, 一般配16~36G
因为:
在(0.4-0.38)*堆大小 这段内存回收时, RegionServer是禁止写数据的, 堆内存过大会增大其值, 导致增加了禁止写数据的时间
Linux系统运行和其他软件运行也需要内存, 不能全分给Hbase了
ZooKeeper会话超时时间
ZooKeeper.session.timeout 默认值为90000毫秒(90s)。当某个RegionServer挂掉了, 90s之后Master才能察觉到。
可适当减小此值,以加快Master响应,可调整至600000毫秒。
设置RPC监听数量
hbase.regionserver.handler.count 默认值为30
可以根据客户端的请求数进行调整,读写请求较多时,增加此值。
手动控制Major Compaction的周期
hbase.hregion.majorcompaction 默认值:604800000秒(7天)
若关闭自动Major Compaction,可将其设为0
优化HFile大小
hbase.hregion.max.filesize 默认值10737418240(10GB), 如果一个HFile(列族)的大小达到这个数值, 则这个Region就会被切分为两个Region
如果要运行Hbase的MR任务, 可以减小此值
因为一个Region对应一个MapTask,如果单个Region过大,会导致MapTask执行时间过长。
优化Hbase客户端缓存大小
hbase.client.write.buffer 默认值2097152bytes(2M)
增大该值可以减少RPC调用次数
指定scan.next扫描Hbase所获取的行数
hbase.client.scanner.caching
BlockCache占用RegionServer堆内存的比例
hfile.block.cache.size 默认0.4
读请求比较多的情况下, 可适当调大
MemStore占用RegionServer堆内存的比例
hbase.regionserver.global.memstore.size 默认0.4
写请求较多的情况下,可适当调大
优化数据的写入效率, 开启压缩
mapreduce.map.output.compress 设为true
mapreduce.map.output.compress.codec 指定压缩方式
速度上:
Hive 查询 和 增加数据 速度缓慢, 因为要走MR等引擎Hbase 查询 和 增加数据 快, 因为布隆过滤器 & BlockCache & 顺序写操作(按timestap判断是否为最新有效数据) 进行优化
增删改方面:
只允许增加数据虽然也是只允许增加数据, 但是通过timestap字段, 可以实现增删改的功能
是否为数据库:
Hive本质上并不是数据库, 而是将MySQL表的元数据 和 HDFS文件进行映射, 形成了逻辑上的数据库Hbase是列式数据库
数据分析方面:
Hive擅长做数据分析
Hbase不擅长做数据分析
相同点:都依赖于HDFS进行存数据, 可以用SQL语句去查数据
Hive集成Hbase:目的: 建立Hive表关联Hbase表,插入数据到Hive表的同时能够影响Hbase表
注意: 不能将数据直接load进Hive(这只会校验存储格式, 合法的法直接剪切, 让Hbase感知不到)
步骤: 创建表&关联 --> 创建临时表 --> load进临时表 --> insert into 原表 select 字段 from 临时表
在hive-site.xml里配置ZooKeeper
hive.ZooKeeper.quorum hadoop102,hadoop103,hadoop104 hive.ZooKeeper.client.port 2181
建表时通过描述语句进行关联:
CREATE TABLE hive_hbase_emp_table(
empno int,
ename string,
job string,
)
-- 描述语句
STORED BY 'org.apache.hadoop.hive.hbase.HbaseStorageHandler'
WITH SERDEPROPERTIES ("hbase.columns.mapping" = ":id, info:ename, info:job")
TBLPROPERTIES ("hbase.table.name" = "hbase_emp_table");
Phoenix:
是Hbase的SQL皮肤
坑: 注意大小写, 在引号内部才能保持原样, 否则就任务是大写
(一级)索引是对RowKey的索引
二级索引是对非RowKey字段的索引, 先找到这个普通字段数据对应的主键, 再通过主键去查找实际数据
全局 & 本地: (优缺点没搞懂)
全局二级索引(默认), 适用于读多写少, 索引 和 数据 不存储在一起
优点: 读数据时 会优先选择索引表来查询
缺点: 写操作时 要更新的索引可能位于不同机器, 加大了网络IO 和 磁盘寻址IO 的损耗, 更占用空间
本地二级索引, 适用于写多读少, 索引 和 数据 存放在同一个Region中(且是同一个),
优点: 写操作时 要更新的索引位于同台机器, 减少了网络IO 和 磁盘寻址IO(实际数据在附近, 顺序寻址较快) 的额外开销, 更节省空间
缺点: 无法提前确定数据在哪个Region上
Hbase的批量加载 底层是靠MapReduce实现的
协处理器(CoProcessor)
分为:
- Observer: 相当于MySQL的触发器, 监听到一个表变化后 便自动地去同步操作另一个表 (如 偶像表 和 粉丝表 需要同步变化)Endpoint: 类似于MySQL地存储过程, 一般用于分布式聚合计算
建立二级索引, 需要同步索引表, 靠的就是Observer协处理器



