[TOC]
简介
Elasticsearch 是一个实时的分布式搜索分析引擎, 它能让你以一个之前从未有过的速度和规模,去探索你的数据。 它被用作全文检索、结构化搜索、分析以及这三个功能的组合.
Elasticsearch 是一个开源的搜索引擎,建立在一个全文搜索引擎库 Apache Lucene™ 基础之上。 Lucene 可能是目前存在的,不论开源还是私有的,拥有最先进,高性能和全功能搜索引擎功能的库。
但是 Lucene 仅仅只是一个库。为了利用它,你需要编写 java 程序,并在你的 java 程序里面直接集成 Lucene 包。 更坏的情况是,你需要对信息检索有一定程度的理解才能明白 Lucene 是怎么工作的。Lucene 是 很 复杂的。
Elasticsearch 也是使用 Java 编写的,它的内部使用 Lucene 做索引与搜索,但是它的目标是使全文检索变得简单, 通过隐藏 Lucene 的复杂性,取而代之的提供一套简单一致的 RESTful API。
然而,Elasticsearch 不仅仅是 Lucene,并且也不仅仅只是一个全文搜索引擎。 它可以被下面这样准确的形容:
- 一个分布式的实时文档存储,每个字段 可以被索引与搜索
- 一个分布式实时分析搜索引擎
- 能胜任上百个服务节点的扩展,并支持 PB 级别的结构化或者非结构化数据
Elasticsearch 将所有的功能打包成一个单独的服务,这样你可以通过程序去访问它提供的简单的 RESTful API 服务, 不论你是使用自己喜欢的编程语言还是直接使用命令行(去充当这个客户端)。
和elasticsearch交互
一个 Elasticsearch 请求和任何 HTTP 请求一样由若干相同的部件组成:
curl -X<VERB> '<PROTOCOL>://<HOST>:<PORT>/<PATH>?<QUERY_STRING>' -d '<BODY>'
被< >
标记的部件:
部件 | 描述 |
---|---|
VERB | 适当的 HTTP 方法 或 谓词 : GET 、 POST 、 PUT 、 HEAD 或者 DELETE 。 |
PROTOCOL |
http 或者 https (如果你在 Elasticsearch 前面有一个 https 代理) |
HOST | Elasticsearch 集群中任意节点的主机名,或者用 localhost 代表本地机器上的节点。 |
PORT | 运行 Elasticsearch HTTP 服务的端口号,默认是 9200 。 |
PATH | API 的终端路径(例如 _count 将返回集群中文档数量)。Path 可能包含多个组件,例如:_cluster/stats 和 _nodes/stats/jvm 。 |
QUERY_STRING | 任意可选的查询字符串参数 (例如 ?pretty 将格式化地输出 JSON 返回值,使其更容易阅读) |
BODY | 一个 JSON 格式的请求体 (如果请求需要的话) |
例如,计算集群中文档的数量,我们可以用这个:
curl -XGET 'http://localhost:9200/_count?pretty' -d '
{
"query": {
"match_all": {}
}
}
'
Elasticsearch 返回一个 HTTP 状态码(例如:200 OK)和(除HEAD请求)一个 JSON 格式的返回值。前面的 curl 请求将返回一个像下面一样的 JSON 体:
{
"count" : 0,
"_shards" : {
"total" : 5,
"successful" : 5,
"failed" : 0
}
}
在返回结果中没有看到 HTTP 头信息是因为我们没有要求curl 显示它们。想要看到头信息,需要结合 -i 参数来使用 curl 命令:
curl -i -XGET 'localhost:9200/'
HTTP/1.1 200 OK
content-type: application/json; charset=UTF-8
content-length: 424
{
"name" : "es102",
"cluster_name" : "jftt",
"cluster_uuid" : "Px8gentBRISS_lMe9gIWwQ",
"version" : {
"number" : "6.2.4",
"build_hash" : "ccec39f",
"build_date" : "2018-04-12T20:37:28.497551Z",
"build_snapshot" : false,
"lucene_version" : "7.2.1",
"minimum_wire_compatibility_version" : "5.6.0",
"minimum_index_compatibility_version" : "5.0.0"
},
"tagline" : "You Know, for Search"
}
逻辑布局
对于存储在es中的数据,逻辑上来看存储在index/type/document这样一个结构中,这三个层级是一个文档(ES存储数据的最小单位。类似sql中的一行)的元数据,这三个元数据唯一地标识了一个在es中的数据单位。
document
Es是面向文档的,文档是es的基础数据单位。对于其数据格式,es内部存储的是json格式。
type
type是document的逻辑容器,像是DB的表的概念,在es6之前支持一个索引包含多个type,但是在es6中不再支持多个type,一个index只能有一个type,type上回绑定容器内数据的schema(但是较之DB的schema要宽松得多),即document中的字段的数据类型,如果在创建时未显式指定,es在导入数据时会采用默认规则进行mapping的设置。
index
index是type的逻辑上的容器,是ES中的次级容器,顶层当然就是es本身,它的容器地位类似于数据库。因为6.0版本一个index只能定义一个type,所以在逻辑上二者的层级关系淡化了。
Indices are containers for mapping types. An Elasticsearch index is an independent chunk of documents, much like a database is in the relational world: each index is stored on the disk in the same set of files; it stores all the fields from all the mapping types in there, and it has its own settings.
物理布局node和shard
es内数据的物理布局涉及到节点和分片,节点是分布式的概念。es是支持分布式的,一个es实例就是一个节点node,也是一个只有一个node的集群。一般分布式会在多个服务器上部署节点,均衡负载来提高性能。在一个机器上甚至可以启动多个es进程形成多个node组成的集群。
分片是真正存储数据的容器,数据被索引之后,会被存储到分片中,实际上一个分片就是一个Lucene的实现。一个索引会分成多个分片,且,每个分片有0或多个副本,副本时主分片的完全拷贝,会分布在集群中,既通过冗余数据来保证可靠性,也通过多份无差别数据来提高检索效率(主分片和副本分片在检索时是无差别的)。
创建一个集群
空集群
如果我们启动了一个单独的节点,里面不包含任何的数据和 索引,那我们的集群看起来就是一个“包含空内容节点的集群”。
节点
一个运行中的 Elasticsearch 实例称为一个 节点,而集群是由一个或者多个拥有相同 cluster.name 配置的节点组成, 它们共同承担数据和负载的压力。当有节点加入集群中或者从集群中移除节点时,集群将会重新平均分布所有的数据。
每个节点都知道任意文档所处的位置,并且能够将我们的请求直接转发到存储我们所需文档的节点。 无论我们将请求发送到哪个节点,它都能负责从各个包含我们所需文档的节点收集回数据,并将最终结果返回給客户端。
主节点
任何节点都可以成为主节点,通过选举产生。当一个节点被选举成为主节点时, 它将负责管理集群范围内的所有变更,例如增加、删除索引,或者增加、删除节点等。 而主节点并不需要涉及到文档级别的变更和搜索等操作,所以当集群只拥有一个主节点的情况下,即使流量的增加它也不会成为瓶颈。
集群健康
RESTful API GET /_cluster/health
, 它在返回的 status
字段中展示为 green 、 yellow 或者 red 。
它的三种颜色含义如下:
color | description |
---|---|
green |
所有的主分片和副本分片都正常运行。 |
yellow |
所有的主分片都正常运行,但不是所有的副本分片都正常运行。 |
red |
有主分片没能正常运行。 |
集群中索引一个数据
过程如图所示,首先会使用路由算法选出存储分片,然后访问主分片所在节点,将数据写入主分片,再写入副本分片中。
集群中检索文档
When you search an index, Elasticsearch has to look in a complete set of shards for
that index. Those shards can be either primary or replicas because primary and replica shards typically contain the same documents. Elasticsearch distributes the search load between the primary and replica shards of the
index you’re searching, making replicas useful for both search performance and
fault tolerance
分片
索引和分片
我们往 Elasticsearch 添加数据时需要用到 索引 —— 保存相关数据的地方。索引实际上是指向一个或者多个物理 分片 的 逻辑命名空间 。
一个 分片 是一个底层的 工作单元 ,它仅保存了全部数据中的一部分,一个分片是一个 Lucene 的实例,以及它本身就是一个完整的搜索引擎。 我们的文档被存储和索引到分片内,但是应用程序是直接与索引而不是与分片进行交互。
Elasticsearch 是利用分片将数据分发到集群内各处的。分片是数据的容器,文档保存在分片内,分片又被分配到集群内的各个节点里。 当你的集群规模扩大或者缩小时, Elasticsearch 会自动的在各节点中迁移分片,使得数据仍然均匀分布在集群里。
一个分片可以是 主 分片或者 副本 分片。索引内任意一个文档都归属于一个主分片,所以主分片的数目决定着索引能够保存的最大数据量。
一个副本分片只是一个主分片的拷贝。副本分片作为硬件故障时保护数据不丢失的冗余备份,并为搜索和返回文档等读操作提供服务。
在索引建立的时候就已经确定了主分片数(后期不可修改,因为分片与路由算法绑定,后期改了分片数将路由不到之前的数据),但是副本分片数可以随时修改。 索引在默认情况下会被分配5个主分片。
添加节点组成集群
当集群中只有一个节点在运行时,意味着会有一个单点故障问题——没有冗余。 幸运的是,我们只需再启动一个节点即可防止数据丢失。
当你在同一台机器上启动了第二个节点时,只要它和第一个节点有同样的 cluster.name 配置,它就会自动发现集群并加入到其中。 但是在不同机器上启动节点的时候,为了加入到同一集群,你需要配置一个可连接到的单播主机列表。
数据输入和输出
Elastcisearch 是分布式的 文档 存储。 它能存储和检索复杂的数据结构--序列化成为JSON文档--以 实时 的方式。在 Elasticsearch 中, 每个字段的所有数据都是 默认被索引的 。即每个字段都有为了快速检索设置的专用倒排索引。
文档相关API另外记录。
分布式文档存储
当索引一个文档的时候,文档会被存储到一个主分片中。选取主分片的算法和简单:
shard = hash(routing) % number_of_primary_shards
由上述公式可以得出索引的number_of_primary_shards
一旦确定就不可更改的原因:更改后文档就找不到了。routing
是一个可变值,默认是文档的 _id
,也可以设置成一个自定义的值。
所有的文档 API( get
、 index
、 delete
、 bulk
、 update
以及 mget
)都接受一个叫做 routing
的路由参数,通过这个参数我们可以自定义文档到分片的映射。一个自定义的路由参数可以用来确保所有相关的文档——例如所有属于同一个用户的文档——都被存储到同一个分片中。
相同分片的副本不会放在同一节点。我们可以发送请求到集群中的任一节点。每个节点都有能力处理任意请求。 每个节点都知道集群中任一文档位置,所以可以直接将请求转发到需要的节点上。 在下面的例子中,将所有的请求发送到 Node 1
,我们将其称为 协调节点(coordinating node) 。
新建、索引和删除请求都是 写 操作,必须在主分片上面完成之后才能被复制到相关的副本分片
搜索
Elasticsearch 不只会存储(stores) 文档,为了能被搜索到也会为文档添加索引(indexes) ,这也是为什么我们使用结构化的 JSON 文档,而不是无结构的二进制数据。
文档中的每个字段都将被索引并且可以被查询 。不仅如此,在简单查询时,Elasticsearch 可以使用 所有(all) 这些索引字段,以惊人的速度返回结果。这是你永远不会考虑用传统数据库去做的一些事情。
搜索(search) 可以做到:
- 在类似于 gender 或者 age 这样的字段 上使用结构化查询,join_date 这样的字段上使用排序,就像SQL的结构化查询一样。
- 全文检索,找出所有匹配关键字的文档并按照相关性(relevance) 排序后返回结果。
以上二者兼而有之。
很多搜索都是开箱即用的,为了充分挖掘 Elasticsearch 的潜力,你需要理解以下三个概念:
- 映射(Mapping)
描述数据在每个字段内如何存储 - 分析(Analysis)
全文是如何处理使之可以被搜索的 - 领域特定查询语言(Query DSL)
Elasticsearch 中强大灵活的查询语言
有两种形式的 搜索 API
:
- 一种是 “轻量的” 查询字符串 版本,要求在查询字符串中传递所有的参数
% curl -XPUT 'localhost:9200/new-index'
% curl 'localhost:9200/get-together/_search?q=sample&pretty' // q是查询内容
% curl 'localhost:9200/get-together/group/_search?q=elasticsearch&fields=name,location&size=1&pretty'
- 另一种是更完整的 请求体 版本,要求使用 JSON 格式和更丰富的查询表达式作为搜索语言。
% curl 'localhost:9200/get-together/group/_search?pretty' -d '{
"query": {
"query_string": {
"query": "elasticsearch san francisco",
"default_field": "name",
"default_operator": "AND"
}
}
}'
此处仅记录一些简单search,具体搜索API,参见Search API。
空搜索
没有指定任何查询,返回所有文档:GET /_search
响应字段
示例:
{
"hits" : { // 命中结果
"total" : 14, // 命中数
"hits" : [ // 结果集
{
"_index": "us",
"_type": "tweet",
"_id": "7",
"_score": 1,
"_source": {
"date": "2014-09-17",
"name": "John Smith",
"tweet": "The Query DSL is really powerful and flexible",
"user_id": 2
}
},
// ...
],
"max_score" : 1
},
"took" : 4, // 消耗时间,告诉我们执行整个搜索请求耗费了多少毫秒。
"_shards" : { // 告诉我们在查询中参与分片的总数, 以及这些分片成功了多少个失败了多少个
"failed" : 0,
"successful" : 10,
"total" : 10
},
"timed_out" : false
// `timed_out` 值告诉我们查询是否超时。默认情况下,搜索请求不会超时。
// **在请求超时之前,Elasticsearch 将会返回已经成功从每个分片获取的结果。**
}
多索引搜索
/_search
在所有的索引中搜索所有的类型
/gb/_search
在 gb 索引中搜索所有的类型
/gb,us/_search
在 gb 和 us 索引中搜索所有的文档
/g*,u*/_search
在任何以 g 或者 u 开头的索引中搜索所有的类型
/gb/user/_search
在 gb 索引中搜索 user 类型
/gb,us/user,tweet/_search
在 gb 和 us 索引中搜索 user 和 tweet 类型 (这个在6.0版本已经移除,@since 6.0,一个索引只支持一个type)
/_all/user,tweet/_search
在所有的索引中搜索 user 和 tweet 类型
分页
和 SQL 使用 LIMIT 关键字返回单个 page 结果的方法相同,Elasticsearch 接受 from 和 size 参数:
- size
显示应该返回的结果数量,默认是 10 - from
显示应该跳过的初始结果数量,默认是 0
考虑到分页过深以及一次请求太多结果的情况,结果集在返回之前先进行排序。 但请记住一个请求经常跨越多个分片,每个分片都产生自己的排序结果,这些结果需要进行集中排序以保证整体顺序是正确的
我们可以假设在一个有 5 个主分片的索引中搜索。 当我们请求结果的第一页(结果从 1 到 10 ),每一个分片产生前 10 的结果,并且返回给 协调节点 ,协调节点对 50 个结果排序得到全部结果的前 10 个。
现在假设我们请求第 1000 页--结果从 10001 到 10010 。所有都以相同的方式工作除了每个分片不得不产生前10010个结果以外。 然后协调节点对全部 50050 个结果排序最后丢弃掉这些结果中的 50040 个结果。
可以看到,在分布式系统中,对结果排序的成本随分页的深度成指数上升。这就是 web 搜索引擎对任何查询都不要返回超过 1000 个结果的原因。
映射和分析
映射是字段类型的定义,ES内置有动态的类型猜测,对索引的文档进行动态映射。可以通过GET /<index>/_mapping/
来查看映射关系。
字段类型的最大的差异在于代表 精确值 (它包括 string
字段)的字段和代表 全文 的字段。这个区别非常重要——它将搜索引擎和所有其他数据库区别开来。
精确值如它们听起来那样精确。例如日期或者用户 ID,但字符串也可以表示精确值,例如用户名或邮箱地址。对于精确值来讲,Foo 和 foo 是不同的,2014 和 2014-09-15 也是不同的。
另一方面,全文是指文本数据(通常以人类容易识别的语言书写),例如一个推文的内容或一封邮件的内容。
数据类型
- Core datatypes
类别 | 数据类型 |
---|---|
string | text (可全文检索,不可精确匹配),keyword(精确匹配) |
Numeric datatypes | long, integer, short, byte, double, float, half_float, scaled_float |
Date datatype | date |
Boolean datatype | boolean |
Binary datatype | binary |
Range datatypes | integer_range, float_range, long_range, double_range, date_range |
- Complex datatypes
类别 | 数据类型 |
---|---|
Array datatype | Array support does not require a dedicated type |
Object datatype | object for single JSON objects |
Nested datatype | nested for arrays of JSON objects |
- Geo datatypes
类别 | 数据类型 |
---|---|
Geo-point datatype | geo_point for lat/lon points |
Geo-Shape datatype | geo_shape for complex shapes like polygons |
- Specialised datatypes
类别 | 数据类型 |
---|---|
IP datatype | ip for IPv4 and IPv6 addresses |
Completion datatype | completion to provide auto-complete suggestions |
Token count datatype | token_count to count the number of tokens in a string |
mapper-murmur3 | murmur3 to compute hashes of values at index-time and store them in the index |
Percolator type | Accepts queries from the query-dsl |
join datatype | Defines parent/child relation for documents within the same index |
倒排索引
Elasticsearch 使用一种称为 倒排索引 的结构,它适用于快速的全文搜索。一个倒排索引由文档中所有不重复词的列表构成,对于其中每个词,有一个包含它的文档列表。
你只能搜索在索引中出现的词条,所以索引文本和查询字符串必须标准化为相同的格式。
分析与分析器
分析 包含下面的过程:
- 首先,将一块文本分成适合于倒排索引的独立的 词条 ,
- 之后,将这些词条统一化为标准格式以提高它们的“可搜索性”,或者 recall分析器执行上面的工作。
分析器 实际上是将三个功能封装到了一个包里:
- 字符过滤器
首先,字符串按顺序通过每个 字符过滤器 。他们的任务是在分词前整理字符串。一个字符过滤器可以用来去掉HTML,或者将 & 转化成and
。 - 分词器
其次,字符串被分词器分为单个的词条。一个简单的分词器遇到空格和标点的时候,可能会将文本拆分成词条。 - Token 过滤器
最后,词条按顺序通过每个token过滤器 。这个过程可能会改变词条(例如,小写化 Quick ),删除词条(例如, 像 a,
and,
the 等无用词),或者增加词条(例如,像 jump 和 leap 这种同义词)。
Elasticsearch提供了开箱即用的字符过滤器、分词器和token 过滤器。 这些可以组合起来形成自定义的分析器以用于不同的目的。
什么时候使用分析器
当我们 索引 一个文档,它的全文域被分析成词条以用来创建倒排索引。但是,当我们在全文域 搜索 的时候,我们需要将查询字符串通过 相同的分析过程 ,以保证我们搜索的词条格式与索引中的词条格式一致。
全文查询,理解每个域是如何定义的,因此它们可以做正确的事:
- 当你查询一个 全文 域时, 会对查询字符串应用相同的分析器,以产生正确的搜索词条列表。
- 当你查询一个 精确值 域时,不会分析查询字符串,而是搜索你指定的精确值。
参考资料
- [1] Elasticsearch Reference [6.2]
- [2] Elasticsearch权威指南
- [3] Elasticsearch in action
网友评论