Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

ElasticSearch技术相关 #295

Open
Bpazy opened this issue Sep 26, 2023 · 4 comments
Open

ElasticSearch技术相关 #295

Bpazy opened this issue Sep 26, 2023 · 4 comments

Comments

@Bpazy
Copy link
Owner

Bpazy commented Sep 26, 2023

一、ElasticSearch概要

1.1 什么是ElasticSearch

ElasticSearch基于全文搜索引擎库Lucene开发,提供了一套RESTful风格的API接口,天生支持分布式特性,易于扩展,不论是结构化还是非结构化数据,都可以提供近实时搜索和分析功能。

1.2 核心概念

1.2.1 对比MySQL数据库

image

  • 关系型数据库中的数据库(DataBase),等价于ES中的索引(Index);
  • 一个数据库下面有N张表(Table),等价于1个索引Index下面有N多类型(Type);
  • 一个数据库表(Table)下的数据由多行(row)多列(column,属性)组成,等价于1个Type由多个文档(Document)和多Field组成;
  • 在一个关系型数据库里面,schema定义了表、每个表的字段,还有表和字段之间的关系。 与之对应的,在ES中:Mapping定义索引下的Type的字段处理规则,即索引如何建立、索引类型、是否保存原始索引JSON文档、是否压缩原始JSON文档、是否需要分词处理、如何进行分词处理等;
  • 在数据库中的增insert、删delete、改update、查search操作等价于ES中的增PUT/POST、删Delete、改UPDATE、查GET;

1.2.2 文档——Document

文档是索引中的基本单元,文档中采用JSON格式存储。一个文档包含多个Field字段,每个字段都有一个名称和值(键值对),Mapping映射中负责定义Field的类型及索引方式。文档类似于数据库表中的一条记录,记录中包含多个字段,ES文档中包含多个索引字段Field。
image

1.2.3 索引——Index

索引是具有相似特征的文档集合,创建索引时,ES要求索引名称必须为英文小写。索引作为动词,表示创建索引这个操作,作为名词时,表示索引。ES中的索引可以类比成数据库中的库,每一个数据库都会有不同的表,表中会定义Schema,而ES索引中可以包含不同的Type(ES6.X后只支持单个Type,ES7.X后将不支持Type),Type中会定义Mapping映射,用于指定不同字段的类型。

1.2.4 类型——Type

Type即Mapping Type,类似于数据库中的schema,在ES6.X之前版本支持多个Type,但是不利于维护,数据稀疏问题严重,6.X开始只支持单个Type。
image

1.2.5 集群——Cluster

集群由一个或者多个ES节点组成,整个集群负责存储数据、提供索引和搜索数据的功能。同一个网络中,具有相同cluster.name的节点会自动加入集群,因此不同环境的cluster.name需要进行区分,防止加入错误的集群。
image

测试环境ES集群,集群名称f6-search-local,包含了s1和s2两个节点。索引内的数据被拆分为5份,即5个分片Shard,每个分片有一个备份数据——副本Replica。

集群是否正常运行可以通过健康状态反映,通过GET /_cat/health?v查看status,包括green、yellow、red三种状态。

  • green:每个索引的primary shard和replica shard都是active状态的;
  • yellow:每个索引的primary shard都是active状态的,但是部分replica shard不是active状态,处于不可用的状态;
  • red:不是所有索引的primary shard都是active状态的,部分索引有数据丢失了;

1.2.6 节点——Node

每一个ES实例都是集群中的一个节点,节点名称默认是一串随机的UUID字符串,集群中通过node.name唯一标识一个节点,可以在elasticsearch.yml配置节点的名称。节点具体地负责索引数据的创建和存储,处理前端搜索请求。

根据节点的不同职责,可以划分为以下几种类型:

  • Master-eligible node和Mater node:每个节点默认都是一个Master-eligible node,通过选主流程,会有一个节点成为Master节点,可以通过配置禁止节点参与选主流程。每个节点都会保存集群状态信息,但是只有Master节点才能修改集群状态信息;
  • Date node:可以保存数据的节点,负责保存分片数据,在数据扩展上至关重要;
  • Coordinating node:协调节点,每个节点默认都是协调节点,负责接受客户端请求,然后将请求路由到合适的节点上,最终将结果汇总,返回给客户端;

1.2.7 主分片——Shards

