我相信很多的小伙伴都做过“搜索”的功能,可能“搜索引擎”不一定接触过,但是站内搜索应该不会陌生。而不同的用户体量、不同的阶段,对于搜索的需求也是不一样的,所以,搜索也是一个不断演化的过程。
初级阶段
在搜索需求的原始阶段,一般我们就使用LIKE。可能我们有一个新闻的Table。
CREATE TABLE `News` ( `Id` int(10) NOT NULL COMMENT '新闻ID', `Title` VARCHAR(200) NOT NULL COMMENT '标题', `Content` TEXT NOT NULL COMMENT '内容',PRIMARY KEY (`Id`))
假如我们想要通过关键字对新闻的内容进行搜索时,我们会写:
SELECT * FROM News WHERE Content LIKE '%关键字%';
不过,这种“搜索”方式的优缺点也是非常明显的。
优点就是:一句SQL就搞定,代码简单,维护方便,成本也非常低。
缺点也显而易见:效率低,每次查询都是全表检索;计算量大,连续多查几次CPU就100%了;而且也不支持分词查找。
对于小型的系统,这种搜索方式自然没有问题,但是规模稍大的系统,都不建议使用此种方式的。
中级阶段
如果我需要提高我的查询效率,并且我还要支持分词,但有不想对我现有的系统改动过大呢?那可以考虑为你需要的字段进行全文索引。也就是使用:
ALTER TABLE News ADD FULLTEXT ft_news(`Title`, `Content`);
如此一来,针对增加了全文索引的字段,你就不能够再写LIKE了,而需要改用
SELECT * FROM News WHERE MATCH(`Title`, `Content`) AGAINST('关键字');
全文索引能够快速的实现分词搜索的需求,并且对现有的系统影响过大。同时,也能够有效的提升性能,绝对比写LIKE要高效得多(不会再进行全表扫描了)。当然,全文检索也不是完美的,他的缺点也比较明显:
- 在MySQL5.6.24之前的版本,全文检索只支持MyISAM,之后的版本加入了InnoDB;
- 此全文检索方式是使用的数据库特性,搜索和其他的CURD相互耦合,当搜索的并发量大时,会影响到CRUD的性能;同样,CRUD的并发量大时,搜索也会变慢;
- 性能容易出现瓶颈,数据量到达百万级别时,速度就会难以接受了;
- 很难进行扩展
所以,我们的数据体量不大,业务量少时,这种全文检索方式是可以的,但是当体量提高以后,就必须重新进行考虑了。
高级阶段
经过了前两个阶段的修炼,单单依靠数据库我们已经很难搞定了。到这里,我们就需要考虑一下外置索引了。核心的思路自然是将原始数据与索引数据进行分离,原始数据用于保证普通的CRUD需求,索引数据用于满足用户的搜索需求。
而索引数据和原始数据之间通过双写,通知,定期重建等机制保证数据的一致性。
常见的外置索引方案有Solr、Lucene、ES,其中,ES(ElasticSearch)是目前最流行的一种方案。
ES是在Lucene的内核的基础上搭建起来的索引服务,它解决了很多Lucene上面的不足,并且,ES将接口封装成了一个个的RESTful的服务,更加的友好,应用上也更加简单。ES能够支持大数据量的数据存储,并发性能也非常强,而且还支持集群。
可以说,ES已经能够解决现在绝大多数科技公司或者互联网公司的搜索需求了,是一个非常强大的解决方案。当然,ES也不是说没有坑,这些坑就需要使用者根据自己的业务逻辑去一个一个的踩一踩了。
再往上,那就我现在暂时没能触碰的领域了,也就不多说了,但是呢,我们也可以聊一聊。
全网搜索引擎
这是一个粗略的搜索引擎架构图:
![19574712c8cde7a7fb84c9d7bd46878d.png](https://i-blog.csdnimg.cn/blog_migrate/853499e56549db6b4adc0cb2509ca369.jpeg)
在搜索引擎中,有三个核心的子系统,分别是爬虫系统、索引系统和打分排序系统。
爬虫系统我们这里就不多说了,我们重点来说说索引系统和排序系统。
索引分成了所以的写入和索引的查询两个部分。当爬虫把网页的数据抓取到以后,会经过简单的处理,然后放到网页仓库中(这里几乎会存储整个互联网的所有html静态页面);然后索引系统就会对这些新来的网页进行数据解析(分词、正倒排索引等),然后生成并保存索引数据,也就是以下这些步骤。
![1f51cf998bd3747324fc57179bba40e7.png](https://i-blog.csdnimg.cn/blog_migrate/8a1d648b5539733ff94981fa49195bc1.jpeg)
当用户要搜索时,索引检索模块会根据用户输入的内容进行分词,然后根据分词的内容查询“倒排索引”,获得匹配的页面结果,然后将此结果交给打分排序的模块,打分排序的模块会根据规则对内容进行重新排序,然后再把结果交给检索模块并最终呈现给用户。
![2e436d0b71c1a572e47ae2ec8a5246a9.png](https://i-blog.csdnimg.cn/blog_migrate/5b330a893474fb45eed72776ab6a9522.jpeg)
当然,这只是一个粗略的架构原理,内部还有很多深层次的内容。不过,我们能够从这个流程中看出,搜索引擎中的索引数据需要消耗大量的时间、大量的服务器资源进行数据的爬取、分析和索引的构建,所以现在很多想要做全网搜索引擎的公司,暂时都很难超过百度。
我们一般的程序员,99%应该都不会接触到全网的搜索引擎,绝大部分的公司也仅仅是需要一个站内的搜索引擎,内站内的搜索引擎和全网的相比差异并不是很大,主要是在范围上做出了限制,并且,由于数据都是自己的,所以也就不需要爬虫系统了,可以通过网站静态化并主动传递给网页仓库的方式来替代爬虫。
![09df5acf01ec7540742b5ccd92b2ae92.png](https://i-blog.csdnimg.cn/blog_migrate/a1cdf0b58c0b5f998e793124a783900d.jpeg)
倒排索引
上面我们提到了,用户搜索的时候,会需要根据分词内容去查询“倒排索引”,什么是倒排索引呢?
要说倒排索引,自然就要先说说正排索引。回到我们的初级阶段,我们用最初的新闻Table来举例。
CREATE TABLE `News` ( `Id` int(10) NOT NULL COMMENT '新闻ID', `Title` VARCHAR(200) NOT NULL COMMENT '标题', `Content` TEXT NOT NULL COMMENT '内容',PRIMARY KEY (`Id`))
假如,我们想要查找某个新闻,然后就使用新闻ID进行查找获得新闻的标题和内容。为此,我们建立了新闻ID的索引,这种就是正排索引。
如果把这种方式扩展到我们的网页查找中,那Url就相当于新闻ID,网页的内容就相当于新闻内容,新闻的内容在分词以后,就会形成很多很多的内容Item,正排索引也就是建立一个Url和List之间的索引关系。
而倒排索引正好相反,就是在分词以后,我们根据分词进行url的归类,最终形成一个Item和List之间的索引关系,而这种索引正式搜索引擎所需要的。
我们在分词后,通过倒排索引查找,可以找到很多的集合,但是,我们最终呈现给用户的只会有一个结果。例如:搜索“会技术的葛大爷”,分词以后,“技术”查找到了一个集合,葛大爷查找到了一个集合,其中的排序顺序各有不同,我们就需要将两个集合合并成为一个集合并按顺序排序呈现给用户。
排序
最简单的方式自然是循环 * 循环,但是效率可想而知。
我们可以用更简单的方法,就是在倒排索引查找的时候,就将结果集进行排序,然后合并集合时,我们可以将集合数组想象成链表,比较链表中的值的大小,取出排序优先的值,然后移动游标再次比较,这样的话,每个元素比较的次数将会大量减少,两两之间最多比较一次。
![17766cc261e79e97d3f15609158a713a.png](https://i-blog.csdnimg.cn/blog_migrate/c11733d28da22aba71b422ab57862347.jpeg)
当然,这种方式在数据量大时并不适用,没有办法做到并行处理。如果想要并行处理,我们可以先将结果集合按照范围进行拆分,再进行比较。例如,我们按照1-10,10-20,20-30进行分组,然后在并行的进行排序,最终将结果合并,这样效率就大大提高了。
![41ec6a8646f6a84680d250abbd2fc449.png](https://i-blog.csdnimg.cn/blog_migrate/ff425a3dbf0a6427e0bf051a5e5a3d65.jpeg)
关于搜索引擎的内容,我们也就差不多都这里,搜索引擎中其实还有很多的内容,这都只有让我们慢慢的去发觉。