搜索引擎基础
文章目录
一、关于数据,索引等概述
Redis 是基于键值存储(k/v,key/value)。以前学过mysql,有知道了 表扫描(table scanning)的概念。对于非常多,表扫描效率非常低效。于是后面有了构建索引的概念。提到MySQL的索引,我们都知道MySQL InnoDB引擎实现的是B tree索引(平衡树),而且是B+ tree,也叫最左前缀索引(能够通过比对一个字段上的前N个字节来检索数据)。但是最左前缀索引也是因为其工作特性,它并不支持全文索引。MySQL的MyISAM 引擎支持全文索引(fulltext)。不过因为受限于MySQL数据集,数据类型等的限制,全文索引也有些局限性。
以用户熟知道的百度或者谷歌这样的浏览器应用而言,它们提供了一个前端的GUI界面包括搜索框,用户可以基于这个搜索框输入关键词或者语句检索出自己想要找到的文章,图片等等。搜索引擎接口,输入 关键字,查询我们关注的内容。即使大规模的站点做了分类,也很少有用户会去导航栏一个一个的选择(当然,这个是针对大数据统计的用户使用习惯,可能不同的时刻或者不同的领域偏向来看,用户习惯就略有不同)。
互联网上搜索引擎面临的主要问题有:
数据的存储
数据处理
而 google 开源很早之前公布了解决问题的思想的论文。(海量数据存储的GFS, 分布式应用程序处理框架 Mapreduce)
Hadoop 的实现正是借鉴了上面的论文思想,开了出来的解决问题的产品:
Hadoop Distributed File System ( HDFS ),它主要用来解决海量数据存储问题
Mapreduce: 它主要用来处理数据,是一个框架。
不过上面的系统是解决了高效存储和数据处理,对于用户是否真正能够基于关键词能够高效的检索,本身没有实现。我们不说做互联网上一类的或者叫通用的搜索引擎(比如像百度,google等搜索引擎)。对于应用的站内搜索的引擎,信息系统的后台分析日志,分析信息等。(面向客户端一侧,让用户可以去搜索站点的某一个商品,或者某一篇文章等等,这是面向客户端一侧提供的站内搜索引擎。如果你不是做类似于百度或者google这样通用的搜索引擎,考虑的点就不一样。只要你负责的业务涉及提供大量的内容的站点类应用,信息系统的后台要做日志分析,数据检索等,都需要用到这样的一个专业的数据高效检索,数据高效分析的系统)
二、日志数据概述
前提:一个中略具规模的站点,一天的日志累计量可能有几千万条,几十亿甚至上百亿条日志信息(几十G甚至上百G的存储空间),如果放在文本中,使用awk这样的工具去处理,恐怖不是件容易的事情。
使用awk逐行进行分析,效率是有多低,可想而知。这个时候就需要有专业的工具,进行分析,聚合,数据处理,数据展示等。而需求,就会有产品,这是一个势头,而Lucene类库的开发就是解决了此类问题的核心部件。
- 何为 Lucene
Lucene 是使用 java 开发的一个搜索引擎类库,它能够将用户被搜索的内容构建成可被搜索的格式。不过它只是一个class library,本身不能执行。
另外一个解决此类问题的引擎叫 Sphinx,它是使用C++研发的,应用不多。主流的还是以推崇 lucene 为主。我们上面说过,Lucene本身是一个类库,它不能执行。如果想要用它,就需要有人些程序去调用它来完成,一般的终端用户可能没有这个能力,于是早些时候,诞生了一个叫Solr的应用或者叫产品。 它是基于 Lucene 的基础,开发的搜索引擎及服务程序,是 ASF 旗下著名的产品之一。
这里有了搜索引擎,可是我们的数据从哪里来,这个是一个问题?
- 对于通用的搜索引擎(google或者百度等),用户输入关键词检索出来的内容部分,这个内容肯定不是实时加载到。它是事先有一个类似于爬虫或者蜘蛛的程序,爬遍互联网上的每一个链接,集成的数据。
- 对于我们站内的搜索,数据这一块本身是有的,所以我们也不用去追溯数据的来源(这些数据通常根据我们的业务情况,一般存储于 Mariadb,redis,mongodb存储系统中)
三、引入ETL工具的概念
分析,切词,正规化,存储属性描述(文档化,每一个文档对于现实生活中的一个个数据项)
ETL 工具,抽取(extra), 转换(transform), 加载(load)
(1) 分析:(比如从 MySQL 检索的数据项,要对它进行分析等操作)
- 切词、切词分析
为什么要切词或者分开呢,因为用户检索的习惯都是根据关键词或者一个短语,很少使用很长一条语句来搜索。做切词分析的根本目的以便能够匹配到用户搜索时所给定关键词 - 格式转换 (比如同义词转换,大小写不区分,纠词提示,近似搜索等)
例如:
bike:用户搜索关键词
(1) 一篇文章中出现了 bicycle,那么这篇文章要不要被展现出来呢?(同义词问题)
(2) 包含 Bike 的文章或者内容是否要被展现出来呢?(大小写字符问题)
(3) 用户检索关键词肯定不符合词语原始正确词义(比如用户检索的关键词是这个 bycycle),要不要提示用户,或者把含有的 bicycle 的文章展现出来等(语义问题,纠词提示问题)
注:分析完成后,就要考虑数据的存储问题。但是数据本身是没有含义的。比如123这个数据,它本身是代表数字123还是年龄123岁,还是说其他的。我们无从得知。为了让数据有意义或者让用户检索的数据有意义,我们通常还要实现数据的属性描述,加一些关键的key描述。
(2) 属性描述
比较灵活的方案(schema),有 xml,json,yaml 等。Lucene 采用的是 JSON 的存储机制。做切词后,把每一个重要的数据定义成 JSON 格式的数组。
比如互联网上的文章,做简单切分后的属性描述类似于:
{"title": "xxx", "boby": "xxxxxxx"}
上面的这种数据叫关联数组,互联网上类似于这样的数据也称之为"文档"(document)。实际上是,boby部分可能还要做切词的,这时候就类似于嵌套的二级文档的概念。
假设,httpd combined 格式的日志,我们要让它变得有意义,就可以类似于这样组织成json:
{clientip:IP, ident:ID, user:USER, timestamp:xxx}
切词后,要文档化(对核心的关键词加上属性描述,表明它在现实中的真正意义是什么)。每一个文档(有点类似于mysql中的一行数据)可以罗列的组合在一起。多个文档组合起来可以,在Lucene的语境中,同一类型的文档的组合叫做"类型"(type),多个类型的数据组合在一起形成一个数据库,而这个库我们反而把它们称之为索引。
Lucene语境和关系型数据库的类比:
Document --> Row
Type --> Table
Index(多个Type) --> Database
注:我们的搜索操作无非是基于Index的某一Type上实现。
四、从Lucene 到 Solr,以及 ElasticSearsh
Lucene 虽然实现了上面的“ETL",“文档化”,但是它本身不会提供服务器给用户使用这样的一组功能。即Lucene本身没有办法接收你ETL,文档化之后的数据。如果你有数据,你让Lucene给你检索数据,它也做不到。(Lucene能够帮你构建Index,但是它本身不具备和用户交互的能力。)
要给Lucene开发一个外壳。能够实现这样一组功能。比如本身通过注册监听一个套接字,然后对外提供服务,对内,它可以把用户的请求转换为Lucene能够理解的语境来实现,这个有个叫 Solr的应用程序实现了,它的本质核心还是Lucene。
4.1、solr的历史和缺点
但是呢,Solr 这个产品出来的比较早,早期是为了解决站内搜索的,它不像百度,google等为了解决全网的通用的搜索。早期的站内的数据量还是挺少的(比如博客网站,我们自己有3000篇文章,我们一个个去看,去手工搜索也是很麻烦的。Solr 在单机上构建一个搜索引擎,完成能够应付早期的这种场景或者任务了)。Solr 当初的主要设计就是单机上的搜索引擎。
但是,现代的互联网站点(略觉规模),一天站内的日志信息都能多达几十亿条,甚至上百亿条。我们在单机上存储,检索,可能会超过了单机上所能承载的存储和计算能力。早期时候的 Solr 版本是不支持多机分布式运行的。于是有人就根据这种需求,基于 Lucene 开发了新的产品。它一样使用 Lucene 作为核心,开发的外壳本身可以扩展为让 Lucene 在多机上分布式协同以集群的方式运行,这个产品就叫ElasticSearch (早期的命名)。所以本质上来讲,ElasticSearch 核心还是Lucene的外壳,这个外壳运行多机协同分布式运行。
4.2、ElasticSearsh
- ElasticSearch 的逻辑
比如有多个节点构建的 ElasticSeach 集群,它接收数据项后,不会把它当一个独立的数据项来存储于某个或者某几个节点,而是把这个完整的数据进行切片后分散存储于多节点上。(回顾一下数据的分布式:第一以节点数量取模进行分布,这种方式非常的固化,以后增减节点不易;第二不再基于节点的数量取模,而是基于某一固定的数取模,这个固定值与节点数没有必然关系,比如redis 就是把整个节点的数据集分成16384个SLOT槽,每个SLOT就是一个存数据的空间。redis就是对这个16384取模来决定数据的存储节点的)
数据分散存储的思路:
- 基于节点的数量取模
- 基于某个固定值数量取模(Shard, 分片)。
4.3、ES 集群的分片和状态的概念
主分片(primary shard): 主分片通常只有一份
副本分片(repli shard):副本数量越多,意味着容错能力越强,而空间利用率就越低。(比如通常的一个主分片,两个副本分片相对比较合理)
ElasticSearch 默认是无论你存储的数据集有多大,都给你存储5片。(这个值可以自己修改)。当然,这个分片不是越多越好,也不是越少越好。(分片多意味着数据切割的很细,虽然可以充分利用都节点的性能,冗余能力也好,但是数据做分布式聚集返回的时候时间也长。分片少,如果节点的数量多,有可能得不到充分的利用,而且有可能还有数据冗余问题或者叫风险)
一般主分片和副本分片不能在一个节点上。如果一个节点上用一个数据的主分片和主分片的副本分片。这个节点坏了,那数据有可能就丢失了。如果基于一个节点上不能同时存在一个数据的主分片和它的副本分片的逻辑来实现,如果集群中的一个节点坏了,就意味着集群中的数据,要么是主分片缺少要么是副本分片缺少,此时我们的集群状态并不是健康的(黄色,yellow),完全健康的集群状态叫 绿色(green)。假设碰巧集群坏了两个节点,某一个数据的主分片在第一个节点上,另外一个分片在第二个节点上(恰好这个数据有只有一主一副),那么集群必然会丢失数据,此时的状态red,红色状态。
yellow 是很容易恢复到green状态的,可以基于副本分片恢复或者叫重构丢失的主分片,可以基于主分片恢复或者叫重构丢失的副本分片。如果是red状态,数据集的丢失是在所难免的了。
4.4、ES集群集群节点的通信方式
ES集群的构建思路比较简单,只需要指定集群名(不过为了更好的管理,建议手动指定ES集群名以及后端的节点的每个IP),早期时候ES集群对成员节点的发现支持单播和多播这两种方式。单播更精确,多播更高效。后来新版本的ES通常只支持单播而不再支持多播。
五、再谈数据到 Logstash 的引入以及beats的引入
5.1、从数据简析引入 logstash
有这样的一个,可以实现切词,正规化,索引化,数据的来源很重要,所谓巧妇难为无米之炊。你的数据从mysql中读取,那么mysql中的数据哪里来呢?这部分才是最核心的。
- 如果是搜索网站页面可以就python等语言写一个爬虫程序,它只爬自己域名的网站,爬出来并导入到ES集群的数据存储当中去;(爬虫爬取的站内的数据也并不是文档格式,我们要把它转换成文档的格式后再存储,因为Lucene只识别文档)
- 如果是mysql中生成了数据,我们要从mysql中读取数据,要自己写一个程序从mysql中读取数据转换成文档格式,然后存于ES集群的存储中去。
很多时候运维工程师构建一个ElasticSearch 集群,都是用来存储日志和分析日志的。那么我们如何把日志导入到 ES 集群中去?
ES 自身并没有这样的一个数据导入的能力。默认思路就是我们可能要写这样一个程序,默认把日志中的每一行抽取出来,转成文档格式,然后存于 ES 集群中去。
这个是一种通用的需求,而且很有可能某类站点中的日志非常多,如果通过运维的简单脚本来完成,效率是很低的。因此就有业界类开发的专门的应用程序来实现。
而此类程序比较有名的是 Logstash (最核心是日志抽取),后来ElasticSearch 收购了 LogStash。
5.2、logstash 工作简述
logstash 可以工作到你生成日志的服务器上,以进程的方式运行。可以监控到你新生成的或者追加的日志,每追加一行,它能够把文件中的一行抽取出来,转成二文档格式,再发给ES。(工作于每一个生成日志的节点之上)它虽然叫logstash,不过它是一个很通用的日志或者数据抓取工具。它可以抓取来自于很多位置的数据,比如 redis,mysql,nginx,apache等。(即它的数据来源非常丰富)
5.3、logstash 的缺点
但是,由于logstash开发语言(Jruby,通过java实现ruby)的特性,它的程序代码逻辑非常的重量。程序本身很大(几百兆),而且也很消耗内存,性能也一般。
Jruby:ruby本身要运行在ruby虚拟机上。Jruby就是通过java实现的虚拟机,让ruby运行。
以一个web站点来讲,假设我们的日志收集是要实际nginx或者tomcat的日志,本身服务器压力比较大,再加上要使用这样一个大型的程序来运行,势必会影响我们的应用程序本身的性能。(很吃内存的)
因为 Logstash 非常重量级,因为是 JRuby 语言研发,效率很低,非常消耗内存,太过于笨重。所以有能力的其他公司就开始研发自己的日志抽取的工具。比如 facebook 的 scibe。
5.4、beats 的引入
Elastic 肯定不甘心市场份额被其他公司吃掉,于是重新用 C + GO 研发了新的程序(核心是日志抽取) ,各种类型的 beats。它里面有很多beats,它是一类工具集统称:
- 从文件中抽取数据
filebeat (一般我们从日志中收集数据,用这个足以解决问题)
- 从网络中收集抓包的
packetbeat
- 从windows的日志事件查看器中收集的
windowsenentbeat
等等。
六、kibana 引入
日志抽取出来后,就需要去存储并供给用户去检索了,ElasticSearch 提供的是一种基于 RestFul 风格的 API 接口。用户完全可以使用一个 web 浏览器向 ES 发请求,包括发出检索请求。ES也通过HTTP或HTTPS协议向客户端响应结果。如果是curl命令,它只能是文本的展示结果。这里还是,一般用户去调用HTTP接口或者自己研发程序去调用,他/她们基本上没有这样的能力,因此,另外一个叫 Kibana 的项目就出现了。(Kibana本质上是一个以ES为数据源的数据展示接口,提供一个类似于百度或者google一样的GUI 的搜索框,用户可以在这个搜索框中输入关键词检索出对应索引中的内容出来,它还可以把搜索出的结果做聚合展示,支持类似于树状图,点状图,雷达图作为对比分析等等。所以展示效果非常的漂亮。granfa就是基于kibana 3.0 代码实现一个专门用于监控领域图形展示的项目)
七、ELK 的概念
7.1、ELK 概述
那么,ELK 到底是什么呢? “ELK”是三个开源项目的首字母缩写,这三个项目分别是:Elasticsearch、Logstash 和 Kibana。Elasticsearch 是一个搜索和分析引擎。Logstash 是服务器端数据处理管道,能够同时从多个来源采集数据,转换数据,然后将数据发送到诸如 Elasticsearch 等“存储库”中。Kibana 则可以让用户在 Elasticsearch 中使用图形和图表对数据进行可视化。Elastic Stack 是 ELK Stack 的更新换代产品。
- Logstash/beats
数据抽取,转换,加载工具(ETL工具);把数据抽取出来,转换为文档格式,把转换的数据加载到Elasticsearch中去; - Elasticsearch
是一个搜索引擎,能够把beats等工具发来的数据,在本地完成分析(切词正规化),支持检索; - Kibana
提供用户一个检索,展示的非常直观的用户界面的可视化工具。
ELK --> EFK–>ELFK (ElasticStack 这是后来的命名,Elastic的工具栈)。Logstash 只是在一定程度上被beats替代,还是有用到它的。(filebeat的文档化能力非常有限,而且一般核心部分只做数据抽取,数据也不是直接给ES,而是给Logstash {server},由Logstash 给 ES)
logstash server 在本地把数据文档化,做无效的数据清理,对日期时间的格式转换等(即filebeat完美的实现了ETL的E的功能,而TL的功能由logstash来完成)
7.2、ES 集群的引入
ElasticSearch 本身是无状态的,也是基于 HTTP 协议访问,而 Kibana 本身只能连接到一个 ElasticSearch 的节点,在 ES 集群和 Kibana 之间会加一个负载均衡调度器。把 Kibana 的请求负载均衡到多个 ES 集群的多个节点上来实现。ES 集群的节点是无中心节点的集群,每一个节点本身都能维系或者维持全局的视角或者数据,即某一个分片在哪一个节点上,每一个 ES 的节点都能响应这样的一个信息,但每一个节点都只放一部分分片,你可以向任何一个节点路由请求,如果分片数据不在本地,这个被请求的 ES 的节点,它会帮你去找其他节点。这里并不是告诉你节点的位置,在由你或者叫客户端自行去查找。
[场景一] kibana 压力不大,不需要把请求负载均衡,只需要冗余考虑
如果要在互联网*问(非站内),走公网或者域名:
[场景二] kibana 压力大,需要把它的请求负载均衡的大概架构图
如果要在互联网*问(非站内),走公网或者域名:
[场景三] 应用日志非常多,而且非常的频繁
上面适用于 tomcat 后端日志服务器比较少(图片的请求都是页面集成,非独立 PV,可以不用统计日志到ES)。如果 tomcat 日志非常多,上面框架的 logstash server 的势必会称为性能瓶颈点。解决思路:
- 如果ES的日志分析可以接收非实时,采用异步的思路,添加消息队列的中间层(redis等队列可以使用,队列要限制处理速度)
- 如果ES的日志分析要实时,AIops 做出实时的智能分析,恐怕不太好实现。
思路图:
ERFLK(Elastic, Redis message queue, Filebeat, Logstash, Kibana),消息队列不一定用 Redis,比如用另外的分布式的消息队列Kafka(其承载能力比redis要强大的多).
八、再看搜索引擎的整体框架
一个完成意义上的搜索引擎的架构:
两部分组件,以 index 为分割(并不精确):
- 上一半,叫搜索组件(在索引中,根据用户的需求去搜索)
- 下一半,叫索引组件(装入数据,并负责建立好索引)
Lucene 能做什么:
- Analyze Document(分析文档)
- Index Document(索引文档)
- 提供索引和一个基于索引的运行的查询接口
ETL工具:
- Raw Content (原始内容)
- Acquire Content(抓取需要的内容)
- Build Document(构建文档,文档化)
前端查询和展示:
- Search User Interface(用户搜索接口)
- Build Query(建立查询)
- Run Query(运行查询)
- Render Results (渲染结果)