单个节点,当存储大量文档时,很容易超过单机限制。为了解决这个问题,ElasticSearch采用分片来切割索引中的数据,在创建索引时可以指定分片数,默认为5分片。每个分片都是一个功能完备的Lucene索引,ES中的索引是一个逻辑索引。分片机制为ES提供了扩展性,多个分片提供了并行处理请求的能力,能有效提高系统吞吐量。

1.2.8 副本分片——Replicas

副本机制通过冗余分片数据(主分片和副本分片不允许分配在一个节点上),保证系统中某个节点宕机时,系统的高可用。同时,增加的副本分片,能够有效提高查询的性能和吞吐量。创建索引时,默认副本分片数为1。
image

1.2.9 近实时——Near Realtime

NRT即Near Realtime,ES是一个近实时的搜索平台,从索引一个文档到文档能够被搜索出来,有短暂延迟,通常为1s。

1.3.0 集群、节点、主分片和副本分片的关系

image

集群(Cluster)、节点(Node)、主分片(Primary Shard)和副本分片(Replica Shard)的关系如上图,上图集群由四个节点node1、node2、node3和node4组成,每一个节点中,分配了不同数量的分片,绿色节点为主分片(Primary Shard),白色节点为副本分片(Replica Shard)。
索引(Index)由一个或多个主分片组成,每个主分片负责存储部分索引数据。副本分片是主分片的拷贝,用于保证系统高可用,提升系统吞吐量。索引查询时,可以通过所有分片(包括主分片和副本分片)进行,而索引创建、删除、更新时,只能通过主分片进行,然后再同步到副本分片。为了保证高可用,主分片和副本分片不允许存储在同一个节点上,当主分片所在节点宕机时,副本分片会被提升为主分片,处理索引的写操作。

二、Mapping

2.1 什么是Mapping

Mapping映射用于定义索引中的Document,以及其中包含了哪些字段Field,这些字段是如何被索引(Index)和存储的(Stored)。

每个索引中包含一个Mapping Type,它决定了文档是如何索引的。Mapping Type中包含了两种类型的字段,一种是元字段,一种是用户自定义字段。元字段(Meta Fields)用于描述文档自身的信息,包括_index, _type, _id(这三个属性唯一指向一条文档)和_source(存储文档原始数据,用于数据查询和更新)。

已经存在的字段类型不允许进行修改,因为改变现有字段数据类型,意味着已有数据全部要失效。通常,都是重新创建一个索引,然后reindex数据,或者增加新的字段替代原有字段。新版本中,允许给字段设置别名来进行更新查询操作,字段类型更新时,可以添加新的字段,然后修改mapping中的别名类型,指向新添加的字段。
image

通常,一个mapping定义如下:

PUT part // index name
{
  "mappings": {
    "default": { // type name,7.X版本默认为_doc
      "dynamic": "false",
      "properties": { // 定义属性字段
        "partId": {
          "type": "keyword", // 字段类型
          "index": true, // 是否索引
          "doc_values": false // 是否存储doc到term数据结构,用于排序和聚合
        },
        "infoId": {
          "type": "keyword",
          "index": true
        },
        "name": {
          "type": "text",
          "doc_values": false,
          "index": true,
          "analyzer": "ik_max_word", // 指定分词分析器
          "search_analyzer": "ik_smart" // 指定查询分词分析器,不指定则使用analyzer
        },
        "creationTime": {
          "type": "date",
          "index": true,
          "doc_values": false,
          "format": "yyyy-MM-dd HH:mm:ss"
        },
        "modifiedTime": {
          "type": "date",
          "index": true,
          "doc_values": false,
          "format": "yyyy-MM-dd HH:mm:ss"
        }
      }
    }
  }
}

2.2 Mapping参数

dynamic参数——控制mapping动态添加字段。

true:mapping映射之外的新字段会自动检测类型,并添加到索引中;
false:mapping映射之外的新字段不会添加到索引中,但是会添加到source字段中存储;
strict:如果检测到新字段,会抛出异常;

type参数——定义字段类型,包含了以下数据类型,其中text类型也被称为全文本类型,会进行分词处理,然后再建立倒排索引。keyword、long、integer等类型都是精确类型,查询时会精确匹配。
image

index参数——决定字段是否索引,默认为true,text类型字段使用倒排索引存储,数值和地理类型使用BKD Tree存储。

true:建立索引,可供用户搜索;
false:不建立索引,无法搜索该字段;

doc_values参数——用于字段排序和聚合操作,默认为true,不支持text字段。

true:存储文档到词的映射;
false:不存储;

