Elasticsearch(六)了解全文搜索

遇到的问题

通过前面的学习,我们已经可以使用elasticsearch来进行数据的搜索了,但此时我们发现了一个问题,这个问题如果没有解决好就很影响我们后续的使用,那么该问题是什么呢?我们来看一下:
搜索“在”关键字.png
上面的截图是我搜索“在”关键字出来的结果,按照正常情况下,我们是不是不应该搜索“在”也出来结果呢?因为我们做的是搜索,不是模糊查询,既然是搜索的话,那像这种没有意义的关键字就不应该搜索出来才对的,还有像类似“在”、“是”、“了”等等这些单独存在不能构成一个中文词义的字符也可以搜索了,这显然是一种搜索效果不好的方式。还有一个问题,我们看看下面的截图:
搜索“中国人”关键字

上面这个截图是我搜索“中国人”这个关键词出来的结果,然后我们通过高亮匹配的内容发现,它是“中”、“国”、“人”拆成3个关键字去匹配内容了,而我们想要的是它匹配“中国人”,“中国”,“人”这样的搜索效果,但现在的情况却是每个中文字符都能搜索,这样就不太智能了。
那从表面上看,出现以上两个问题的原因貌似是因为elasticsearch对中文的分词不太智能,它现在的分词只是简单的安照每个中文字符来分,那如果我们想让elasticsearch的搜索能更智能一点,分析出我们搜索的关键字是否有含义,然后只搜这些有含义的词语,比如“中国人”,“中国”,“人”,这样的话我们要做什么操作才能让elasticsearch支持呢?这个就是我们接下来要探讨的问题了。
其实这个问题对elasticsearch来说是很好解决的,我们只需要换一个分词算法就可以了,但是为了让大家对elasticsearch的搜索过程和底层的分词原理有一个比较深刻的了解,我们不忙着切换其他分词算法,而是先来看看全文检索的一些基础知识,不然的话大家也仅仅只停留在会用的阶段,都不知道它底层做了什么事情。

数据类型

在详细了解全文搜索前,我们先来看看平时我们开发一个应用会遇到什么样的数据,这些数据是属于什么类型的,通过对这些数据的了解,我们接下来才能知道全文搜索,搜索的是什么样的数据。
在开发过程中通常都会接触到3种数据类型:结构化数据、半结构化数据和非结构化数据,这3种数据有什么特点呢?
1、结构化数据:是一种二维结构的数据,也就是说它的数据看上去是一个横向和纵向组成的,横向数据每一行都代表着一个实体的信息,纵向的列是固定的,并需要事先确定下来,是用来描述该实体的属性,所以每一行数据对应的属性都是一样的。结构化数据可以使用关系形数据库来储存,比如我们很熟悉的mysql,oracle等。如下图所示的数据:
结构化数据

2、半结构化数据:这种数据类型其实也是结构化的数据,但是不能使用关系型数据库来存储和描述数据,因为它每个实体的属性可以不一样,比如我们描述一个人的实体信息,其中一个人可以不描述年龄,一个人可以描述年龄,而存储在关系型数据库中的数据每个实体信息的属性都是需要一样的,但是它的扩展性很好,也比纯结构化数据灵活。我们经常使用的json、xml、yml就是半结构化数据。如下图所示的数据:
半结构化数据

3、非结构化数据:跟结构化数据相对应的一种数据,他没有固定的结构,也就说我们不需要为该数据事先定义纵向的属性列。比如各种文档、图片、视频/音频等。而接下来我们要讲的全文搜索,就是搜索这种非结构化的数据。

创建索引过程

通过刚刚我们对数据类型的讲解,我们知道,全文搜索是搜索类似文档这种非结构化数据,并且它们的数据量都会比较大,那这样的话,如果我们按照传统的思维来对这些文档搜索的话,那肯定是搜索的速度会很慢,因为它要对这些文档逐字逐字的扫描,就好比我们看一本书,如果这本书没有目录的话,那么我们要找一个内容,就要在茫茫字海中寻找,那这样的话,我们是否可以把这些内容归纳成一个一个章节,这些章节有对应的页码,并统一放到一个地方,我们想要找某个内容的时候,先找这个内容对应的章节,再找到该章节对应的页面,这样的话就极大的提高了我们寻找内容的速度了,而这些归纳出来的章节就是我们所说的目录。
那别人在做全文搜索的时候,是否也能按照类似书的目录这种思想来来设计呢?答案是肯定的,但是大家要知道,这是一种思想,具体的实现细节有很多,我们来看看全文搜索的一种很常用的实现,具体过程如下:

1、给文档分词

首先,搜索引擎会有个分词组件,我们新建的文档会传给分词组件,然后分词组件根据它特定的分词算法,把文档拆分成一个一个的词元,如果以elasticsearch默认的分词算法来举例,那么如果我们创建以下这个文档:“我是一个中国人,张姓在中国是大姓。”,首先传到分词组件,然后得到以下新的数据:

我
是
一
个
中
国
人
张
姓
在
中
国
是
大
姓

这些就是分词组件拿到文档后拆分出来的词元集合。

2、将得到的词元创建索引

通过分词组件把文档拆成词元后,搜索引擎会把这些词元传给索引组件,索引组件会做以下事情:
(1)利用得到的词创建一个字典。
索引组件首先会使用刚刚新建文档得到的词元放到字典中,如下图所示:
字典
该字典会有对应的文档ID

(2)对字典排序
对这些词进行排序,这里的排序算法就以中文拼音首字母顺序为例:
排序.png

(3)合并相同的词
排好序后,会合并相同的词:
合并
Frequency为词频率,表示此文档中包含了几个此词。

(4)生成索引文件
最后对排序并合并好的字典生成索引文件,该索引文件是一个倒排表,链表状的数据结构,每个词都会标记总共有多少文档包含此词,并且会关联着该词所在的文档id,文档id又会包含着此文档中包含了几个此词。
Document Frequency 即为文档频次,表示总共有多少文档包含此词。
索引文件
如果此时我们再新增一个文档,那么该索引文件会发生什么变化呢?我们来看一下:添加“中国是全球人口最多的国家”
此时,搜索引擎又开始执行创建索引的步骤,经过1-4的步骤后,索引文件变成如下这样:
更新后的索引文件
那么索引引擎在新增一个文档的时候就要做这些事情了,最后生成出这些倒排表结构的索引文件,每个词都会关联着所有包含该词的文档id,找到指定的词,就相当于找到该词对应的文档了。
但是我们通过上述的这些步骤发现,添加一个文档要做的事情还挺多的,那这样岂不是降低了我们系统的性能?如果单单从这方面来看,确实是,但是大家要知道,一般来说,我们录入一个需要做全文索引的数据,一般都是采用异步的方式去给该数据创建索引的,也就是说我们会发一个消息告诉专门创建索引的服务,并把相关的数据放到消息体,整个发送消息、获取消息和创建索引的过程都是异步的。业务系统本身只是发了个消息,然后就接着做下面的业务,真正耗性能的创建索引过程由搜索服务器处理,而搜素服务器要做集群太简单了,所以这点性能的消耗并不是什么问题。
最重要的是,有了这个索引文件,我们要搜索就变得很简单了,我们不需要在这些文档中扫描我们要找的内容了,而是查找索引文件就可以了。那接下来,我们来看看我们在输入一个关键字后,是如何在索引文件查找内容的。

搜索过程

从我们输入要搜索的关键字到返回搜索结果这个过程一般要经过这几个步骤:

1、给搜索关键字分词

首先还是使用分词组件给搜索关键字分词,比如用户输入“舌尖上的中国”,那么经过分词组件拆出来的词就有:

舌
尖
上
的
中
国

2、搜索索引

接着搜索引擎拿着这些拆出来的词去索引文件找对应的词,由于索引文件是有顺序的,一旦找到就停止向下找,然后获取该词对应的整个文档链表,如下图:
搜索结果
搜索出来的结果有“的”,“中”,“国”,并且后面带着该词所在的文档id链表。“的”有一个id为2的文档,“中”和“国”分别都有id为1和id为2这两个文档。但是由于“舌尖上”在索引文件中没有找到,所以也就没有搜索结果了。

3、链表合并

对包含“的”,“中”,“国”的链表进行合并操作,得到包含这些词的所有文档id,所以最终就得到id为1和2这两个文档了。最后进行搜索结果相关度排序,使用特定的相关度排序算法,根据词在文档出现的频率和我们提交的排序条件等计算,最终把这些文档id排序,排序好后默认获取前10条文档id,如果用户有提交查询数量,那么就获取用户提交的数量,最后通过文档id查询出整个文档返回给用户。

后记

那么这就是elasticsearch为我们提供搜索服务时,它要做的事情了,了解了这些大致的过程有利于我们更好的使用搜索引擎,并扩展一些基础知识。那么现在我们回到最开始说的遇到的问题上,我们说elasticsearch不够智能,它只是简单的按照一个一个字符去搜索文档的内容,导致没有语义的字符也搜索出结果了。那这个问题,我们通过上面的全文搜索过程可以知道,问题是出在分词这一块,也就是说分词的时候它是一个一个字符串去分的,最终导致索引文件保存的是文档中所有词。
那这个问题如何解决呢?elasticsearch是否提供其他的分词算法,让它支持中文语义来分词,这样的话,保存在索引文件中的词就不再是一个一个的字符,而是有意义的词语,比如“我是一个中国人,张姓在中国是大姓。”这个文档,就变成了这样:

是一个
中国
中国人
人
张姓

那我们下一章就来了解一下elasticsearch的分词组件,看看它给我们提供了哪些分词组件,和这些分词组件的特点是怎么样的,从而来确定elasticsearch是否内置一个更智能的中文分词方式。

 

没有更多推荐了,返回首页