zoukankan      html  css  js  c++  java
  • Elasticsearch深入搜索之结构化搜索及JavaAPI的使用

    一、Es中创建索引

    1.创建索引:

    在之前的Es插件的安装和使用中说到创建索引自定义分词器和创建type,当时是分开写的,其实创建索引时也可以创建type,并指定分词器。

    PUT /my_index
    {
      "settings": {
            "analysis": {
                "analyzer": {
                    "ik_smart_pinyin": {
                        "type": "custom",
                        "tokenizer": "ik_smart",
                        "filter": ["my_pinyin", "word_delimiter"]
                    },
                    "ik_max_word_pinyin": {
                        "type": "custom",
                        "tokenizer": "ik_max_word",
                        "filter": ["my_pinyin", "word_delimiter"]
                    }
                },
                "filter": {
                    "my_pinyin": {
                        "type" : "pinyin",
                        "keep_separate_first_letter" : true,
                        "keep_full_pinyin" : true,
                        "keep_original" : true,
                        "limit_first_letter_length" : 16,
                        "lowercase" : true,
                        "remove_duplicated_term" : true 
                    }
                }
            }
      },
      "mappings": {
        "my_type":{
          "properties": {
            "id":{
              "type": "integer"
            },
            "name":{
              "type": "text",
              "analyzer": "ik_max_word_pinyin"
            },
            "age":{
              "type":"integer"
            }
          }
        }
      }
    }

    2.添加数据

    POST /my_index/my_type/_bulk
    { "index": { "_id":1}}
    { "id":1,"name": "张三","age":20}
    { "index": { "_id": 2}}
    { "id":2,"name": "张四","age":22}
    { "index": { "_id": 3}}
    { "id":3,"name": "张三李四王五","age":20}

    3.查看数据类型

    GET /my_index/my_type/_mapping
    
    结果:
    {
      "my_index": {
        "mappings": {
          "my_type": {
            "properties": {
              "age": {
                "type": "integer"
              },
              "id": {
                "type": "integer"
              },
              "name": {
                "type": "text",
                "analyzer": "ik_max_word_pinyin"
              }
            }
          }
        }
      }
    }

    二、结合JAVA(在这之前需在项目中配置好es,网上有好多例子可以参考)

    1.创建Es实体类

    package com.example.es_query_list.entity.es;

    import lombok.Getter;
    import lombok.Setter;
    import org.springframework.data.annotation.Id;
    import org.springframework.data.elasticsearch.annotations.Document;

    @Setter
    @Getter
    @Document(indexName = "my_index",type = "my_type")
    public class User {
    @Id
    private Integer id;
    private String name;
    private Integer age;
    }
     

    2.创建dao层

    package com.example.es_query_list.repository.es;
    
    import com.example.es_query_list.entity.es.User;
    import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
    
    public interface EsUserRepository extends ElasticsearchRepository<User,Integer> {
    }

    三、基本工作完成后,开始查询

    1.精确值查询

    查询非文本类型数据

    GET /my_index/my_type/_search
    {
      "query": {
        "term": {
          "age": {
            "value": "20"
          }
        }
      }
    }
    
    
    结果:
    {
      "took": 0,
      "timed_out": false,
      "_shards": {
        "total": 5,
        "successful": 5,
        "skipped": 0,
        "failed": 0
      },
      "hits": {
        "total": 2,
        "max_score": 1,
        "hits": [
          {
            "_index": "my_index",
            "_type": "my_type",
            "_id": "1",
            "_score": 1,
            "_source": {
              "name": "张三",
              "age": 20
            }
          },
          {
            "_index": "my_index",
            "_type": "my_type",
            "_id": "3",
            "_score": 1,
            "_source": {
              "name": "李四",
              "age": 20
            }
          }
        ]
      }
    }

    2.查询文本类型

    {
      "took": 0,
      "timed_out": false,
      "_shards": {
        "total": 5,
        "successful": 5,
        "skipped": 0,
        "failed": 0
      },
      "hits": {
        "total": 0,
        "max_score": null,
        "hits": []
      }
    }

    这时小伙伴们可能看到查询结果为空,为什么精确匹配却查不到我输入的准确值呢???之前说过咱们在创建type时,字段指定的分词器,如果输入未被分析出来的词是查不到结果的,让我们证明一下!!!!

    首先先查看一下咱们查询的词被分析成哪几部分

    GET my_index/_analyze
    {
      "text":"张三李四王五",
      "analyzer": "ik_max_word"
    }
    
    结果:
    {
      "tokens": [
        {
          "token": "张三李四",
          "start_offset": 0,
          "end_offset": 4,
          "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": "TYPE_CNUM",
          "position": 2
        },
        {
          "token": "李四",
          "start_offset": 2,
          "end_offset": 4,
          "type": "CN_WORD",
          "position": 3
        },
        {
          "token": "四",
          "start_offset": 3,
          "end_offset": 4,
          "type": "TYPE_CNUM",
          "position": 4
        },
        {
          "token": "王",
          "start_offset": 4,
          "end_offset": 5,
          "type": "CN_CHAR",
          "position": 5
        },
        {
          "token": "五",
          "start_offset": 5,
          "end_offset": 6,
          "type": "TYPE_CNUM",
          "position": 6
        }
      ]
    }

    结果说明,张三李四王五被没有被分析成张三李四王五,所以查询结果为空。

    解决方法:更新type中字段属性值,自定义一个映射指定类型为keyword类型,该类型在es中是指不会被分词器分析,也就是说这就是传说中的准确不能再准确的值了

    POST /my_index/_mapping/my_type
    {
      "properties": {
        "name": {
                "type": "text",
                "analyzer": "ik_max_word_pinyin",
                "fields": {
                  "keyword":{  //自定义映射名
                    "type": "keyword"
                  }
                }
              }
      }
    }

    设置好完成后,需将原有的数据删除在添加一遍,再次查询就能查到了

     public List<User> termQuery() {
            QueryBuilder queryBuilder = QueryBuilders.termQuery("age",20);
    //        QueryBuilder queryBuilder = QueryBuilders.termQuery("name.keyword","张三李四王五");
            SearchQuery searchQuery = new NativeSearchQueryBuilder()
                    .withIndices("my_index")
                    .withTypes("my_type")
                    .withQuery(queryBuilder)
                    .build();
    
            List<User> list = template.queryForList(searchQuery,User.class);
            return list;
        }

    四、组合过滤器

    布尔过滤器

    注意:官方文档有点问题,在5.X后,filtered 被bool代替了,The filtered query is replaced by the bool query。

    一个 bool 过滤器由三部分组成:

    {
       "bool" : {
          "must" :     [],
          "should" :   [],
          "must_not" : [],
       }
    }

     

    must所有的语句都 必须(must) 匹配,与 AND 等价。

    must_not所有的语句都 不能(must not) 匹配,与 NOT 等价。

    should至少有一个语句要匹配,与 OR 等价。

     就这么简单! 当我们需要多个过滤器时,只须将它们置入 bool 过滤器的不同部分即可。
    GET /my_index/my_type/_search
    {
      "query" : {
                "bool" : {
                  "should" : [
                     { "term" : {"age" : 20}}, 
                     { "term" : {"age" : 30}} 
                  ],
                  "must" : {
                     "term" : {"name.keyword" : "张三"} 
                  }
               }
          }
    }
     public List<User> boolQuery() {
            BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
            boolQueryBuilder.should(QueryBuilders.termQuery("age",20));
            boolQueryBuilder.should(QueryBuilders.termQuery("age",30));
            boolQueryBuilder.must(QueryBuilders.termQuery("name.keyword","张三"));
            SearchQuery searchQuery = new NativeSearchQueryBuilder()
                    .withIndices("my_index")
                    .withTypes("my_type")
                    .withQuery(boolQueryBuilder)
                    .build();
            List<User> list = template.queryForList(searchQuery,User.class);
            return list;
        }

    嵌套布尔过滤器

    尽管 bool 是一个复合的过滤器,可以接受多个子过滤器,需要注意的是 bool 过滤器本身仍然还只是一个过滤器。 这意味着我们可以将一个 bool 过滤器置于其他 bool 过滤器内部,这为我们提供了对任意复杂布尔逻辑进行处理的能力。

    GET /my_index/my_type/_search
    {
      "query" : {
                "bool" : {
                  "should" : [
                     { "term" : {"age" : 20}}, 
                     { "bool" : {
                       "must": [
                         {"term": {
                           "name.keyword": {
                             "value": "李四"
                           }
                         }}
                       ]
                     }} 
                  ]
               }
          }
    }
    
    结果:
    {
      "took": 0,
      "timed_out": false,
      "_shards": {
        "total": 5,
        "successful": 5,
        "skipped": 0,
        "failed": 0
      },
      "hits": {
        "total": 2,
        "max_score": 1,
        "hits": [
          {
            "_index": "my_index",
            "_type": "my_type",
            "_id": "1",
            "_score": 1,
            "_source": {
              "id": 1,
              "name": "张三",
              "age": 20
            }
          },
          {
            "_index": "my_index",
            "_type": "my_type",
            "_id": "3",
            "_score": 1,
            "_source": {
              "id": 3,
              "name": "张三李四王五",
              "age": 20
            }
          }
        ]
      }
    }

    因为 term 和 bool 过滤器是兄弟关系,他们都处于外层的布尔逻辑 should 的内部,返回的命中文档至少须匹配其中一个过滤器的条件。

    这两个 term 语句作为兄弟关系,同时处于 must 语句之中,所以返回的命中文档要必须都能同时匹配这两个条件。

     五、查找多个精确值

    GET my_index/my_type/_search
    {
      "query": {
        "terms": {
          "age": [
            20,
            22
          ]
        }
      }
    }
    
    结果:
    {
      "took": 0,
      "timed_out": false,
      "_shards": {
        "total": 5,
        "successful": 5,
        "skipped": 0,
        "failed": 0
      },
      "hits": {
        "total": 3,
        "max_score": 1,
        "hits": [
          {
            "_index": "my_index",
            "_type": "my_type",
            "_id": "2",
            "_score": 1,
            "_source": {
              "id": 2,
              "name": "张四",
              "age": 22
            }
          },
          {
            "_index": "my_index",
            "_type": "my_type",
            "_id": "1",
            "_score": 1,
            "_source": {
              "id": 1,
              "name": "张三",
              "age": 20
            }
          },
          {
            "_index": "my_index",
            "_type": "my_type",
            "_id": "3",
            "_score": 1,
            "_source": {
              "id": 3,
              "name": "张三李四王五",
              "age": 20
            }
          }
        ]
      }
    }

     一定要了解 term 和 terms 是 包含(contains) 操作,而非 等值(equals) (判断)。 

    TermsQueryBuilder termsQueryBuilder = QueryBuilders.termsQuery("age",list);

    六、范围查询

    1、数字范围查询

    GET my_index/my_type/_search
    {
      "query": {
        "range": {
          "age": {
            "gte": 10,
            "lte": 20
          }
        }
      }
    }
    
    结果:
    {
      "took": 0,
      "timed_out": false,
      "_shards": {
        "total": 5,
        "successful": 5,
        "skipped": 0,
        "failed": 0
      },
      "hits": {
        "total": 2,
        "max_score": 1,
        "hits": [
          {
            "_index": "my_index",
            "_type": "my_type",
            "_id": "1",
            "_score": 1,
            "_source": {
              "id": 1,
              "name": "张三",
              "age": 20
            }
          },
          {
            "_index": "my_index",
            "_type": "my_type",
            "_id": "3",
            "_score": 1,
            "_source": {
              "id": 3,
              "name": "张三李四王五",
              "age": 20
            }
          }
        ]
      }
    }

    注:gt(大于)   gte(大于等于)   lt(小于)  lte(小于等于)

    RangeQueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery("age").gte(10).lte(20);

    2.对于时间范围查询

    更新type,添加时间字段

    POST /my_index/_mapping/my_type
    {
    "properties": {
    "date":{
    "type":"date",
    "format":"yyyy-MM-dd"
    }
    }
    }

    添加数据:

    POST /my_index/my_type/_bulk
    { "index": { "_id":4}}
    { "id":4,"name": "赵六","age":20,"date":"2018-10-1"}
    { "index": { "_id": 5}}
    { "id":5,"name": "对七","age":22,"date":"2018-11-20"}
    { "index": { "_id": 6}}
    { "id":6,"name": "王八","age":20,"date":"2018-7-28"}

    查询:

    GET my_index/my_type/_search
    {
      "query": {
        "range": {
          "date": {
            "gte": "2018-10-20",
            "lte": "2018-11-29"
          }
        }
      }
    }
    
    结果:
    {
      "took": 0,
      "timed_out": false,
      "_shards": {
        "total": 5,
        "successful": 5,
        "skipped": 0,
        "failed": 0
      },
      "hits": {
        "total": 1,
        "max_score": 1,
        "hits": [
          {
            "_index": "my_index",
            "_type": "my_type",
            "_id": "5",
            "_score": 1,
            "_source": {
              "id": 5,
              "name": "对七",
              "age": 22,
              "date": "2018-11-20"
            }
          }
        ]
      }
    }
    RangeQueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery("date").gte("2018-10-20").lte("2018-11-29");

    七、处理null值

    1.添加数据

    POST /my_index/posts/_bulk
    { "index": { "_id": "1"              }}
    { "tags" : ["search"]                }  
    { "index": { "_id": "2"              }}
    { "tags" : ["search", "open_source"] }  
    { "index": { "_id": "3"              }}
    { "other_field" : "some data"        }  
    { "index": { "_id": "4"              }}
    { "tags" : null                      }  
    { "index": { "_id": "5"              }}
    { "tags" : ["search", null]          }  

    2.查询指定字段存在的数据

    GET /my_index/posts/_search
    {
        "query" : {
            "constant_score" : {    //不在去计算评分,默认都是1
                "filter" : {    
                    "exists" : { "field" : "tags" }
                }
            }
        }
    } 
    
    结果:
    {
      "took": 3,
      "timed_out": false,
      "_shards": {
        "total": 5,
        "successful": 5,
        "skipped": 0,
        "failed": 0
      },
      "hits": {
        "total": 3,
        "max_score": 1,
        "hits": [
          {
            "_index": "my_index",
            "_type": "posts",
            "_id": "5",
            "_score": 1,
            "_source": {
              "tags": [
                "search",
                null
              ]
            }
          },
          {
            "_index": "my_index",
            "_type": "posts",
            "_id": "2",
            "_score": 1,
            "_source": {
              "tags": [
                "search",
                "open_source"
              ]
            }
          },
          {
            "_index": "my_index",
            "_type": "posts",
            "_id": "1",
            "_score": 1,
            "_source": {
              "tags": [
                "search"
              ]
            }
          }
        ]
      }
    }
    BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
    boolQueryBuilder.filter(QueryBuilders.constantScoreQuery(QueryBuilders.existsQuery("tags")));

    3.查询指定字段缺失数据

    注:Filter Query Missing 已经从 ES 5 版本移除

    GET /my_index/posts/_search
    {
        "query" : {
            "bool": {
              "must_not": [
                {"constant_score": {
                  "filter": {
                    "exists": {
                    "field": "tags"
                  }}
                }}
              ]
            }
        }
    }
    
    
    查询结果:
    {
      "took": 1,
      "timed_out": false,
      "_shards": {
        "total": 5,
        "successful": 5,
        "skipped": 0,
        "failed": 0
      },
      "hits": {
        "total": 2,
        "max_score": 1,
        "hits": [
          {
            "_index": "my_index",
            "_type": "posts",
            "_id": "4",
            "_score": 1,
            "_source": {
              "tags": null
            }
          },
          {
            "_index": "my_index",
            "_type": "posts",
            "_id": "3",
            "_score": 1,
            "_source": {
              "other_field": "some data"
            }
          }
        ]
      }
    }

    注:处理null值,当字段内容为空时,将自定义将其当做为null值处理

    boolQueryBuilder.mustNot(QueryBuilders.boolQuery().filter(QueryBuilders.constantScoreQuery(QueryBuilders.existsQuery("tags"))));

     八、关于缓存

    1.核心

       其核心实际是采用一个 bitset 记录与过滤器匹配的文档。Elasticsearch 积极地把这些 bitset 缓存起来以备随后使用。一旦缓存成功,bitset 可以复用 任何 已使用过的相同过滤器,而无需再次计算整个过滤器。

    这些 bitsets 缓存是“智能”的:它们以增量方式更新。当我们索引新文档时,只需将那些新文档加入已有 bitset,而不是对整个缓存一遍又一遍的重复计算。和系统其他部分一样,过滤器是实时的,我们无需担心缓存过期问题。

     2.独立的过滤器缓存

      属于一个查询组件的 bitsets 是独立于它所属搜索请求其他部分的。这就意味着,一旦被缓存,一个查询可以被用作多个搜索请求。bitsets 并不依赖于它所存在的查询上下文。这样使得缓存可以加速查询中经常使用的部分,从而降低较少、易变的部分所带来的消耗。

    同样,如果单个请求重用相同的非评分查询,它缓存的 bitset 可以被单个搜索里的所有实例所重用。

    让我们看看下面例子中的查询,它查找满足以下任意一个条件的电子邮件:

    查询条件(例子):(1)在收件箱中,且没有被读过的  (2)不在 收件箱中,但被标注重要的

    GET /inbox/emails/_search
    {
      "query": {
          "constant_score": {
              "filter": {
                  "bool": {
                     "should": [
                        { "bool": {                                                  1
                              "must": [
                                 { "term": { "folder": "inbox" }}, 
                                 { "term": { "read": false }}
                              ]
                        }},
                        { "bool": {                              2    
                              "must_not": {
                                 "term": { "folder": "inbox" } 
                              },
                              "must": {
                                 "term": { "important": true }
                              }
                        }}
                     ]
                  }
                }
            }
        }
    }

     1和2共用的一个过滤器,所以使用同一个bitset

    尽管其中一个收件箱的条件是 must 语句,另一个是 must_not 语句,但他们两者是完全相同的。这意味着在第一个语句执行后, bitset 就会被计算然后缓存起来供另一个使用。当再次执行这个查询时,收件箱的这个过滤器已经被缓存了,所以两个语句都会使用已缓存的 bitset 。

    这点与查询表达式(query DSL)的可组合性结合得很好。它易被移动到表达式的任何地方,或者在同一查询中的多个位置复用。这不仅能方便开发者,而且对提升性能有直接的益处。

    3.自动缓存行为

    在 Elasticsearch 的较早版本中,默认的行为是缓存一切可以缓存的对象。这也通常意味着系统缓存 bitsets 太富侵略性,从而因为清理缓存带来性能压力。不仅如此,尽管很多过滤器都很容易被评价,但本质上是慢于缓存的(以及从缓存中复用)。缓存这些过滤器的意义不大,因为可以简单地再次执行过滤器。

    检查一个倒排是非常快的,然后绝大多数查询组件却很少使用它。例如 term 过滤字段 "user_id" :如果有上百万的用户,每个具体的用户 ID 出现的概率都很小。那么为这个过滤器缓存 bitsets 就不是很合算,因为缓存的结果很可能在重用之前就被剔除了。

    这种缓存的扰动对性能有着严重的影响。更严重的是,它让开发者难以区分有良好表现的缓存以及无用缓存。

    为了解决问题,Elasticsearch 会基于使用频次自动缓存查询。如果一个非评分查询在最近的 256 次查询中被使用过(次数取决于查询类型),那么这个查询就会作为缓存的候选。但是,并不是所有的片段都能保证缓存 bitset 。只有那些文档数量超过 10,000 (或超过总文档数量的 3% )才会缓存 bitset 。因为小的片段可以很快的进行搜索和合并,这里缓存的意义不大。

    一旦缓存了,非评分计算的 bitset 会一直驻留在缓存中直到它被剔除。剔除规则是基于 LRU 的:一旦缓存满了,最近最少使用的过滤器会被剔除。

    如果一个人没有梦想,和咸鱼有什么区别?
  • 相关阅读:
    链表(1) --- 单链表
    .Net性能调优-MemoryPool
    .Net性能调优-WeakReference
    .Net性能调优-ArrayPool
    .Net Core 中的选项Options
    .Net Core配置Configuration源码研究
    .Net中异步任务的取消和监控
    nginx 实现jira反向代理
    k8s-harbor拉取镜像权限问题-imagePullSecrets
    Studio 3T 重置试用日期
  • 原文地址:https://www.cnblogs.com/dashuaiguo/p/10016717.html
Copyright © 2011-2022 走看看