目录:
- 简介
- 基本概念
- Elasticsearch操作
参考/来源:
简介
Elasticsearch 是一个分布式的开源搜索和分析引擎,适用于所有类型的数据,包括文本、数字、地理空间、结构化和非结构化数据。Elasticsearch 在 Apache Lucene 的基础上开发而成,由 Elasticsearch N.V.(即现在的 Elastic)于 2010 年首次发布。
Elasticsearch 以其简单的 REST 风格 API、分布式特性、速度和可扩展性而闻名,是 Elastic Stack 的核心组件;Elastic Stack 是适用于数据采集、充实、存储、分析和可视化的一组开源工具。人们通常将 Elastic Stack 称为 ELK Stack(代指 Elasticsearch、Logstash 和 Kibana),目前 Elastic Stack 包括一系列丰富的轻量型数据采集代理,这些代理统称为 Beats,可用来向 Elasticsearch 发送数据。
着重功能就是用来做数据的检索和分析
- 应用程序搜索
- 网站搜索
- 企业搜索
- 日志处理和分析
- 基础设施指标和容器监测
- 应用程序性能监测
- 地理空间数据分析和可视化
- 安全分析
- 业务分析
数据分类
搜索引擎是对数据的检索,所以我们先从生活中的数据说起。我们生活中的数据总体分为两种:
- 结构化数据
- 非结构化数据
结构化数据:也称作行数据,是由二维表结构来逻辑表达和实现的数据,严格地遵循数据格式与长度规范,主要通过关系型数据库进行存储和管理。指具有固定格式或有限长度的数据,如数据库,元数据等。
非结构化数据:又可称为全文数据,不定长或无固定格式,不适于由数据库二维表来表现,包括所有格式的办公文档、XML、HTML、Word 文档,邮件,各类报表、图片和咅频、视频信息等。
说明:如果要更细致的区分的话,XML、HTML 可划分为半结构化数据。因为它们也具有自己特定的标签格式,所以既可以根据需要按结构化数据来处理,也可抽取出纯文本按非结构化数据来处理。
根据两种数据分类,搜索也相应的分为两种:
- 结构化数据搜索
- 非结构化数据搜索
对于结构化数据,因为它们具有特定的结构,所以我们一般都是可以通过关系型数据库(MySQL,Oracle 等)的二维表(Table)的方式存储和搜索,也可以建立索引。
对于非结构化数据,也即对全文数据的搜索主要有两种方法:
- 顺序扫描
- 全文检索
顺序扫描:通过文字名称也可了解到它的大概搜索方式,即按照顺序扫描的方式查询特定的关键字。
例如给你一张报纸,让你找到该报纸中“平安”的文字在哪些地方出现过。你肯定需要从头到尾把报纸阅读扫描一遍然后标记出关键字在哪些版块出现过以及它的出现位置。这种方式无疑是最耗时的最低效的,如果报纸排版字体小,而且版块较多甚至有多份报纸,等你扫描完你的眼睛也差不多了。
全文搜索:对非结构化数据顺序扫描很慢,我们是否可以进行优化?把我们的非结构化数据想办法弄得有一定结构不就行了吗?
将非结构化数据中的一部分信息提取出来,重新组织,使其变得有一定结构,然后对此有一定结构的数据进行搜索,从而达到搜索相对较快的目的。这种方式就构成了全文检索的基本思路。这部分从非结构化数据中提取出的然后重新组织的信息,我们称之为索引。
这种方式的主要工作量在前期索引的创建,但是对于后期搜索却是快速高效的。
关于Lucene
目前市场上开放源代码的最好全文检索引擎工具包就属于 Apache 的 Lucene了。但是 Lucene 只是一个工具包,它不是一个完整的全文检索引擎。Lucene 的目的是为软件开发人员提供一个简单易用的工具包,以方便的在目标系统中实现全文检索的功能,或者是以此为基础建立起完整的全文检索引擎。
Lucene 能实现全文搜索主要是因为它实现了倒排索引的查询结构。
如何理解倒排索引
假如现有三份数据文档,文档的内容如下分别是:
- Java is the best programming language.
- PHP is the best programming language.
- Javascript is the best programming language.
为了创建倒排索引,我们通过分词器将每个文档的内容域拆分成单独的词(我们称它为词条或 Term),创建一个包含所有不重复词条的排序列表,然后列出每个词条出现在哪个文档。
结果如下所示:
Term Doc_1 Doc_2 Doc_3
-------------------------------------
Java | X | |
is | X | X | X
the | X | X | X
best | X | X | X
programming | x | X | X
language | X | X | X
PHP | | X |
Javascript | | | X
-------------------------------------
这种结构由文档中所有不重复词的列表构成,对于其中每个词都有一个文档列表与之关联。
这种由属性值来确定记录的位置的结构就是倒排索引。带有倒排索引的文件我们称为倒排文件。
我们将上面的内容转换为图的形式来说明倒排索引的结构信息,如下图所示:
几个核心术语
词条(Term):索引里面最小的存储和查询单元,对于英文来说是一个单词,对于中文来说一般指分词后的一个词。
词典(Term Dictionary):或字典,是词条 Term 的集合。搜索引擎的通常索引单位是单词,单词词典是由文档集合中出现过的所有单词构成的字符串集合,单词词典内每条索引项记载单词本身的一些信息以及指向“倒排列表”的指针。
倒排表(Post list):一个文档通常由多个词组成,倒排表记录的是某个词在哪些文档里出现过以及出现的位置。
每条记录称为一个倒排项(Posting)。倒排表记录的不单是文档编号,还存储了词频等信息。
倒排文件(Inverted File):所有单词的倒排列表往往顺序地存储在磁盘的某个文件里,这个文件被称之为倒排文件,倒排文件是存储倒排索引的物理文件。
从上图我们可以了解到倒排索引主要由两个部分组成:
- 词典
- 倒排文件
词典和倒排表是 Lucene 中很重要的两种数据结构,是实现快速检索的重要基石。词典和倒排文件是分两部分存储的,词典在内存中而倒排文件存储在磁盘上。
基本概念
Index(索引)
Elastic 会索引所有字段,经过处理后写入一个反向索引(Inverted Index)。查找数据的时候,直接查找该索引。
所以,Elastic 数据管理的顶层单位就叫做 Index(索引)。它是单个数据库的同义词。每个 Index (即数据库)的名字必须是小写。
Type(类型)7.0废弃
在index(索引)中,可以定义一个或多个类型,类似于mysql中的table,同一种类型的数据放在一起
Document(文档)
保存在某个索引(Index)下、某种类型(Type)的一个数据(Document),文档是json格式的数据,Document1相当于mysql中某个table数据
分片
单个节点由于物理机硬件限制,存储的文档是有限的,如果一个索引包含海量文档,则不能在单个节点存储。ES 提供分片机制,同一个索引可以存储在不同分片(数据容器)中。
分片分为主分片 (primary shard) 以及从分片 (replica shard)。主分片会被尽可能平均地 (rebalance) 分配在不同的节点上。
例如你有 2 个节点,4 个主分片(不考虑备份),那么每个节点会分到 2 个分片,后来你增加了 2 个节点,那么你这 4 个节点上都会有 1 个分片,这个过程叫 relocation,ES 感知后自动完成
从分片只是主分片的一个副本,它用于提供数据的冗余副本,从分片和主分片不会出现在同一个节点上(防止单点故障)。
默认情况下一个索引创建 5 个主分片,每个主分片会有一个从分片 (5 primary + 5 replica = 10 个分片)。如果你只有一个节点,那么 5 个 replica 都无法被分配 (unassigned),此时 cluster status 会变成 Yellow。
分片是独立的,对于一个 Search Request 的行为,每个分片都会执行这个 Request。每个分片都是一个 Lucene Index,所以一个分片只能存放 Integer.MAX_VALUE - 128 = 2,147,483,519 个 docs。
PUT /myIndex
{
"settings" : {
"number_of_shards" : 5,
"number_of_replicas" : 1
}
}
replica 的作用主要包括:
- 容灾:primary 分片丢失,replica 分片就会被顶上去成为新的主分片,同时根据这个新的主分片创建新的 replica,集群数据安然无恙;
- 提高查询性能:replica 和 primary 分片的数据是相同的,所以对于一个 query 既可以查主分片也可以查从分片,在合适的范围内多个 replica 性能会更优(但要考虑资源占用也会提升 [cpu/disk/heap]),另外 Index Request 只能发生在主分片上,replica 不能执行 Index Request。
注意:对于一个索引,除非重建索引否则不能调整主分片的数目 (number_of_shards),但可以随时调整 replica 的数目 (number_of_replicas)。
节点
一个 ES 节点就是一个运行的 ES 实例,可以实现数据存储并且搜索的功能。每个节点都有一个唯一的名称作为身份标识,如果没有设置名称,默认使用 UUID 作为名称。最好给每个节点都定义上有意义的名称,在集群中区分出各个节点。
一个机器可以有多个实例,所以并不能说一台机器就是一个 node,大多数情况下每个 node 运行在一个独立的环境或虚拟机上。
节点类型
- master 节点: 集群中的一个节点会被选为 master 节点,它将负责管理集群范畴的变更,例如创建或删除索引,添加节点到集群或从集群中删除节点。master 节点无需参与文档层面的变更和搜索,这意味着仅有一个 master 节点并不会因流量增长而成为瓶颈。任意一个节点都可以成为 master 节点。
- data 节点: 持有数据和倒排索引。默认情况下,每个节点都可以通过设定配置文件 elasticsearch.yml 中的 node.data 属性为 true (默认) 成为数据节点。如果需要一个专门的主节点 (一个节点既可以是 master 节点,同时也可以是 data 节点),应将其 node.data 属性设置为 false。
- client 节点: 如果将 node.master 属性和 node.data 属性都设置为 false,那么该节点就是一个客户端节点,扮演一个负载均衡的角色,将到来的请求路由到集群中的各个节点。
集群
节点通过设置集群名称,在同一网络中发现具有相同集群名称的节点,组成集群。每个集群都有一个 cluster name 作为标识,默认的集群名称为 elasticsearch。如果在同一网络中只有一个节点,则这个节点成为一个单节点集群。
集群状态
- Green:所有主分片和从分片都准备就绪(分配成功),即使有一台机器挂了(假设一台机器一个实例),数据都不会丢失,但会变成 Yellow 状态。
- Yellow:所有主分片准备就绪,但存在至少一个主分片(假设是 A)对应的从分片没有就绪,此时集群属于警告状态,意味着集群高可用和容灾能力下降,如果刚好 A 所在的机器挂了,而从分片还处于未就绪状态,那么 A 的数据就会丢失(查询结果不完整),此时集群进入 Red 状态。
- Red:至少有一个主分片没有就绪(直接原因是找不到对应的从分片成为新的主分片),此时查询的结果会出现数据丢失(不完整)。
Elasticsearch操作
新建和删除 Index
新建 Index,可以直接向 Elastic 服务器发出 PUT 请求。下面的例子是新建一个名叫weather
的 Index。
$ curl -X PUT 'localhost:9200/weather'
服务器返回一个 JSON 对象,里面的acknowledged
字段表示操作成功。
{ "acknowledged":true, "shards_acknowledged":true }
然后,我们发出 DELETE 请求,删除这个 Index。
$ curl -X DELETE 'localhost:9200/weather'
中文分词设置
首先,安装中文分词插件。这里使用的是 ik,也可以考虑其他插件(比如 smartcn)。
$ ./bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v5.5.1/elasticsearch-analysis-ik-5.5.1.zip
上面代码安装的是5.5.1版的插件,与 Elastic 5.5.1 配合使用。
接着,重新启动 Elastic,就会自动加载这个新安装的插件。
然后,新建一个 Index,指定需要分词的字段。这一步根据数据结构而异,下面的命令只针对本文。基本上,凡是需要搜索的中文字段,都要单独设置一下。
$ curl -X PUT 'localhost:9200/accounts' -d ' { "mappings": { "person": { "properties": { "user": { "type": "text", "analyzer": "ik_max_word", "search_analyzer": "ik_max_word" }, "title": { "type": "text", "analyzer": "ik_max_word", "search_analyzer": "ik_max_word" }, "desc": { "type": "text", "analyzer": "ik_max_word", "search_analyzer": "ik_max_word" } } } } }'
上面代码中,首先新建一个名称为accounts
的 Index,里面有一个名称为person
的 Type。person
有三个字段。
- user
- title
- desc
这三个字段都是中文,而且类型都是文本(text),所以需要指定中文分词器,不能使用默认的英文分词器。
Elastic 的分词器称为 analyzer。我们对每个字段指定分词器。
"user": { "type": "text", "analyzer": "ik_max_word", "search_analyzer": "ik_max_word" }
上面代码中,analyzer
是字段文本的分词器,search_analyzer
是搜索词的分词器。ik_max_word
分词器是插件ik
提供的,可以对文本进行最大数量的分词。
数据操作
新增记录
向指定的 /Index/Type 发送 PUT 请求,就可以在 Index 里面新增一条记录。比如,向/accounts/person
发送请求,就可以新增一条人员记录。
$ curl -X PUT 'localhost:9200/accounts/person/1' -d ' { "user": "张三", "title": "工程师", "desc": "数据库管理" }'
服务器返回的 JSON 对象,会给出 Index、Type、Id、Version 等信息。
{ "_index":"accounts", "_type":"person", "_id":"1", "_version":1, "result":"created", "_shards":{"total":2,"successful":1,"failed":0}, "created":true }
如果你仔细看,会发现请求路径是/accounts/person/1
,最后的1
是该条记录的 Id。它不一定是数字,任意字符串(比如abc
)都可以。
新增记录的时候,也可以不指定 Id,这时要改成 POST 请求。
$ curl -X POST 'localhost:9200/accounts/person' -d ' { "user": "李四", "title": "工程师", "desc": "系统管理" }'
上面代码中,向/accounts/person
发出一个 POST 请求,添加一个记录。这时,服务器返回的 JSON 对象里面,_id
字段就是一个随机字符串。
{ "_index":"accounts", "_type":"person", "_id":"AV3qGfrC6jMbsbXb6k1p", "_version":1, "result":"created", "_shards":{"total":2,"successful":1,"failed":0}, "created":true }
注意,如果没有先创建 Index(这个例子是accounts
),直接执行上面的命令,Elastic 也不会报错,而是直接生成指定的 Index。所以,打字的时候要小心,不要写错 Index 的名称。
查看记录
向/Index/Type/Id
发出 GET 请求,就可以查看这条记录。
$ curl 'localhost:9200/accounts/person/1?pretty=true'
上面代码请求查看/accounts/person/1
这条记录,URL 的参数pretty=true
表示以易读的格式返回。
返回的数据中,found
字段表示查询成功,_source
字段返回原始记录。
{ "_index" : "accounts", "_type" : "person", "_id" : "1", "_version" : 1, "found" : true, "_source" : { "user" : "张三", "title" : "工程师", "desc" : "数据库管理" } }
如果 Id 不正确,就查不到数据,found
字段就是false
。
$ curl 'localhost:9200/weather/beijing/abc?pretty=true' { "_index" : "accounts", "_type" : "person", "_id" : "abc", "found" : false }
删除记录
删除记录就是发出 DELETE 请求。
$ curl -X DELETE 'localhost:9200/accounts/person/1'
这里先不要删除这条记录,后面还要用到。
更新记录
更新记录就是使用 PUT 请求,重新发送一次数据。
$ curl -X PUT 'localhost:9200/accounts/person/1' -d ' { "user" : "张三", "title" : "工程师", "desc" : "数据库管理,软件开发" }' { "_index":"accounts", "_type":"person", "_id":"1", "_version":2, "result":"updated", "_shards":{"total":2,"successful":1,"failed":0}, "created":false }
上面代码中,我们将原始数据从”数据库管理”改成”数据库管理,软件开发”。 返回结果里面,有几个字段发生了变化。
"_version" : 2, "result" : "updated", "created" : false
可以看到,记录的 Id 没变,但是版本(version)从1
变成2
,操作类型(result)从created
变成updated
,created
字段变成false
,因为这次不是新建记录。
数据查询
返回所有记录
使用 GET 方法,直接请求/Index/Type/_search
,就会返回所有记录。
$ curl 'localhost:9200/accounts/person/_search' { "took":2, "timed_out":false, "_shards":{"total":5,"successful":5,"failed":0}, "hits":{ "total":2, "max_score":1.0, "hits":[ { "_index":"accounts", "_type":"person", "_id":"AV3qGfrC6jMbsbXb6k1p", "_score":1.0, "_source": { "user": "李四", "title": "工程师", "desc": "系统管理" } }, { "_index":"accounts", "_type":"person", "_id":"1", "_score":1.0, "_source": { "user" : "张三", "title" : "工程师", "desc" : "数据库管理,软件开发" } } ] } }
上面代码中,返回结果的 took
字段表示该操作的耗时(单位为毫秒),timed_out
字段表示是否超时,hits
字段表示命中的记录,里面子字段的含义如下。
total
:返回记录数,本例是2条。max_score
:最高的匹配程度,本例是1.0
。hits
:返回的记录组成的数组。
返回的记录中,每条记录都有一个_score
字段,表示匹配的程序,默认是按照这个字段降序排列。
全文搜索
Elastic 的查询非常特别,使用自己的查询语法,要求 GET 请求带有数据体。
$ curl 'localhost:9200/accounts/person/_search' -d ' { "query" : { "match" : { "desc" : "软件" }} }'
上面代码使用 Match 查询,指定的匹配条件是desc
字段里面包含”软件”这个词。返回结果如下。
{ "took":3, "timed_out":false, "_shards":{"total":5,"successful":5,"failed":0}, "hits":{ "total":1, "max_score":0.28582606, "hits":[ { "_index":"accounts", "_type":"person", "_id":"1", "_score":0.28582606, "_source": { "user" : "张三", "title" : "工程师", "desc" : "数据库管理,软件开发" } } ] } }
Elastic 默认一次返回10条结果,可以通过size
字段改变这个设置。
$ curl 'localhost:9200/accounts/person/_search' -d ' { "query" : { "match" : { "desc" : "管理" }}, "size": 1 }'
上面代码指定,每次只返回一条结果。
还可以通过from
字段,指定位移。
$ curl 'localhost:9200/accounts/person/_search' -d ' { "query" : { "match" : { "desc" : "管理" }}, "from": 1, "size": 1 }'
上面代码指定,从位置1开始(默认是从位置0开始),只返回一条结果。
逻辑运算
如果有多个搜索关键字, Elastic 认为它们是or
关系。
$ curl 'localhost:9200/accounts/person/_search' -d ' { "query" : { "match" : { "desc" : "软件 系统" }} }'
上面代码搜索的是软件 or 系统
。
如果要执行多个关键词的and
搜索,必须使用布尔查询。
$ curl 'localhost:9200/accounts/person/_search' -d ' { "query": { "bool": { "must": [ { "match": { "desc": "软件" } }, { "match": { "desc": "系统" } } ] } } }'