fields参数——同一个字段使用不同的方式进行索引,从而满足不同的使用场景。例如要对customCode字段进行排序,但是因为搜索时需要支持模糊查询,已经进行了ngram分词处理,直接对分词结果进行排序,毫无意义。这时可以通过fileds可以定义一个keyword类型,对原字段内容进行排序。

"customCode": {
  "type": "text",
  "index": true,
  "doc_values": false,
  "analyzer": "ngram_analyzer",
  "fields": {
    "raw": {
      "type": "keyword"
    }
  }
}

三、Analysis分析

3.1 什么是Analysis

Analysis is the process of converting text, like the body of any email, into tokens or terms which are added to the inverted index for searching. Analysis is performed by an analyzer which can be either a built-in analyzer or a custom analyzer defined per index.
Analysis分析是指将文本(如邮件正文)转换成token或term的过程,token或term会被添加到倒排索引中用于搜索。分析是通过每个索引中定义的内置分析器Analyzer或用户自定义分析器实现。

Analysis分析过程是通过分析器Analyzer实现的,分析器Analyzer负责将文本解析成term,进而存储到倒排索引中。搜索时会采用同样的分析器,对原始的搜索文本进行分析,然后匹配到相应的文档。ES中提供了内置的分析器,也提供了自定义分析器的方式,可以对现有分析器中的功能进行组合。
分析器analyzer包好了三个模块:

  • charfilter:负责对原始的文本进行字符过滤,可以有多个,按照顺序执行;
  • tokenizer:负责将过滤过的文本进行分词,得到一个个的语汇单元token,同时负责记录这些token在原文本中的位置。只能包含一个分词器tokenizer;
  • filter:接受处理好的token,进行过滤转换处理,例如:小写转换、停用词过滤、转拼音等,最后得到一个个term。可以包含多个,按照顺序执行;
    image

3.2 常用Analyzer分析器及效果

3.2.1 Standard分析器

standard分析器是ES的默认分析器,根据字符边界(英文根据空格,汉字根据单字)拆分,对于类似英语的语言效果较好,对汉语分词效果比较差,测试效果如下:

POST part/_analyze
{
  "analyzer": "standard",
  "text": ["宝马X5 2018"]
}
//结果
{
  "tokens" : [
    {
      "token" : "",
      "start_offset" : 0,
      "end_offset" : 1,
      "type" : "<IDEOGRAPHIC>",
      "position" : 0
    },
    {
      "token" : "",
      "start_offset" : 1,
      "end_offset" : 2,
      "type" : "<IDEOGRAPHIC>",
      "position" : 1
    },
    {
      "token" : "x5",
      "start_offset" : 2,
      "end_offset" : 4,
      "type" : "<ALPHANUM>",
      "position" : 2
    },
    {
      "token" : "2018",
      "start_offset" : 5,
      "end_offset" : 9,
      "type" : "<NUM>",
      "position" : 3
    }
  ]
}

3.2.2 IK分析器

IK分词器是一款著名的中文分词器,用户可以配置词库来达到很好的分词效果,ES的IK插件提供了 ik_smartik_max_word 分析器,同时也提供了 ik_smartik_max_word的分词器Tokenizer,用户可以引用,然后自定义自己的Analyzer分析器。

ik_smart分析器效果:

POST part/_analyze
{
  "analyzer": "ik_smart",
  "text": ["宝马X5 2018"]
}
//结果
{
  "tokens" : [
    {
      "token" : "宝马",
      "start_offset" : 0,
      "end_offset" : 2,
      "type" : "CN_WORD",
      "position" : 0
    },
    {
      "token" : "x5",
      "start_offset" : 2,
      "end_offset" : 4,
      "type" : "LETTER",
      "position" : 1
    },
    {
      "token" : "2018",
      "start_offset" : 5,
      "end_offset" : 9,
      "type" : "ARABIC",
      "position" : 2
    }
  ]
}

ik_max_word分析器效果:

POST part/_analyze
{
  "analyzer": "ik_max_word",
  "text": ["宝马X5 2018"]
}
//结果
{
  "tokens" : [
    {
      "token" : "宝马",
      "start_offset" : 0,
      "end_offset" : 2,
      "type" : "CN_WORD",
      "position" : 0
    },
    {
      "token" : "",
      "start_offset" : 1,
      "end_offset" : 2,
      "type" : "CN_WORD",
      "position" : 1
    },
    {
      "token" : "x5",
      "start_offset" : 2,
      "end_offset" : 4,
      "type" : "LETTER",
      "position" : 2
    },
    {
      "token" : "x",
      "start_offset" : 2,
      "end_offset" : 3,
      "type" : "ENGLISH",
      "position" : 3
    },
    {
      "token" : "5",
      "start_offset" : 3,
      "end_offset" : 4,
      "type" : "CN_WORD",
      "position" : 4
    },
    {
      "token" : "2018",
      "start_offset" : 5,
      "end_offset" : 9,
      "type" : "ARABIC",
      "position" : 5
    },
    {
      "token" : "0",
      "start_offset" : 6,
      "end_offset" : 7,
      "type" : "CN_WORD",
      "position" : 6
    },
    {
      "token" : "8",
      "start_offset" : 8,
      "end_offset" : 9,
      "type" : "CN_WORD",
      "position" : 7
    }
  ]
}

