ElasticSearch 是基于 Apache Lucene 的分布式搜索引擎, 提供面向文档的搜索服务.
安装 ElasticSearch
文档
创建文档
访问文档
更新文档
删除文档
索引
分析器
类型和映射
管理索引
查询
基本查询
term 查询
terms 查询
match 查询
组合查询
bool 查询
dismax 查询
排序
聚合
安装 ElasticSearch
可以在 官网 下载压缩包, 在解压目录中执行 bin/elasticsearch 来启动服务, 或者使用包管理器来安装启动.
ES 默认端口为 9200, 本地启动 ES 后向
http://localhost:9200
发送 GET 请求可以查看 ES 的基本信息:
GET 'localhost:9200' {
"name": "hiTUe19",
"cluster_name": "elasticsearch_finley",
"cluster_uuid": "cfKnyFL1Rx6URmrmAuMBFw",
"version": {
"number": "5.1.2",
"build_hash": "c8c4c16",
"build_date": "2017-01-11T20:18:39.146Z",
"build_snapshot": false,
"lucene_version": "6.3.0"
},
"tagline": "You Know, for Search"
}
文档
ElasticSearch 采用三层数据结构来管理数据:
索引 (index): 索引是最高层的数据结构, 可以定义独立的搜索索引和分片存储策略
类型 (type): 每个 index 可以拥有多个 type, 用于存储不同类型的文档
文档: 文档是最基本的数据结构, 存储和搜索都是围绕文档展开的
ElasticSearch 中的文档是一个 Json 对象, 搜索的结果是文档的集合因此被称为面向文档的搜索.
与三层数据结构相对应, 我们可以使用三个字段来唯一标识文档:
_index: 代表文档所在的索引. 索引名必须小写, 不能以下划线开头, 不能包含逗号.
_type: 代表文档所在的类型集. type 名可以为大小写, 不能以下划线开头, 不能包含逗号.
_id: 用于唯一标识某个 type 中的文档
创建文档
IndexAPI 可以用于创建文档:
$ POST 'localhost:9200/blog/user/'
content-type: application/json
body:
{
"id": 1,
"nickname": "finley"
}
response:
{
"_index": "blog",
"_type": "user",
"_id": "AV5WoO0MdsHuOObNBTWU",
"_version": 1,
"result": "created",
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
},
"created": true
}
使用 POST 请求创建文档, 在 url 中指定_index 和_type, 在请求体中使用 json 格式提交文档数据.
若_index 或_type 不存在 ES 会自动创建. 上述请求中文档的_id 字段由 ElasticSearch 创建, 我们也可以自己指定_id:
POST localhost:9200/blog/user/2/
content-type: application/json
{
"id": 2,
"nickname": "easy"
}
response:
{
"_index": "blog",
"_type": "user",
"_id": "2",
"_version": 1,
"result": "created",
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
},
"created": true
}
访问文档
使用 GET 请求访问文档, 需要提供_index, _type 和_id 三个参数唯一标识文档.
GET localhost:9200/blog/user/2/
response:
{
"_index": "blog",
"_type": "user",
"_id": "2",
"_version": 2,
"found": true,
"_source": {
"id": 2,
"nickname": "easy"
}
}
更新文档
因为修改文档后难以更新索引, 因此 ElasticSearch修改文档的操作是通过删除原文档后重新添加新文档来进行的.
使用 IndexAPI 对已存在的文档发送 POST 请求则会更新文档:
POST localhost:9200/blog/user/2/
content-type: application/json
{
"nickname": "easy",
"gender": "male"
}
response:
{
"_index": "blog",
"_type": "user",
"_id": "2",
"_version": 2,
"result": "updated",
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
},
"created": false
}
注意_version, created, result 字段显示文档已被更新. 通过 GET 请求查看更新后的文档:
GET localhost: 9200 / blog / user / 2 / {
"_index": "blog",
"_type": "user",
"_id": "2",
"_version": 2,
"found": true,
"_source": {
"nickname": "easy2",
"gender": "male"
}
}
注意到原文档中的_id 字段已经不见了, 文档完全由我们发送的上一个 POST 请求定义.
修改文档也可以通过 PUT 方法:
PUT localhost:9200/blog/user/2/
content-type: application/json
{
"id": 2,
"nickname": "easy3"
}
{
"_index": "blog",
"_type": "user",
"_id": "2",
"_version": 3,
"result": "updated",
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
},
"created": false
}
再次通过 GET 请求确认文档已被修改:
GET localhost:9200/blog/user/2/
{
"_index": "blog",
"_type": "user",
"_id": "2",
"_version": 3,
"found": true,
"_source": {
"id": 2
"nickname": "easy3",
}
}
删除文档
删除文档需要发送 DELETE 请求:
DELETE localhost:9200/blog/user/2/
response:
{
"found": true,
"_index": "blog",
"_type": "user",
"_id": "2",
"_version": 4,
"result": "deleted",
"_shards": {
"total": 2,
"successful": 1,
"failed": 0
}
}
索引
ElasticSearch 中的 Index 是最高级的逻辑的结构, 类似于 MySQL 中的数据库 (schema), 可以配置独立的搜索策略和存储策略.
ElasticSearch 通过倒排索引进行搜索, 所谓倒排索引是指对文档进行分析提取关键词, 然后建立关键词到文档的索引, 当我们搜索关键词时就可以找到它所关联的文档.
我们可以通过在 Index 中配置分析器和映射来设置倒排索引的策略.
分析器是通用的从文档提取关键词的方法, 即将文档中某个字段映射为关键字的方法. 例如: 过滤停用词, 分词, 添加同义词等.
映射则是具体指定文档中的某个字段应该使用什么分析器来提取关键词.
分析器
这个拆分的过程即是分析的过程, 分析执行的操作包括不限于: 字符过滤, 分词, 停用词过滤, 添加同义词.
ES 提供了很多内置分析器:
standard: 默认分析器, 根据 Unicode 定义删除标点并将词条小写
simple: 根据空格分词并将词条小写
whitespace: 根据空格分词但不将词条小写
english: 过滤英文停用词并将词条还原为词根, 详情参考 官方文档 .
ngram : 滑动窗口分析器, 取文本中所有子串作为关键词. 比如对 easy 进行处理可以得到关键词: e, a, s, y, ea, as, sy, eas, asy, easy.
edge-ngram: 边缘固定滑动窗口分析器, 取文本所有从头开始的子串作为关键词. 比如对 easy 进行处理可以得到关键词: e, ea, eas, easy. 常用于联想搜索, 根据用户输入前几个字符进行搜索.
此外, 也可以通过配置字符过滤器 (char_filter), 词过滤器 (filter), 分词器 (tokenizer) 的方式来自定义分析器.
这里展示基于 ngram 的分析器定义:
PUT / blog {
"settings": {
"analysis": {
"filter": {
"grams_filter": {
"type": "ngram",
"min_gram": 1,
"max_gram": 5
}
},
"analyzer": {
"gram_analyzer": {
"type": "custom",
"tokenizer": "standard",
"filter": ["lowercase", "grams_filter"]
}
}
}
}
},
mappings: {...
}
}
自定义分析器的更多信息可以查阅官方文档:
字符过滤器
分词器
词条过滤器
若需要中文支持, 则可以使用插件 elastic-analysis-ik
类型和映射
映射则是具体指定文档中的某个字段应该使用什么分析器来提取关键词:
PUT / blog {
"settings": {...
},
"mappings": {
"user": {
"properties": {
"nickname": {
"type": "string",
"analyzer": "gram_analyzer",
"fields": {
"keyword:": {
"type": "keyword"
}
}
},
"status": {
"type": "text",
"fields": {
"keyword:": {
"type": "keyword"
}
}
}
}
}
}
}
上述 JSON 中 user 项定义了一个根对象, 它通常与文档对应. 根对象下可以包含下列几个字段:
properties: 定义文档中可能包含字段和它们的映射
元数据字段
动态设置项 : 控制如何动态处理新字段
其它设置项
文档中每一个字段都有 3 个配置项:
type: 指定字段类型, 如: text, long, double 和 date.
index: 指定字段索引的类型:
no: 不可被搜索
not_analyzed: 必须精确匹配
analyzed: 使用分析器建立倒排索引
analyzer: 该字段使用的默认分析器
fields: 字段的属性, 可以配置独立的 type, index 和 analyzer. 用于将一个字段映射为不同类型适应不同用途.
管理索引
在分析器及映射两节中展示了创建索引所需的 PUT 请求片段, 将 类型和映射 一节中 PUT 请求的 settings 字段, 用 分析器 一节中的 settings 字段替换即可得到完整创建索引请求.
发送 DELETE 请求可以删除索引:
DELETE /user: 删除 user 索引
DELET /user1,user2: 删除 user1 和 user2 两个 suoyin
DELETE /user*: 根据通配符删除索引
DELET /_all, DELETE /*: 删除所有索引
GET /_cat/indices 可以列出 ElasticSearch 上的所有索引.
GET /blog?pretty 列出索引 blog 的所有信息.
查询
虽然 ES 提供了 简易搜索 API 但在应用中我们通常更多地使用 结构化搜索 .
结构化搜索将查询条件以 json 的形式包含在 http 请求的 body 中, 通常情况下搜索请求应该使用 GET 方法但不是所有的客户端和服务端都支持 GET 请求包含 body. 因此, ElasticSearch 支持使用 GET 或 POST 进行搜索.
# 列出所有文档
GET /_search
# 列出索引 blog 下的所有文档
GET /blog/_search
# 列出类型 / blog/user 下的所有文档
GET /blog/user/_search
基本查询
term 查询
term 查询类似于 SQL 中的 =:
POST / blog / user / _search {
"query": {
"term": {
"nickname": "eas"
}
}
}
response: {
"took": 23,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"failed": 0
},
"hits": {
"total": 2,
"max_score": 0.45532417,
"hits": [{
"_index": "blog",
"_type": "user",
"_id": "1",
"_score": 0.45532417,
"_source": {
"nickname": "easy",
"status": "normal"
}
},
{
"_index": "blog",
"_type": "user",
"_id": "2",
"_score": 0.43648314,
"_source": {
"nickname": "ease",
"status": "normal"
}
}]
}
}
根据我们上文配置的 gram_analyzer 分析器, 关键词 eas 会匹配到 easy 和 ease 两个文档.
在响应的 hits.hits 字段中我们可以看到匹配的文档, 文档_score 字段是采用 TF-IDF 算法得出匹配程度得分, 结果集中的文档按照得分降序排列.
terms 查询
terms 查询可以视为多个 term 查询的组合:
POST / blog / user / _search {
"query": {
"terms": {
"nickname": ["easy", "ease"]
}
}
}
response: {
"took": 18,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"failed": 0
},
"hits": {
"total": 2,
"max_score": 1.0970675,
"hits": [{
"_index": "blog",
"_type": "user",
"_id": "2",
"_score": 1.5779335,
"_source": {
"nickname": "ease",
"status": "normal"
}
},
{
"_index": "blog",
"_type": "user",
"_id": "4",
"_score": 1.0970675,
"_source": {
"nickname": "easy",
"content": "simple",
"status": "normal"
}
}]
}
}
match 查询
很多情况下用户可能输入多个关键词进行查询, match 查询会将用户输入的内容分词生成多个 term 查询进行处理:
POST / blog / user / _search {
"query": {
"match": {
"nickname": "eas sim"
}
}
}
response: {
"took": 19,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"failed": 0
},
"hits": {
"total": 3,
"max_score": 1.1382749,
"hits": [{
"_index": "blog",
"_type": "user",
"_id": "3",
"_score": 1.1382749,
"_source": {
"nickname": "simple",
"status": "normal"
}
},
{
"_index": "blog",
"_type": "user",
"_id": "2",
"_score": 1.0548241,
"_source": {
"nickname": "ease",
"status": "normal"
}
},
{
"_index": "blog",
"_type": "user",
"_id": "1",
"_score": 1.049597,
"_source": {
"nickname": "easy",
"status": "normal"
}
}]
}
}
若以 eas sim 为关键词进行 term 查询不会匹配到任何文档.
组合查询
bool 查询
Bool 查询用于组合多个条件查询相关度最高的文档, 下面展示了一个 Bool 查询请求:
POST / user / naive / _search {
"query": {
"bool": {
"must": {
"match": {
"nickname": "easy"
}
},
"must_not": {
"match": {
"nickname": "hard"
}
},
"should": {
"match": {
"nickname": "simple"
}
},
"filter": [{
"term": {
"status": "normal"
}
}]
}
}
}
response: {
"took": 20,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"failed": 0
},
"hits": {
"total": 3,
"max_score": 1.3847495,
"hits": [{
"_index": "blog",
"_type": "user",
"_id": "4",
"_score": 1.3847495,
"_source": {
"nickname": "easy",
"content": "simple",
"status": "normal"
}
},
{
"_index": "blog",
"_type": "user",
"_id": "5",
"_score": 0.45532417,
"_source": {
"nickname": "easy",
"content": "bitter",
"status": "normal"
}
}]
}
}
上述 bool 查询的目标为:
must 条件必须满足, 即 nickname 字段必须与词条 easy 匹配, 字段的匹配程度会影响得分
must_not 条件必须不满足, 即 nickname 字段不能与词条 hard 匹配
should 条件不做要求, 但满足 should 条件的文档会获得更高的相关度评分_score. 当 should 查询中包含多个字段时, 会将各字段得分的和作为总分. 所以查询到两个 nickname 与 easy 匹配的文档, 但是 content 为 simple 的字段获得了更高的评分.
filter 条件必须满足, 但是匹配程度不会影响得分.
dismax 查询
上文已经提到 bool 查询的 should 查询会将各字段得分之和作为总分, 然而在实际应用中通常一个字段高度匹配的文档可能比拥有多个字段低匹配更符合用户的期望.
dismax 查询同样用于组合多个查询, 但是按照匹配程度最高的字段确定总分:
{
"query": {
"dis_max": {
"queries": [{
"match": {
"nickname": "easy"
}
},
{
"match": {
"content": "easy"
}
}]
}
}
}
排序
ElasticSearch 的搜索结果默认按照_score 进行降序排列, 在一些情况下我们希望自定义排序方式, 比如按创建时间排列.
POST /blog/user/_search
{
"query" : {
"bool" : {
"filter" : { "term" : { "uid" : 1 }}
}
},
"sort": { "date": { "order": "desc" }}
}
我们甚至可以使用 ElasticSearch 提供的 painless 脚本语言 编写一个复杂的排序函数:
POST /blog/user/_search
{
"query" : {
"bool" : {
"filter" : { "term" : { "uid" : 1 }}
}
},
"sort": {
"_script": {
"type": "number",
"script": {
"inline": "doc['followerNum'].value * Math.sqrt(1.0 / (0.01 + Math.pow(params.now - doc['createTime'].value, 2))",
"lang": "painless",
"params": {
"now": 1517128545269
}
},
"order": "desc"
}
}
}
聚合
聚合用于分析查询结果集的统计指标, ElasticSearch 引入了两个相关概念:
桶 (Buckets): 结果集中满足特定条件的文档的集合
指标 (Metrics): 桶中文档的统计值, 如所有文档特定字段的平均值
POST / car / _search {
"size": 0,
"aggs": {
"colors": {
"terms": {
"field": "color"
},
"aggs": {
"avg_price": {
"avg": {
"field": "price"
}
}
}
}
}
}
response: {
"took": 81,
"timed_out": false,
"_shards": {
"total": 5,
"successful": 5,
"failed": 0
},
"hits": {
"total": 7,
"max_score": 0,
"hits": []
},
"aggregations": {
"colors": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [{
"key": "red",
"doc_count": 4,
"avg_price": {
"value": 32500
}
},
{
"key": "green",
"doc_count": 2,
"avg_price": {
"value": 21000
}
},
{
"key": "blue",
"doc_count": 1,
"avg_price": {
"value": 15000
}
}]
}
}
}
我们统计了不同颜色车辆的平均价格, 因为设置了 size=0 所以不会有任何 hits 返回.
来源: https://www.cnblogs.com/Finley/p/8372213.html