添加链接
link管理
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

Repository files navigation

ElasticSearch简单使用说明

  • 安装并运行Elasticsearch
  • 与Elasticsearch交互
  • 索引创建与删除
  • 文档简单的CURD操作
  • 自定义配置
  • 集群内的原理
  • 映射和分析
  • 排序与相关性
  • 实际使用中的Q&A
  • 检索包含中文的关键字时返回结果不准确
  • 中文分词器
  • 中文参数预处理
  • Reindex API
  • 索引别名实现零停机
  • 检索关键字包含特殊字符
  • 保留字消义
  • 检索关键字个数太多报错
  • 权限控制&监控
  • 中文姓名检索不准确
  • 参考( Copy )内容
  • ElasticSearch 是一个基于 Lucene (路(第一声)森(第三声))的搜索服务器。 它提供了一个 分布式 多用户能力的全文搜索引擎,基于RESTful web( Delete、Post、Get、Put )接口。 Elasticsearch是用 Java 开发的,并作为Apache许可条款下的开放源码发布,是当前流行的企业级搜索引擎。设计用于云计算中,能够达到实时搜索,稳定,可靠,快速,安装使用方便。 Elasticsearch 不仅仅是 Lucene,并且也不仅仅只是一个全文搜索引擎。 它可以被下面这样准确的形容:

  • 一个分布式的实时文档存储,每个字段 可以被索引与搜索
  • 一个分布式实时分析搜索引擎
  • 能胜任上百个服务节点的扩展,并支持 PB 级别的结构化或者非结构化数据
  • 安装并运行 Elasticsearch

    安装 Elasticsearch 之前,你需要先安装一个较新的版本的 Java。

    安装完jdk后可以从 elastic 的官网 elastic.co/downloads/elasticsearch 获取最新版本的 Elasticsearch。 先下载并解压适合你操作系统的 Elasticsearch 版本。 本次以 Windows ElasticSearch5.5 版本来进行说明 。

    下载好ElasticSearch的压缩包后进行解压,运行

    bin\elasticsearch.bat
    

    测试 Elasticsearch 是否启动成功,可以打开另一个终端,执行以下操作:

    curl 'http://localhost:9200/?pretty'
      "name": "yjh-node1",
      "cluster_name": "es-cluster",
      "cluster_uuid": "KmI4nt3RSCmd2HDa6JD-aA",
      "version": {
        "number": "5.5.0",
        "build_hash": "260387d",
        "build_date": "2017-06-30T23:16:05.735Z",
        "build_snapshot": false,
        "lucene_version": "6.6.0"
      "tagline": "You Know, for Search"
    

    这就意味着你现在已经启动并运行一个 Elasticsearch 节点了,你可以用它做实验了。 单个节点可以作为一个运行中的 Elasticsearch 的实例。 而一个集群是一组拥有相同cluster.name的节点,他们能一起工作并共享数据,还提供容错与可伸缩性。 (当然,一个单独的节点也可以组成一个集群) 你可以在elasticsearch.yml配置文件中 修改cluster.name,该文件会在节点启动时加载(重启服务后才会生效)。

    与ElasticSearch交互

    和 Elasticsearch 的交互方式取决于 你是否使用 Java

    JAVA API

    如果你正在使用 Java,在代码中你可以使用 Elasticsearch 内置的两个客户端:

  • 节点客户端(Node client) 节点客户端作为一个非数据节点加入到本地集群中。 换句话说,它本身不保存任何数据,但是它知道数据在集群中的哪个节点中,并且可以把请求转发到正确的节点。
  • 传输客户端(Transport client) 轻量级的传输客户端可以将请求发送到远程集群。 它本身不加入集群,但是它可以将请求转发到集群中的一个节点上。
  • 两个 Java 客户端都是通过9300端口并使用本地 Elasticsearch 传输 协议和集群交互。 集群中的节点通过端口9300彼此通信。如果这个端口没有打开,节点将无法形成一个集群。

    传统数据库都是关系型数据库,数据是存在于表中,通常一个字段>对应一列。

    Elasticsearch 是面向文档的,意味着它存储整个对象或 文档。Elasticsearch 不仅存储文档,而且 _索引 每个文档的内容使之可以被检索。在 Elasticsearch 中,你 对文档进行索引、检索、排序和过滤--而不是对行列数据。这是一种完全不同的思考数据的方式,也是 Elasticsearch 能支持复杂全文检索的原因。

    Elasticsearch 使用 JavaScript Object Notation 或者 JSON 作为文档的序列化格式。JSON 序列化被大多数编程语言所支持,并且已经成为 NoSQL 领域的标准格式。 它简单、简洁、易于阅读。

    Elasticsearch与关系数据的类比对应关系如下:

    Relational DB ⇒ Databases ⇒ Tables ⇒ Rows ⇒ Columns
    Elasticsearch ⇒ Indices ⇒ Types ⇒ Documents ⇒ Fields
    

    文档举例,它代表了一个 test 对象:

    "_index": "test", "_type": "test", "_id": "test", "_version": 1, "found": true, "_source": { "aaa": "zzz"
  • 集群 (Cluster)
  • 节点 (Node)
  • 索引 (Index)
  • 类型 (Type)
  • 文档 (Document)
  • 分片与副本(Shards & Replicas)
  • 集群 (Cluster)

    一个ES集群可以由一个或者多个节点(nodes or servers)组成。 所有这些节点用来存储所有的数据以及提供联合索引,为我们提供跨节点查询的能力。 一个ES集群的名称是唯一的,默认情况下为“elasticsearch”。这个名称非常重要,因为一个节点(node)会通过这个名称来判断是否加入已有的集群。 必须保证在不同环境下使用不同的集群名称,否则节点可能会加入错误的集群。

    节点 (Node)

    一个节点是一个集群中的一台服务器,它用来存储数据,参与集群的索引以及提供搜索能力。 可以简单的理解为一个ElasticSearch的启动实例,在集群中,节点是由它的唯一名称来做为标识。 这个名称对于管理ES集群非常重要,我们用它来定位网络或集群中的某一节点。 一个节点可以通过指定集群名称让它加入某个集群。 在单集群下,我们可以有任意数量的节点,如果当前网络下没有任何ES节点,那么在启动节点后,当前节点会默认形成一个单节点集群。

    索引 (Index)

    一个索引是一组具有相似特性的文档的集合。 一个索引由它的名称唯一标识(必须所有字母为小写字母)。 在一个单集群下,我们可以定义任意多的索引。

    类型 (Type)

    在一个索引下,我们可以定义一个或多个类型(types)。 一个类型是一个索引逻辑分类或分区(category/partition),而分类或分区的划分方法由我们自己决定。 通常情况下,我们会为具有相类似的字段的一组文档定义类型。 比如,如果我们运行一个博客平台,所有的数据都使用同一索引,我们为用户数据定义一种类型,为博客数据定义另一种类型,同时为评论数据定义另一种类型。

    文档 (Document)

    一个文档是一个可以被索引的基本信息单元。 可以理解为传统关系型数据库的表中的一条数据。

    分片与副本(Shards & Replicas)

    一个索引可能会存储大量数据从而超过单个节点硬件的限制。 为了解决这个问题,ES提供了一种分片(shard)能力,让我们将一个索引切分成片。 当我们创建一个索引时,我们可以为它指定分片的数量。 每个分片自己都能独立工作,并且存在与集群的任一节点中。 至于副本(Replicas)则是分片的备份文件。 分片数和副本数可以通过配置来进行修改。

    索引创建与删除

    一个 Elasticsearch 集群可以包含多个索引 ,相应的每个索引可以包含多个类型。 这些不同的类型存储着多个文档,每个文档又有多个属性

    下面举例创建一个保存员工信息的索引:

  • 每个员工索引一个文档,包含该员工的所有信息。
  • 每个文档都将是employee类型 。
  • 该类型位于索引sinobest内。
  • 该索引保存在我们的 Elasticsearch 集群中。
  • 实践中这非常简单(尽管看起来有很多步骤),我们可以通过一条命令完成所有这些动作:

    PUT /sinobest/employee/1
        "name" : "张三",
        "age" :  25,
        "position" : "程序员",
        "interests": [ "吃饭", "睡觉", "写bug" ],
        "jobNums" : 1234
    

    注意,路径 /sinobest/employee/1 包含了三部分的信息:

  • sinobest:索引名称
  • employee:类型名称
  • 1:特定雇员的ID
  • 操作成功后返回:

    "_index": "sinobest", "_type": "employee", "_id": "1", "_version": 1, "result": "created", "_shards": { "total": 2, "successful": 1, "failed": 0 "created": true

    插入索引后通过以下命令查看当前索引的状态:

    GET /_cat/indices
    

    返回结果:

    health status index    uuid                   pri rep docs.count docs.deleted store.size pri.store.size
    yellow open   sinobest Q1el2g90QhKD0jDNQxgXag   5   1          1            0      5.8kb          5.8kb
    

    当我们想删除这个索引时,可以用DELETE命令:

    DELETE /my_index
    

    你也可以这样删除多个索引:

    DELETE /index_one,index_two
    DELETE /index_*
    

    你甚至可以这样删除全部索引:

    DELETE /_all
    DELETE /*
    
  • elasticsearch.yml ES的基本配置文件,主要讲解这里的常用配置
  • jvm.options JVM相关参数配置
  • log4j2.properties 日志配置文件,es是使用log4j来记录日志的,所以logging.yml里的设置按普通log4j配置文件来设置就行了。
  • Cluster

    cluster.name可以确定你的集群名称,当你的elasticsearch集群在同一个网段中elasticsearch会自动的找到具有相同cluster.name的elasticsearch服务。 所以当同一个网段具有多个elasticsearch集群时cluster.name就成为同一个集群的标识。

    cluster.name: elasticsearch
    

    节点名称同理,可自动生成也可手动配置

    node.name: node-1
    

    允许一个节点是否可以成为一个master节点,es是默认集群中的第一台机器为master,如果这台机器停止就会重新选举master.

    node.master: true
    

    允许该节点存储数据(默认开启)

    node.data: true
    

    Index

    设置索引的分片数,默认为5

    index.number_of_shards: 5
    

    设置索引的副本数,默认为1:

    index.number_of_replicas: 1
    

    Discovery

    设置这个参数来保证集群中的节点可以知道其它N个有master资格的节点.默认为1,官方推荐设置为(N/2)+1

    discovery.zen.minimum_master_nodes: 1
    

    探查的超时时间,默认3秒,提高一点以应对网络不好的时候,防止脑裂

    discovery.zen.ping.timeout: 3s
    

    这是一个集群中的主节点的初始列表,当节点(主节点或者数据节点)启动时使用这个列表进行探测

    discovery.zen.ping.unicast.hosts: ["host1", "host2:port"]
    

    集群内的原理

    介绍 Elasticsearch 在分布式环境中的运行原理。 在这里大概讲述下ES的扩容机制,以及如何处理硬件故障的内容(想起来市局停电带来的惨痛经历)。

    如果我们启动了一个单独的节点,里面不包含任何的数据和 索引,那我们的集群看起来就是一个 下图 “包含空内容节点的集群”。

    一个运行中的 Elasticsearch 实例称为一个 节点,而集群是由一个或者多个拥有相同cluster.name配置的节点组成, 它们共同承担数据和负载的压力。当有节点加入集群中或者从集群中移除节点时,集群将会重新平均分布所有的数据。

    当一个节点被选举成为主节点(Master)时, 它将负责管理集群范围内的所有变更,例如增加、删除索引,或者增加、删除节点等。 而主节点并不需要涉及到文档级别的变更和搜索等操作,所以当集群只拥有一个主节点的情况下,即使流量的增加它也不会成为瓶颈。 任何节点都可以成为主节点。我们的示例集群就只有一个节点,所以它同时也成为了主节点。

    作为用户,我们可以将请求发送到 集群中的任何节点 ,包括主节点。 每个节点都知道任意文档所处的位置,并且能够将我们的请求直接转发到存储我们所需文档的节点。 无论我们将请求发送到哪个节点,它都能负责从各个包含我们所需文档的节点收集回数据,并将最终结果返回給客户端。 Elasticsearch 对这一切的管理都是透明的。

    Elasticsearch 的集群监控信息中包含了许多的统计数据,其中最为重要的一项就是集群健康, 它在status字段中展示为greenyellow或者red

    GET /_cluster/health
    

    在一个不包含任何索引的空集群中,它将会有一个类似于如下所示的返回内容:

    "cluster_name": "elasticsearch", "status": "green", "timed_out": false, "number_of_nodes": 1, "number_of_data_nodes": 1, "active_primary_shards": 0, "active_shards": 0, "relocating_shards": 0, "initializing_shards": 0, "unassigned_shards": 0
  • green 所有的主分片和副本分片都正常运行。
  • yellow 所有的主分片都正常运行,但不是所有的副本分片都正常运行。
  • red 有主分片没能正常运行。
  • 我们往 Elasticsearch 添加数据时需要用到 索引 —— 保存相关数据的地方。 索引实际上是指向一个或者多个物理分片的逻辑命名空间 。

    Elasticsearch 是利用分片将数据分发到集群内各处的。分片是数据的容器,文档保存在分片内,分片又被分配到集群内的各个节点里。 当你的集群规模扩大或者缩小时, Elasticsearch 会自动的在各节点中迁移分片,使得数据仍然均匀分布在集群里。

    一个分片可以是主分片或者副本分片。 索引内任意一个文档都归属于一个主分片,所以主分片的数目决定着索引能够保存的最大数据量。

    举例说明,创建索引blogs,设置主分片数为3,副本为1(副本1说明每个主分片有1个副本分片)

    PUT /blogs
       "settings" : {
          "number_of_shards" : 3,
          "number_of_replicas" : 1
    

    我们的集群现在是下图“拥有一个索引的单节点集群”。所有3个主分片都被分配在 Node 1 。

    如果我们现在查看集群健康, 我们将看到如下内容:

    "cluster_name": "elasticsearch", "status": "yellow", "timed_out": false, "number_of_nodes": 1, "number_of_data_nodes": 1, "active_primary_shards": 3, "active_shards": 3, "relocating_shards": 0, "initializing_shards": 0, "unassigned_shards": 3, "delayed_unassigned_shards": 0, "number_of_pending_tasks": 0, "number_of_in_flight_fetch": 0, "task_max_waiting_in_queue_millis": 0, "active_shards_percent_as_number": 50
  • status:集群 status 值为 yellow 。
  • unassigned_shards :没有被分配到任何节点的副本数。
  • 集群的健康状况为yellow则表示全部主分片都正常运行(集群可以正常服务所有请求), 但是副本分片没有全部处在正常状态。 实际上,所有3个副本分片都是 unassigned —— 它们都没有被分配到任何节点。 在同一个节点上既保存原始数据又保存副本是没有意义的,因为一旦失去了那个节点,我们也将丢失该节点上的所有副本数据。

    当前我们的集群是正常运行的,但是在硬件故障时有丢失数据的风险。

    添加故障转移编辑

    当集群中只有一个节点在运行时,意味着会有一个单点故障问题。 幸运的是,我们只需再启动一个节点即可防止数据丢失。

    启动第二个节点

    如果启动了第二个节点,我们的集群将会如下图“拥有两个节点的集群——所有主分片和副本分片都已被分配”所示。

    当第二个节点加入到集群后,3个副本分片将会分配到这个节点上——每个主分片对应一个副本分片。 这意味着当集群内任何一个节点出现问题时,我们的数据都完好无损。

    所有新近被索引的文档都将会保存在主分片上,然后被并行的复制到对应的副本分片上。这就保证了我们既可以从主分片又可以从副本分片上获得文档。

    cluster-health现在展示的状态为green,这表示所有6个分片(包括3个主分片和3个副本分片)都在正常运行。

    "cluster_name": "elasticsearch", "status": "green", "timed_out": false, "number_of_nodes": 2, "number_of_data_nodes": 2, "active_primary_shards": 3, "active_shards": 6, "relocating_shards": 0, "initializing_shards": 0, "unassigned_shards": 0, "delayed_unassigned_shards": 0, "number_of_pending_tasks": 0, "number_of_in_flight_fetch": 0, "task_max_waiting_in_queue_millis": 0, "active_shards_percent_as_number": 100

    怎样为我们的正在增长中的应用程序按需扩容呢? 当启动了第三个节点,我们的集群将会看起来如下图“拥有三个节点的集群——为了分散负载而对分片进行重新分配”所示。

    Node 1Node 2上各有一个分片被迁移到了新的Node 3节点,现在每个节点上都拥有2个分片,而不是之前的3个。 这表示每个节点的硬件资源(CPU, RAM, I/O)将被更少的分片所共享,每个分片的性能将会得到提升。

    分片是一个功能完整的搜索引擎,它拥有使用一个节点上的所有资源的能力。 我们这个拥有6个分片(3个主分片和3个副本分片)的索引可以最大扩容到6个节点,每个节点上存在一个分片,并且每个分片拥有所在节点的全部资源。

    更多的扩容

    但是如果我们想要扩容超过6个节点怎么办呢?

    主分片的数目在索引创建时 就已经确定了下来。实际上,这个数目定义了这个索引能够存储的最大数据量。(实际大小取决于你的数据、硬件和使用场景。) 但是,读操作——搜索和返回数据——可以同时被主分片副本分片所处理,所以当你拥有越多的副本分片时,也将拥有越高的吞吐量。

    在运行中的集群上是可以动态调整副本分片数目的 ,我们可以按需伸缩集群。让我们把副本数从默认的1增加到2

    PUT /blogs/_settings
       "number_of_replicas" : 2
    

    如下图“将参数 number_of_replicas 调大到 2”所示, blogs 索引现在拥有9个分片:3个主分片和6个副本分片。 这意味着我们可以将集群扩容到9个节点,每个节点上一个分片。

    我们之前说过 Elasticsearch 可以应对节点故障,接下来让我们尝试下这个功能。 如果我们关闭第一个节点,这时集群的状态为下图“关闭了一个节点后的集群”

    我们关闭的节点是一个主节点。而集群必须拥有一个主节点来保证正常工作,所以发生的第一件事情就是选举一个新的主节点: Node 2 。

    在我们关闭 Node 1 的同时也失去了主分片 1 和 2 ,并且在缺失主分片的时候索引也不能正常工作。 如果此时来检查集群的状况,我们看到的状态将会为 red :不是所有主分片都在正常工作。

    幸运的是,在其它节点上存在着这两个主分片的完整副本, 所以新的主节点立即将这些分片在 Node 2 和 Node 3 上对应的副本分片提升为主分片, 此时集群的状态将会为 yellow 。 这个提升主分片的过程是瞬间发生的,如同按下一个开关一般。

    为什么我们集群状态是 yellow 而不是 green 呢? 虽然我们拥有所有的三个主分片,但是同时设置了每个主分片需要对应2份副本分片,而此时只存在一份副本分片。 所以集群不能为 green 的状态,不过我们不必过于担心:如果我们同样关闭了 Node 2 ,我们的程序 依然 可以保持在不丢任何数据的情况下运行,因为 Node 3 为每一个分片都保留着一份副本。

    如果我们重新启动 Node 1 ,集群可以将缺失的副本分片再次进行分配。如果 Node 1 依然拥有着之前的分片,它将尝试去重用它们,同时仅从主分片复制发生了修改的数据文件。

    映射和分析

    倒排索引(inverted index)

    传统的关系型数据库,可以通过给某列建立一个B树索引来加快检索的速度。通常是文档->关键字。 而Elasticsearch与之相反,是关键字->文档。其使用一种称为倒排索引的结构,可能是因为将正常的索引倒过来了吧,所以大家叫他倒排索引,叫反向索引我觉得ok,它适用于快速的全文搜索。 一个倒排索引由文档中所有不重复词的列表构成,对于其中每个词,有一个包含它的文档列表。

    例如,假设我们有两个文档,每个文档的 content 域包含如下内容:

  • The quick brown fox jumped over the lazy dog
  • Quick brown foxes leap over lazy dogs in summer
  • 为了创建倒排索引,我们首先将每个文档的 content 域拆分成单独的词(我们称它为 词条 或 tokens ),创建一个包含所有不重复词条的排序列表,然后列出每个词条出现在哪个文档。结果如下所示:

    Term      Doc_1  Doc_2
    -------------------------
    Quick   |       |  X
    The     |   X   |
    brown   |   X   |  X
    dog     |   X   |
    dogs    |       |  X
    fox     |   X   |
    foxes   |       |  X
    in      |       |  X
    jumped  |   X   |
    lazy    |   X   |  X
    leap    |       |  X
    over    |   X   |  X
    quick   |   X   |
    summer  |       |  X
    the     |   X   |
    ------------------------
    

    现在,如果我们想搜索 quick brown ,我们只需要查找包含每个词条的文档:

    Term      Doc_1  Doc_2
    -------------------------
    brown   |   X   |  X
    quick   |   X   |
    ------------------------
    Total   |   2   |  1
    

    两个文档都匹配,但是第一个文档比第二个匹配度更高。如果我们使用仅计算匹配词条数量的简单相似性算法,那么,我们可以说,对于我们查询的相关性来讲,第一个文档比第二个文档更佳。

    分析与分析器

    分析包含下面的过程:

  • 首先,将一块文本分成适合于倒排索引的独立的 词条 ,
  • 之后,将这些词条统一化为标准格式以提高它们的“可搜索性”
  • 分析器执行上面的工作。 分析器 实际上是将三个功能封装到了一个包里:

    字符过滤器

    首先,字符串按顺序通过每个 字符过滤器 。他们的任务是在分词前整理字符串。一个字符过滤器可以用来去掉HTML,或者将 & 转化成 and

    其次,字符串被 分词器 分为单个的词条。一个简单的分词器遇到空格和标点的时候,可能会将文本拆分成词条。

    Token 过滤器

    最后,词条按顺序通过每个 token 过滤器 。这个过程可能会改变词条(例如,小写化 Quick ),删除词条(例如, 像 aandthe 等无用词),或者增加词条(例如,像 jump 和 leap 这种同义词)。

    Elasticsearch提供了开箱即用的字符过滤器、分词器和token 过滤器。 这些可以组合起来形成自定义的分析器以用于不同的目的。

    内置分析器

    但是, Elasticsearch还附带了可以直接使用的预包装的分析器。 接下来我们会列出最重要的分析器。为了证明它们的差异,我们看看每个分析器会从下面的字符串得到哪些词条:

    "Set the shape to semi-transparent by calling set_trans(5)"
    

    标准分析器

    标准分析器是Elasticsearch默认使用的分析器。它是分析各种语言文本最常用的选择。它根据 Unicode 联盟 定义的 单词边界 划分文本。删除绝大部分标点。最后,将词条小写。它会产生

    set, the, shape, to, semi, transparent, by, calling, set_trans, 5
    

    简单分析器

    简单分析器在任何不是字母的地方分隔文本,将词条小写。它会产生

    set, the, shape, to, semi, transparent, by, calling, set, trans
    

    空格分析器

    空格分析器在空格的地方划分文本。它会产生

    Set, the, shape, to, semi-transparent, by, calling, set_trans(5)
    

    语言分析器 特定语言分析器可用于很多语言(并不包括中文= =日文韩文这种块型语言也不包括)。 它们可以考虑指定语言的特点。 例如, english分析器附带了一组英语无用词(常用单词,例如 and 或者 the ,它们对相关性没有多少影响),它们会被删除。 由于理解英语语法的规则,这个分词器可以提取英语单词的词干

    english分词器会产生下面的词条:

    set, shape, semi, transpar, call, set_tran, 5
    

    注意看 transparent`、 `calling 和 set_trans 已经变为词根格式。

    什么时候使用分析器

    当我们索引一个文档,它的全文域被分析成词条以用来创建倒排索引。 但是,当我们在全文域搜索的时候,我们需要将查询字符串通过相同的分析过程, 以保证我们搜索的词条格式与索引中的词条格式一致。

  • 当你查询一个全文域时, 会对查询字符串应用相同的分析器,以产生正确的搜索词条列表。
  • 当你查询一个精确值域时,不会分析查询字符串, 而是搜索你指定的精确值。
  • 我们可以通过Mapping API来设置分析器

    映射(Mapping)

    在传统数据库中,我们建表的时候要指定字段的类型、主键、长度、默认值等等, 而ES的映射操作就是类似这样的操作。 为ES的文档的字段设置类型、分析器、过滤器等等。

    字段数据类型

    ES支持以下多种的数据类型:

  • text and keyword
  • long, integer, short, byte, double, float, half_float, scaled_float
  • boolean
  • binary
  • integer_range, float_range, long_range, double_range, date_range
  • 复杂的数据类型

  • Array support does not require a dedicated type
  • object for single JSON objects
  • nested for arrays of JSON objects
  • 通过 /_mapping ,我们可以查看 Elasticsearch 在一个或多个索引中的一个或多个类型的映射 。

    GET /index/_mapping/type
    

    analyzer

    对于analyzed字符串域,用analyzer属性指定在搜索和索引时使用的分析器。 默认, Elasticsearch 使用standard分析器,但你可以指定一个内置的分析器替代它, 例如whitespacesimpleenglish,也可以配置为第三方的分析器插件(需要另外安装):

    PUT /index/type/_mapping
       "properties": {
          "field": {
             "search_analyzer": "simple",
             "analyzer": "simple",
             "type": "string"
    

    当你首次创建一个索引的时候,可以指定类型的映射。你也可以使用/_mapping为新类型增加映射。

    但是当索引有数据时,说明倒排索引已经创建,这时候不能修改映射,所以只能通过删除原来的索引,然后再重新建立索引和映射。。。

    排序与相关性

    默认情况下,返回的结果是按照相关性进行排序的——最相关的文档排在最前。 这里大概解释一下相关性意味着什么以及它是如何计算的,同时介绍一下使用sort参数进行自定义的排序。

    为了按照相关性来排序,需要将相关性表示为一个数值。 在 Elasticsearch 中,相关性得分由一个浮点数进行表示,并在搜索结果中通过_score参数返回, 默认排序是_score降序。

    "took": 11, "timed_out": false, "_shards": { "total": 5, "successful": 5, "failed": 0 "hits": { "total": 1, "max_score": 1, "hits": [ "_index": "sinobest", "_type": "employee", "_id": "1", "_score": 1, "_source": { "name": "张三", "age": 25, "position": "程序员", "interests": [ "吃饭", "睡觉", "写bug" "jobNums": 1234

    当我们想按实际的使用情况来排序,比如年龄的大小,这时候我们可以使用sort参数:

    GET /_search
        "query" : {
            "bool" : {
                "filter" : { "term" : { "name" : "张三" }}
        "sort": { "age": { "order": "desc" }}
    

    返回结果中的_score为null:

    "took": 11, "timed_out": false, "_shards": { "total": 5, "successful": 5, "failed": 0 "hits": { "total": 1, "max_score": null, "hits": [ "_index": "sinobest", "_type": "employee", "_id": "1", "_score": null, "_source": { "name": "张三", "age": 25, "position": "程序员", "interests": [ "吃饭", "睡觉", "写bug" "jobNums": 1234

    什么是相关性

    默认情况下,返回结果是按相关性倒序排列的。 但是什么是相关性? 相关性如何计算?

    Elasticsearch 的相似度算法被定义为检索词频率/反向文档频率TF/IDF,包括以下内容:

    相关性并不只是全文本检索的专利。也适用于 yes|no 的子句,匹配的子句越多,相关性评分越高。

    如果多条查询子句被合并为一条复合查询语句 ,比如bool查询,则每个查询子句计算得出的评分会被合并到总的相关性评分中。

    当在传统数据库调优一个复杂的sql的时候,通常会用到执行计划,来查看sql执行的时间损耗在哪个方面。 同理,ElasticSearch也可以通过explain 参数来查看当次查询的细节。

    GET /_search?explain
       "query"   : { "match" : { "name" : "张三" }}
    

    ElasticSearch检索文档,是从倒排索引中查找包含此词条的文档,所以我在想是不是索引没建立好。 当初建立索引的时候没有设置相关的映射,所以插入ElasticSearch的时候用的是默认的分析器,于是使用Analyze API检验一下。

    POST _analyze
      "tokenizer": "standard",
      "text":      "天河区天河公园"
    

    返回结果如下:

    "tokens": [ "token": "", "start_offset": 0, "end_offset": 1, "type": "<IDEOGRAPHIC>", "position": 0 "token": "", "start_offset": 1, "end_offset": 2, "type": "<IDEOGRAPHIC>", "position": 1 "token": "", "start_offset": 2, "end_offset": 3, "type": "<IDEOGRAPHIC>", "position": 2 "token": "", "start_offset": 3, "end_offset": 4, "type": "<IDEOGRAPHIC>", "position": 3 "token": "", "start_offset": 4, "end_offset": 5, "type": "<IDEOGRAPHIC>", "position": 4 "token": "", "start_offset": 5, "end_offset": 6, "type": "<IDEOGRAPHIC>", "position": 5 "token": "", "start_offset": 6, "end_offset": 7, "type": "<IDEOGRAPHIC>", "position": 6

    关键字被分词成单独的每个汉字,然后排重后建立倒排索引,每个单独的汉字都为一个倒排索引

    A: 使用中文分析器,添加字典和中文关键字预处理

    中文分词器的选择

  • pinyin 分词器
  • Mmseg 分词器
  • ik-analyzer
  • ik-analyzer

    安装方式请参考gayhub地址

    IKAnalyzer是一个开源的,基于java语言开发的轻量级的中文分词工具包。 采用了特有的“正向迭代最细粒度切分算法“,支持细粒度和最大词长两种切分模式;具有83万字/秒(1600KB/S)的高速处理能力。 采用了多子处理器分析模式,支持:英文字母、数字、中文词汇等分词处理,兼容韩文、日文字符 优化的词典存储,更小的内存占用。支持用户词典扩展定义 针对Lucene全文检索优化的查询分析器IKQueryParser(作者吐血推荐);引入简单搜索表达式,采用歧义分析算法优化查询关键字的搜索排列组合,能极大的提高Lucene检索的命中率。

    ik 带有两个分词器

  • ik_max_word:会将文本做最细粒度的拆分;尽可能多的拆分出词语
  • ik_smart:会做最粗粒度的拆分;已被分出的词语将不会再次被其它词语占有
  • ik_max_word分词效果

    POST _analyze
      "tokenizer": "ik_max_word",
      "text":      "天河区天河公园"
       "tokens": [
             "token": "天河区",
             "start_offset": 0,
             "end_offset": 3,
             "type": "CN_WORD",
             "position": 0
             "token": "天河",
             "start_offset": 0,
             "end_offset": 2,
             "type": "CN_WORD",
             "position": 1
             "token": "",
             "start_offset": 1,
             "end_offset": 2,
             "type": "CN_WORD",
             "position": 2
             "token": "",
             "start_offset": 2,
             "end_offset": 3,
             "type": "CN_CHAR",
             "position": 3
             "token": "天河",
             "start_offset": 3,
             "end_offset": 5,
             "type": "CN_WORD",
             "position": 4
             "token": "",
             "start_offset": 4,
             "end_offset": 5,
             "type": "CN_WORD",
             "position": 5
             "token": "公园",
             "start_offset": 5,
             "end_offset": 7,
             "type": "CN_WORD",
             "position": 6
    

    ik_smart分析效果

    POST _analyze
      "tokenizer": "ik_smart",
      "text":      "天河区天河公园"
       "tokens": [
             "token": "天河区",
             "start_offset": 0,
             "end_offset": 3,
             "type": "CN_WORD",
             "position": 0
             "token": "天河",
             "start_offset": 3,
             "end_offset": 5,
             "type": "CN_WORD",
             "position": 1
             "token": "公园",
             "start_offset": 5,
             "end_offset": 7,
             "type": "CN_WORD",
             "position": 2
    

    上述的两种分词模式虽然能满足很多使用场景,但对于一些特殊情况, 例如张小三是一个用户的名字,分析结果如下:

    "tokens": [ "token": "张", "start_offset": 0, "end_offset": 1, "type": "CN_CHAR", "position": 0 "token": "小三", "start_offset": 1, "end_offset": 3, "type": "CN_WORD", "position": 1

    我们想让分析器以张小三为一个词条而不是小三。这时候可以使用ik分词器的字典功能。

    修改分析器目录下的config/IKAnalyzer.cfg.xml

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
    <properties>
    	<comment>IK Analyzer 扩展配置</comment>
    	<!--用户可以在这里配置自己的扩展字典 -->
    	<entry key="ext_dict">custom/mydict.dic;custom/single_word_low_freq.dic</entry>
    	 <!--用户可以在这里配置自己的扩展停止词字典-->
    	<entry key="ext_stopwords">custom/ext_stopword.dic</entry>
     	<!--用户可以在这里配置远程扩展字典 -->
    	<entry key="remote_ext_dict">location</entry>
     	<!--用户可以在这里配置远程扩展停止词字典-->
    	<entry key="remote_ext_stopwords">http://xxx.com/xxx.dic</entry>
    </properties>

    在使用中文分析器后,查询精度虽然提高了,但还是不准确,搜索天河区天河公园还是得不到想要的结果, 但我发现关键字输入天河区 天河 公园,用空格隔开就能查询到很准确的结果。所以针对中文的关键字, 还做了一下预处理(预分词)来提高查询准确度。

    //分词器
    String analyzer = PropertiesUtil.getAsString(ES_CONFIG,"search.cn_analyzer");
    //构建分析请求
    AnalyzeRequestBuilder analyzeRequestBuilder = new AnalyzeRequestBuilder(client, AnalyzeAction.INSTANCE,
            simpleParam.getIndex(),chineseParamsList.toArray(new String[]{}));
    analyzeRequestBuilder.setTokenizer(analyzer);
    List<AnalyzeResponse.AnalyzeToken> ikTokenList = analyzeRequestBuilder.execute().actionGet().getTokens();
    //处理结果
    List<String> pretreatedCNList = new ArrayList<>();
    ikTokenList.forEach(ikToken -> {
        String item = ikToken.getTerm();
        if(!pretreatedCNList.contains(item)){
            pretreatedCNList.add(item);
    

    Q: 索引重建

    为了提高查询的精确度,经常会对索引的映射进行修改。但是对于有数据的索引来说, 必须要先删除索引才能重建,这样又要重新通过JDBC从数据库里获取数据。非常耗时而且要停机。

    A: 使用Reindex API进行重建,索引别名实现零停机

    Reindex API

    从Elasticsearch v2.3.0开始, Reindex API 被引入。它能够对文档重建索引而不需要任何插件或外部工具。

    POST _reindex
      "source": {
        "index": "twitter"
      "dest": {
        "index": "new_twitter"
    

    类似于传统数据库中的

    CREATE TABLE NEW_TAB AS (SELECT * FROM TAB)
    

    当然Reindex API也是支持按条件进行重建,此处不详讲。

    在前面提到的,重建索引的问题是必须更新应用中的索引名称。 索引别名就是用来解决这个问题的!

    索引别名就像一个快捷方式或软连接,可以指向一个或多个索引,也可以给任何一个需要索引名的API来使用。 可以理解为视图

    有两种方式管理别名:_alias用于单个操作,_aliases用于执行多个原子级操作。

    假设你的应用有一个叫 my_index 的索引。事实上, my_index 是一个指向当前真实索引的别名。真实索引包含一个版本号: my_index_v1 , my_index_v2 等等。

    首先,创建索引 my_index_v1 ,然后将别名 my_index 指向它:

    PUT /my_index_v1                    --创建索引 my_index_v1 。
    PUT /my_index_v1/_alias/my_index    --设置别名 my_index 指向 my_index_v1 。
    

    你可以检测这个别名指向哪一个索引:

    GET /*/_alias/my_index
    

    或哪些别名指向这个索引:

    GET /my_index_v1/_alias/*
    

    两者都会返回下面的结果:

    "my_index_v1" : { "aliases" : { "my_index" : { }

    然后,我们决定修改my_index_v1中一个字段的映射。当然,我们不能修改现存的映射,所以我们必须重新索引数据。 首先, 我们用新映射创建索引my_index_v2,然后从my_index_v1Reindex数据到my_index_v2

    一旦我们确定文档已经被正确地重索引了,我们就将别名指向新的索引。

    一个别名可以指向多个索引,所以我们在添加别名到新索引的同时必须从旧的索引中删除它。这个操作需要原子化,这意味着我们需要使用 _aliases 操作:

    POST /_aliases
        "actions": [
            { "remove": { "index": "my_index_v1", "alias": "my_index" }},
            { "add":    { "index": "my_index_v2", "alias": "my_index" }}
    

    此时别名my_index已经从my_index_v1指向到了my_index_v2。 外部访问my_index时,实际上是指向到了my_index_v2。 这时候把旧的my_index_v1索引删除掉。这样一套操作下来,开销很小,应该广泛使用。

    Q: 检索关键字包含特殊字符

    大家都知道很多系统都有自己的保留字,有特殊含意,不能作为条件或者参数使用。 ElasticSearch也不例外,不小心中招的话,轻则报错,重则宕机。

    A: 保留字消义

    现在列举一下ElasticSearch的保留字符:

    + - = && || > < ! ( ) { } [ ] ^ " ~ * ? : \ /
    

    如果你想查询(1+1)=2这个内容,关键字要处理成这样:

    \(1\+1\)\=2
    

    Q: 检索关键字个数太多

    有次随便瞎搞的时候,放了2000多个关键字去进行检索,结果报错

    Query DSL: Allow to control (globally) the max clause count for `bool` query (defaults to 1024)
    

    A: 控制关键字个数Or修改设置

    就跟select * from tab where field in (...)一样,in里面参数的数量不能超过1000。 ElasticSearch也有数量控制,也刚好是个整数1024。

    为了避免这种情况,最好的处理方式是控制参数的个数,因为大数量的参数会影响检索效率。 如果一定要查这么多参数。ElasticSearch的issues给出了方法: 设置match_phrase_prefix的大小。

    Q: 权限控制&监控

    不知道大家有没有发现,当我们启动了ElasticSearch之后,就能通过Restful API来进行相关操作。 并不需要登录,也没有什么权限控制,只要知道ElasticSearch的ip和port就能为所欲为。 这时怕是被人脱库或者删库都不知道是谁干的。

    A: 使用官方提供的插件

  • Kibana
  • X‑Pack
  • Kibana让您能够可视化Elasticsearch中的数据并对其进行操作。 有了Kibana,在为Elastic Stack管理设置或配置X‑Pack security功能来保护、监控和配置Elastic Stack。 命令行不再是唯一途径。 与此同时,得益于非常出色的 API,现在的Elastic Stack操作变得更加直观, 能够让更加广泛的受众上手操作。

    详细可参见官方网址

    注:官方的插件并不是完全免费的喔:joy:

    Q: 现场最新的反馈问题,中文姓名检索内容不准确

    ik分词器对中文姓名的支持不好啊。。。(并没有支持中文姓名的分词)。导致检索非字典内的中文姓名时,出来的结果不准确。

    A: 换个能正常进行中文分词,同时能支持中文姓名的分词器

    看了用得比较多的几个中文分词器,最后选定了ansj分词器,github地址

    分词效果不错并支持中文姓名识别。于是之后就是修改mapping,然后reindex的操作了。

  • Elasticsearch: 权威指南(难得的中文文档喔,不过针对的是 Elasticsearch 2.x的,阅读时请注意)
  • Elasticsearch Reference(官方文档,英语好的小伙伴可以看这个,此处是5.5版本的,目前最新的更新为5.6版本)
  • elastic/elasticsearch · GitHub(ElasticSearch的开源地址)
  • java客户端 API
  •