3.2.3 pinyin分析器

pinyin分析器插件提供了 pinyin分析器、pinyin 分词器以及一个语汇单元过滤器token-filterpinyin

pinyin分析器效果如下:

POST part/_analyze
{
  "analyzer": "pinyin",
  "text": ["宝马"]
}
//结果
{
  "tokens" : [
    {
      "token" : "bao",
      "start_offset" : 0,
      "end_offset" : 0,
      "type" : "word",
      "position" : 0
    },
    {
      "token" : "ma",
      "start_offset" : 0,
      "end_offset" : 0,
      "type" : "word",
      "position" : 1
    },
    {
      "token" : "bm",
      "start_offset" : 0,
      "end_offset" : 0,
      "type" : "word",
      "position" : 1
    }
  ]
}

3.2.4 自定义分析器

实际使用中,对于分词的场景多种多样,有的需要使用拼音分词品牌,有的需要使用ik中文分词匹配,有的字段需要实现类似于like的模糊搜索,所以我们需要自定义分析器来满足各种场景。
如下的语句中,定义了三个自定义分析器:

  1. pinyin_analyzer——使用ik_smart分词,然后转换成拼音,包含首字母拼音和全拼;
  2. ngram_analyzer——使用ngram分词,然后进行小写转化;
  3. ik_ngram_analyzer——先使用ik_smart分词,然后再进行ngram分词,然后进行小写转换;
PUT part
{
  "settings": {
    "index": {
      "translog": {
        "sync_interval": "5s",
        "durability": "async"
      },
      "refresh_interval": "5s",
      "analysis": {
        "analyzer": {
          "pinyin_analyzer": {
            "tokenizer": "ik_smart",
            "filter": [
              "pinyin_filter"
            ]
          },
          "ngram_analyzer": {
            "tokenizer": "ngram_tokenizer",
            "filter": [
              "lowercase"
            ]
          },
          "ik_ngram_analyzer": {
            "tokenizer": "ik_smart",
            "filter": [
              "ngram_filter",
              "lowercase"
            ]
          }
        },
        "tokenizer": {
          "ngram_tokenizer": {
            "type": "ngram",
            "min_gram": 1,
            "max_gram": 25,
            "token_chars": [
              "letter",
              "digit",
              "punctuation"
            ]
          },
          "edge_ngram_tokenizer": {
            "type": "edge_ngram",
            "min_gram": 1,
            "max_gram": 25,
            "token_chars": [
              "letter",
              "digit",
              "punctuation"
            ]
          }
        },
        "filter": {
          "pinyin_filter": {
            "type": "pinyin",
            "keep_first_letter": true,
            "keep_full_pinyin": true,
            "keep_joined_full_pinyin": true,
            "keep_none_chinese_in_joined_full_pinyin": true,
            "limit_first_letter_length": 50,
            "lowercase": true,
            "remove_duplicated_term": true
          },
          "ngram_filter": {
            "type": "nGram",
            "min_gram": 1,
            "max_gram": 25
          }
        }
      }
    }
  }
}

四、常用API

4.1 index api

添加文档到索引,PUT操作为创建文档,默认操作类似于put-if-absent,不存在时则创建,存在时则更新(文档覆盖更新):

