ES入门 - 介绍
什么是ElasticSearch
Elastic
Elastic 有一条完整的产品线及解决方案:Elasticsearch、Kibana、Logstash等,前面说的三个就是大家常说的 ELK 技术栈。
ElasticSearch
Elasticsearch官网:https://www.elastic.co/cn/products/elasticsearchopen in new window
Elasticsearchopen in new window 是一个分布式、RESTful 风格的搜索和数据分析引擎,能够解决不断涌现出的各种用例。 作为 Elastic Stack 的核心,它集中存储您的数据,帮助您发现意料之中以及意料之外的情况。Elasticsearch 基于搜索库 Luceneopen in new window 开发。ElasticSearch 隐藏了 Lucene 的复杂性,提供了简单易用的 REST API / Java API 接口(另外还有其他语言的 API 接口)。
ElasticSearch 可以视为一个文档存储,它将复杂数据结构序列化为 JSON 存储。ElasticSearch 是近乎于实时的全文搜素,这是指:
- 从写入数据到数据可以被搜索,存在较小的延迟(大概是 1s)
- 基于 ES 执行搜索和分析可以达到秒级
它被用作全文检索、结构化搜索、分析以及这三个功能的组合:
- Wikipedia 使用 Elasticsearch 提供带有高亮片段的全文搜索,还有 search-as-you-type 和 did-you-mean 的建议。
- 卫报 使用 Elasticsearch 将网络社交数据结合到访客日志中,为它的编辑们提供公众对于新文章的实时反馈。
- Stack Overflow 将地理位置查询融入全文检索中去,并且使用 more-like-this 接口去查找相关的问题和回答。
- GitHub 使用 Elasticsearch 对1300亿行代码进行查询。
- ...
除了搜索,结合Kibana、Logstash、Beats开源产品,Elastic Stack(简称ELK)还被广泛运用在大数据近实时分析领域,包括:日志分析、指标监控、信息安全等。它可以帮助你探索海量结构化、非结构化数据,按需创建可视化报表,对监控数据设置报警阈值,通过使用机器学习,自动识别异常状况。
ElasticSearch 是基于Restful WebApi,使用 Java 语言开发的搜索引擎库类,并作为 Apache 许可条款下的开放源码发布,是当前流行的企业级搜索引擎。其客户端在 Java、C#、PHP、Python 等许多语言中都是可用的。
为什么不是直接使用Lucene
ElasticSearch是基于Lucene的,那么为什么不是直接使用Lucene呢?
Lucene 可以说是当下最先进、高性能、全功能的搜索引擎库。
但是 Lucene 仅仅只是一个库。为了充分发挥其功能,你需要使用 Java 并将 Lucene 直接集成到应用程序中。 更糟糕的是,您可能需要获得信息检索学位才能了解其工作原理。Lucene 非常 复杂。
Elasticsearch 也是使用 Java 编写的,它的内部使用 Lucene 做索引与搜索,但是它的目的是使全文检索变得简单,通过隐藏 Lucene 的复杂性,取而代之的提供一套简单一致的 RESTful API。
然而,Elasticsearch 不仅仅是 Lucene,并且也不仅仅只是一个全文搜索引擎。 它可以被下面这样准确的形容:
- 一个分布式的实时文档存储,每个字段可以被索引与搜索
- 一个分布式实时分析搜索引擎
- 能胜任上百个服务节点的扩展,并支持 PB 级别的结构化或者非结构化数据
ElasticSearch的主要功能及应用场景
主要功能:
- 海量数据的分布式存储以及集群管理,达到了服务与数据的高可用以及水平扩展;
- 近实时搜索,性能卓越。对结构化、全文、地理位置等类型数据的处理;
- 海量数据的近实时分析(聚合功能)。
应用场景:
- 网站搜索、垂直搜索、代码搜索;
- 日志管理与分析、安全指标监控、应用性能监控、Web抓取舆情分析。
ElasticSearch基础概念
Node 与 Cluster
Elastic 本质上是一个分布式数据库,允许多台服务器协同工作,每台服务器可以运行多个 Elastic 实例。
单个 Elastic 实例称为一个节点(node)。一组节点构成一个集群(cluster)。
Cluster 集群:包含多个节点。一个集群由一个唯一的名字标识,默认为“elasticsearch”。集群名称非常重要,具有相同集群名的节点才会组成一个集群。集群名称可以在配置文件中指定。对于中小型应用来说,刚开始一个集群就一个节点很正常。
Node 节点:存储集群的数据,参与集群的索引和搜索功能。像集群有名字,节点也有自己的名称,默认在启动时会以一个随机的 UUID 的前七个字符作为节点的名字,你可以为其指定任意的名字。通过集群名在网络中发现同伴组成集群。一个节点也可是集群。
Index
Index 索引:可以认为是文档(document)的优化集合。每个索引有唯一的名字,通过这个名字来操作它。一个集群中可以有任意多个索引。
Elastic 会为所有字段建立索引,经过处理后写入一个反向索引(Inverted Index)。查找数据的时候,直接查找该索引。
所以,Elastic 数据管理的顶层单位就叫做 Index(索引)。它是单个数据库的同义词。每个 Index (即数据库)的名字必须是小写。
下面的命令可以查看当前节点的所有 Index:
$ curl -X GET 'http://localhost:9200/_cat/indices?v'
Type
Type 类型:指在一个索引(Index )中,可以索引不同类型的文档,如用户数据、博客数据。是 index 的一个逻辑分类。
Document 可以分组,比如 weather
这个 Index 里面,可以按城市分组(北京和上海),也可以按气候分组(晴天和雨天)。这种分组就叫做 Type,它是虚拟的逻辑分组,用来过滤 Document。
不同的 Type 应该有相似的结构(schema),举例来说,id
字段不能在这个组是字符串,在另一个组是数值。这是与关系型数据库的表的一个区别open in new window。性质完全不同的数据(比如 products
和 logs
)应该存成两个 Index,而不是一个 Index 里面的两个 Type(虽然可以做到)。
下面的命令可以列出每个 Index 所包含的 Type。
$ curl 'localhost:9200/_mapping?pretty=true'
根据规划open in new window,Elastic 6.x 版只允许每个 Index 包含一个 Type,7.x 版将会彻底移除 Type。
Document
Document 文档:被索引的一条数据,索引的基本信息单元,以 JSON 格式来表示。Index 里面单条的记录称为 Document。许多条 Document 构成了一个 Index。
每个 Document 都是字段(Field)的集合。
Document 使用 JSON 格式表示,下面是一个例子:
{
"user": "张三",
"title": "工程师",
"desc": "数据库管理"
}
2
3
4
5
同一个 Index 里面的 Document,不要求有相同的结构(scheme),但是最好保持相同,这样有利于提高搜索效率。
Field
Field 字段:是包含数据的键值对。
默认情况下,Elasticsearch 对每个字段中的所有数据建立索引,并且每个索引字段都具有专用的优化数据结构。
Shard
Shard 分片:在创建一个索引时可以指定分成多少个分片来存储。每个分片本身也是一个功能完善且独立的“索引”,可以被放置在集群的任意节点上。
当单台机器不足以存储大量数据时,Elasticsearch 可以将一个索引中的数据切分为多个 shard 。shard 分布在多台服务器上存储。有了 shard 就可以横向扩展,存储更多数据,让搜索和分析等操作分布到多台服务器上去执行,提升吞吐量和性能。每个 shard 都是一个 lucene index。
Replica
Replication 备份:一个分片可以有多个备份(副本)。
任何一个服务器随时可能故障或宕机,此时 shard 可能就会丢失,因此可以为每个 shard 创建多个 replica。replica 可以在 shard 故障时提供备用服务,保证数据不丢失,多个 replica 还可以提升搜索操作的吞吐量和性能。
默认每个索引 10 个 shard,5 个 primary shard,5 个 replica shard,最小的高可用配置,是 2 台服务器。primary shard(建立索引时一次设置,不能修改,默认 5 个),replica shard(随时修改数量,默认 1 个)。
核心数据类型
Elasticsearch中支持的数据类型非常丰富:
我们说几个关键的:
String 类型,又分两种:
text
:可分词,不可参与聚合keyword
:不可分词,数据会作为完整字段进行匹配,可以参与聚合
Numerical:数值类型,分两类
- 基本数据类型:
long
、interger
、short
、byte
、double
、float
、half_float
- 浮点数的高精度类型:
scaled_float
- 需要指定一个精度因子,比如 10 或 100。elasticsearch 会把真实值乘以这个因子后存储,取出时再还原。
- 基本数据类型:
boolean 数值型
date:日期类型
elasticsearch 可以对日期格式化为字符串存储,但是建议我们存储为毫秒值,存储为 long,节省空间。
由于 Json 没有 date 类型,所以 es 通过识别字符串是否符合 format 定义的格式来判断是否为 date 类型。format 默认为:
strict_date_optional_time
、epoch_millis
binary:二进制,该类型的字段把值当做经过 base64 编码的字符串,默认不存储,且不可搜索
Range datatypes,范围类型。范围类型表示值是一个范围,而不是⼀个具体的值。分为:integer_range, float_range, long_range, double_range, date_range。
譬如 age 的类型是 integer_range,那么值可以是
{"gte" : 20, "lte" : 40}
;搜索"term" : {"age": 21}
可以搜索该值。
ES 核心概念 vs. DB 核心概念
为了方便理解,作一个ES和数据库的对比:
ElasticSearch基本原理
ES 写数据过程
- 客户端选择一个 node 发送请求过去,这个 node 就是
coordinating node
(协调节点)。 coordinating node
对 document 进行路由,将请求转发给对应的 node(有 primary shard)。- 实际的 node 上的
primary shard
处理请求,然后将数据同步到replica node
。 coordinating node
如果发现primary node
和所有replica node
都搞定之后,就返回响应结果给客户端。
ES 读数据过程
可以通过 doc id
来查询,会根据 doc id
进行 hash,判断出来当时把 doc id
分配到了哪个 shard 上面去,从那个 shard 去查询。
- 客户端发送请求到任意一个 node,成为
coordinate node
。 coordinate node
对doc id
进行哈希路由,将请求转发到对应的 node,此时会使用round-robin
随机轮询算法,在primary shard
以及其所有 replica 中随机选择一个,让读请求负载均衡。- 接收请求的 node 返回 document 给
coordinate node
。 coordinate node
返回 document 给客户端。
ES 搜索数据过程
es 最强大的是做全文检索,就是比如你有三条数据:
java真好玩儿啊
java好难学啊
j2ee特别牛
2
3
你根据 java
关键词来搜索,将包含 java
的 document
给搜索出来。es 就会给你返回:java真好玩儿啊、java好难学啊。
- 客户端发送请求到一个
coordinate node
。 - 协调节点将搜索请求转发到所有的 shard 对应的
primary shard
或replica shard
,都可以。 - query phase:每个 shard 将自己的搜索结果(其实就是一些
doc id
)返回给协调节点,由协调节点进行数据的合并、排序、分页等操作,产出最终结果。 - fetch phase:接着由协调节点根据
doc id
去各个节点上拉取实际的document
数据,最终返回给客户端。
写请求是写入 primary shard,然后同步给所有的 replica shard;读请求可以从 primary shard 或 replica shard 读取,采用的是随机轮询算法。
写数据底层原理
先写入内存 buffer,在 buffer 里的时候数据是搜索不到的;同时将数据写入 translog 日志文件。
如果 buffer 快满了,或者到一定时间,就会将内存 buffer 数据 refresh
到一个新的 segment file
中,但是此时数据不是直接进入 segment file
磁盘文件,而是先进入 os cache
。这个过程就是 refresh
。
每隔 1 秒钟,es 将 buffer 中的数据写入一个新的 segment file
,每秒钟会产生一个新的磁盘文件 segment file
,这个 segment file
中就存储最近 1 秒内 buffer 中写入的数据。
但是如果 buffer 里面此时没有数据,那当然不会执行 refresh 操作,如果 buffer 里面有数据,默认 1 秒钟执行一次 refresh 操作,刷入一个新的 segment file 中。
操作系统里面,磁盘文件其实都有一个东西,叫做 os cache
,即操作系统缓存,就是说数据写入磁盘文件之前,会先进入 os cache
,先进入操作系统级别的一个内存缓存中去。只要 buffer
中的数据被 refresh 操作刷入 os cache
中,这个数据就可以被搜索到了。
为什么叫 es 是准实时的? NRT
,全称 near real-time
。默认是每隔 1 秒 refresh 一次的,所以 es 是准实时的,因为写入的数据 1 秒之后才能被看到。可以通过 es 的 restful api
或者 java api
,手动执行一次 refresh 操作,就是手动将 buffer 中的数据刷入 os cache
中,让数据立马就可以被搜索到。只要数据被输入 os cache
中,buffer 就会被清空了,因为不需要保留 buffer 了,数据在 translog 里面已经持久化到磁盘去一份了。
重复上面的步骤,新的数据不断进入 buffer 和 translog,不断将 buffer
数据写入一个又一个新的 segment file
中去,每次 refresh
完 buffer 清空,translog 保留。随着这个过程推进,translog 会变得越来越大。当 translog 达到一定长度的时候,就会触发 commit
操作。
commit 操作发生第一步,就是将 buffer 中现有数据 refresh
到 os cache
中去,清空 buffer。然后,将一个 commit point
写入磁盘文件,里面标识着这个 commit point
对应的所有 segment file
,同时强行将 os cache
中目前所有的数据都 fsync
到磁盘文件中去。最后清空 现有 translog 日志文件,重启一个 translog,此时 commit 操作完成。
这个 commit 操作叫做 flush
。默认 30 分钟自动执行一次 flush
,但如果 translog 过大,也会触发 flush
。flush 操作就对应着 commit 的全过程,我们可以通过 es api,手动执行 flush 操作,手动将 os cache 中的数据 fsync 强刷到磁盘上去。
translog 日志文件的作用是什么?你执行 commit 操作之前,数据要么是停留在 buffer 中,要么是停留在 os cache 中,无论是 buffer 还是 os cache 都是内存,一旦这台机器死了,内存中的数据就全丢了。所以需要将数据对应的操作写入一个专门的日志文件 translog
中,一旦此时机器宕机,再次重启的时候,es 会自动读取 translog 日志文件中的数据,恢复到内存 buffer 和 os cache 中去。
translog 其实也是先写入 os cache 的,默认每隔 5 秒刷一次到磁盘中去,所以默认情况下,可能有 5 秒的数据会仅仅停留在 buffer 或者 translog 文件的 os cache 中,如果此时机器挂了,会丢失 5 秒钟的数据。但是这样性能比较好,最多丢 5 秒的数据。也可以将 translog 设置成每次写操作必须是直接 fsync
到磁盘,但是性能会差很多。
实际上你在这里,如果面试官没有问你 es 丢数据的问题,你可以在这里给面试官炫一把,你说,其实 es 第一是准实时的,数据写入 1 秒后可以搜索到;可能会丢失数据的。有 5 秒的数据,停留在 buffer、translog os cache、segment file os cache 中,而不在磁盘上,此时如果宕机,会导致 5 秒的数据丢失。
总结一下,数据先写入内存 buffer,然后每隔 1s,将数据 refresh 到 os cache,到了 os cache 数据就能被搜索到(所以我们才说 es 从写入到能被搜索到,中间有 1s 的延迟)。每隔 5s,将数据写入 translog 文件(这样如果机器宕机,内存数据全没,最多会有 5s 的数据丢失),translog 大到一定程度,或者默认每隔 30mins,会触发 commit 操作,将缓冲区的数据都 flush 到 segment file 磁盘文件中。
数据写入 segment file 之后,同时就建立好了倒排索引。
删除/更新数据底层原理
如果是删除操作,commit 的时候会生成一个 .del
文件,里面将某个 doc 标识为 deleted
状态,那么搜索的时候根据 .del
文件就知道这个 doc 是否被删除了。
如果是更新操作,就是将原来的 doc 标识为 deleted
状态,然后新写入一条数据。
buffer 每 refresh 一次,就会产生一个 segment file
,所以默认情况下是 1 秒钟一个 segment file
,这样下来 segment file
会越来越多,此时会定期执行 merge。每次 merge 的时候,会将多个 segment file
合并成一个,同时这里会将标识为 deleted
的 doc 给物理删除掉,然后将新的 segment file
写入磁盘,这里会写一个 commit point
,标识所有新的 segment file
,然后打开 segment file
供搜索使用,同时删除旧的 segment file
。
底层 lucene
简单来说,lucene 就是一个 jar 包,里面包含了封装好的各种建立倒排索引的算法代码。我们用 Java 开发的时候,引入 lucene jar,然后基于 lucene 的 api 去开发就可以了。
通过 lucene,我们可以将已有的数据建立索引,lucene 会在本地磁盘上面,给我们组织索引的数据结构。
倒排索引
在搜索引擎中,每个文档都有一个对应的文档 ID,文档内容被表示为一系列关键词的集合。例如,文档 1 经过分词,提取了 20 个关键词,每个关键词都会记录它在文档中出现的次数和出现位置。
那么,倒排索引就是关键词到文档 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
,搜索系统查找倒排索引,从中读出包含这个单词的文档,这些文档就是提供给用户的搜索结果。
要注意倒排索引的两个重要细节:
- 倒排索引中的所有词项对应一个或多个文档;
- 倒排索引中的词项根据字典顺序升序排列
上面只是一个简单的栗子,并没有严格按照字典顺序升序排列。
参考资料
- http://www.ruanyifeng.com/blog/2017/08/elasticsearch.html
- https://dunwu.github.io/db-tutorial/nosql/elasticsearch/elasticsearch-quickstart.html
- https://doocs.github.io/advanced-java/#/./docs/high-concurrency/es-write-query-search