分享一下“搜索”的架构和实现机制

我相信很多的小伙伴都做过“搜索”的功能,可能“搜索引擎”不一定接触过,但是站内搜索应该不会陌生。而不同的用户体量、不同的阶段,对于搜索的需求也是不一样的,所以,搜索也是一个不断演化的过程。

初级阶段

在搜索需求的原始阶段,一般我们就使用LIKE。可能我们有一个新闻的Table。

<code>

CREATE

TABLE

`News`

(

`Id`

 

int

(

10

)

NOT

NULL

COMMENT

'新闻ID'

,

`Title`

VARCHAR

(

200

)

NOT

NULL

COMMENT

'标题'

,

`Content`

 

TEXT

 

NOT

NULL

COMMENT

'内容'

, PRIMARY

KEY

(

`Id`

) ) /<code>

假如我们想要通过关键字对新闻的内容进行搜索时,我们会写:

<code>

SELECT

*

FROM

News

WHERE

Content

LIKE

'%关键字%'

;/<code>

不过,这种“搜索”方式的优缺点也是非常明显的。

优点就是:一句SQL就搞定,代码简单,维护方便,成本也非常低。

缺点也显而易见:效率低,每次查询都是全表检索;计算量大,连续多查几次CPU就100%了;而且也不支持分词查找。

对于小型的系统,这种搜索方式自然没有问题,但是规模稍大的系统,都不建议使用此种方式的。

中级阶段

如果我需要提高我的查询效率,并且我还要支持分词,但有不想对我现有的系统改动过大呢?那可以考虑为你需要的字段进行全文索引。也就是使用:

<code>

ALTER

TABLE

News

ADD

FULLTEXT ft_news(

`Title`

,

`Content`

);/<code>

如此一来,针对增加了全文索引的字段,你就不能够再写LIKE了,而需要改用

<code>

SELECT

*

FROM

News

WHERE

MATCH

(

`Title`

,

`Content`

) AGAINST(

'关键字'

);/<code>

全文索引能够快速的实现分词搜索的需求,并且对现有的系统影响过大。同时,也能够有效的提升性能,绝对比写LIKE要高效得多(不会再进行全表扫描了)。当然,全文检索也不是完美的,他的缺点也比较明显:

  1. 在MySQL5.6.24之前的版本,全文检索只支持MyISAM,之后的版本加入了InnoDB;
  2. 此全文检索方式是使用的数据库特性,搜索和其他的CURD相互耦合,当搜索的并发量大时,会影响到CRUD的性能;同样,CRUD的并发量大时,搜索也会变慢;
  3. 性能容易出现瓶颈,数据量到达百万级别时,速度就会难以接受了;
  4. 很难进行扩展

所以,我们的数据体量不大,业务量少时,这种全文检索方式是可以的,但是当体量提高以后,就必须重新进行考虑了。

高级阶段

经过了前两个阶段的修炼,单单依靠数据库我们已经很难搞定了。到这里,我们就需要考虑一下外置索引了。核心的思路自然是将原始数据与索引数据进行分离,原始数据用于保证普通的CRUD需求,索引数据用于满足用户的搜索需求。

而索引数据和原始数据之间通过双写,通知,定期重建等机制保证数据的一致性。

常见的外置索引方案有Solr、Lucene、ES,其中,ES(ElasticSearch)是目前最流行的一种方案。

ES是在Lucene的内核的基础上搭建起来的索引服务,它解决了很多Lucene上面的不足,并且,ES将接口封装成了一个个的RESTful的服务,更加的友好,应用上也更加简单。ES能够支持大数据量的数据存储,并发性能也非常强,而且还支持集群。

可以说,ES已经能够解决现在绝大多数科技公司或者互联网公司的搜索需求了,是一个非常强大的解决方案。当然,ES也不是说没有坑,这些坑就需要使用者根据自己的业务逻辑去一个一个的踩一踩了。

再往上,那就我现在暂时没能触碰的领域了,也就不多说了,但是呢,我们也可以聊一聊。

全网搜索引擎

这是一个粗略的搜索引擎架构图:


分享一下“搜索”的架构和实现机制

在搜索引擎中,有三个核心的子系统,分别是爬虫系统、索引系统和打分排序系统。

