本项目为学校大数据工程实训项目,共开发4周,答辩成绩不错。代码仓库放文章尾,写的不好,代码仅供参考。
搜索
对于结构化数据,因为它们具有特定的结构,所以我们一般都是可以通过关系型数据库(MySQL,Oracle 等)的二维表(Table)的方式存储和搜索,也可以建立索引。
对于非结构化数据,也即对全文数据的搜索主要有两种方法:
- 顺序扫描
- 全文检索
(1)顺序扫描:通过文字名称也可了解到它的大概搜索方式,即按照顺序扫描的方式查询特定的关键字。
例如一张报纸,让找到该报纸中“平安”的文字在哪些地方出现过。肯定需要从头到尾把报纸阅读扫描一遍然后标记出关键字在哪些版块出现过以及它的出现位置。
这种方式无疑是最耗时的最低效的,如果报纸排版字体小。
(2)全文搜索:对非结构化数据顺序扫描很慢,是否可以进行优化?即把非结构化数据整理有一定结构。
将非结构化数据中的一部分信息提取出来,重新组织,使其变得有一定结构,然后对此有一定结构的数据进行搜索,从而达到搜索相对较快的目的。
这种方式就构成了全文检索的基本思路。这部分从非结构化数据中提取出的然后重新组织的信息,我们称之为索引。
这种方式的主要工作量在前期索引的创建,但是对于后期搜索却是快速高效的。
Hbase
HBase 是一个面向列式存储的分布式数据库,其设计思想来源于 Google 的 BigTable 论文。HBase 底层存储基于 HDFS 实现,集群的管理基于 ZooKeeper 实现。HBase 良好的分布式架构设计为海量数据的快速存储、随机访问提供了可能,基于数据副本机制和分区机制可以轻松实现在线扩容、缩容和数据容灾,是大数据领域中 Key-Value 数据结构存储最常用的数据库方案。
HBase 是一个面向列式存储的分布式数据库。HBase 的数据模型与 BigTable 十分相似。在 HBase 表中,一条数据拥有一个全局唯一的键(RowKey)和任意数量的列(Column),一列或多列组成一个列族(Column Family),同一个列族中列的数据在物理上都存储在同一个 HFile 中,这样基于列存储的数据结构有利于数据缓存和查询。 HBase 中的表是疏松地存储的,因此用户可以动态地为数据定义各种不同的列。HBase中的数据按主键排序,同时,HBase 会将表按主键划分为多个 Region 存储在不同 Region Server 上,以完成数据的分布式存储和读取。
HBase 根据列成来存储数据,一个列族对应物理存储上的一个 HFile,列族包含多列列族在创建表的时候被指定。
1.Column Family
Column Family 即列族,HBase 基于列划分数据的物理存储,一个列族可以包含包意多列。
一般同一类的列会放在一个列族中,每个列族都有一组存储属性:
是否应该缓存在内存中;
数据如何被压缩或行键如何编码等。
HBase 在创建表的时候就必须指定列族。HBase的列族不是越多越好,官方荐一个表的列族数量最好小于或者等于3,过多的列族不利于 HBase 数据的管理和索引。
2.RowKey
RowKey的概念与关系型数据库中的主键相似,HBase 使用 RowKey 来唯一标识某行的数据。
访问 HBase 数据的方式有三种:
基于 RowKey的单行查询;
基于RowKey的范围查询;
全表扫描查询。
3.Region
HBase 将表中的数据基于 RowKey 的不同范围划分到不同 Region 上,每个Region都负责一定范围的数据存储和访问。
每个表一开始只有一个 Region,随着数据不断插入表,Region 不断增大,当增大到一个阀值的时候,Region 就会等分成两个新的 Region。当table中的行不断增多,就会有越来越多的 Region。
另外,Region 是 Hbase 中分布式存储和负载均衡的最小单元,不同的 Region 可以分布在不同的 HRegion Server上。但一个Hregion是不会拆分到多个server上的。
拓展:谈一下你对 HBase 的认识?
这样即使有一个包括上百亿条数据的表,由于数据被划分到不同的 Region上,每个 Region 都可以独立地进行写入和查询,HBase 写查询时候可以于多 Region 分布式并发操作,因此访问速度也不会有太大的降低。
4.TimeStamp
TimeStamp 是实现 HBase 多版本的关键。在HBase 中,使用不同 TimeStamp 来标识相同RowKey对应的不同版本的数据。相同 RowKey的数据按照 TimeStamp 倒序排列。默认查询的是最新的版本,当然用户也可以指定 TimeStamp 的值来读取指定版本的数据。
大数据场景下,列式存储的优势
对于 OLAP 场景,大多都是对一整行记录进行增删改查操作的,那么行式存储采用以行的行式在磁盘上存储数据就是一个不错的选择。
当查询基于需求字段查询和返回结果时,由于这些字段都埋藏在各行数据中,就必须读取每一条完整的行记录,大量磁盘转动寻址的操作使得读取效率大大降低。数据在磁盘上是以行的形式存储在磁盘上,同一行的数据紧挨着存放在一起。
对于 OLAP 场景,一个典型的查询需要遍历整个表,进行分组、排序、聚合等操作,这样一来行式存储中把一整行记录存放在一起的优势就不复存在了。而且,分析型 SQL 常常不会用到所有的列,而仅仅对其中某些需要的的列做运算,那一行中无关的列也不得不参与扫描。
然而在列式存储中,由于同一列的数据被紧挨着存放在了一起
列式存储不仅具有按需查询来提高效率的优势,由于同一列的数据属于同一种类型,如数值类型,字符串类型等,相似度很高,还可以选择使用合适的编码压缩可减少数据的存储空间,进而减少IO提高读取性能。
Elasticsearch
ES 是使用 Java 编写的一种开源搜索引擎,它在内部使用 Lucene 做索引与搜索,通过对 Lucene 的封装,隐藏了 Lucene 的复杂性,取而代之的提供一套简单一致的 RESTful API。
然而,Elasticsearch 不仅仅是 Lucene,并且也不仅仅只是一个全文搜索引擎。
它可以被下面这样准确的形容:
- 一个分布式的实时文档存储,每个字段可以被索引与搜索。
- 一个分布式实时分析搜索引擎。
- 能胜任上百个服务节点的扩展,并支持 PB 级别的结构化或者非结构化数据。
官网对 Elasticsearch 的介绍是 Elasticsearch 是一个分布式、可扩展、近实时的搜索与数据分析引擎。
Lucene
Lucene 只是一个工具包,它不是一个完整的全文检索引擎。Lucene 的目的是为软件开发人员提供一个简单易用的工具包,以方便的在目标系统中实现全文检索的功能,或者是以此为基础建立起完整的全文检索引擎。
目前以 Lucene 为基础建立的开源可用全文搜索引擎主要是 Solr 和 Elasticsearch。
Solr 和 Elasticsearch 都是比较成熟的全文搜索引擎,能完成的功能和性能也基本一样。
ES 本身就具有分布式的特性和易安装使用的特点,而 Solr 的分布式需要借助第三方来实现,例如通过使用 ZooKeeper 来达到分布式协调管理。
倒排索引
为了创建倒排索引,我们通过分词器将每个文档的内容域拆分成单独的词(我们称它为词条或 Term),创建一个包含所有不重复词条的排序列表,然后列出每个词条出现在哪个文档。这种结构由文档中所有不重复词的列表构成,对于其中每个词都有一个文档列表与之关联。这种由属性值来确定记录的位置的结构就是倒排索引。带有倒排索引的文件我们称为倒排文件。
- Java is the best programming language.
- PHP is the best programming language.
- Javascript is the best programming language.
结果如下所示:
Term Doc_1 Doc_2 Doc_3
-------------------------------------
Java | X | |
is | X | X | X
the | X | X | X
best | X | X | X
programming | x | X | X
language | X | X | X
PHP | | X |
Javascript | | | X
-------------------------------------
将上面的内容转换为图的形式来说明倒排索引的结构信息,如下图所示:
-
词条(Term):索引里面最小的存储和查询单元,对于英文来说是一个单词,对于中文来说一般指分词后的一个词。
-
词典(Term Dictionary):或字典,是词条 Term 的集合。搜索引擎的通常索引单位是单词,单词词典是由文档集合中出现过的所有单词构成的字符串集合,单词词典内每条索引项记载单词本身的一些信息以及指向“倒排列表”的指针。
-
倒排表(Post list):一个文档通常由多个词组成,倒排表记录的是某个词在哪些文档里出现过以及出现的位置。
每条记录称为一个倒排项(Posting)。倒排表记录的不单是文档编号,还存储了词频等信息。
-
**倒排文件(Inverted File):**所有单词的倒排列表往往顺序地存储在磁盘的某个文件里,这个文件被称之为倒排文件,倒排文件是存储倒排索引的物理文件。
-
可以了解到倒排索引主要由两个部分组成:
- 词典
- 倒排文件
词典和倒排表是 Lucene 中很重要的两种数据结构,是实现快速检索的重要基石。词典和倒排文件是分两部分存储的,词典在内存中而倒排文件存储在磁盘上。
Elasticsearch+Hbase
为什么做数据检索要加上Hbase,ElasticSearch本身的存储性能就足以支撑海量数据.
首先ElasticSearch针对海量数据的存储存在两个较大的缺点:
1、写入效率相对较低,虽然和Hbase一样都是采用LSM树(LSM 通过将磁盘的随机写转化为顺序写来提高写性能 ,而付出的代价就是牺牲部分读性能),但是ES在写入时需要消耗大量的时间去进行分词、主副本构建倒排索引等操作。
2、在大数据领域,在使用数据的时候,经常用到的字段就几个,剩下的字段没有价值或使用频率极低。
以上无用或低频字段会占用大量的索引空间,由于ES的倒排索引存储在文件中,为提高访问速度,在启动时都会把它加载到内存中。上述问题会导致倒排索引文件过大,从而导致集群性能下降并降低数据访问速度,且会增加不必要的内存硬件成本。
架构设计为了解决上述中出现的问题,需要引入Hbase对上述出现的问题进行弥补。
1、写入效率问题:Hbase基于HDFS存储,其次因为没有索引,不需要考虑海量数据下因为索引导致的性能瓶颈,所以Hbase非常适合存储海量数据,写入速度快,可扩展性强。
2、字段过多导致的性能问题:我们可以将原始数据存储至Hbase中,仅将需要进行检索的字段抽取出来存储至ES中,这样可以大大降低ES因为倒排索引文件过大导致的性能压力。
其次Hbase支持根据rowkey进行模糊查询,rowkey我们可以把它看做成Hbase中单条数据的唯一ID,同时也可以作为一个简单的索引。
框架设计:
根据rowkey将Hbase的原始数据和ES中的检索数据关联起来。
在原始数据(也可以叫源数据、贴源层数据、日志数据)入库时,可以根据数据生成rowkey(rowkey的设计因人而异,最好根据具体的业务来设计,注意要确保唯一性),之后将需要检索的字段抽取出来形成单独的一条索引数据,并将刚才设计好的rowkey放进去,之后将原始数据根据rowkey写入至Hbase,检索数据根据rowkey写入至ES中。
在进行检索时首先根据检索条件去ES中搜索,搜索出相对应的检索数据后,根据检索数据中的rowkey直接去Hbase中获取原始数据即可。(将HBase的rowkey设定为ES的文档ID,搜索时根据业务条件先从ES里面全文检索出相对应的文档,从而获取出文档ID,即拿到了rowkey,再从HBase里面抽取数据。)
架构效果
发挥了Elasticsearch的全文检索的优势,能够快速根据关键字检索出相关度最高的结果;
同时减少了Elasticsearch的存储压力,这种场景下不需要存储检索无关的内容,甚至可以禁用_source,节约一半的存储空间,同时提升最少30%的写入速度;
避免了Elasticsearch大数据量下查询返回慢的问题,大数据量下Hbase的抽取速度明显优于Elasticsearch;
各取所长,发挥两个组件各自的优势。
存在问题
1、两个组件之间存在时效不一致的问题
相对而言,Elasticsearch的入库速度肯定是要快于Hbase的,这是需要业务容忍一定的时效性,对业务的要求会比较高。
2、同时管理两个组件增加了管理成本
显而易见,同时维护两套组件的成本肯定是更大的。
3、两者的数据不一致问题
针对此项目,有一个核心功能点,如何在ES中同步对HBase中的数据建立索引?
大致有下面这几种方案:
1:方案1
在将原始数据入库HBase的时候,同时在ES中对数据建立索引,此时可以把入库HBase和ES的代码放在一个事务中,保证HBase和ES的数据一致性。
这种方案的优点是操作方便,缺点是入库HBase和ES的代码绑定到一起了,耦合性太高,如果遇到ES出现故障,会导致入库HBase的操作也会失败,或者是ES集群压力过大的时候,会导致数据入库HBase的效率降低。
2、方案二
在将原始数据入库HBase的时候,通过HBase中的协处理器实现数据同步,让协处理器监听HBase中的新增和删除操作,在协处理器内部操作ES,实现对数据建立索引的功能。
HBase中的协处理器其实类似于MySQL中的触发器。
这种方案的优点是通过协处理器可以很方便的获取到HBase中新增和变化的数据,如果入库HBase的程序是之前已经开发好的,此时不需要对之前的代码进行任何改动,影响程度比较低。缺点是过于依赖HBase了,如果后期涉及到HBase集群版本升级,无法保证协处理器功能的可用性。
3、方案3
在将原始数据入库HBase的时候,同时在Redis中使用list数据类型模拟一个队列,存储数据的Rowkey。此时将入库HBase和Redis的操作放在一个事务里面,保证数据的一致性。然后再通过另外一个同步程序,从Redis的list队列中读取Rowkey,根据Rowkey到HBase中获取数据的详细信息,在ES中建立索引,将HBase中数据的Rowkey作为ES中数据的ID。
在这个方案里面是将入库HBase和在ES中建立索引这两个功能解耦了,借助于中间层的Redis实现的。
这种方案的缺点是把入库HBase和Redis的功能耦合在一起了,但是Redis是轻量级的,出现问题的概率是比较低的,对性能损耗也不高,所以是可以接受的。
此时就算ES出现问题,只需要在同步程序内部实现正常的异常处理即可,将建立索引失败数据的Rowkey重新添加到Redis的list列表里面即可,不会导致HBase和ES数据不一致的问题。
使用第3种方案,可控性高一些,在项目中也会使用第3种方案实现。
四、项目整体执行流程
接下来分析一下项目底层细节流程
如下图所示
1:通过入库程序向HBase中入库数据,同时在Redis中存储数据的Rowkey。
2:从Redis中获取数据的Rowkey,根据Rowkey到HBase中查询数据的详细信息,然后在ES中建立索引。
此时我们的海量数据已经存储到HBase中,并且将需要查询的字段在ES中建立索引了。
3:用户向ES发送查询请求。
4:ES返回符合条件的数据的ID,其实就是HBase中数据的Rowkey。在这里也可以根据需求额外再返回一些字段信息都是可以的。
5:当用户想要查看数据完整详细信息的时候,需要根据Rowkey到HBase中查询。
6:HBase会给用户返回Rowkey对应数据的详细信息。
实测性能
原来只使用ElasticSearch的时候,数据只需要写入一遍,也只需要查询一次ES就可返回想要的数据。现使用了Hbase+ElasticSearch后,不仅数据需要写入两份,查询也需要先查ES,然后再根据ES的查询结果去Hbase中取数据。这样多此一举性能难道真的能提升吗?
单ES架构和Hbase+ES架构的性能对比可由上图直观的展示出来:
在数据量小的时候,单ES架构的性能是优于Hbase+ES架构的。
数据量过了一定量级的时候,Hbase+ES架构的性能就远远的把单ES架构给甩开了。
项目最终整体架构
项目展示
地址:zjczdzs/Z-HE: 基于Elasticsearch+hbase的海量数据查询、即时推送服务系统 (github.com)
参考博客:
https://blog.csdn.net/mrliqifeng/article/details/111771109
https://blog.csdn.net/sdksdk0/article/details/53966430
https://blog.csdn.net/wudingmei1023/article/details/103914052
https://blog.csdn.net/weixin_40612128/article/details/123507161
https://blog.csdn.net/bxg_kyjgs/article/details/125993647