//创建文档 index type id
PUT twitter/_doc/1
{
    "user" : "kimchy",
    "post_date" : "2009-11-15T14:12:12",
    "message" : "trying out Elasticsearch"
}
//返回结果
{
  "_index" : "twitter",
  "_type" : "_doc",
  "_id" : "1",
  "_version" : 1, //文档版本号
  "result" : "created", //新增文档
  "_shards" : {
    "total" : 2,
    "successful" : 1,
    "failed" : 0
  },
  "_seq_no" : 0,
  "_primary_term" : 1
}
//再执行一次,查看返回值
{
  "_index" : "twitter",
  "_type" : "_doc",
  "_id" : "1",
  "_version" : 2, //版本号变为2
  "result" : "updated", //更新历史文档
  "_shards" : {
    "total" : 2,
    "successful" : 2,
    "failed" : 0
  },
  "_seq_no" : 1,
  "_primary_term" : 1
}
//再更新一次,删除一个字段
PUT twitter/_doc/1
{
    "user" : "kimchy",
    "post_date" : "2009-11-15T14:12:12"
}
//查看文档
GET twitter/_doc/1
//返回结果
{
  "_index" : "twitter",
  "_type" : "_doc",
  "_id" : "1",
  "_version" : 3,
  "found" : true,
  "_source" : {
    "user" : "kimchy",
    "post_date" : "2009-11-15T14:12:12"
  }
}

如果想实现只创建文档,存在了不覆盖更新,可以使用op_type=create参数进行控制,已存在文档则会报错。

PUT twitter/_doc/1?op_type=create
{
    "user" : "kimchy",
    "post_date" : "2009-11-15T14:12:12"
}
//或
PUT twitter/_doc/1/_create
{
    "user" : "kimchy",
    "post_date" : "2009-11-15T14:12:12"
}
//返回结果
{
  "error": {
    "root_cause": [
      {
        "type": "version_conflict_engine_exception",
        "reason": "[_doc][1]: version conflict, document already exists (current version [4])",
        "index_uuid": "dNh_bNeUS72uA57_YJb4QQ",
        "shard": "3",
        "index": "twitter"
      }
    ],
    "type": "version_conflict_engine_exception",
    "reason": "[_doc][1]: version conflict, document already exists (current version [4])",
    "index_uuid": "dNh_bNeUS72uA57_YJb4QQ",
    "shard": "3",
    "index": "twitter"
  },
  "status": 409
}

上面的示例中,我们都指定了index、type和id,其实ES也可以实现自动生成ID的操作(使用POST请求),但是实际中会使用业务ID:

POST twitter/_doc/
{
    "user" : "kimchy",
    "post_date" : "2009-11-15T14:12:12",
    "message" : "trying out Elasticsearch"
}
//返回结果
{
  "_index" : "twitter",
  "_type" : "_doc",
  "_id" : "2qEYGG8BUSb_03vD5B-t",
  "_version" : 1,
  "result" : "created",
  "_shards" : {
    "total" : 2,
    "successful" : 2,
    "failed" : 0
  },
  "_seq_no" : 4,
  "_primary_term" : 1
}

4.2 delete api

