由客户端发起写入数据的请求, 首先会先连接zookeeper
从zookeeper中获取到当前HMaster的信息,并与HMaster建立连接从HMaster中获取RegionServer列表信息
连接meta表对应的RegionServer地址, 从meta表获取当前要写入的表对应region被那个RegionServer所管理(一般只会返回一个RegionServer地址, 除非一次性写入多条数据)
连接对应要写入RegionServer的地址, 开始写入数据, 将数据首先会写入到HLog中,然后将数据写入到对应Region的对应Store模块的MemStore中(有可能会写入到MemStore), 当这两个地方都写入完成后, 客户端认为数据写入完成了服务端写入过程: 异步操作(可能客户端执行N多次写入后, 服务端才开始对之前的数据进行操作)
随着客户端不断的写入操作, memstore中数据会越来越多, 当内存中数据达到阈值(128M / 1h)后, 就会触发flush刷新机制, 将数据<最终>刷新到HDFS上形成StoreFile(小Hfile)文件.
随着不断的刷新, 在HDFS上StoreFile文件会越来越多, 当StoreFlie文件数量达到阈值(3个及以上)后, 就会触发compact合并压缩机制, 将多个StoreFlie文件<最终>合并为一个大的HFile文件
随着不断的合并, 大的HFile也会越来越大, 当大HFile达到一定的阈值(<最终>10GB)后, 就会触发Split分裂机制, 将大HFile进行一分为二,形成两个新的大HFile, 同时管理这个大HFile的Region也会形成两个新的Region, 形成的两个新的Region和两个新的大HFile 进行一对一的管理即可, 原来的Region和原来的大的HFile就会下线删除掉。
客户端从zookeeper中获取Hmaster信息并且建立连接获取到regionserver列表信息
客户端访问meta表所在的regionserver节点,获取到region所在的regionserver信息
客户端访问具体的region所在的regionserver,找到对应的region及store
首先从memstore中读取数据,如果读取到了那么直接将数据返回,如果没有,则去blockcache读取数据
如果blockcache中读取到数据,则直接返回数据给客户端,如果读取不到,则遍历storefile文件,查找数据
如果从storefile中读取不到数据,则返回客户端为空,如果读取到数据,那么需要将数据先缓存到blockcache中(方便下一次读取),然后再将数据返回给客户端。
blockcache是内存空间,如果缓存的数据比较多,满了之后会采用LRU策略,将比较老的数据进行删除。
LSM树的由来
在了解LSM树之前,我们需要对hash表和B+树有所了解。hash表就不用说了,通过key值经过hash算法,直接定位到数据存储地址,然后取出value值。时间复杂度O(1),找数据和存数据就需要那么一下子,就给找到了。B+树我们常用的数据库Mysql的底层数据结构,例如我们的索引就是B+树结构。既有排序树的优点,能够很快沿着树枝找到目标节点,又能防止树的高度过高,大大减少磁盘IO次数。还能进行快速全表扫描遍历。
hash存储方式支持增、删、改以及随机读取操作,但不支持顺序扫描,对应的存储系统为key-value存储系统。对于key-value的插入以及查询,哈希表的复杂度都是O(1),明显比树的操作O(n)快,如果不需要有序的遍历数据,哈希表就是最佳选择。B+树不仅支持单条记录的增、删、读、改操作,还支持顺序扫描(B+树的叶子节点之间的指针),对应的存储系统就是关系数据库(Mysql等)。但是删除和更新操作比较麻烦。正是基于以上结构的分析,LSM树应运而生。LSM树(Log-Structured Merge Tree)存储引擎和B树存储引擎一样,同样支持增、删、读、改、顺序扫描操作。而且通过批量存储技术规避磁盘随机写入问题。当然凡事有利有弊,LSM树和B+树相比,LSM树牺牲了部分读性能,用来大幅提高写性能。
LSM树的原理
LSM树的设计思想非常简单:将对数据的修改增量保持在内存中,达到指定的大小限制后将这些修改操作批量写入磁盘,不过读取的时候稍微麻烦,需要合并磁盘中历史数据和内存中最近修改操作,所以写入性能大大提升,读取时可能需要先看是否命中内存,否则需要访问较多的磁盘文件。极端的说,基于LSM树实现的HBase的写性能比Mysql高了一个数量级,读性能低了一个数量级。(有舍有得)
LSM树原理把一棵大树拆分成N棵小树,它首先写入内存中,随着小树越来越大,内存中的小树会flush到磁盘中,磁盘中的树定期可以做merge操作,合并成一棵大树,以优化读性能。
在hbase中LSM的应用流程对应说下:
1、因为小树先写到内存中,为了防止内存数据丢失,写内存的同时需要暂时持久化到磁盘,对应了HBase的MemStore和HLog
2、MemStore上的树达到一定大小之后,需要flush到HRegion磁盘中(一般是Hadoop DataNode),这样MemStore就变成了DataNode上的磁盘文件StoreFile,定期HRegionServer对DataNode的数据做merge操作,彻底删除无效空间,多棵小树在这个时机合并成大树,来增强读性能。
lsm tree,理论上,可以是内存中树的一部分和磁盘中第一层树做merge,对于磁盘中的树直接做update操作有可能会破坏物理block的连续性,但是实际应用中,一般lsm有多层,当磁盘中的小树合并成一个大树的时候,可以重新排好顺序,使得block连续,优化读性能。
hbase在实现中,是把整个内存在一定阈值后,flush到disk中,形成一个file,这个file的存储也就是一个小的B+树,因为hbase一般是部署在hdfs上,hdfs不支持对文件的update操作,所以hbase这么整体内存flush,而不是和磁盘中的小树merge update,这个设计也就能讲通了。内存flush到磁盘上的小树,定期也会合并成一个大树。整体上hbase就是用了lsm tree的思路。
Hbase主要包含Hbase Client/HMaster/HRegionServer/Zookeeper/HDFS
HBase Client 负责接受用户的各种请求
HBase Client 为用户提供了访问 HBase 的接口,可以通过元数据表来定位到目标数据的 RegionServer,另外 HBase Client 还维护了对应的 cache 来加速 Hbase 的访问,比如缓存元数据的信息。
HRegionServer 负责实际数据的读写. 当访问数据时, 客户端直接与RegionServer通信
HBase的表根据Row Key的区域分成多个Region, 一个Region包含这这个区域内所有数据. 而Region server负责管理多个Region, 负责在这个Region server上的所有region的读写操作.
HMaster 负责管理Region的位置, DDL(新增和删除表结构)
协调RegionServer
在集群处于数据恢复或者动态调整负载时,分配Region到某一个RegionServer中
管控集群,监控所有Region Server的状态
提供DDL相关的API, 新建(create),删除(delete)和更新(update)表结构.
Zookeeper 负责维护和记录整个Hbase集群的状态
zookeeper探测和记录Hbase集群中服务器的状态信息.如果zookeeper发现服务器宕机,它会通知Hbase的master节点.
HDFS 负责为Hbase提供数据存储
HDFS 为 HBase 提供底层数据存储服务,同时为 HBase提供高可用的支持, HBase 将 HLog 存储在 HDFS 上,当服务器发生异常宕机时,可以重放 HLog 来恢复数据。
百亿数据:证明数据量非常大;
存入HBase:证明是跟HBase的写入数据有关;
保证数据的正确:要设计正确的数据结构保证正确性;
在规定时间内完成:对存入速度是有要求的。
解决思路:
假设一整天60x60x24 = 86400秒都在写入数据,那么每秒的写入条数高达100万条,HBase当然是支持不了每秒百万条数据的, 所以这百亿条数据可能不是通过实时地写入,而是批量地导入。批量导入推荐使用BulkLoad方式,性能是普通写入方式几倍以上;
存入HBase:普通写入是用JavaAPI put来实现,批量导入推荐使用BulkLoad;
保证数据的正确:这里需要考虑RowKey的设计、预建分区和列族设计等问题;
还有region热点的问题,如果你的hbase数据不是那种每天增量的数据,建议跑个mapreduce对你的数据进行各评判,看看如何能将数据尽可能均匀的分配到每个region中,当然这需要预先分配region
rowkey长度原则
rowkey是一个二进制码流,可以是任意字符串,最大长度64kb,实际应用中一般为10-100bytes,以byte[]形式保存,一般设计成定长。议越短越好,不要超过16个字节,设计过长会降低memstore内存的利用率和HFile存贮数据的效率。
原因如下,首先一条数据是根据rowkey来当成索引的,如果过长就会快速占据memstore的128M,然后被刷写到磁盘,也就是说相同的空间存贮的内容被rowkey占据了一大部分,减少了主要内容的存贮
rowkey唯一原则
必须在设计上保证其唯一性,rowkey是按照字典顺序排序存储的,因此,设计rowkey的时候,要充分利用这个排序的特点,可以将经常读取的数据存储到一块,将最近可能会被访问的数据放到一块。
rowkey散列原则
建议将rowkey的高位作为散列字段,这样将提高数据均衡分布在每个RegionServer,以实现负载均衡的几率。如果没有散列字段,首字段直接是时间信息。
所有的数据都会集中在一个RegionServer上,这样在数据检索的时候负载会集中在个别的RegionServer上,造成热点问题,会降低查询效率。
其中常见的散列方法有如下:
reverse反转,将原来的rowkey反过来
加盐,随机生成字符串加载rowkey前面
hashcode或求模,通过一个固定的算法计算hash值获得一个字符串加载rowkey前面 如果前缀是纯数字的可以使用求模方法