1. 基础知识
1.1 认识Lucene
Lucene是一套用于全文检索和搜索的开放源码程序库,由Apache软件基金会支持和提供。Lucene提供了一个简单却强大的应用程序接口,能够做全文索引和搜索,在Java开发环境里Lucene是一个成熟的免费开放源代码工具;就其本身而论,Lucene是现在并且是这几年,最受欢迎的免费Java信息检索程序库。
Lucene官网:http://lucene.apache.org
1.2 倒排索引
在搜索引擎中,每个文档都有一个对应的文档 ID,文档内容被表示为一系列关键词的集合。例如,文档 1 经过分词,提取了 20 个关键词,每个关键词都会记录它在文档中出现的次数和出现位置。
那么,倒排索引就是关键词到文档 ID 的映射,每个关键词都对应着一系列的文件,这些文件中都出现了关键词。
在搜索引擎中,每个文档都有一个对应的文档 ID,文档内容被表示为一系列关键词的集合。
那么,倒排索引就是关键词到文档 ID 的映射,每个关键词都对应着一系列的文件,这些文件中都出现了关键词。
DocId | Doc |
---|---|
1 | 谷歌地图之父跳槽 Facebook |
2 | 谷歌地图之父加盟 Facebook |
3 | 谷歌地图创始人拉斯离开谷歌加盟 Facebook |
4 | 谷歌地图之父跳槽 Facebook 与 Wave 项目取消有关 |
5 | 谷歌地图之父拉斯加盟社交网站 Facebook |
对文档进行分词之后,得到以下倒排索引。
WordId | Word | DocIds |
---|---|---|
1 | 谷歌 | 1,2,3,4,5 |
2 | 地图 | 1,2,3,4,5 |
3 | 之父 | 1,2,4,5 |
4 | 跳槽 | 1,4 |
5 | 1,2,3,4,5 | |
6 | 加盟 | 2,3,5 |
7 | 创始人 | 3 |
8 | 拉斯 | 3,5 |
9 | 离开 | 3 |
10 | 与 | 4 |
.. | .. | .. |
另外,实用的倒排索引还可以记录更多的信息,比如文档频率信息,表示在文档集合中有多少个文档包含某个单词。
那么,有了倒排索引,搜索引擎可以很方便地响应用户的查询。
比如用户输入查询 Facebook
,搜索系统查找倒排索引,从中读出包含这个单词的文档,这些文档就是提供给用户的搜索结果。
要注意倒排索引的两个重要细节:
倒排索引中的所有词项对应一个或多个文档;
倒排索引中的词项根据字典顺序升序排列;
上面只是一个简单的栗子,并没有严格按照字典顺序升序排列。
Lucene是一个开源的全文检索引擎工具包(类似于Java api),而Elasticsearch底层是基于这些包,对其进行了扩展,提供了比Lucene更为丰富的查询语言,可以非常方便的通过Elasticsearch的HTTP接口与底层Lucene交互。
如果在应用程序中直接使用Lucene,你需要覆盖大量的集成框架工作,而使用ElasticSearch就省下了这些集成工作。
一句话概括:Elasticsearch是Lucene面向企业搜索应用的扩展,极大的缩短研发周期。
Lucene是一个开源的全文检索引擎工具包(类似于Java api),而Elasticsearch底层是基于这些包,对其进行了扩展,提供了比Lucene更为丰富的查询语言,可以非常方便的通过Elasticsearch的HTTP接口与底层Lucene交互。
如果在应用程序中直接使用Lucene,你需要覆盖大量的集成框架工作,而使用ElasticSearch就省下了这些集成工作。
一句话概括:Elasticsearch是Lucene面向企业搜索应用的扩展,极大的缩短研发周期。
刚刚入门Elasticsearch,只需稍微了解下Lucene,无需去真正学习它,就可以很好的完成全文索引的工作,很好的进行开发。等熟练使用es之后,可以反过头来学习Lucene里面底层的原理,也是一种提升。
因为Lucene是一个编程库,您可以按原始接口来调用。但是Elasticsearch是在它基础上扩展的应用程序,就可以直接拿来使用了。
举个例子,你直接拿汽车(Elasticsearch)来开,开好车就行,无需了解里面的发动机、各个组件(Lucene library)。后面你在去了解一些原理,对于修车等等会有帮助。
因为Lucene是一个编程库,您可以按原始接口来调用。但是Elasticsearch是在它基础上扩展的应用程序,就可以直接拿来使用了。
举个例子,你直接拿汽车(Elasticsearch)来开,开好车就行,无需了解里面的发动机、各个组件(Lucene library)。后面你在去了解一些原理,对于修车等等会有帮助。
1.4 ES的核心面试题
(1)es的分布式架构原理是什么(es是如何实现分布式的)?
(2)es写入数据的工作原理是什么?es查询数据的工作原理是什么?
(3)es在数据量很大的情况下(数十亿级别)如何提高查询性能?
(4)es生产集群的部署架构是什么?每个索引的数据量大概有多少?每个索引大概有多少个分片?
2. 认识ES
Lucene 是最先进、功能最强大的搜索库。如果直接基于 lucene 开发,非常复杂,即便写一些简单的功能,也要写大量的 Java 代码,需要深入理解原理。
elasticsearch 基于 lucene,简称es,隐藏了 lucene 的复杂性,提供了简单易用的 restful api / Java api 接口(另外还有其他语言的 api 接口)。现在分布式搜索基本已经成为大部分互联网行业的Java系统的标配,其中尤为流行的就是es,前几年es没火的时候,大家一般用solr。但是这两年基本大部分企业和项目都开始转向es了。
- 分布式的文档存储引擎
- 分布式的搜索引擎和分析引擎
- 分布式,支持 PB 级数据
2.1 ES 的核心概念
Near Realtime
近实时,有两层含义:
从写入数据到数据可以被搜索到有一个小延迟(大概是 1s)
基于 es 执行搜索和分析可以达到秒级
Cluster 集群
集群包含多个节点,每个节点属于哪个集群都是通过一个配置来决定的,对于中小型应用来说,刚开始一个集群就一个节点很正常。
Node 节点
Node 是集群中的一个节点,每个节点有一个唯一的名称,这个名称默认是随机分配的。默认节点会去加入一个名称为 elasticsearch
的集群。如果直接启动一堆节点,那么它们会自动组成一个 elasticsearch 集群,当然一个节点也可以组成 elasticsearch集群。
Document & field
文档是 es 中最小的数据单元,一个 document 可以是一条客户数据、一条商品分类数据、一条订单数据,通常用 json 数据结构来表示。
每个 index 下的 type,都可以存储多条 document。一个 document 里面有多个 field,每个 field 就是一个数据字段。
{ "product_id": "1", "product_name": "iPhone X", "product_desc": "苹果手机", "category_id": "2", "category_name": "电子产品" }
Index
索引包含了一堆有相似结构的文档数据,比如商品索引。一个索引包含很多 document,一个索引就代表了一类相似或者相同的 ducument。
Type
类型,每个索引里可以有一个或者多个 type,type 是 index 的一个逻辑分类,比如商品 index 下有多个 type:日化商品 type、电器商品 type、生鲜商品 type。每个 type 下的 document 的 field 可能不太一样。
shard
单台机器无法存储大量数据,es 可以将一个索引中的数据切分为多个 shard,分布在多台服务器上存储。有了 shard 就可以横向扩展,存储更多数据,让搜索和分析等操作分布到多台服务器上去执行,提升吞吐量和性能。每个 shard 都是一个 Lucene index。
replica
任何一个服务器随时可能故障或宕机,此时 shard 可能就会丢失,因此可以为每个 shard 创建多个 replica 副本。replica 可以在 shard 故障时提供备用服务,保证数据不丢失,多个 replica 还可以提升搜索操作的吞吐量和性能。primary shard(建立索引时一次设置,不能修改,默认 5 个),replica shard(随时修改数量,默认 1 个),默认每个索引 10 个 shard,5 个 primary shard,5个 replica shard,最小的高可用配置,是 2 台服务器。
这么说吧,shard 分为 primary shard 和 replica shard。而 primary shard 一般简称为 shard,而 replica shard 一般简称为 replica。
ES 核心概念 vs DB 核心概念
es | db |
---|---|
index | 数据库 |
type | 数据表 |
docuemnt | 一行数据 |
以上是一个简单的类比。
3. ES架构原理
elasticsearch设计的理念就是分布式搜索引擎,底层其实还是基于lucene的。核心思想就是在多台机器上启动多个es进程实例,组成了一个es集群。
es中存储数据的基本单位是索引,比如现在要在es中存储一些订单数据,此时就应该在es中创建一个索引,order_idx,所有的订单数据就都写到这个索引里面去,一个索引相当于mysql里的一张表。
index -> type -> mapping -> document -> field
以下是对上述名词概念的一种类比,仅仅是一种类比描述:
index: mysql 里的一张表。
type:没法跟mysql里去对比,一个index里可以有多个type,每个type的字段都是差不多的,但是有一些略微的差别。
很多情况下,一个 index 里可能就一个 type,但是确实如果说是一个 index 里有多个 type 的情况(注意,mapping types
这个概念在 ElasticSearch 7.X 已被完全移除,详细说明可以参考官方文档)。
可以认为 index 是一个类别的表,具体的每个 type 代表了 mysql 中的一个表。。每个 type 有一个 mapping,如果你认为一个 type 是具体的一个表,index 就代表多个 type 同属于的一个类型,而 mapping 就是这个 type 的表结构定义。
场景类比:在 mysql 中创建一个表的时候,需要定义表结构、表结构中的字段、每个字段的类型。因此 index 里的一个 type 里面写的一条数据,这条数据叫做一条 document,一条 document 就代表了 mysql 中某个表里的一行,每个 document 有多个 field,每个 field 就代表了这个 document 中的一个字段的值。
ES的架构原理:
elasticsearch设计的理念就是分布式搜索引擎,底层其实还是基于lucene的。核心思想就是在多台机器上启动多个es进程实例,组成了一个es集群。
es中存储数据的基本单位是索引,比如现在要在es中存储一些订单数据,此时就应该在es中创建一个索引,order_idx,所有的订单数据就都写到这个索引里面去,一个索引相当于mysql里的一张表。
index -> type -> mapping -> document -> field
以下是对上述名词概念的一种类比,仅仅是一种类比描述:
index: mysql 里的一张表。
type:没法跟mysql里去对比,一个index里可以有多个type,每个type的字段都是差不多的,但是有一些略微的差别.
很多情况下,一个 index 里可能就一个 type,但是确实如果说是一个 index 里有多个 type 的情况(注意,mapping types
这个概念在 ElasticSearch 7.X 已被完全移除,详细说明可以参考官方文档)。
创建一个es 索引的时候,这个索引可以拆分成多个 shard
,每个 shard 存储部分数据。拆分多个 shard 是有好处:
支持横向扩展
比如整个数据量是 3T,3 个 shard,每个 shard 可以分别拆分成 1T 的数据,若现在数据量增加到 4T 该怎么扩展?很简单,重新建一个有 4 个 shard 的索引,将数据导进去;
数据分布在多个 shard,即多台服务器上,所有的操作都会在多台机器上并行分布式执行,提高了吞吐量和性能。
另外 shard 的数据实际是有多个备份,也就是说每个 shard 都有一个 primary shard
,这个 primary shard 专门负责写入数据,同时这个还有几个副本 replica shard
。primary shard
写入数据之后,会将数据同步到自己的所有副本 replica shard
上去(primary shard和其副本的replica shard不会在同一台机器上)。
es集群会自动从众多节点中选举某一个节点为master节点,这个master节点其实就是干一些管理的工作,比如维护索引元数据拉,负责切换primary shard和replica shard身份等。
当master节点宕机了,那么es集群会重新选举一个节点成为新的master节点。
当非master节点宕机了,那么会由master节点,让那个宕机节点上的primary shard的身份转移到其他机器上的replica shard。
随后当修复了那个宕机机器并重启之后,master 节点会控制将缺失的 replica shard 分配过去,同步后续修改的数据之类的,让集群恢复正常。
4. 写入&查询数据的工作原理
4.1 写数据过程
-
客户端选择一个node发送请求过去,这个node就是
coordinating node
(协调节点)。 -
coordinating node
对document进行路由,将请求转发给对应的node(有primary shard)。 -
实际的node上的
primary shard
处理请求,然后将数据同步到有对应replica shard
的node上。 -
coordinating node,如果发现primary node和所有replica node都搞定之后,就返回响应结果给客户端。
4.2 读数据过程
数据写入某个document的时候,es会自动会给这个document自动分配一个全局唯一的id,称为doc id
,协同节点也是根据doc id进行hash路由到对应的primary shard上面去。也可以手动指定doc id,比如用订单id,用户id。
读数据的过程大致是通过doc id查询,es会根据doc id进行hash,判断出来当时把doc id分配到了哪个shard上面去,再从那个shard去查询:
-
客户端发送请求到任意一个node,当前node成为
coordinate node
(协调节点)。 -
coordinate node
对doc id
进行哈希路由,将请求转发到对应的 node,此时会使用round-robin
随机轮询算法,在primary shard
以及其所有 replica 中随机选择一个,让读请求负载均衡。 -
接收请求的 node 返回 document 给
coordinate node
。 -
coordinate node
返回 document 给客户端。
4.3 搜索数据过程
es 最强大的是做全文检索,就是比如你有三条数据:
java真好玩儿啊 java好难学啊 j2ee特别牛
es可以根据 "java" 关键词来搜索,将包含 "java"关键词的 document
给搜索出来。es 就会给你返回:"java真好玩儿啊","java好难学啊"。
客户端发送请求到一个 coordinate node
。
协调节点将搜索请求转发到所有的 shard 对应的 primary shard
或 replica shard
。
query phase:每个 shard 将自己的搜索结果(其实就是一些 doc id
)返回给协调节点,由协调节点进行数据的合并、排序、分页等操作,产出最终结果。
fetch phase:接着由协调节点根据 doc id
去各个节点上拉取实际的 document
数据,最终返回给客户端。
写请求写入primary shared,然后同步给所有的 replica shard;读请求可以从 primary shard 或 replica shard 读取,采用的是随机轮询算法。
1)数据先写入内存buffer,在写入buffer的同时将数据写入translog
日志文件,注意:此时数据还没有被成功es索引记录,因此无法搜索到对应数据;
2)如果buffer快满了或者到一定时间,es就会将buffer数据refresh
到一个新的segment file
中,但是此时数据不是直接进入segment file
的磁盘文件,而是先进入os cache
的。这个过程就是refre
每隔1秒钟,es将buffer中的数据写入一个新的segment file
,因此每秒钟会产生一个新的磁盘文件segment filesh.
这个segment file中就存储最近1秒内buffer中写入的数据。
如果buffer里面此时没有数据,那当然不会执行refresh操作咯,每秒创建换一个空的segment file,如果buffer里面有数据,默认1秒钟执行一次refresh操作,刷入一个新的segment file中。
操作系统中,磁盘文件其实都有一个操作系统缓存os cache
,因此数据写入磁盘文件之前,会先进入操作系统级别的内存缓存os cache
中。
这就是为什么es被称为准实时(NRT,near real-time):因为写入的数据默认每隔1秒refresh
一次,也就是数据每隔一秒才能被 es 搜索到,之后才能被看到,所以称为准实时。
es可以通过restful api或者java api,手动执行一次refresh
操作,也就是手动将buffer中的数据刷入os cache中,让数据立马就可以被搜索到。
只要数据被输入os cache
中,buffer就会被清空,并且数据在translog
日志文件里面持久化到磁盘了一份,此时就可以让这个segment file
的数据对外提供搜索了.
3)重复1~2步骤,新的数据不断进入buffer
和translog
,不断将buffer数据写入一个又一个新的segment file中去,每次refresh
完,buffer就会被清空,同时translog保留一份日志数据。随着这个过程推进,translog
文件会不断变大。当translog
文件达到一定程度时,就会执行commit
操作.
4)commit操作发生第一步,就是将buffer中现有数据refresh
到os cache
中去,清空buffer
。
5)将一个 commit point
写入磁盘文件,里面标识着这个 commit point
对应的所有 segment file
,同时强行将 os cache
中目前所有的数据都 fsync
到磁盘文件中去
8)将现有的translog
清空,然后再次重启启用一个translog
,此时commit操作完成。
默认每隔30分钟会自动执行一次commit
,但是如果translog
文件过大,也会触发commit。整个commit的过程,叫做flush
操作。我们可以手动执行flush
操作,就是将所有os cache
数据刷到磁盘文件中去。
我们也可以通过es的api,手动执行flush
操作,手动将os cache
中的数据fsync强刷到磁盘上去,记录一个commit point,清空translog日志文件。
综上可以看出:
- es是准实时的,因此数据写入1秒后才可以搜索到。
- es可能会丢失数据:有5秒的数据停留在buffer、translog的os cache、segment file的os cache中,也就是这5秒的数据不在磁盘上,此时如果宕机,会导致5秒的数据丢失
总结一下:
-
数据先写入内存 buffer,然后每隔 1s,将数据 refresh 到 os cache,到了 os cache 数据就能被搜索到(所以我们才说 es 是准实时的,因为从写入到能被搜索到中间有 1s 的延迟)。
- 每隔 5s,将数据写入 translog 文件(这样如果机器宕机,内存数据全没,最多会有 5s 的数据丢失)。
-
translog
大到一定程度,或者默认每隔 30mins,会触发 commit 操作,将缓冲区的数据都 flush 到 segment file 磁盘文件中。
数据写入 segment file 之后,同时就建立好了倒排索引
比如说你现在有一行数据。id,name,age等 30 个字段。实际需求只需要根据 id,name,age 这三个字段来搜索数据,但是如果往 es 里写入一行数据所有的字段,就会导致 90%
的数据不用来搜索却硬是占据了 es 机器上的 filesystem cache
的大部分空间,单条数据的数据量越大,就会导致 filesystem cahce
能缓存的数据就越少。
因此,写入 es 中要用来检索的少数几个字段就可以了,比如写入 es 的是 id,name,age 这三个字段,其他的字段数据存在 mysql/hbase 里,我们一般是建议用 es + hbase
的组合架构。
hbase 的特点是适用于海量数据的在线存储,就是对 hbase 可以写入海量数据,但是不要做复杂的搜索,做很简单的一些根据 id 或者范围进行查询的这么一个操作就可以了。从 es 中根据 name 和 age 去搜索,拿到的结果可能就 20 个 doc id
,然后根据 doc id
到 hbase 里去查询每个 doc id
对应的完整的数据,给查出来,再返回给前端。
写入 es 的数据最好小于等于(略微大于一点也可) es 的 filesystem cache 的内存容量。粗略估算从 es 检索可能需要耗时 20ms,然后再根据 es 返回的 id 去 hbase 里查询,查 20 条数据,可能需要耗时 30ms,这样每次查询就是 50ms。而原来的一次查询就是耗时 5~10s,这种性能提升是非常显著的.
参考博文:
来源:CSDN
作者:thoughtCodes
链接:https://blog.csdn.net/xiamaocheng/article/details/104107587