删除操作比较简单,使用DELETE请求,指定好index、type和id即可(文档删除后不会立刻被清除,ES会在合适的时机执行段合并操作,合并之后才会真正删除文档:

DELETE /twitter/_doc/1
//返回结果
{
  "_index" : "twitter",
  "_type" : "_doc",
  "_id" : "1",
  "_version" : 8,
  "result" : "deleted",
  "_shards" : {
    "total" : 2,
    "successful" : 2,
    "failed" : 0
  },
  "_seq_no" : 8,
  "_primary_term" : 1
}
//这个时候再创建一下这个文档
PUT twitter/_doc/1
{
    "user" : "kimchy",
    "post_date" : "2009-11-15T14:12:12",
    "message" : "trying out Elasticsearch"
}
//观察返回结果
{
  "_index" : "twitter",
  "_type" : "_doc",
  "_id" : "1",
  "_version" : 11, //version在原有版本基础上继续增加
  "result" : "created",
  "_shards" : {
    "total" : 2,
    "successful" : 2,
    "failed" : 0
  },
  "_seq_no" : 11,
  "_primary_term" : 1
}
//再次删除,然后执行段合并
DELETE /twitter/_doc/1
POST /twitter/_forcemerge
//再次添加
PUT twitter/_doc/1
{
    "user" : "kimchy",
    "post_date" : "2009-11-15T14:12:12",
    "message" : "trying out Elasticsearch"
}
//查看返回结果
{
  "_index" : "twitter",
  "_type" : "_doc",
  "_id" : "1",
  "_version" : 1,
  "result" : "created",
  "_shards" : {
    "total" : 2,
    "successful" : 2,
    "failed" : 0
  },
  "_seq_no" : 13,
  "_primary_term" : 1
}

4.3 update api

使用index api可以更新文档,但是是覆盖更新,实际使用场景中,需要更新用户传入的更新字段,其他字段保持不变,这个时候就需要使用update api。
第一种方式是脚本更新,更新之前文档中的用户字段(painless脚本功能强大,可以实现一些动态功能,但是注意性能消耗,不推荐使用):

POST twitter/_doc/1/_update
{
    "script" : {
        "source": "ctx._source.user = params.user", //表达式
        "lang": "painless",
        "params" : {
            "user" : "wade" //参数
        }
    }
}
GET /twitter/_doc/1
{
  "_index" : "twitter",
  "_type" : "_doc",
  "_id" : "1",
  "_version" : 2,
  "found" : true,
  "_source" : {
    "user" : "wade",
    "post_date" : "2009-11-15T14:12:12",
    "message" : "trying out Elasticsearch"
  }
}

第二种方式是使用局部更新:

POST twitter/_doc/1/_update
{
    "doc" : {
        "user" : "james"
    }
}
//查看结果
{
  "_index" : "twitter",
  "_type" : "_doc",
  "_id" : "1",
  "_version" : 3,
  "found" : true,
  "_source" : {
    "user" : "james",
    "post_date" : "2009-11-15T14:12:12",
    "message" : "trying out Elasticsearch"
  }
}

但是有时候想要实现upsert操作——存在则更新部分字段,不存在则插入,则需要添加doc_as_upsert参数:

POST twitter/_doc/1/_update
{
    "doc" : {
        "user" : "kobe"
    },
    "doc_as_upsert" : true
}
//更新完结果
{
  "_index" : "twitter",
  "_type" : "_doc",
  "_id" : "1",
  "_version" : 4,
  "found" : true,
  "_source" : {
    "user" : "kobe",
    "post_date" : "2009-11-15T14:12:12",
    "message" : "trying out Elasticsearch"
  }
}
//删除再次更新,然后查看
DELETE /twitter/_doc/1

{
  "_index" : "twitter",
  "_type" : "_doc",
  "_id" : "1",
  "_version" : 6,
  "found" : true,
  "_source" : {
    "user" : "kobe"
  }
}

4.4 get api

get api用于根据index、type、id查询某个文档,使用较少:

GET twitter/_doc/1
{
  "_index" : "twitter",
  "_type" : "_doc",
  "_id" : "1",
  "_version" : 6,
  "found" : true,
  "_source" : {
    "user" : "kobe"
  }
}

4.5 bulk api

bulk api支持批量执行多个操作,每个操作包含两行,格式如下,第一行声明操作类型,指定对应的元数据,第二行传入请求体,delete操作没有请求体,格式如下:

POST /_bulk
{ action: { metadata }}\n
{ request body        }\n
{ action: { metadata }}\n
{ request body        }\n
...

action代表操作类型,包含以下几种:

  • create:新增操作,和PUT twitter/_doc/1?op_type=create或PUT twitter/_doc/1/_create作用相同,强制创建,不会更新,存在文档则会报错;
  • delete:删除操作;
  • index:和普通的PUT操作相同,不存在则新增,存在则全量替换;
  • update:局部更新操作;

bulk操作中,任意一个操作失败,是不会影响其他的操作的,但是在返回结果里,会告诉你异常日志。

bulk request会加载到内存里,如果太大的话,性能反而会下降,因此需要反复尝试一个最佳的bulk size。一般从1000~5000条数据开始,尝试逐渐增加。另外,如果看大小的话,最好是在5~15MB之间。

// 批量创建文档
POST /_bulk
{"index": { "_index": "twitter", "_type": "_doc", "_id": "1" }}
{"user" : "kobe", "post_date" : "2009-11-15T14:12:12", "message" : "trying out Elasticsearch"}
{"index": { "_index": "twitter", "_type": "_doc", "_id": "2" }}
{"user" : "wade", "post_date" : "2012-11-15T14:12:12", "message" : "trying out Java"}
{"index": { "_index": "twitter", "_type": "_doc", "_id": "3" }}
{"user" : "james", "post_date" : "2019-11-15T14:12:12", "message" : "trying out MySQL"}
{"index": { "_index": "twitter", "_type": "_doc", "_id": "4" }}
{"user" : "curry", "post_date" : "2016-11-15T14:12:12", "message" : "trying out C#"}

// 新增、删除、更新文档
POST /twitter/_doc/_bulk
{"create": { "_id": "1" }}
{"user" : "kobe", "post_date" : "2009-11-15T14:12:12", "message" : "trying out Elasticsearch"}
{"delete": { "_id": "2" }}
{"index": { "_id": "3" }}
{"user" : "lebron james", "post_date" : "2019-11-15T14:12:12", "message" : "trying out MySQL"}
{"update": { "_index": "twitter", "_type": "_doc", "_id": "4" }}
{"doc" : {"user" : "seth curry"}, "doc_as_upsert" : true}
//返回结果
{
  "took" : 103,
  "errors" : true,
  "items" : [
    {
      "create" : {
        "_index" : "twitter",
        "_type" : "_doc",
        "_id" : "1",
        "status" : 409,
        "error" : {
          "type" : "version_conflict_engine_exception",
          "reason" : "[_doc][1]: version conflict, document already exists (current version [8])",
          "index_uuid" : "dNh_bNeUS72uA57_YJb4QQ",
          "shard" : "3",
          "index" : "twitter"
        }
      }
    },
    {
      "delete" : {
        "_index" : "twitter",
        "_type" : "_doc",
        "_id" : "2",
        "_version" : 3,
        "result" : "deleted",
        "_shards" : {
          "total" : 2,
          "successful" : 2,
          "failed" : 0
        },
        "_seq_no" : 3,
        "_primary_term" : 1,
        "status" : 200
      }
    },
    {
      "index" : {
        "_index" : "twitter",
        "_type" : "_doc",
        "_id" : "3",
        "_version" : 3,
        "result" : "updated",
        "_shards" : {
          "total" : 2,
          "successful" : 2,
          "failed" : 0
        },
        "_seq_no" : 2,
        "_primary_term" : 1,
        "status" : 200
      }
    },
    {
      "update" : {
        "_index" : "twitter",
        "_type" : "_doc",
        "_id" : "4",
        "_version" : 2,
        "result" : "updated",
        "_shards" : {
          "total" : 2,
          "successful" : 2,
          "failed" : 0
        },
        "_seq_no" : 4,
        "_primary_term" : 1,
        "status" : 200
      }
    }
  ]
}
//查看更新后数据
POST twitter/_doc/_search
{
  "query": {
    "match_all": {}
  }
}
//结果
{
  "took" : 1,
  "timed_out" : false,
  "_shards" : {
    "total" : 5,
    "successful" : 5,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : 3,
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "twitter",
        "_type" : "_doc",
        "_id" : "4",
        "_score" : 1.0,
        "_source" : {
          "user" : "seth curry",
          "post_date" : "2016-11-15T14:12:12",
          "message" : "trying out C#"
        }
      },
      {
        "_index" : "twitter",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 1.0,
        "_source" : {
          "user" : "kobe",
          "post_date" : "2009-11-15T14:12:12",
          "message" : "trying out Elasticsearch"
        }
      },
      {
        "_index" : "twitter",
        "_type" : "_doc",
        "_id" : "3",
        "_score" : 1.0,
        "_source" : {
          "user" : "lebron james",
          "post_date" : "2019-11-15T14:12:12",
          "message" : "trying out MySQL"
        }
      }
    ]
  }
}

4.6 search api

search api的结构通常如下,是一个match查询语句,既可以匹配全文搜索,也可以匹配精确查询,取决于字段的定义是text还是其他精确字段:

POST part/default/_search
{
  "query": {
    "match": {
      "name": "机油"
    }
  }
}

查询语句的含义很容易理解,匹配name字段,匹配的关键词是机油。
查询时会根据字段定义的分析器,进行分词,然后匹配ES倒排索引中的数据,计算文档匹配度,返回结果默认根据匹配程度排序。

复合查询语句:

POST part/default/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "term": {
            "idOwnOrg": {
              "value": "25965086392717358"
            }
          }
        },
        {
          "match": {
            "name": "机油"
          }
        }
      ],
      "should": [
        {
          "term": {
            "isDel": {
              "value": "0"
            }
          }
        }
      ],
      "minimum_should_match": 1
    }
  }
}

复合语句查询使用bool语句,可以包含must、must_not、should等子语句,minimum_should_match用于控制should语句中最小需要满足条件的个数。

multi_match查询:

POST part/default/_search
{
  "query": {
    "multi_match": {
        "query": "宝马X5",
        "fields": ["name", "name.pinyin", "name.fuzzy", "brand", "brand.pinyin", "brand.fuzzy", "spec", "customCode", "supplierCode", "oe", "supplierCodeNoBlank", "oeNoBlank", "applyModel"],
        "type": "cross_fields",
        "operator": "and",
        "analyzer": "ik_smart"
    }
  }
}

multi_match一次可以匹配多个字段,type属性有六个取值,其中best_fields、most_fields、cross_fields比较常用。