爬虫系统我们这里就不多说了,我们重点来说说索引系统和排序系统。

索引分成了所以的写入和索引的查询两个部分。当爬虫把网页的数据抓取到以后,会经过简单的处理,然后放到网页仓库中(这里几乎会存储整个互联网的所有html静态页面);然后索引系统就会对这些新来的网页进行数据解析(分词、正倒排索引等),然后生成并保存索引数据,也就是以下这些步骤。


分享一下“搜索”的架构和实现机制

当用户要搜索时,索引检索模块会根据用户输入的内容进行分词,然后根据分词的内容查询“倒排索引”,获得匹配的页面结果,然后将此结果交给打分排序的模块,打分排序的模块会根据规则对内容进行重新排序,然后再把结果交给检索模块并最终呈现给用户。


分享一下“搜索”的架构和实现机制

当然,这只是一个粗略的架构原理,内部还有很多深层次的内容。不过,我们能够从这个流程中看出,搜索引擎中的索引数据需要消耗大量的时间、大量的服务器资源进行数据的爬取、分析和索引的构建,所以现在很多想要做全网搜索引擎的公司,暂时都很难超过百度。

我们一般的程序员,99%应该都不会接触到全网的搜索引擎,绝大部分的公司也仅仅是需要一个站内的搜索引擎,内站内的搜索引擎和全网的相比差异并不是很大,主要是在范围上做出了限制,并且,由于数据都是自己的,所以也就不需要爬虫系统了,可以通过网站静态化并主动传递给网页仓库的方式来替代爬虫。

分享一下“搜索”的架构和实现机制

倒排索引

上面我们提到了,用户搜索的时候,会需要根据分词内容去查询“倒排索引”,什么是倒排索引呢?

要说倒排索引,自然就要先说说正排索引。回到我们的初级阶段,我们用最初的新闻Table来举例。

<code>

CREATE

TABLE

`News`

(

`Id`

 

int

(

10

)

NOT

NULL

COMMENT

'新闻ID'

,

`Title`

VARCHAR

(

200

)

NOT

NULL

COMMENT

'标题'

,

`Content`

 

TEXT

 

NOT

NULL

COMMENT

'内容'

, PRIMARY

KEY

(

`Id`

) )/<code>

假如,我们想要查找某个新闻,然后就使用新闻ID进行查找获得新闻的标题和内容。为此,我们建立了新闻ID的索引,这种就是正排索引。

如果把这种方式扩展到我们的网页查找中,那Url就相当于新闻ID,网页的内容就相当于新闻内容,新闻的内容在分词以后,就会形成很多很多的内容Item,正排索引也就是建立一个Url和List之间的索引关系。

而倒排索引正好相反,就是在分词以后,我们根据分词进行url的归类,最终形成一个Item和List之间的索引关系,而这种索引正式搜索引擎所需要的。

我们在分词后,通过倒排索引查找,可以找到很多的集合,但是,我们最终呈现给用户的只会有一个结果。例如:搜索“会技术的葛大爷”,分词以后,“技术”查找到了一个集合,葛大爷查找到了一个集合,其中的排序顺序各有不同,我们就需要将两个集合合并成为一个集合并按顺序排序呈现给用户。

排序

最简单的方式自然是循环 * 循环,但是效率可想而知。

我们可以用更简单的方法,就是在倒排索引查找的时候,就将结果集进行排序,然后合并集合时,我们可以将集合数组想象成链表,比较链表中的值的大小,取出排序优先的值,然后移动游标再次比较,这样的话,每个元素比较的次数将会大量减少,两两之间最多比较一次。

分享一下“搜索”的架构和实现机制

当然,这种方式在数据量大时并不适用,没有办法做到并行处理。如果想要并行处理,我们可以先将结果集合按照范围进行拆分,再进行比较。例如,我们按照1-10,10-20,20-30进行分组,然后在并行的进行排序,最终将结果合并,这样效率就大大提高了。

分享一下“搜索”的架构和实现机制

关于搜索引擎的内容,我们也就差不多都这里,搜索引擎中其实还有很多的内容,这都只有让我们慢慢的去发觉。


分享到:


相關文章: