Elasticsearch基础教程

准备写 Elasticsearch 系列教程

作者: judasn  分类: Elasticsearch  发布时间: 2017-05-27 10:11 
  • 网上的资料我觉得不够系统,所以想系统地整理出这样的一套材料出来。
  • 除了文章知识点,还提供 Github 上的 Demo 例子。

  • 希望能借这个系列,让 YouMeek.com 有一个好的新开端!

1.1 Elasticsearch 介绍 + CentOS 7 下安装部署

作者: judasn  分类: Elasticsearch  发布时间: 2017-05-29 21:01 

课程环境


Elasticsearch 介绍

Elasticsearch 场景

  • 只要用到搜索功能的地方都可以
  • 日志数据分析
  • BI、大数据系统(听说业界有 PB 级别的应用)
  • 数据分析
  • NoSQL 数据库

Elasticsearch 经历

  • 搜索系统无处不在,对于一般程序员来讲,更多的是自己业务系统内的搜索。
  • 传统数据库的搜索功能,在数据量大的情况下性能很差,所以必须要有一个搜索功能来替代这个。
  • 过去大家的方案是:Solr,现在大家的方案是:Elasticsearch。后者比前者新,能力更强,更符合现在时代。
  • 不管是 Solr 或是 Elasticsearch 底层都是:Lucene(比较复杂、底层)
  • Lucene 的根本:全文检索、倒排索引

Elasticsearch 优点

  • 自带高可用,有冗余副本
  • 自带分布式,支持分片,可分配到多机上,所以数据很大也扛得住
  • 封装了很多高级功能,方便我们调用

Elasticsearch 核心概念

  • 官网介绍:https://www.elastic.co/guide/en/elasticsearch/reference/5.2/_basic_concepts.html#_near_realtime_nrt
  • Near Realtime(NRT):近实时(Elasticsearch 是有小延迟的,一般为 1 秒,一般情况是感受不到这个延迟的)。
  • Cluster:集群,可以一个或多个节点,这些节点共同保存这个集群的数据。集群有一个名称,很重要,各个节点的配置就需要用到这个名称,节点是用集群名称加入到集群中的。理论上单节点是最优方案,可惜只适合小数据量。对于各个环节的集群名称建议这样命名:youmeek-dev、youmeek-prod、youmeek-test
  • Node:节点,归属集群。如果整个集群就一个节点,那这个节点也就是这个集群本身。节点也有名称(默认是在启动的时候随机分配的 UUID),节点名称也可以自定义,一般都建议自定义,节点名称很重要编译运维中进行管理。
  • Index:索引,类似数据库结构中的库,是一堆 document 的集合。索引名称必须全部是小写,不能用下划线开头,不能包含逗号,比如:youmeek-index
  • Type:类型,类似数据库结构中的表。虽然现在一个 Index 可以有多个 Type,但是正在开发的 Elasticsearch 6 打算废弃这个特性了,一个 Index 只能有一个 Type,具体看:https://elasticsearch.cn/article/158
  • Document:文档,Elasticsearch 中的最小数据单元,类似数据库结构中的一行数据。所以一行数据中也会有多个 field 也就是字段。Document 通常用 JSON 格式来表示。
  • Shard:分片。全称 primary shards(一般用在写操作)。Elasticsearch 可以将一个 Index 中的 Document 数据切分为多个 shard,分布在多台服务器上存储。每个 shard 都是一个 Lucene index,最多能有 Document 这么多(官网原文):the limit is 2147483519 (Integer.MAX_VALUE – 128) documents。
  • Replica:副本。全称 replica shards(一般读操作可以被分配到进行使用)。Replica 主要用来保证高可用(故障转移)、数据备份、增强高吞吐的并行搜索。
  • 假设我们现在有 2 台机子,要做一个这样的环境:2 台机子当做 2 个 Node 组成 1 个 Cluster。现在创建 1 个 Index,默认会有 5 个 primary shards(创建的时候也可以指定其他数量,创建完成后是不可修改的,因为跟数据存储时候的路由功能有关系,具体可以看:路由字段资料自定义路由字段)。5 个 primary shards 分片各自有 1 个 Replica 副本分片用作备份,则这两台机子分片最终结果是:有 5 个 primary shards,有 5 个 replica shards 与之对应。
  • 一般最小的 高可用配置,是 2 台服务器。而一般推荐是 5 台机子,最好是奇数台机子。
  • 如果是 5 台机子可以这样规划:两台节点作为 master ,这两个节点都是作为 commander 统筹集群层面的事务,取消这两台的 data 权利。然后在规划出三个节点的 data 集群,取消这三个节点的 master 权利。让他们安心的做好数据存储和检索服务。这样做的好处,就是职责分明,可以最大限度的防止 master 节点有事 data 节点,导致不稳定因素发生。比如 data 节点的数据复制,数据平衡,路由等等,直接影响 master 的稳定性。进而可能会发生脑裂问题。

其他资料辅助

1.2 Elasticsearch 索引集群的管理

作者: judasn  分类: Elasticsearch  发布时间: 2017-06-12 14:17 

课程环境


Elasticsearch 介绍

Elasticsearch 索引简单操作

  • 上一篇章我们已经安装了 Elasticsearch 和 Kibana,所以我们现在启动 Elasticsearch 和 Kibana 访问:http://192.168.1.127:5601/app/kibana#/dev_tools/console?_g=(),如下图:
  • 需要注意的细节是:Kibana Dev Tools 上面可以写多条 DSL 语句,光标放在哪一条上面,那一条后面有一个播放号可以单独执行,所以没必要删掉旧的语句。

Kibana Dev Tools

  • 查询集群健康状况:GET /_cat/health?v
  • 查询集群中有哪些索引:GET /_cat/indices?v
  • 简单的索引操作:
    • 新增索引:PUT /product_index
    • 删除指定索引:DELETE /product_index
    • 删除指定多个索引:DELETE /product_index,order_index
    • 删除匹配符索引:DELETE /product_*
    • 删除所有索引:DELETE /_all
    • 查询索引配置信息:GET /product_index/_settings
    • 查询多个索引配置信息:GET /product_index,order_index/_settings
    • 查询所有索引配置信息:GET /_all/_settings

Elasticsearch 索引较复杂操作

  • 新增索引,并指定 primary shards 和 replica shards 数量。
PUT /order_index
{
  "settings": {
    "index": {
      "number_of_shards": 5,
      "number_of_replicas": 1
    }
  }
}
  • 新增完索引后,更改 replica shards 数量:
PUT /order_index/_settings
{
    "number_of_replicas": 2
}
  • 新增索引并设置 mapping(Dynamic Mapping):
  • mapping 你可以理解为是传统数据库中的设置表结构一样的作用,比如有个字段叫做 introduce,传统数据库文本字段你会考虑设置为:char、varchar、text,是否为空,是否有默认值等。
  • Elasticsearch 中的 mapping 类似上面,因为你一样要考虑比如这个字段:article_title 是否设置为 text 类型,要不要分词等。
  • 下面的 mapping 使用了 ik 分词器(5.2.0 版本)。field 新增后是不能修改的。
PUT /product_index
{
  "settings": {
    "refresh_interval": "5s",
    "number_of_shards": 5,
    "number_of_replicas": 1
  },
  "mappings": {
    "product": {
      "properties": {
        "id": {
          "type": "text",
          "index": "not_analyzed"
        },
        "product_name": {
          "type": "text",
          "store": "no",
          "term_vector": "with_positions_offsets",
          "analyzer": "ik_max_word",
          "search_analyzer": "ik_max_word",
          "boost": 5
        },
        "product_desc": {
          "type": "text",
          "index": "not_analyzed"
        },
        "price": {
          "type": "double",
          "index": "not_analyzed"
        },
        "created_date_time": {
          "type": "date",
          "index": "not_analyzed",
          "format": "basic_date_time"
        },
        "last_modified_date_time": {
          "type": "date",
          "index": "not_analyzed",
          "format": "basic_date_time"
        },
        "version": {
          "type": "long",
          "index": "no"
        }
      }
    }
  }
}

1.3 Elasticsearch Document(文档)的管理

作者: judasn  分类: Elasticsearch  发布时间: 2017-06-12 14:19 

课程环境


Document 介绍

  • Document 是 Elasticsearch 最小的数据单元,可以理解为数据库结构中的一行数据。
  • Document 的数据格式是用 JSON 来表示的,如下:
{
  "email": "gitnavi@qq.com",
  "login_name": "youmeek",
  "card_info": {
    "card_name": "zhangsan",
    "card_num": "350111199002205317"
  },
  "create_date": "2017-05-30 12:04:54"
}
  • 用 JSON 的好处是可以表达复杂的对象结构,比如一个对象中含有另外一个子对象,子对象中还有集合的子子对象等,也符合当前业界的易用的数据交流。

Document 的简单 CURD

  • 新增 3 条 Document(如果没有 Index 和 Type 会自动提前在创建):
PUT /product_index/product/1
{
    "product_name" : "PHILIPS toothbrush HX6730/02",
    "product_desc" :  "【3?9 元,前 1000 名赠刷头,6 月 1 日 0 点火爆开抢,618 开门红巅峰 48 小时,抢先加入购物车】飞利浦畅销款,万千好评!深入净齿,智能美白!",
    "price" :  399.00
}

PUT /product_index/product/2
{
    "product_name" : "Braun toothbrush 2000 3D",
    "product_desc" :  "6 月 1 日 16 点秒杀,仅 329 元!限量 1000 支,抢完即止!带压力感应提醒,保护牙龈,高效清洁",
    "price" :  499.00
}

PUT /product_index/product/3
{
    "product_name" : "iphone7 shell",
    "product_desc" :  "一说到星空,就有太多美好的记忆,美丽的浩瀚宇宙,有太多说不清的神秘之处,星空太美丽,太绚烂!",
    "price" :  36.00
}

PUT /product_index/product/4
{
    "product_name" : "iphone7 shell4",
    "product_desc" :  "一说到星空4,就有太多美好的记忆,美丽的浩瀚宇宙,有太多说不清的神秘之处,星空太美丽,太绚烂!",
    "price" :  36.00
}

PUT /product_index/product/5
{
    "product_name" : "iphone7 shell5",
    "product_desc" :  "一说到星空5,就有太多美好的记忆,美丽的浩瀚宇宙,有太多说不清的神秘之处,星空太美丽,太绚烂!",
    "price" :  36.00
}

## POST 方式新增数据,不指向 ID 会自动生成一个 20 位的字符串 ID。
POST /product_index/product
{
    "product_name" : "iphone6 shell",
    "product_desc" :  "真正 360° 全包边 防指纹防摔",
    "price" :  28.00
}
  • 查询/检索 Document:
    • 通过 ID 查询(默认返回所有元数据):GET /product_index/product/3
    • 通过 ID 查询(返回指定元数据):GET /product_index/product/3?_source=product_name,product_desc
    • 查询指定索引的所有数据:GET /product_index/product/_search
    • 设置查询超时:GET /product_index/product/_search?timeout=5s官网资料
    • 查询多个索引:GET /product_index,order_index/_search
    • 查询多个索引、多个类型:GET /product_index,order_index/product,order/_search
    • 查询所有索引、多个类型:GET /_all/product,order/_search
    • 匹配符模糊查询多个索引:GET /product_*/_search
    • 通过商品名搜索,并价格倒序:GET /product_index/product/_search?q=product_name:toothbrush&sort=price:desc
    • 通过商品名搜索(商品名称不等于搜索词,这里用了减号),并价格倒序:GET /product_index/product/_search?q=-product_name:toothbrush&sort=price:desc
  • 普通分页查询:
    • 查询所有结果(假设一共 5 条数据):GET /product_index/product/_search
    • 普通分页(查询第 1 页,每页 2 条数据。from 不是页数,是第几条数据开始):GET /product_index/product/_search?from=0&size=2
    • 普通分页(查询第 2 页,每页 2 条数据。from 不是页数,是第几条数据开始):GET /product_index/product/_search?from=2&size=2
    • 普通分页(查询第 3 页,每页 2 条数据。from 不是页数,是第几条数据开始):GET /product_index/product/_search?from=4&size=2
  • 深度分页(Deep Paging):
    • 深度分页的机制过程可以看这篇:使用scroll实现Elasticsearch数据遍历和深度分页
    • 简单讲就是,用 from&size 这样的方式查询大页数是很耗 CPU、IO、内存、网络,所以对于大分页都是被避免的。你可以看下 Google、百度、淘宝、京东等网站的搜索结果页面,一般最多 100 页。
    • 解决办法用:scroll,一般用于一批一批处理数据,比如做定时任务处理某一批大数据的时候。
    • scroll = 1m 表示用来缓存时间
    • “size”: 2 用来控制查询结果数量
GET /product_index/product/_search?scroll=1m
{
  "query": {
    "match_all": {}
  },
  "sort": [
    "_doc"
  ],
  "size": 2
}
  • 上面的查询结果会返回一个 scroll_id,下面的查询可以用 scroll_id 继续查询。其他条件不需要再加,scroll_id 已经有记录。因为只缓存 1m,必须在 1m 中查询,不然 scroll_id 会失效。
GET /_search/scroll
{
    "scroll": "1m", 
    "scroll_id" : "DnF1ZXJ5VGhlbkZldGNoBQAAAAAAABtNFlMxdDcyNlVaVHBtMDhkWDVIakZIVHcAAAAAAAAbUxYtQUdCeVVCY1RBSzI3eDFjVVlaZXpnAAAAAAAAG08WUzF0NzI2VVpUcG0wOGRYNUhqRkhUdwAAAAAAABtUFi1BR0J5VUJjVEFLMjd4MWNVWVplemcAAAAAAAAbThZTMXQ3MjZVWlRwbTA4ZFg1SGpGSFR3"
}
  • 更新整个 Document(需要带上所有属性,注意细节,这里改了 product_name):
    • 这种方式的本质是:软删除。把旧版本标记为 deleted,实际还没物理删除,该条数据的 _version 元数据其实会再 +1 的。如果你再 PUT 下还是这个 ID 数据进去,_version 还是会继续 +1。当 Elasticsearch 数据越来越多,会物理删除这些标记的数据。
PUT /product_index/product/3
{
    "product_name" : "星空太空 iphone7 plus 蓝紫色 6s 繁星 7plus 宇宙 se 原创保护苹果 5 包手机壳",
    "product_desc" :  "一说到星空,就有太多美好的记忆,美丽的浩瀚宇宙,有太多说不清的神秘之处,星空太美丽,太绚烂!",
    "price" :  36.00
}
  • 更新 Document 其中一个 field:(这种叫做:partial update,整个流程本质跟全量更新差不多,都是先把旧的标记为 deleted,新的数据创建。只是查询这个过程是在 Elasticsearch 内部,效率更高。)
POST /product_index/product/3/_update
{
  "doc": {
    "product_name": "星空太空 iphone7 蓝紫色 6s 繁星 iphone7 plus 宇宙 se 原创保护苹果 5 包手机壳"
  }
}
  • 删除 Document:
    • 通过 ID 删除:DELETE /product_index/product/3,本质:标记为 deleted,等数据越来越大的情况 Elasticsearch 才去物理删除。

Document 操作返回结果的几个重要参数讲解

  • 查询所有 Document:GET /product_index/product/_search,返回结果:
{
  "took": 3, ## 表示查询花费的时间(单位毫秒)
  "timed_out": false, ## 是否超时
  "_shards": {
    "total": 5,
    "successful": 5,
    "failed": 0
  },
  "hits": {
    "total": 3, ## 查询结果的数量
    "max_score": 1, ## 最大相关数分数是 1,每一条查询出来的结果数据,跟 search 条件越相关的,排名越靠前,分数也就越大。
    "hits": [ ## 查询结果的详细数据集合
      {
        "_index": "product_index",
        "_type": "product",
        "_id": "2",
        "_score": 1,
        "_source": {
          "product_name": "Braun toothbrush 2000 3D",
          "product_desc": "6 月 1 日 16 点秒杀,仅 329 元!限量 1000 支,抢完即止!带压力感应提醒,保护牙龈,高效清洁",
          "price": 499
        }
      },
      {
        "_index": "product_index",
        "_type": "product",
        "_id": "1",
        "_score": 1,
        "_source": {
          "product_name": "PHILIPS toothbrush HX6730/02",
          "product_desc": "【3?9 元,前 1000 名赠刷头,6 月 1 日 0 点火爆开抢,618 开门红巅峰 48 小时,抢先加入购物车】飞利浦畅销款,万千好评!深入净齿,智能美白!",
          "price": 399
        }
      },
      {
        "_index": "product_index",
        "_type": "product",
        "_id": "3",
        "_score": 1,
        "_source": {
          "product_name": "iphone7 shell",
          "product_desc": "一说到星空,就有太多美好的记忆,美丽的浩瀚宇宙,有太多说不清的神秘之处,星空太美丽,太绚烂!",
          "price": 36
        }
      }
    ]
  }
}

    
    

1.4 Elasticsearch DSL 常用语法介绍

作者: judasn  分类: Elasticsearch  发布时间: 2017-06-12 14:20 

课程环境


DSL 介绍

  • 这个才是实际最常用的方式,可以构建复杂的查询条件。
  • 不用一开始就想着怎样用 Java Client 端去调用 Elasticsearch 接口。DSL 会了,Client 的也只是用法问题而已。

DSL 语句的校验以及 score 计算原理

  • 对于复杂的查询,最好都先校验下,看有没有报错。
GET /product_index/product/_validate/query?explain
{
  "query": {
    "match": {
      "product_name": "toothbrush"
    }
  }
}

DSL 简单用法

  • 查询所有的商品:
GET /product_index/product/_search
{
  "query": {
    "match_all": {}
  }
}
  • 查询商品名称包含 toothbrush 的商品,同时按照价格降序排序:
GET /product_index/product/_search
{
  "query": {
    "match": {
      "product_name": "toothbrush"
    }
  },
  "sort": [
    {
      "price": "desc"
    }
  ]
}
  • 分页查询商品:
GET /product_index/product/_search
{
  "query": {
    "match_all": {}
  },
  "from": 0, ## 从第几个商品开始查,最开始是 0
  "size": 1  ## 要查几个结果
}
  • 指定查询结果字段(field)
GET /product_index/product/_search
{
  "query": {
    "match_all": {}
  },
  "_source": [
    "product_name",
    "price"
  ]
}
符号标识代表含义
gte大于或等于
gt大于
lte小于或等于
lt小于
  • 搜索商品名称包含 toothbrush,而且售价大于 400 元,小于 700 的商品
GET /product_index/product/_search
{
  "query": {
    "bool": {
      "must": {
        "match": {
          "product_name": "toothbrush"
        }
      },
      "filter": {
        "range": {
          "price": {
            "gt": 400,
            "lt": 700
          }
        }
      }
    }
  }
}
  • full-text search 全文检索,倒排索引
  • 索引中只要有任意一个匹配拆分后词就可以出现在结果中,只是匹配度越高的排越前面
  • 比如查询:PHILIPS toothbrush,会被拆分成两个单词:PHILIPS 和 toothbrush。只要索引中 product_name 中只要含有任意对应单词,都会在搜索结果中,只是如果有数据同时含有这两个单词,则排序在前面。
GET /product_index/product/_search
{
  "query": {
    "match": {
      "product_name": "PHILIPS toothbrush"
    }
  }
}
  • phrase search 短语搜索
  • 索引中必须同时匹配拆分后词就可以出现在结果中
  • 比如查询:PHILIPS toothbrush,会被拆分成两个单词:PHILIPS 和 toothbrush。索引中必须有同时有这两个单词的才会在结果中。
GET /product_index/product/_search
{
  "query": {
    "match_phrase": {
      "product_name": "PHILIPS toothbrush"
    }
  }
}
  • Highlight Search 高亮搜索
  • 给匹配拆分后的查询词增加高亮的 html 标签,比如这样的结果:"<em>PHILIPS</em> <em>toothbrush</em> HX6730/02"
GET /product_index/product/_search
{
  "query": {
    "match": {
      "product_name": "PHILIPS toothbrush"
    }
  },
  "highlight": {
    "fields": {
      "product_name": {}
    }
  }
}
  • range 用法,查询数值、时间区间:
GET /product_index/product/_search
{
  "query": {
    "range": {
      "price": {
        "gte": 30.00
      }
    }
  }
}
  • match 用法(与 term 进行对比):
  • 查询的字段内容是进行分词处理的,只要分词的单词结果中,在数据中有满足任意的分词结果都会被查询出来
GET /product_index/product/_search
{
  "query": {
    "match": {
      "product_name": "PHILIPS toothbrush"
    }
  }
}
  • match 还有一种情况,就是必须满足分词结果中所有的词,而不是像上面,任意一个就可以的。(这个常见,所以很重要
  • 看下面的 JSON 其实你也可以猜出来,其实上面的 JSON 和下面的 JSON 本质是:operator 的差别,上面是 or,下面是 and 关系。
GET /product_index/product/_search
{
  "query": {
    "match": {
      "product_name": {
        "query": "PHILIPS toothbrush",
        "operator": "and"
      }
     }
   }
}
  • match 还还有一种情况,就是必须满足分词结果中百分比的词,比如搜索词被分成这样子:java 程序员 书 推荐,这里就有 4 个词,假如要求 50% 命中其中两个词就返回,我们可以这样:
  • 当然,这种需求也可以用 must、must_not、should 匹配同一个字段进行组合来查询
GET /product_index/product/_search
{
  "query": {
    "match": {
      "product_name": {
        "query": "java 程序员 书 推荐",
        "minimum_should_match": "50%"
      }
    }
  }
}
  • multi_match 用法:
  • 查询 product_name 和 product_desc 字段中,只要有:toothbrush 关键字的就查询出来。
GET /product_index/product/_search
{
  "query": {
    "multi_match": {
      "query": "toothbrush",
      "fields": [
        "product_name",
        "product_desc"
      ]
    }
  }
}
  • multi_match 跨多个 field 查询,表示查询分词必须出现在相同字段中。
GET /product_index/product/_search
{
  "query": {
    "multi_match": {
      "query": "PHILIPS toothbrush",
      "type": "cross_fields",
      "operator": "and",
      "fields": [
        "product_name",
        "product_desc"
      ]
    }
  }
}
  • match_phrase 用法(短语搜索)(与 match 进行对比):
  • 对这个查询词不进行分词,必须完全匹配查询词才可以作为结果显示。
GET /product_index/product/_search
{
  "query": {
    "match_phrase": {
      "product_name": "PHILIPS toothbrush"
    }
  }
}
  • match_phrase + slop(与 match_phrase 进行对比):
  • 在说 slop 的用法之前,需要先说明原数据是:PHILIPS toothbrush HX6730/02,被分词后至少有:PHILIPS,toothbrush,HX6730 三个 term。
  • match_phrase 的用法我们上面说了,按理说查询的词必须完全匹配才能查询到,PHILIPS HX6730 很明显是不完全匹配的。
  • 但是有时候我们就是要这种不完全匹配,只要求他们尽可能靠谱,中间有几个单词是没啥问题的,那就可以用到 slop。slop = 2 表示中间如果间隔 2 个单词以内也算是匹配的结果()。
  • 其实也不能称作间隔,应该说是移位,查询的关键字分词后移动多少位可以跟 doc 内容匹配,移动的次数就是 slop。所以 HX6730 PHILIPS 其实也是可以匹配到 doc 的,只是 slop = 5 才行。
GET /product_index/product/_search
{
  "query": {
    "match_phrase": {
      "product_name" : {
          "query" : "PHILIPS HX6730",
          "slop" : 1
      }
    }
  }
}
  • match + match_phrase + slop 组合查询,使查询结果更加精准和结果更多
  • 但是 match_phrase 性能没有 match 好,所以一般需要先用 match 第一步进行过滤,然后在用 match_phrase 进行进一步匹配,并且重新打分,这里又用到了:rescore,window_size 表示对前 10 个进行重新打分
  • 下面第一个是未重新打分的,第二个是重新打分的
GET /product_index/product/_search
{
  "query": {
    "bool": {
      "must": {
        "match": {
          "product_name": {
            "query": "PHILIPS HX6730"
          }
        }
      },
      "should": {
        "match_phrase": {
          "product_name": {
            "query": "PHILIPS HX6730",
            "slop": 10
          }
        }
      }
    }
  }
}

GET /product_index/product/_search
{
  "query": {
    "match": {
      "product_name": "PHILIPS HX6730"
    }
  },
  "rescore": {
    "window_size": 10,
    "query": {
      "rescore_query": {
        "match_phrase": {
          "product_name": {
            "query": "PHILIPS HX6730",
            "slop": 10
          }
        }
      }
    }
  }
}
  • match_phrase_prefix 用法(不常用),一般用于类似 Google 搜索框,关键字输入推荐
  • max_expansions 用来限定最多匹配多少个 term,优化性能
  • 但是总体来说性能还是很差,因为还是会扫描整个倒排索引。推荐用 edge_ngram 做该功能
GET /product_index/product/_search
{
  "query": {
    "match_phrase_prefix": {
      "product_name": "PHILIPS HX",
      "slop": 5,
      "max_expansions": 20
    }
  }
}
  • term 用法(与 match 进行对比)(term 一般用在不分词字段上的,因为它是完全匹配查询,如果要查询的字段是分词字段就会被拆分成各种分词结果,和完全查询的内容就对应不上了。):
  • 所以自己设置 mapping 的时候有些不分词的时候就最好设置不分词。
  • 其实 Elasticsearch 5.X 之后给 text 类型的分词字段,又默认新增了一个子字段 keyword,这个字段的类型就是 keyword,是不分词的,默认保留 256 个字符。假设 product_name 是分词字段,那有一个 product_name.keyword 是不分词的字段,也可以用这个子字段来做完全匹配查询。
GET /product_index/product/_search
{
  "query": {
    "term": {
      "product_name": "PHILIPS toothbrush"
    }
  }
}

GET /product_index/product/_search
{
  "query": {
    "constant_score": {
      "filter":{
        "term": {
          "product_name": "PHILIPS toothbrush"
        }
      }
    }
  }
}
  • terms 用法,类似于数据库的 in
GET /product_index/product/_search
{
  "query": {
    "constant_score": {
      "filter": {
        "terms": {
          "product_name": [
            "toothbrush",
            "shell"
          ]
        }
      }
    }
  }
}

query 和 filter 差异

  • 只用 query:
GET /product_index/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "terms": {
            "product_name": [
              "PHILIPS",
              "toothbrush"
            ]
          }
        },
        {
          "range": {
            "price": {
              "gt": 12.00
            }
          }
        }
      ]
    }
  }
}
GET /product_index/product/_search
{
  "query": {
    "constant_score": {
      "filter": {
        "range": {
          "price": {
            "gte": 30.00
          }
        }
      }
    }
  }
}

多搜索条件组合查询(最常用)

  • bool 下包括:must(必须匹配,类似于数据库的 =),must_not(必须不匹配,类似于数据库的 !=),should(没有强制匹配,类似于数据库的 or),filter(过滤)
GET /product_index/product/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "product_name": "PHILIPS toothbrush"
          }
        }
      ],
      "should": [
        {
          "match": {
            "product_desc": "刷头"
          }
        }
      ],
      "must_not": [
        {
          "match": {
            "product_name": "HX6730"
          }
        }
      ],
      "filter": {
        "range": {
          "price": {
            "gte": 33.00
          }
        }
      }
    }
  }
}

GET /product_index/product/_search
{
  "query": {
    "bool": {
      "should": [
        {
          "term": {
            "product_name": "飞利浦"
          }
        },
        {
          "bool": {
            "must": [
              {
                "term": {
                  "product_desc": "刷头"
                },
                "term": {
                  "price": 30
                }
              }
            ]
          }
        }
      ]
    }
  }
}
  • should 有一个特殊性,如果组合查询中没有 must 条件,那么 should 中必须至少匹配一个。我们也还可以通过 minimum_should_match 来限制它匹配更多个。
GET /product_index/product/_search
{
  "query": {
    "bool": {
      "should": [
        {
          "match": {
            "product_name": "java"
          }
        },
        {
          "match": {
            "product_name": "程序员"
          }
        },
        {
          "match": {
            "product_name": "书"
          }
        },
        {
          "match": {
            "product_name": "推荐"
          }
        }
      ],
      "minimum_should_match": 3
    }
  }
}
  • 下面还用到自定义排序。
  • 排序最好别用到字符串字段上。因为字符串字段会进行分词,Elasticsearch 默认是拿分词后的某个词去进行排序,排序结果往往跟我们想象的不一样。解决这个办法是在设置 mapping 的时候,多个这个字段设置一个 fields raw,让这个不进行分词,然后查询排序的时候使用这个 raw,具体看这里:https://www.elastic.co/guide/cn/elasticsearch/guide/current/multi-fields.html
GET /product_index/product/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "product_name": "PHILIPS toothbrush"
          }
        }
      ],
      "should": [
        {
          "match": {
            "product_desc": "刷头"
          }
        }
      ],
      "filter": {
        "bool": {
          "must": [
            {
              "range": {
                "price": {
                  "gte": 33.00
                }
              }
            },
            {
              "range": {
                "price": {
                  "lte": 555.55
                }
              }
            }
          ],
          "must_not": [
            {
              "term": {
                "product_name": "HX6730"
              }
            }
          ]
        }
      }
    }
  },
  "sort": [
    {
      "price": {
        "order": "desc"
      }
    }
  ]
}
  • boost 用法(默认是 1)。在搜索精准度的控制上,还有一种需求,比如搜索:PHILIPS toothbrush,要比:Braun toothbrush 更加优先,我们可以这样:
GET /product_index/product/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "product_name": "toothbrush"
          }
        }
      ],
      "should": [
        {
          "match": {
            "product_name": {
              "query": "PHILIPS",
              "boost": 4
            }
          }
        },
        {
          "match": {
            "product_name": {
              "query": "Braun",
              "boost": 3
            }
          }
        }
      ]
    }
  }
}
  • dis_max 用法,也称作:best fields 策略。
  • 由于查询关键字是会被分词的,默认 query bool 查询多个字段的语法时候,每个字段匹配到一个或多个的时候分数比:一个字段匹配到查询分词的所有结果的分数来的大。但是对于我们来讲这样的不够精准的。所以我们希望查询字段中,匹配的关键字越多排序越靠前,而不是每个字段查询了一个分词就排前,我们可以使用 dis_max。
  • 但是使用 dis_max,一般还不够,建议再加上 tie_breaker。
  • tie_breaker 是一个小数值,在 0~1 之间用来将其他查询结果分数,乘以 tie_breaker 的值,然后再综合与 dis_max 最高分数的的分数一起进行计算。除了取 dis_max 的最高分以外,还会考虑其他的查询结果的分数。
  • 在 dis_max 基础上,为了增加精准,我们还可以加上:boost、minimum_should_match 等相关参数。其中 minimum_should_match 比较常用,因为查询字段的分词中如果只有一个分词查询上了这种结果基本是没啥用的。
  • 官网资料:https://www.elastic.co/guide/en/elasticsearch/guide/current/_best_fields.html
GET /product_index/product/_search
{
  "query": {
    "dis_max": {
      "queries": [
        {
          "match": {
            "product_name": "PHILIPS toothbrush"
          }
        },
        {
          "match": {
            "product_desc": "iphone shell"
          }
        }
      ],
      "tie_breaker": 0.2
    }
  }
}

GET /product_index/product/_search
{
  "query": {
    "dis_max": {
      "queries": [
        {
          "match": {
            "product_name": {
              "query": "PHILIPS toothbrush",
              "minimum_should_match": "50%",
              "boost": 3
            }
          }
        },
        {
          "match": {
            "product_desc": {
              "query": "iphone shell",
              "minimum_should_match": "50%""boost": 2
            }
          }
        }
      ],
      "tie_breaker": 0.3
    }
  }
}
  • prefix 前缀搜索(性能较差,扫描所有倒排索引)
  • 比如有一个不分词字段 product_name,分别有两个 doc 是:iphone-6,iphone-7。我们搜索 iphone 这个前缀关键字就可以搜索到结果
GET /product_index/product/_search
{
  "query": {
    "prefix": {
      "product_name": {
        "value": "iphone"
      }
    }
  }
}
  • 通配符搜索(性能较差,扫描所有倒排索引)
GET /product_index/product/_search
{
  "query": {
    "wildcard": {
      "product_name": {
        "value": "ipho*"
      }
    }
  }
}
  • 正则搜索(性能较差,扫描所有倒排索引)
GET /product_index/product/_search
{
  "query": {
    "regexp": {
      "product_name": "iphone[0-9].+"
    }
  }
}
  • fuzzy 纠错查询
  • 参数 fuzziness 默认是 2,表示最多可以纠错两次,但是这个值不能很大,不然没效果。一般 AUTO 是自动纠错。
  • 下面的关键字漏了一个字母 o。
GET /product_index/product/_search
{
  "query": {
    "match": {
      "product_name": {
        "query": "PHILIPS tothbrush",
        "fuzziness": "AUTO",
        "operator": "and"
      }
    }
  }
}

其他资料辅助

1.5 Elasticsearch DSL 聚合语法介绍

作者: judasn  分类: Elasticsearch  发布时间: 2017-06-12 14:22 

课程环境


数据准备

  • 先删除前面章节的索引:DELETE /product_index?pretty
  • 创建带有 Tags 的索引数据:
PUT /product_index/product/1
{
  "product_name": "PHILIPS toothbrush HX6730/02",
  "product_desc": "【3?9 元,前 1000 名赠刷头,6 月 1 日 0 点火爆开抢,618 开门红巅峰 48 小时,抢先加入购物车】飞利浦畅销款,万千好评!深入净齿,智能美白!",
  "price": 399.00,
  "tags": [
    "toothbrush",
    "PHILIPS"
  ]
}

PUT /product_index/product/2
{
  "product_name": "Braun toothbrush 2000 3D",
  "product_desc": "6 月 1 日 16 点秒杀,仅 329 元!限量 1000 支,抢完即止!带压力感应提醒,保护牙龈,高效清洁",
  "price": 499.00,
  "tags": [
    "toothbrush",
    "Braun"
  ]
}

PUT /product_index/product/3
{
  "product_name": "iphone7 shell",
  "product_desc": "一说到星空,就有太多美好的记忆,美丽的浩瀚宇宙,有太多说不清的神秘之处,星空太美丽,太绚烂!",
  "price": 36.00,
  "tags": [
    "iphone7",
    "phone",
    "shell"
  ]
}

简单分析案例

计算每个 tag 下的商品数量,自己取一个分组聚合结果名称:product_group_by_tags

GET /product_index/product/_search
{
  "aggs": {
    "product_group_by_tags": {
      "terms": {
        "field": "tags"
      }
    }
  }
}

GET /product_index/product/_search
{
  "size": 0, ## 不显示 hits 原数据,只显示聚合统计结果
  "aggs": {
    "product_group_by_tags": {
      "terms": {
        "field": "tags"
      }
    }
  }
}
  • 聚合得到的结果如下:
{
  "took": 9,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "failed": 0
  },
  "hits": {
    "total": 3,
    "max_score": 0,
    "hits": []
  },
  "aggregations": {
    "product_group_by_tags": {
      "doc_count_error_upper_bound": 0,
      "sum_other_doc_count": 0,
      "buckets": [
        {
          "key": "toothbrush",
          "doc_count": 2
        },
        {
          "key": "braun",
          "doc_count": 1
        },
        {
          "key": "iphone7",
          "doc_count": 1
        },
        {
          "key": "philips",
          "doc_count": 1
        },
        {
          "key": "phone",
          "doc_count": 1
        },
        {
          "key": "shell",
          "doc_count": 1
        }
      ]
    }
  }
}
  • 默认情况下你应该会报一个这样的错误

Fielddata is disabled on text fields by default. Set fielddata=true on [tags] in order to load fielddata in memory by uninverting the inverted index. Note that this can however use significant memory.

  • 解决办法,给 tags 字段的 fielddata 设置为 true:
PUT /product_index/_mapping/product
{
  "properties": {
    "tags": {
      "type": "text",
      "fielddata": true
    }
  }
}

搜索商品名称中包含 toothbrush 的商品结果中,计算每个 tag 下的商品数量

GET /product_index/product/_search
{
  "size": 0,
  "query": {
    "match": {
      "product_name": "toothbrush"
    }
  },
  "aggs": {
    "query_product_group_by_tags": {
      "terms": {
        "field": "tags"
      }
    }
  }
}

搜索商品名称中包含 toothbrush 的商品结果中,先用 tags 字段进行分组,然后再计算每组中商品价格的平均值

GET /product_index/product/_search
{
  "size": 0,
  "query": {
    "match": {
      "product_name": "toothbrush"
    }
  },
  "aggs": {
    "query_product_group_by_tags_and_avg": {
      "terms": {
        "field": "tags"
      },
      "aggs": {
        "product_price_avg_price": {
          "avg": {
            "field": "price"
          }
        }
      }
    }
  }
}

搜索商品名称中包含 toothbrush 的商品结果中,先用 tags 字段进行分组,然后再计算每组中商品价格的平均值,并按平均价格进行倒序

GET /product_index/product/_search
{
  "size": 0,
  "query": {
    "match": {
      "product_name": "toothbrush"
    }
  },
  "aggs": {
    "query_product_group_by_tags_and_avg": {
      "terms": {
        "field": "tags",
        "order": {
          "product_price_avg_price": "desc"
        }
      },
      "aggs": {
        "product_price_avg_price": {
          "avg": {
            "field": "price"
          }
        }
      }
    }
  }
}

搜索商品名称中包含 toothbrush 的商品结果中,按照指定的价格范围区间进行分组聚合,然后再按 tag 进行分组,最后再计算每组的平均价格

GET /product_index/product/_search
{
  "size": 0,
  "query": {
    "match": {
      "product_name": "toothbrush"
    }
  },
  "aggs": {
    "proudct_group_by_price": {
      "range": {
        "field": "price",
        "ranges": [
          {
            "from": 0,
            "to": 300
          },
          {
            "from": 300,
            "to": 400
          },
          {
            "from": 400,
            "to": 1000
          }
        ]
      },
      "aggs": {
        "product_group_by_tags": {
          "terms": {
            "field": "tags"
          },
          "aggs": {
            "product_average_price": {
              "avg": {
                "field": "price"
              }
            }
          }
        }
      }
    }
  }
}

    
    

1.6 Elasticsearch DSL 批量操作

作者: judasn  分类: Elasticsearch  发布时间: 2017-06-12 14:25 

课程环境


数据准备

  • 先删除前面章节的索引:DELETE /product_index?pretty
  • 创建带有 Tags 的索引数据:
PUT /product_index/product/1
{
  "product_name": "PHILIPS toothbrush HX6730/02",
  "product_desc": "【3?9 元,前 1000 名赠刷头,6 月 1 日 0 点火爆开抢,618 开门红巅峰 48 小时,抢先加入购物车】飞利浦畅销款,万千好评!深入净齿,智能美白!",
  "price": 399.00,
  "tags": [
    "toothbrush",
    "PHILIPS"
  ]
}

PUT /product_index/product/2
{
  "product_name": "Braun toothbrush 2000 3D",
  "product_desc": "6 月 1 日 16 点秒杀,仅 329 元!限量 1000 支,抢完即止!带压力感应提醒,保护牙龈,高效清洁",
  "price": 499.00,
  "tags": [
    "toothbrush",
    "Braun"
  ]
}

PUT /product_index/product2/1
{
  "product_name": "Braun toothbrush 2000 3D type2",
  "product_desc": "6 月 1 日 16 点秒杀,仅 329 元!限量 1000 支,抢完即止!带压力感应提醒,保护牙龈,高效清洁",
  "price": 499.00,
  "tags": [
    "toothbrush",
    "Braun"
  ]
}

PUT /product_index2/product2/2
{
  "product_name": "iphone7 shell",
  "product_desc": "一说到星空,就有太多美好的记忆,美丽的浩瀚宇宙,有太多说不清的神秘之处,星空太美丽,太绚烂!",
  "price": 36.00,
  "tags": [
    "iphone7",
    "phone",
    "shell"
  ]
}

批量操作的重要性

  • 批量操作最重要的地方就是:可以减少大量的网络开销和连接资源。

mget 批量查询

  • 根据 index 名称,type 名称,ID 进行查询(可以是不同 index、type、id):
GET /_mget
{
  "docs": [
    {
      "_index": "product_index",
      "_type": "product",
      "_id": 1
    },
    {
      "_index": "product_index2",
      "_type": "product2",
      "_id": 1
    }
  ]
}
  • 根据相同 index 名称,不同 type 名称,ID 进行查询:
GET /product_index/_mget
{
  "docs": [
    {
      "_type": "product",
      "_id": 1
    },
    {
      "_type": "product2",
      "_id": 1
    }
  ]
}
  • 根据相同 index 名称,相同 type 名称,ID 进行查询:
GET /product_index/product/_mget
{
  "ids": [1, 2]
}

bulk 批量增删改

  • 特别注意:bulk 对 JSON 有严格的要求,每个整体的 json 串不能换行,只能同一行。多个整体的 json 串和 json 串之间,必须要换行。可能 kibana Dev Tools 语法解析上会提示有错误,但是不用管它。
  • bulk 的格式要求官网资料:Bulk APICheaper in Bulk
  • 可以是多个 JSON 组合起来,按 JSON 顺序执行。
  • 顺序执行过程中,前面的操作失败,不会影响后续的操作。
  • 为了更加清晰表达,我这里不组合起来,拆分开来,但是你们可以考虑组合起来。
  • 删除操作:
POST /_bulk
{"delete": {"_index": "product_index","_type": "product","_id": "1"}}
  • 新增操作(product_name 开始是单独一行):
POST /_bulk
{ "create": { "_index": "product_index", "_type": "product", "_id": "333" } }
{ "product_name": "iphone7 shell2", "product_desc": "一说到星空,就有太多美好的记忆,美丽的浩瀚宇宙,有太多说不清的神秘之处,星空太美丽,太绚烂!", "price": 36.00, "tags": [ "iphone7", "phone", "shell" ] }
  • 更新操作(doc 属性开始是单独一行):
POST /_bulk
{"update":{"_index": "product_index","_type": "product","_id": "1"}}
{"doc":{"product_name": "iphone7 shell2222"}}

    
    

1.7 Elasticsearch 锁相关介绍

作者: judasn  分类: Elasticsearch  发布时间: 2017-06-12 14:36 

课程环境


数据准备

  • 先删除前面章节的索引:DELETE /product_index?pretty
  • 创建带有 Tags 的索引数据:
PUT /product_index/product/111
{
  "product_name": "PHILIPS toothbrush HX6730/02",
  "product_desc": "【3?9 元,前 1000 名赠刷头,6 月 1 日 0 点火爆开抢,618 开门红巅峰 48 小时,抢先加入购物车】飞利浦畅销款,万千好评!深入净齿,智能美白!",
  "price": 399.00,
  "tags": [
    "toothbrush",
    "PHILIPS"
  ]
}

PUT /product_index/product/222
{
  "product_name": "PHILIPS toothbrush HX6730/02",
  "product_desc": "【3?9 元,前 1000 名赠刷头,6 月 1 日 0 点火爆开抢,618 开门红巅峰 48 小时,抢先加入购物车】飞利浦畅销款,万千好评!深入净齿,智能美白!",
  "price": 399.00,
  "custom_version": 1,
  "tags": [
    "toothbrush",
    "PHILIPS"
  ]
}

乐观锁相关概念

乐观锁操作

Elasticsearch 默认的 _version 控制

  • 核心:
    • Elasticsearch 内部的 _version 只要等于当前数据存储的 _version 值即可修改成功。
  • 下面是全量更新的操作测试:

  • 客户端 1 执行:
PUT /product_index/product/111?version=1
{
  "product_name": "PHILIPS toothbrush HX6730/02 update1",
  "product_desc": "【3?9 元,前 1000 名赠刷头,6 月 1 日 0 点火爆开抢,618 开门红巅峰 48 小时,抢先加入购物车】飞利浦畅销款,万千好评!深入净齿,智能美白!",
  "price": 399.00,
  "tags": [
    "toothbrush",
    "PHILIPS"
  ]
}
  • 客户端 2 执行
PUT /product_index/product/111?version=1
{
  "product_name": "PHILIPS toothbrush HX6730/02 update2",
  "product_desc": "【3?9 元,前 1000 名赠刷头,6 月 1 日 0 点火爆开抢,618 开门红巅峰 48 小时,抢先加入购物车】飞利浦畅销款,万千好评!深入净齿,智能美白!",
  "price": 399.00,
  "tags": [
    "toothbrush",
    "PHILIPS"
  ]
}
  • 客户端 2 会执行失败,因为 version 已经不等于 1 了,这时候需要重新 GET 一次获取到最新的数据,然后再重新带上最新的 version 值进行更新。

Elasticsearch 自定义的 version 控制

  • 核心:

    • Elasticsearch 内部的 _version 只要大于当前数据存储的 _version 值即可(不能等于)。
  • 客户端 1 执行:

PUT /product_index/product/222?version=3&version_type=external
{
  "product_name": "PHILIPS toothbrush HX6730/02 update3",
  "product_desc": "【3?9 元,前 1000 名赠刷头,6 月 1 日 0 点火爆开抢,618 开门红巅峰 48 小时,抢先加入购物车】飞利浦畅销款,万千好评!深入净齿,智能美白!",
  "price": 399.00,
  "tags": [
    "toothbrush",
    "PHILIPS"
  ]
}
  • 客户端 2 执行:
PUT /product_index/product/222?version=5&version_type=external
{
  "product_name": "PHILIPS toothbrush HX6730/02 update5",
  "product_desc": "【3?9 元,前 1000 名赠刷头,6 月 1 日 0 点火爆开抢,618 开门红巅峰 48 小时,抢先加入购物车】飞利浦畅销款,万千好评!深入净齿,智能美白!",
  "price": 399.00,
  "tags": [
    "toothbrush",
    "PHILIPS"
  ]
}
  • partial update 更新方式内置乐观锁并发控制

1.8 Elasticsearch Java API

作者: judasn  分类: Elasticsearch  发布时间: 2017-06-12 14:37 

课程环境



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值