best_fields是默认类型,表示使用匹配得分最高的字段分值作为文档分值。
most_fields表示使用匹配字段最多的组合分值作为文档分值。
cross_fields则类似于将所有查询字段组合成一个大字段进行匹配,这种方式要求各个字段在搜索时使用相同的analyzer。

term查询单个值;terms查询多个值

POST part/default/_search
{
  "query": {
    "term": {
      "partId": {
        "value": "18978119"
      }
    }
  }
}

POST part/default/_search
{
  "query": {
    "terms": {
      "partId": [
        "18978119",
        "19045431"
      ]
    }
  }
}

range查询

POST /stock_search_fifo/_search
{
  "query": {
    "range": {
      "creationTime": {
        "gte": "2020-06-28 00:00:00",
        "lte": "2020-06-28 23:00:00"
      }
    }
  }
}

聚合查询
image

POST /stock_search_fifo/_search
{
  "from": 0,
  "size": 0,
  "aggs": {
    "sumStockNumber": {
      "sum": {
        "field": "stockNumber"
      }
    }
  }
}

// 结果返回
{
  "took" : 292,
  "timed_out" : false,
  "_shards" : {
    "total" : 6,
    "successful" : 6,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 10000,
      "relation" : "gte"
    },
    "max_score" : null,
    "hits" : [ ]
  },
  "aggregations" : {
    "sumStockNumber" : {
      "value" : 1.36167644723E9
    }
  }
}

按字段进行排序【支持多字段排序】

POST /stock_search_fifo_alias_test/_search
{
  "from": 0,
  "size": 10,
  "sort": [
    {
      "creationTime": {
        "missing": "_last",
        "order": "desc"
      },
      "stockNumber": {
        "missing": "_last"
        , "order": "desc"
      }
    }
  ]
}
@Bpazy
Copy link
Owner Author

Bpazy commented Sep 26, 2023

BKD tree 参考这里: #296

@Bpazy
Copy link
Owner Author

Bpazy commented Oct 18, 2023

开发注意点

  1. 调用频率(长时间无调用增加job探活)。ES隔一段时刻不操作后,再请求就会报错:Connection reset by peer。详情可参考: https://www.6hu.cc/archives/40573.html

@Bpazy
Copy link
Owner Author

Bpazy commented Nov 4, 2023

ES 设计注意点

  1. 单个 shard 大小限制在 20G ~ 40G 之间,不要超过 50G
  2. 副本越多,可靠性越高,内部 IO 开销越大,建议为 1,针对数据要求不高的,可设置为 0

@Bpazy
Copy link
Owner Author

Bpazy commented Nov 7, 2023

ES 查询注意点

  1. 尽量避免采用正则表达式和通配符的使用。
  2. 默认情况下,from+size的设定为1万条数据,请不要再进行调整,1万条已足够。这类似于MySQL,随着翻页的增多,查询速度会变慢。
  3. 如果需要进行翻页查看,请使用滚动(scroll)API(没有总页数,但可以连续翻页)。每次滚动获取的数据量不要超过500条。scroll 使用文档: https://www.elastic.co/guide/en/elasticsearch/reference/current/scroll-api.html
  4. 对于查询语句较为复杂的集群,请尽量减少使用聚合(aggregation),因为它会占用大量堆内存;如果确实需要使用聚合,可以在另一个集群中执行。
  5. 与聚合类似,Elasticsearch中的排序(sort)也会使用堆内存,如果能够避免使用就尽量避免。
  6. "插入然后查询"这种分为两步的动作,在查询时可能无法立即获取结果,因为Elasticsearch是近实时(near real time)的。
  7. 如果不需要计算分数,请使用过滤器(filter)而不是必须匹配(must)。
  8. 当根据from+size进行分页时,可能会出现同一条结果在翻页前后都存在的情况,这是由于数据在多个分片上存储所致。为了避免这个问题,可以使用preference=xxxroutingkey。
  9. 谨慎使用使用 function_score ,因为它较慢。
  10. 在使用Elasticsearch的Java客户端时,请不要再使用TransportClient SDK。直接使用高级REST客户端(high level rest client)和低级REST客户端(low level rest client)即可。
  11. 将日期数据类型设置为 "format":"yyyy-MM-dd HH:mm:ss||yyyy-MM-dd||epoch_millis",以便在查询时方便使用。
  12. 如果不需要进行全文搜索,请将字段设置为keyword而不是text类型。
  13. 在查询时,使用source参数来控制返回的字段,避免返回所有字段,只返回所需字段。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant