• ElasticSearch指南


    ElasticSearch快速指南

    ElasticSearch是基于Apache Lucene的分布式搜索引擎, 提供面向文档的搜索服务。

     

     

    安装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
    }

     

    注意_versioncreatedresult字段显示文档已被更新。通过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进行处理可以得到关键词:easyeaassyeasasyeasy
    • edge-ngram: 边缘固定滑动窗口分析器,取文本所有从头开始的子串作为关键词。 比如对easy进行处理可以得到关键词:eeaeaseasy。常用于联想搜索,根据用户输入前几个字符进行搜索。

     

    此外, 也可以通过配置字符过滤器(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项定义了一个根对象, 它通常与文档对应。根对象下可以包含下列几个字段:

     

     

    文档中每一个字段都有3个配置项:

     

    • type: 指定字段类型, 如:textlongdoubledate.
    • 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 /_allDELETE /*: 删除所有索引

     

    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会匹配到easyease两个文档。

     

    在响应的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匹配的文档,但是contentsimple的字段获得了更高的评分。
    • 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返回。

  • 相关阅读:
    nosql数据库:mongodb,redis,memcached,其优缺点和使用应用场景
    进程和线程的定义和区别
    PHP中的 抽象类(abstract class)和 接口(interface)
    简单理解php的socket编程
    session跨域共享解决方案
    MySQL 对于千万级的大表要怎么优化
    关于存session,cookie还是数据库或者memcache的优劣,部分网上抄录
    MYSQL 索引类型、什么情况下用不上索引、什么情况下不推荐使用索引
    MySQL把一个大表拆分多个表后,如何解决跨表查询效率问题
    PHP + NGINX 控制视频文件播放,并防止文件下载
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/8397564.html
Copyright © 2020-2023  润新知