当前位置 : 首页 » 文章分类 :  开发  »  Elasticsearch

Elasticsearch

Elasticsearch 笔记

Elasticsearch 中文官网
https://www.elastic.co/cn/

全文搜索引擎 Elasticsearch 入门教程
https://www.ruanyifeng.com/blog/2017/08/elasticsearch.html


docker 部署 Elasticsearch 7.6.0

Install Elasticsearch with Docker
https://www.elastic.co/guide/en/elasticsearch/reference/7.6/docker.html

拉取Elasticsearch 7.6.0官方docker镜像

从 es 官方仓库 拉取 Elasticsearch 7.6.0 官方镜像

docker pull docker.elastic.co/elasticsearch/elasticsearch:7.6.0

或者
从 docker hub 拉取 Elasticsearch 7.6.0 官方镜像
https://hub.docker.com/_/elasticsearch

docker pull elasticsearch:7.6.0

我更偏向于从默认的 docker hub 拉取,不需要额外指明仓库前缀。
注意 es 没有 latest 标签,必须指明具体的版本号,否则提示:
Error response from daemon: manifest for elasticsearch:latest not found: manifest unknown: manifest unknown

docker中以single模式启动Elasticsearch 7.6.0

docker run -d \
--rm \
--network host \
--name es \
-e "discovery.type=single-node" \
-e ES_JAVA_OPTS="-Xms512m -Xmx512m" \
-v /home/centos/git/masikkk/es/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml \
elasticsearch:7.6.0

解释下:
-d 后台运行
--rm 停止容器后删掉容器文件
--network host 与宿主机完全共享网络,默认是bridge桥接,无法在nginx中通过localhost转发请求。一般都是通过 -p 9200:9200 -p 9300:9300 做端口映射,我直接共享宿主机网络了。
--name es 指定启动的容器名,方便按名称stop等操作
-e 设置两个环境变量,es的模式,jvm堆大小
-v 映射配置文件,具体说是宿主机配置文件覆盖容器中的配置文件,我的配置文件在 git 仓库中,方便保存,也可以记录修改历史。

docker安装elasticsearch和kibana (7.5.0)
http://www.leileitang.com/views/article/2019/120904.html

curl localhost:9200 测试

es 默认以 9200 端口启动,在服务器本地 curl localhost:9200,结果如下

{
  "name" : "node-1",
  "cluster_name" : "docker-es",
  "cluster_uuid" : "FLmzxlAdQzK0TuewMW1SLw",
  "version" : {
    "number" : "7.6.0",
    "build_flavor" : "default",
    "build_type" : "docker",
    "build_hash" : "7f634e9f44834fbc12724506cc1da681b0c3b1e3",
    "build_date" : "2020-02-06T00:09:00.449973Z",
    "build_snapshot" : false,
    "lucene_version" : "8.4.0",
    "minimum_wire_compatibility_version" : "6.8.0",
    "minimum_index_compatibility_version" : "6.0.0-beta1"
  },
  "tagline" : "You Know, for Search"
}

Elasticsearch安装ik分词器插件

medcl / elasticsearch-analysis-ik
https://github.com/medcl/elasticsearch-analysis-ik

两种方式
1、执行 docker exec 命令进入容器,再按照物理机的步骤来安装,缺点是每次创建容器都要安装一次。
2、打包一个安装了 ik 分词器的 elasticsearch docker 镜像,这样每个容器运行的时都自带了ik分词器。
这里使用第二种方式

打包集成ik分词器的Elasticsearch镜像

创建 Dockerfile

FROM elasticsearch:7.6.0

# 注意 es 与 ik 版本要对应
RUN ./bin/elasticsearch-plugin install --batch https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.6.0/elasticsearch-analysis-ik-7.6.0.zip

在 Dockerfile 所在的项目根目录下 构建镜像 docker build -t es .
打包的镜像名为 es
结果如下图

$ docker build -t es .
Sending build context to Docker daemon  14.85kB
Step 1/2 : FROM elasticsearch:7.6.0
 ---> 5d2812e0e41c
Step 2/2 : RUN ./bin/elasticsearch-plugin install --batch https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.6.0/elasticsearch-analysis-ik-7.6.0.zip
 ---> Running in 319ebb25c314
-> Installing https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.6.0/elasticsearch-analysis-ik-7.6.0.zip
-> Downloading https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.6.0/elasticsearch-analysis-ik-7.6.0.zip
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
@     WARNING: plugin requires additional permissions     @
@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
* java.net.SocketPermission * connect,resolve
See http://docs.oracle.com/javase/8/docs/technotes/guides/security/permissions.html
for descriptions of what these permissions allow and the associated risks.
-> Installed analysis-ik
Removing intermediate container 319ebb25c314
 ---> b1a1b68ae053
Successfully built b1a1b68ae053
Successfully tagged es:latest

过程中会提示 plugin requires additional permissions 不过没关系,能成功。

启动包含ik分词器的自定义镜像

docker run -d \
--rm \
--network host \
--name es \
-e "discovery.type=single-node" \
-e ES_JAVA_OPTS="-Xms512m -Xmx512m" \
-v /home/centos/git/masikkk/es/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml \
es

plugin requires additional permissions

有些 es 插件需要额外的权限,安装时会让用户确认,如果使用自动脚本安装,可设置 --batch 参数,这个参数告诉插件当前是自动脚本安装,自动获取所有权限。

vm.max_map_count [65530] is too low

{"type": "server", "timestamp": "2020-04-18T14:20:24,003Z", "level": "WARN", "component": "o.e.b.BootstrapChecks", "cluster.name": "docker-es", "node.name": "node-1", "message": "max virtual memory areas vm.max_map_count [65530] is too low, increase to at least [262144]" }

Using custom Docker images
https://www.elastic.co/guide/en/elasticsearch/reference/7.6/docker.html#_c_customized_image

Other command line parameters
https://www.elastic.co/guide/en/elasticsearch/plugins/7.6/_other_command_line_parameters.html


配置elasticsearch

Elasticsearch有三个配置文件:
elasticsearch.yml 用于配置Elasticsearch
jvm.options 用于配置Elasticsearch JVM设置的
log4j2.properties 用于配置Elasticsearch日志记录

Configuring Elasticsearch
https://www.elastic.co/guide/en/elasticsearch/reference/current/settings.html


elasticsearch.yml

我启动 es 容器使用的配置文件 elasticsearch.yml 如下:

# 开启跨域
http.cors.enabled: true
# 允许任何域访问
http.cors.allow-origin: "*"

# 节点名称
node.name: "node-1"
# 集群名称
cluster.name: "docker-es"
# 节点ip 单机默认回环地址 集群必须绑定真实ip
network.host: 0.0.0.0

默认情况下,Elastic 只允许本机访问,如果需要远程访问,可以修改 Elastic 安装目录的 config/elasticsearch.yml 文件,去掉 network.host 的注释,将它的值改成 0.0.0.0,然后重新启动 Elastic。


log4j2.properties 日志配置

elasticsearch 暴露了三个变量
${sys:es.logs.base_path} 等于 elasticsearch.yml 中的 path.logs 目录
${sys:es.logs.cluster_name} 集群名称
${sys:es.logs.node_name} 结点名称
供 log4j2.properties 配置文件使用

elasticsearch 慢日志

es 中有两种慢日志:
索引慢日志(index slow logs) elasticsearch_index_indexing_slowlog.log
搜索慢日志(search slow logs) elasticsearch_index_search_slowlog.log

Logging configuration
https://www.elastic.co/guide/en/elasticsearch/reference/current/logging.html


docker启动kibana 7.6.0

拉取kibana 7.6.0官方镜像

https://hub.docker.com/_/kibana

从 dockerhub 拉取 kibana 7.6.0 官方镜像

docker pull kibana:7.6.0

当然也可以从 es 官方镜像仓库拉取

docker pull docker.elastic.co/kibana/kibana:7.6.0

我用的是 docker hub

docker启动kibana 7.6.0

docker run -d \
--rm \
--network host \
--name kibana \
-e ELASTICSEARCH_HOSTS=http://127.0.0.1:9200 \
kibana:7.6.0

解释下:
-d 后台运行
--rm 停止容器后删掉容器文件
--network host 与宿主机完全共享网络,默认是bridge桥接,无法在nginx中通过localhost转发请求。一般都是通过 -p 5601:5601 做端口映射,我直接共享宿主机网络了。
--name kibana 指定启动的容器名,方便按名称stop等操作
-e 设置环境变量 ELASTICSEARCH_HOSTS,指定连接的 es 服务地址,不指定的话默认是 http://localhost:9200
注意低版本的 kibana 中用 ELASTICSEARCH_URL 指定 es 地址,高版本中改为 ELASTICSEARCH_HOSTS

Kibana Guide [7.6] » Set Up Kibana » Running Kibana on Docker
https://www.elastic.co/guide/en/kibana/current/docker.html#docker

配置index

打开 kibana 后首先会让配置 index,我只建立了一个名为 article 的索引,也不需要使用正则匹配,pattern 直接填 article 即可


Kibana 创建索引匹配模式

创建完索引匹配模式后,可以直接在 discover 中查看这个模式匹配的索引的数据,这里我直接就可以看到我的 article 索引的所有数据了:


Kibana查看索引数据

elasticsearch-head

docker部署elasticsearch-head:5

elasticsearch-head 是一个 ES 集群的可视化管理界面,可以查看 es 集群信息,查询、操作数据等,否则只能通过 restful api 和 es 交互,不直观。

docker 拉取 elasticsearch-head 官方镜像

docker pull mobz/elasticsearch-head:5

启动 es-head

docker run -d \
--rm \
--network host \
--name es-head \
mobz/elasticsearch-head:5

默认启动端口 9100 ,可以通过 -p 9100:9100 把容器的 9100 端口暴露出来,我这里为了方便 nginx 转发,直接 --network host 共享主机网络了。

elasticsearch-head连接es集群

启动后打开 localhost:9100 出现下图,点击连接本地的 es 集群。


elasticsearch-head 初始界面

连接上es集群后如图:


elasticsearch-head 查看ES结点状态

mobz / elasticsearch-head
https://github.com/mobz/elasticsearch-head

360EntSecGroup-Skylar / ElasticHD
https://github.com/360EntSecGroup-Skylar/ElasticHD


Elasticsearch基础

Elastic 本质上是一个分布式数据库,允许多台服务器协同工作,每台服务器可以运行多个 Elastic 实例。
单个 Elastic 实例称为一个节点(node)
一组节点构成一个集群(cluster)

Index索引

index 相当于数据库的表,是 es 数据管理的顶层单位

PUT创建索引

创建索引 article
curl -X PUT 'http://localhost:9200/article'
返回如下

{
    "acknowledged": true,
    "shards_acknowledged": true,
    "index": "article"
}

Elasticsearch Reference [7.6] » REST APIs » Index APIs » Create index API
https://www.elastic.co/guide/en/elasticsearch/reference/7.6/indices-create-index.html


DELETE删除索引

curl -X DELETE 'localhost:9200/article'
返回如下

{
    "acknowledged": true
}

Elasticsearch Reference [7.6] » REST APIs » Index APIs » Delete index API
https://www.elastic.co/guide/en/elasticsearch/reference/7.6/indices-delete-index.html


查看所有索引

curl -X GET "localhost:9200/_cat/indices?v"
返回如下

health status index   uuid                   pri rep docs.count docs.deleted store.size pri.store.size
yellow open   article 8wczi0SdTfqTjrOwqM5FOg   1   1          1            0      5.1kb          5.1kb

Document文档

Index 里面单条的记录称为 Document(文档)。许多条 Document 构成了一个 Index。

写入文档

PUT指定id创建文档

向指定的 /Index/Type/ID 发送 PUT 请求,就可以在 Index 里面新增一条记录。 ID 是自己指定的唯一ID
在 ElasticSearch 7.0 及以上的版本中已经把 type 这个概念了,统一用 “_doc” 这个占位符来表示 “_type”,你可以把 “_type” 看作是文档就行了,相当于 ElasticSearch 7.0 及以上版本只有索引和文档这两个概念了。

curl -X PUT 'http://localhost:9200/article/_doc/1' \
-H 'Content-Type: application/json' \
-d '{
    "title":"文章的标题",
    "pathname":"/article/postlink",
    "content":"美国留给伊拉克的是个烂摊子吗"
}'

返回

{
    "_index": "article",
    "_type": "_doc",
    "_id": "1",
    "_version": 1,
    "result": "created",
    "_shards": {
        "total": 2,
        "successful": 1,
        "failed": 0
    },
    "_seq_no": 0,
    "_primary_term": 1
}

POST不指定id创建文档

新增记录的时候,也可以不指定 Id,这时要改成 POST 请求。
向指定的 /Index/Type 发送 POST 请求,可以在 Index 里面新增一条记录,系统会自动生成唯一ID。

curl --location --request POST 'http://localhost:9200/article/_doc' \
--header 'Content-Type: application/json' \
--data-raw '{
    "title":"es的使用",
    "pathname":"/article/es",
    "content":"新增记录的时候,也可以不指定 Id,这时要改成 POST 请求。"
}'

返回

{
    "_index": "article",
    "_type": "_doc",
    "_id": "uv_ZjXEBrN9oq5tgVMuj",
    "_version": 1,
    "result": "created",
    "_shards": {
        "total": 2,
        "successful": 1,
        "failed": 0
    },
    "_seq_no": 0,
    "_primary_term": 1
}

返回的 _id 是自动生成的唯一 id

Elasticsearch Reference [7.6] » REST APIs » Document APIs » Index API
https://www.elastic.co/guide/en/elasticsearch/reference/7.6/docs-index_.html


更新文档

index 插入或更新文档

Index API
https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-index_.html

_update 指定文档id更新

update API 实现的逻辑中,其实可以理解为三步操作:
1、qeury:根据document ID去索引中获取到对应的document快照信息;
2、update:根据script脚本来更新document;
3、reindex:将更新后的document重新写回到索引;

在上述的第一步和第三步执行时,update 操作都会应用到ES内部的 version 以实现版本控制,从而保证 document 在更新的过程中没有发生改变。
因此,需要注意的是,ES的update API依然是需要对文档做一次完全的reindex操作,而不是直接去修改原始document
但是,update API所能做的是减少了网络交互次数,当然这比起我们自己通过index获取数据并在业务代码中更新再写回到ES来实现,大大的减少了版本冲突的概率。

在遇到版本冲突问题时,ES将会返回409 Conflict HTTP错误码。因此,当遇到409后,为了保证数据的最终插入,我们就必须要考虑到retry机制。为了实现冲突后的retry,有两种方案来实现:
1、业务代码自定义
通过识别 409 错误,在业务代码中,跟据自己的需求来进行retry。因为是自定义的逻辑,所以我们可以任意的操作retry的回退策略,以及retry的内容等;
2、retry_on_conflict
通过在参数中指定来实现retry_on_conflict来实现

POST crm/_doc/1/_update?retry_on_conflict=3
{
    "script" : {
        "source": "ctx._source.level=params.level;ctx._source.age+= params.count",
        "lang": "painless",
        "params" : {
            "level": 2
            "count" : 1
        }
    }
}

Update API
https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-update.html

_update_by_query 根据查询更新

Update By Query API
https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-update-by-query.html

Elasticsearch数据更新全方位解析
https://cloud.tencent.com/developer/article/1348591


URI参数查询

根据ID查询文档

/Index/Type/Id 发出 GET 请求,就可以查看这条记录。

curl --location --request GET 'http://localhost:9200/article/_doc/uv_ZjXEBrN9oq5tgVMuj' \
--header 'Content-Type: application/json' \
--data-raw '{
    "title":"es的使用",
    "pathname":"/article/es",
    "content":"新增记录的时候,也可以不指定 Id,这时要改成 POST 请求。"
}'

返回

{
    "_index": "article",
    "_type": "_doc",
    "_id": "uv_ZjXEBrN9oq5tgVMuj",
    "_version": 1,
    "_seq_no": 0,
    "_primary_term": 1,
    "found": true,
    "_source": {
        "title": "es的使用",
        "pathname": "/article/es",
        "content": "新增记录的时候,也可以不指定 Id,这时要改成 POST 请求。"
    }
}

ID 不存在时,返回 "found": false

{
    "_index": "article",
    "_type": "_doc",
    "_id": "1",
    "found": false
}

查询index下全部文档

使用 GET 方法,直接请求 /Index/Type/_search,就会返回 index 下的所有记录。

curl --location --request GET 'http://localhost:9200/article/_doc/_search' \
--header 'Content-Type: application/json' \
--data-raw ''

返回

{
    "took": 102,
    "timed_out": false,
    "_shards": {
        "total": 1,
        "successful": 1,
        "skipped": 0,
        "failed": 0
    },
    "hits": {
        "total": {
            "value": 2,
            "relation": "eq"
        },
        "max_score": 1.0,
        "hits": [
            {
                "_index": "article",
                "_type": "_doc",
                "_id": "uv_ZjXEBrN9oq5tgVMuj",
                "_score": 1.0,
                "_source": {
                    "title": "es的使用",
                    "pathname": "/article/es",
                    "content": "新增记录的时候,也可以不指定 Id,这时要改成 POST 请求。"
                }
            },
            {
                "_index": "article",
                "_type": "_doc",
                "_id": "u__ojXEBrN9oq5tgGcul",
                "_score": 1.0,
                "_source": {
                    "title": "java基础",
                    "pathname": "/article/java-basic",
                    "content": "java基础介绍"
                }
            }
        ]
    }
}

Elasticsearch Reference [7.6] » REST APIs » Document APIs » Get API
https://www.elastic.co/guide/en/elasticsearch/reference/7.6/docs-get.html

Elasticsearch Reference [7.6] » REST APIs » Search APIs » Search
https://www.elastic.co/guide/en/elasticsearch/reference/7.6/search-search.html


Type(7.x后废弃)

index 中的 document 可以分组,这种分组就叫做 Type
Elastic 6.x 版只允许每个 Index 包含一个 Type,7.x 版将会彻底移除 Type
ES 7.x 开始,彻底废弃一个 index 下多个 type 支持,包括 api 层面,之前版本可在一个索引库下创建多个 type


Mapping

mapping 用来规定 index 中的字段数据类型

动态Mapping

不需要提前定义 mapping,甚至不需要提前创建 index ,直接向一个 index 插入任意字段,ES都会自动创建 index,并自动添加field,这就叫 动态映射。

Dynamic Mapping
https://www.elastic.co/guide/en/elasticsearch/reference/current/dynamic-mapping.html


bulk批量操作

Bulk API
https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-bulk.html

Malformed action/metadata line

原因:批量操作的 body 必须是一行 action 一行数据(delete 不需要数据),数据必须在一行中且中间不能换行,一行数据结束后必须换行才能接下一个 action。
比如

{ "index": {"_index": "user_profile", "_type": "base_info", "_id": 1234567 } }
{ "user_id": 1234567 }

是正确的,但如果 改为

{ "index": {"_index": "user_profile", "_type": "base_info", "_id": 1234567 } }
{
  "user_id": 1234567
}

就会报下面的错误

{
    "error": {
        "root_cause": [
            {
                "type": "illegal_argument_exception",
                "reason": "Malformed action/metadata line [3], expected START_OBJECT but found [VALUE_STRING]"
            }
        ],
        "type": "illegal_argument_exception",
        "reason": "Malformed action/metadata line [3], expected START_OBJECT but found [VALUE_STRING]"
    },
    "status": 400
}

如果请求 body 的最后没有换行 \n,就会报下面的错误:

{
    "error":{
        "root_cause":[
            {
                "type":"illegal_argument_exception",
                "reason":"The bulk request must be terminated by a newline [
]"
            }
        ],
        "type":"illegal_argument_exception",
        "reason":"The bulk request must be terminated by a newline [
]"
    },
    "status":400
}

BULK API : Malformed action/metadata line [3], expected START_OBJECT but found [VALUE_STRING]
https://stackoverflow.com/questions/45792309/bulk-api-malformed-action-metadata-line-3-expected-start-object-but-found


乐观并发控制

每个文档都有一个 _version (版本)号,当文档被修改时版本号递增。 Elasticsearch 使用这个 _version 号来确保变更以正确顺序得到执行。如果旧版本的文档在新版本之后到达,它可以被简单的忽略。

为了避免丢失数据, 更新 API 会在获取步骤中获取当前文档中的 _version ,然后将其传递给重新索引步骤中的 索引 请求。如果其他的进程在这两步之间修改了这个文档,那么 _version 就会不同,这样更新就会失败。

Optimistic concurrency control
https://www.elastic.co/guide/en/elasticsearch/reference/current/optimistic-concurrency-control.html

乐观并发控制
https://www.elastic.co/guide/cn/elasticsearch/guide/current/optimistic-concurrency-control.html

409/Conflict

2 个请求并发对同一个 id 的文档进行更新:
请求 1 获取文档版本号是 1
请求 2 获取文档版本号是 1
请求 2 重新索引文档,写入成功,版本号更新为 2
请求 1 重新索引文档时,发现已有的文档 版本号是 2,索引失败,返回 409 Conflict


分词器

GET /_analyze 用于对一段文本进行分词

standard 默认分词器

不指定额外分词器时,es 使用一个名为 standard 的默认分词器
对于中文,standard 分词器只是将汉字拆分成一个个的汉字。

curl --location --request GET 'http://localhost:9200/_analyze' \
--header 'Content-Type: application/json' \
--data-raw '{
  "text" : "怪兽小当家,我是masikkk"
}'

返回分词结果

{
    "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": 6,
            "end_offset": 7,
            "type": "<IDEOGRAPHIC>",
            "position": 5
        },
        {
            "token": "是",
            "start_offset": 7,
            "end_offset": 8,
            "type": "<IDEOGRAPHIC>",
            "position": 6
        },
        {
            "token": "masikkk",
            "start_offset": 8,
            "end_offset": 15,
            "type": "<ALPHANUM>",
            "position": 7
        }
    ]
}

ik_max_word/ik_smart

ik_max_word: 会将文本做最细粒度的拆分,比如会将“中华人民共和国国歌”拆分为“中华人民共和国,中华人民,中华,华人,人民共和国,人民,人,民,共和国,共和,和,国国,国歌”,会穷尽各种可能的组合,适合 Term Query;

ik_smart: 会做最粗粒度的拆分,比如会将“中华人民共和国国歌”拆分为“中华人民共和国,国歌”,适合 Phrase 查询。

curl --location --request GET 'http://localhost:9200/_analyze' \
--header 'Content-Type: application/json' \
--data-raw '{
    "analyzer": "ik_smart",
  "text" : "怪兽小当家,我是masikkk"
}'

分词结果

{
    "tokens": [
        {
            "token": "怪兽",
            "start_offset": 0,
            "end_offset": 2,
            "type": "CN_WORD",
            "position": 0
        },
        {
            "token": "小",
            "start_offset": 2,
            "end_offset": 3,
            "type": "CN_CHAR",
            "position": 1
        },
        {
            "token": "当家",
            "start_offset": 3,
            "end_offset": 5,
            "type": "CN_WORD",
            "position": 2
        },
        {
            "token": "我",
            "start_offset": 6,
            "end_offset": 7,
            "type": "CN_CHAR",
            "position": 3
        },
        {
            "token": "是",
            "start_offset": 7,
            "end_offset": 8,
            "type": "CN_CHAR",
            "position": 4
        },
        {
            "token": "masikkk",
            "start_offset": 8,
            "end_offset": 15,
            "type": "ENGLISH",
            "position": 5
        }
    ]
}

里面有 ik 分词演示
SpringBoot集成Elasticsearch7.4 实战(一)
https://www.jianshu.com/p/1fbfde2aefa5


Query DSL

term 精确词查询
match 匹配查询
multi_match 多条件查询

query 和 filter

query 需要计算相关性
filter 不需要计算相关性,且 es 会缓存频繁查询的 filter 结果
所以 filter 会更快

Elasticsearch DSL 查询优化

使用 query-bool-filter 组合取代普通 query

默认情况下,ES 通过一定的算法计算返回的每条数据与查询语句的相关度,并通过 score 字段来表征。
但对于非全文索引的使用场景,用户并不 care 查询结果与查询条件的相关度,只是想精确的查找目标数据。
此时,可以通过 query-bool-filter 组合来让 ES 不计算 score,并且尽可能的缓存 filter 的结果集,供后续包含相同 filter 的查询使用,提高查询效率。


Match query

使用 match 查询 content 字段中的 java 关键词

curl --location --request GET 'http://localhost:9200/article/_doc/_search' \
--header 'Content-Type: application/json' \
--header 'Content-Type: application/json' \
--data-raw '{
    "query" : {
        "match" : {
            "content" : "java"
        }
    }
}'
{
    "took": 2,
    "timed_out": false,
    "_shards": {
        "total": 1,
        "successful": 1,
        "skipped": 0,
        "failed": 0
    },
    "hits": {
        "total": {
            "value": 1,
            "relation": "eq"
        },
        "max_score": 0.9336268,
        "hits": [
            {
                "_index": "article",
                "_type": "_doc",
                "_id": "u__ojXEBrN9oq5tgGcul",
                "_score": 0.9336268,
                "_source": {
                    "title": "java基础",
                    "pathname": "/article/java-basic",
                    "content": "java基础介绍"
                }
            }
        ]
    }
}

Elasticsearch Reference [7.6] » Query DSL » Full text queries » Match query
https://www.elastic.co/guide/en/elasticsearch/reference/7.6/query-dsl-match-query.html


分页

from/size 分页

和 SQL 使用 LIMIT 关键字返回单个 page 结果的方法相同,Elasticsearch 接受 from 和 size 参数:
size 显示应该返回的结果数量,默认是 10
from 显示应该跳过的初始结果数量,默认是 0

如果每页展示 5 条结果,可以用下面方式请求得到 1 到 3 页的结果:

GET /_search?size=5
GET /_search?size=5&from=5
GET /_search?size=5&from=10

https://www.elastic.co/guide/cn/elasticsearch/guide/current/pagination.html
https://www.elastic.co/guide/en/elasticsearch/reference/current/search-request-body.html#request-body-search-from-size

分布式系统中的深度分页问题

假设在一个有 5 个主分片的索引中搜索。 当我们请求结果的第一页(结果从 1 到 10 ),每一个分片产生前 10 的结果,并且返回给 协调节点 ,协调节点对 50 个结果排序得到全部结果的前 10 个。

现在假设我们请求第 1000 页—​结果从 10001 到 10010 。所有都以相同的方式工作除了每个分片不得不产生前10010个结果以外。 然后协调节点对全部 50050 个结果排序最后丢弃掉这些结果中的 50040 个结果。

可以看到,在分布式系统中,对结果排序的成本随分页的深度成指数上升。
所以 ES 就默认限制最多只能访问前 1w 个文档。这是通过 index.max_result_window 控制的。

es 目前支持最大的 skip 值是 index.max_result_window ,默认为 10000 。
也就是当 from + size > index.max_result_window 时,es 将返回错误
临时解决方法可以将 index.max_result_window 调高,但不是长久解决方案。

scroll分页(缓存快照,无法跳页)

为了满足深度分页的场景,es 提供了 scroll 的方式进行分页读取。
原理是缓存首次查询的结果快照,之后从每次根据游标 scroll_id 从快照中取数据。
原理上是对某次查询生成一个游标 scroll_id , 后续的查询只需要根据这个游标去取数据,直到结果集中返回的 hits 字段为空,就表示遍历结束。scroll_id 的生成可以理解为建立了一个临时的历史快照,在此之后的增删改查等操作不会影响到这个快照的结果。

scroll 分页过程:
1、初始化时<将所有符合搜索条件的搜索结果缓存起来,可以想象成快照;

GET fs/_search?scroll=3m
{
  "query": {"match_all": {}}
}

初始化的时候就像是普通的 search 一样
其中的 scroll=3m 代表当前查询的数据缓存 3 分钟

2、遍历时,从这个快照里取数据;
在遍历时候,拿到上一次遍历中的 _scroll_id,然后带 scroll 参数,重复上一次的遍历步骤,直到结果集中返回的 hits 字段为空,表示遍历完成。
每次都要传参数 scroll,刷新搜索结果的缓存时间,另外不需要指定 index 和 type(不要把缓存的时时间设置太长,占用内存)。
请求指定的 scroll_id 时就不需要 /index/_type 等信息了。每读取一页都会重新设置 scroll_id 的生存时间
如果 srcoll_id 的生存期很长,那么每次返回的 scroll_id 都是一样的,直到该 scroll_id 过期,才会返回一个新的 scroll_id。

scroll 快照分页的问题:
1、Search context 开销不小。
2、是一个临时快照,并不是实时的分页结果。

https://www.elastic.co/guide/en/elasticsearch/reference/current/search-request-body.html#request-body-search-scroll

search after(实时分页,无法跳页)

ES 5.0 开始推出了 Search After 机制可以提供了更实时的游标(live cursor)。它的思想是利用上一页的分页结果来提高下一页的分页请求。

第一次查询时指定唯一且稳定的分页方式

GET twitter/tweet/_search
{
    "size": 10,
    "query": {
        "match" : {
            "title" : "elasticsearch"
        }
    },
    "sort": [
        {"date": "asc"},
        {"_id": "desc"}
    ]
}

这里为了避免 sort 字段相同值的导致排序不确定,这里增加了 _id 字段。
返回的结果会包含每个文档的 sort 字段的 sort value。这个就是上面所说的 “live cursor”。

使用最后一个文档的 sort value 作为 search after 请求值,我们就可以这样子请求下一页结果了:

GET twitter/tweet/_search
{
    "size": 10,
    "query": {
        "match" : {
            "title" : "elasticsearch"
        }
    },
    "search_after": [1463538857, "654323"],
    "sort": [
        {"date": "asc"},
        {"_id": "desc"}
    ]
}

search_after 使用方式上跟 scroll 很像,但是相对于 scroll 它是无状态的(stateless),没有 search context 开销;而且它是每次请求都实时计算的,所以也没有一致性问题(相反,有索引变化的话,每次排序顺序会变化呢)。但是比起 from+size 方式,还是有同样的问题没法解决:就是只能顺序的翻页,不能随意跳页。

app 中的信息流翻页很适合这种方式,因为无法跳页。

问题:
1、无法跳页
2、有新的符合查询条件的数据被插入后,会查询到上一页已经返回的数据。
比如用户当前正在看第 2 页的历史数据,如果此时后台数据源新增了一条数据,那么当用户继续上推操作查看第 3 页的历史数据时,就会把第 2 页的最后一条数据获取,并且会把该条数据作为第3页的第一条数据进行展示
解决方法: 查询时参数代入 上次最后一条数据的 create_time ,下一页的数据都必须大于这个 create_time

https://www.elastic.co/guide/en/elasticsearch/reference/current/search-request-body.html#request-body-search-search-after

ElasticSearch如何支持深度分页
http://arganzheng.life/deep-pagination-in-elasticsearch.html


排序

1、默认按相关性 _score 排序
在 Elasticsearch 中, 相关性得分 由一个浮点数进行表示,并在搜索结果中通过 _score 参数返回, 默认排序是 _score 降序。

2、按单字段排序
例如按 user_id 升序排序

{
    "sort": {
        "user_id": {
            "order": "asc"
        }
    }
}

3、多字段排序
结果首先按第一个条件排序,仅当结果集的第一个 sort 值完全相同时才会按照第二个条件进行排序,以此类推。

{
    "sort": [
        { "date":   { "order": "desc" }},
        { "_score": { "order": "desc" }}
    ]
}

4、多值字段排序
一种情形是字段有多个值的排序, 需要记住这些值并没有固有的顺序;一个多值的字段仅仅是多个值的包装,这时应该选择哪个进行排序呢?
对于数字或日期,你可以将多值字段减为单值,这可以通过使用 min 、 max 、 avg 或是 sum 排序模式 。
例如你可以按照每个 date 字段中的最早日期进行排序,通过以下方法:

{
  "sort": {
    "dates": {
        "order": "asc",
        "mode":  "min"
    }
  }
}

https://www.elastic.co/guide/cn/elasticsearch/guide/current/_Sorting.html


_source 指定返回字段

比如只返回文档的 _iduser_id 字段

{
    "_source": ["_id", "user_id"],
    "sort": {
        "user_id": {
            "order": "asc"
        }
    }
}

Request Body Search - Source filtering
https://www.elastic.co/guide/en/elasticsearch/reference/current/search-request-body.html#request-body-search-source-filtering


nested嵌套查询

Nested query
https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-nested-query.html


SpringBoot 集成 Elasticsearch

Spring Boot 中集成 Elasticsearch 的方式:
1、使用官方的 REST Client,又分为 Low Level Rest Api/High Leve Rest Api
2、使用 Spring Data Elasticsearch, Spring Data 提供的一套方案,低版本里面使用了 TransportClient ,这个 client 已被 es 宣布废弃。但是升级到 Spring Data Elasticsearch 3.2.6 以上版本后,添加了对 High Leve Rest Client 的支持,也可以使用的。

Spring Data Elasticsearch
https://spring.io/projects/spring-data-elasticsearch

REST/Transport

Elasticsearch(ES) 有两种连接方式:transport、rest。
REST API ,端口 9200,使用基于 HTTP 的 RESTful API 进行访问。es 官方建议使用这种方式。
Transport 连接, 端口 9300,基于 TCP 连接的访问方式,TransportClient 客户端 在 7.0 版本中不建议使用,在 8.X 的版本中废弃。

Low Level Rest Api/High Leve Rest Api

ES 官方提供了两个 JAVA REST client 版本: Low Level Rest Api(低级 Rest Api)和 High Leve Rest Api(高级 Rest Api)。

所谓低级 Api 并不是功能比较弱,而是指 Api 离底层实现比较近。官方提供的低级 Api 是对原始的 Rest Api 的第一层封装。只是把 Http 调用的细节封装起来。程序还是要自己组装查询的条件字符串、解析返回的结果 json 字符串等。同时也要处理 http 协议的 各种方法、协议头等内容。兼容所有ES版本。

高级 api 是在低级 api 上的进一步封装,不用在意接口的方法,协议头,也不用人工组合调用的参数字符串,同时对返回的 json 字符串有一定的解析。使用上更方便一些。但是高级 api 并没有实现所有低级 api 实现的功能。所以如果遇到这种情况,还需要利用低级 api 来实现自己功能。使用的版本需要保持和 ES 服务端的版本一致,否则会有版本问题。


Java High Level REST Client

初始化Client

指定 ES 集群地址和端口创建 RestHighLevelClient 实例

RestHighLevelClient client = new RestHighLevelClient(
        RestClient.builder(
                new HttpHost("localhost", 9200, "http"),
                new HttpHost("localhost", 9201, "http")));

high-level client 内部使用 low-level client 来进行具体的请求操作,而 low-level client 内部维护了一个连接池,所以当使用结束后,为了节省资源可以通过

client.close();

关闭连接。

Java REST Client [7.6] » Java High Level REST Client » Getting started » Initialization
https://www.elastic.co/guide/en/elasticsearch/client/java-rest/current/java-rest-high-getting-started-initialization.html


判断索引是否存在

// 判断索引是否存在
@Test
public void testIndexExist() {
    GetIndexRequest request = new GetIndexRequest("article");
    request.local(false);
    request.humanReadable(true);
    request.includeDefaults(false);
    try {
        boolean exists = restHighLevelClient.indices().exists(request, RequestOptions.DEFAULT);
        System.out.println(exists ? "索引存在" : "索引不存在");
    } catch (IOException e) {
        e.printStackTrace();
    }
}

Java REST Client [7.6] » Java High Level REST Client » Index APIs » Index Exists API
https://www.elastic.co/guide/en/elasticsearch/client/java-rest/current/java-rest-high-indices-exists.html#java-rest-high-indices-exists


Bulk Request操作文档

可以单个操作文档,也可以使用 Bulk Request API 批量操作,一般我们直接使用批量操作就可以了。

Java REST Client [7.6] » Java High Level REST Client » Document APIs » Bulk API
https://www.elastic.co/guide/en/elasticsearch/client/java-rest/current/java-rest-high-document-bulk.html

IndexRequest 用于插入文档
DeleteRequest 用于删除文档
UpdateRequest 用于根据 ID 更新文档

Java High Level REST Client操作文档完整实例

public class ElaticsearchRestHighLevelTest {
    private RestHighLevelClient restHighLevelClient;

    // 索引
    private static final String INDEX_ARTICLE = "article";

    @Before
    public void initClient() {
        restHighLevelClient = new RestHighLevelClient(
                RestClient.builder(new HttpHost("es.masikkk.com", 80, "http")));
    }

    // 批量添加文档
    @Test
    public void testBulkRequestAddDocument() {
        BulkRequest bulkRequest = new BulkRequest();
        Article article1 = Article.builder().pathname("/article/Interview-13-Algorithm/").title("面试准备13-算法").
                content("面试准备之算法").build();
        Article article2 = Article.builder().pathname("/article//article/Elasticsearch/").title("Elasticsearch使用笔记").
                content("全文搜索引擎 Elasticsearch 入门教程").build();
        Article article3 = Article.builder().pathname("/article/Linux-Commands/").title("Linux-常用命令").
                content("top命令可以实时动态地查看系统的整体运行情况,是一个综合了多方信息监测系统性能和运行信息的实用工具。").build();
        bulkRequest.add(new IndexRequest(INDEX_ARTICLE).id(article1.getPathname()).source(JSONUtils.writeValue(article1), XContentType.JSON));
        bulkRequest.add(new IndexRequest(INDEX_ARTICLE).id(article2.getPathname()).source(JSONUtils.writeValue(article2), XContentType.JSON));
        bulkRequest.add(new IndexRequest(INDEX_ARTICLE).id(article3.getPathname()).source(JSONUtils.writeValue(article3), XContentType.JSON));
        try {
            BulkResponse bulkResponse = restHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT);
            System.out.println(JSONUtils.writePrettyValue(bulkResponse));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    // 批量删除文档
    @Test
    public void testBulkRequestRemoveDocument() {
        BulkRequest bulkRequest = new BulkRequest();
        bulkRequest.add(new DeleteRequest(INDEX_ARTICLE, "u__ojXEBrN9oq5tgGcul"));
        bulkRequest.add(new DeleteRequest(INDEX_ARTICLE, "vP-iknEBrN9oq5tgActC"));
        try {
            BulkResponse bulkResponse = restHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT);
            System.out.println(JSONUtils.writePrettyValue(bulkResponse));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @After
    public void close() {
        try {
            restHighLevelClient.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

Java REST Client [7.6] » Java Low Level REST Client
https://www.elastic.co/guide/en/elasticsearch/client/java-rest/current/java-rest-low.html

Java REST Client [7.6] » Java High Level REST Client » Document APIs
https://www.elastic.co/guide/en/elasticsearch/client/java-rest/current/java-rest-high-supported-apis.html

Elastic Search Java Api
https://www.jianshu.com/p/bf21cb2bd79c

springboot整合elasticsearch(基于es7.2和官方high level client)
https://www.tapme.top/blog/detail/2019-07-29-14-59/

springboot集成ES实现磁盘文件全文检索
https://www.jianshu.com/p/7e3c6a82671b


Circuit Breaker断路器

https://www.elastic.co/guide/en/elasticsearch/reference/7.6/circuit-breaker.html

Request circuit breaker请求断路器

indices.breaker.request.limit 请求允许使用的最大内存, 默认JVM堆内存的 60%

circuit_breaking_exception Data too large

{
    "error": {
        "root_cause": [
            {
                "type": "circuit_breaking_exception",
                "reason": "[parent] Data too large, data for [<http_request>] would be [128107988/122.1mb], which is larger than the limit of [123273216/117.5mb], real usage: [128107696/122.1mb], new bytes reserved: [292/292b], usages [request=0/0b, fielddata=0/0b, in_flight_requests=292/292b, accounting=2309/2.2kb]",
                "bytes_wanted": 128107988,
                "bytes_limit": 123273216,
                "durability": "PERMANENT"
            }
        ],
        "type": "circuit_breaking_exception",
        "reason": "[parent] Data too large, data for [<http_request>] would be [128107988/122.1mb], which is larger than the limit of [123273216/117.5mb], real usage: [128107696/122.1mb], new bytes reserved: [292/292b], usages [request=0/0b, fielddata=0/0b, in_flight_requests=292/292b, accounting=2309/2.2kb]",
        "bytes_wanted": 128107988,
        "bytes_limit": 123273216,
        "durability": "PERMANENT"
    },
    "status": 429
}

原因
jvm 堆内存不够当前查询加载数据所以会报 data too large, 请求被熔断,indices.breaker.request.limit 默认为 jvm heap 的 60%
我的 es 的堆大小设为 128M ,只在里面创建了一个 index,插入了两个 document,每个只有一句话,就报这个错了。
看来还需要给 es 多分配写内存。

Circuit Breaker
https://www.elastic.co/guide/en/elasticsearch/reference/7.6/circuit-breaker.html


Elasticsearch 压测

压测点

1、单 doc 最多多少字段写入最优(不出问题)?写入TPS(0错误)?
2、单个 doc 查询最多支持多少字段数?查询QPS(0错误)?
3、多 index 之间 join 查询性能如何?
0-10 字段
20-50
50 及以上

4、多 index 分页排序性能?
1-3 字段
3-5

查询:1字段,5字段、10字段
排序:不排序,或者1字段 id 排序
分页:每页 20 数据,测 200 - 220 分页场景

5、单 index 记录数大于300万时,分页性能?解决方案?

6、多 index 联合查询底层实现逻辑?
7、数据初始化导入工具?做成通用的, table.col1 -> index.doc.field
8、故障处理?如何快速重建索引?

Elasticsearch 压测方案对比

一、es 官方的压测工具 rally
elastic 官方开源的一款基于 python3 实现的针对 es 的压测工具
优势:1、专用的es压测工具,有较为详细的文档。2、定义了一整套数据集(track)、操作(operation)、任务(challenge,一系列操作的组合)标准,包含压测过程管理、存储、对比工具。3、自带数据集。
劣势:1、需要一定的学习成本,对于简单的单次测试显得大材小用。

二、jmeter 压测 es http api
优势:1、有使用经验,上手容易。2、测试报告易懂
劣势:1、需要自己准备数据集。2、压测轮次较多时不方便管理,不方便对比结果


Rally 官方ES压测工具

esrally 是 elastic 官方开源的一款基于 python3 实现的针对 es 的压测工具
自动创建、压测和销毁 es 集群
可分 es 版本管理压测数据和方案
完善的压测数据展示,支持不同压测之间的数据对比分析,也可以将数据存储到指定的es中进行二次分析
支持收集 JVM 详细信息,比如内存、GC等数据来定位性能问题

环境:
Python 3.4+ 和 pip3
JDK 8
git 1.9+

概念
track 赛道,压测用的数据和测试策略,使用 git 存储
elastic / rally-tracks 自带数据集
https://github.com/elastic/rally-tracks

operations 具体的操作,操作类型 create-index, search, raw-request 原始请求
challenges 通过组合 operations 定义一系列 task,再组合成一个压测的流程

car 赛车,指 es 实例
race 比赛,指一次压测

官方压测结果
https://elasticsearch-benchmarks.elastic.co/

elastic / rally
https://github.com/elastic/rally

文档
https://esrally.readthedocs.io/en/stable/index.html#

Announcing Rally: Our benchmarking tool for Elasticsearch
https://www.elastic.co/cn/blog/announcing-rally-benchmarking-for-elasticsearch

Elasticsearch 压测方案之 esrally 简介
https://segmentfault.com/a/1190000011174694


JMeter压测 ES HTTP 接口

Elasticsearch 6.8 默认配置下,使用 JMeter 1000 线程持续 5 分钟压测,写入doc,有大约 15% 的 429/Too Many Requests es_rejected_execution_exception 错误,需要调高 Elasticsearch 的 bulk 线程池(6.8 中是 write 线程池)队列大小(不建议),或者增加结点。


Elasticsearch 线程池设置

查看结点状态,里面有线程池配置数据

curl -XGET 'http://localhost:9200/_nodes/stats?pretty'

2.0 之后 5.0 之前,可通过 http api 动态修改线程池大小,无需重启,5.0 之后不能动态修改了,必须重启。

curl -XPUT 'localhost:9200/_cluster/settings' -d '{
    "transient": {
        "threadpool.index.type": "fixed",
        "threadpool.index.size": 100,
        "threadpool.index.queue_size": 500
    }
}'

elasticsearch 的3种线程池类型

elasticsearch 线程池的线程按照源码的实现来看分为 fixed 固定大小线程池, fixed_auto_queue_size 固定大小带阻塞队列的线程池 和 scaling 可变大小线程池 三种,其中 fixed_auto_queue_size 是实现类型,可能在之后的版本中去除。

fixed 固定大小线程池
fixed_auto_queue_size 固定大小带阻塞队列的线程池
scaling 可变大小线程池


search 线程池

用作 count/search/suggest 操作,线程池类型是 fixed_auto_queue_size ,线程池默认大小为 int((# of available_processors * 3) / 2) + 1,queue_size 默认大小为 1000
配置示例

thread_pool:
    search:
        size: 30
        queue_size: 500
        min_queue_size: 10
        max_queue_size: 1000
        auto_queue_frame_size: 2000
        target_response_time: 1s

write 线程池

用作 index/delete/update 及 bulk 批量操作,线程池类型是 fixed ,默认大小为 # of available processors, 允许设置的最大值是 1 + # of available processors, queue_size 默认大小为 200,
配置示例

thread_pool:
    write:
        size: 30
        queue_size: 1000

Thread Pool
https://www.elastic.co/guide/en/elasticsearch/reference/current/modules-threadpool.html

processors 处理器个数设置

线程池配置中的 # of available processors 指的是自动检测到的 逻辑处理器 个数,等于

# 查看逻辑CPU的个数
cat /proc/cpuinfo| grep "processor"| wc -l

比如 thread_pool.write.size 要求最大值是 1 + # of available processors ,如果逻辑cpu个数为4,则线程池最大为5,如果配置项中指定了比5更大的值会报错

java.lang.IllegalArgumentException: Failed to parse value [30] for setting [thread_pool.write.size] must be <= 5

如果确定想改为更大的值,可以在配置文件 elasticsearch.yml 中手动指定 processors 个数,例如

processors: 2

429 es_rejected_execution_exception

429/Too Many Requests

写入时报错 es_rejected_execution_exception

{
    "error":{
        "root_cause":[
            {
                "type":"remote_transport_exception",
                "reason":"[ZKjMEXP][127.0.0.1:9300][indices:data/write/bulk[s][p]]"
            }
        ],
        "type":"es_rejected_execution_exception",
        "reason":"rejected execution of processing of [2026943][indices:data/write/bulk[s][p]]: request: BulkShardRequest [[user_profile_indicator_data][0]] containing [index {[user_profile_indicator_data][indicator_base_info][5725976], source[n/a, actual length: [2.4kb], max length: 2kb]}], target allocation id: IbC5nk5CSOO9ReABdDvcvA, primary term: 1 on EsThreadPoolExecutor[name = ZKjMEXP/write, queue capacity = 200, org.elasticsearch.common.util.concurrent.EsThreadPoolExecutor@1f44c59d[Running, pool size = 4, active threads = 4, queued tasks = 200, completed tasks = 1442357]]"
    },
    "status":429
}

查询时报错 EsRejectedExecutionException

[2020-05-18T15:48:31,645][DEBUG][o.e.a.s.TransportSearchAction] [ZKjMEXP] All shards failed for phase: [query]
org.elasticsearch.ElasticsearchException$1: rejected execution of org.elasticsearch.common.util.concurrent.TimedRunnable@4475dcce on QueueResizingEsThreadPoolExecutor[name = ZKjMEXP/search, queue capacity = 100, min queue capacity = 100, max queue capacity = 1000, frame size = 1000, targeted response rate = 1s, task execution EWMA = 21.5ms, adjustment amount = 50, org.elasticsearch.common.util.concurrent.QueueResizingEsThreadPoolExecutor@1328861b[Running, pool size = 30, active threads = 30, queued tasks = 384, completed tasks = 19350]]
        at org.elasticsearch.ElasticsearchException.guessRootCauses(ElasticsearchException.java:657) ~[elasticsearch-6.8.7.jar:6.8.7]
        at org.elasticsearch.action.search.AbstractSearchAsyncAction.executeNextPhase(AbstractSearchAsyncAction.java:131) ~[elasticsearch-6.8.7.jar:6.8.7]
        at org.elasticsearch.action.search.AbstractSearchAsyncAction.onPhaseDone(AbstractSearchAsyncAction.java:259) ~[elasticsearch-6.8.7.jar:6.8.7]
        at org.elasticsearch.action.search.InitialSearchPhase.onShardFailure(InitialSearchPhase.java:100) ~[elasticsearch-6.8.7.jar:6.8.7]
        at org.elasticsearch.action.search.InitialSearchPhase.access$100(InitialSearchPhase.java:48) ~[elasticsearch-6.8.7.jar:6.8.7]
        at org.elasticsearch.action.search.InitialSearchPhase$2.lambda$onFailure$1(InitialSearchPhase.java:220) ~[elasticsearch-6.8.7.jar:6.8.7]
        at org.elasticsearch.action.search.InitialSearchPhase$1.doRun(InitialSearchPhase.java:187) [elasticsearch-6.8.7.jar:6.8.7]
        at org.elasticsearch.common.util.concurrent.AbstractRunnable.run(AbstractRunnable.java:37) [elasticsearch-6.8.7.jar:6.8.7]
        at org.elasticsearch.common.util.concurrent.TimedRunnable.doRun(TimedRunnable.java:41) [elasticsearch-6.8.7.jar:6.8.7]
        at org.elasticsearch.common.util.concurrent.ThreadContext$ContextPreservingAbstractRunnable.doRun(ThreadContext.java:751) [elasticsearch-6.8.7.jar:6.8.7]
        at org.elasticsearch.common.util.concurrent.AbstractRunnable.run(AbstractRunnable.java:37) [elasticsearch-6.8.7.jar:6.8.7]
        at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [?:1.8.0_191]
        at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [?:1.8.0_191]
        at java.lang.Thread.run(Thread.java:748) [?:1.8.0_191]
Caused by: org.elasticsearch.common.util.concurrent.EsRejectedExecutionException: rejected execution of org.elasticsearch.common.util.concurrent.TimedRunnable@4475dcce on QueueResizingEsThreadPoolExecutor[name = ZKjMEXP/search, queue capacity = 100, min queue capacity = 100, max queue capacity = 1000, frame size = 1000, targeted response rate = 1s, task execution EWMA = 21.5ms, adjustment amount = 50, org.elasticsearch.common.util.concurrent.QueueResizingEsThreadPoolExecutor@1328861b[Running, pool size = 30, active threads = 30, queued tasks = 384, completed tasks = 19350]]
        at org.elasticsearch.common.util.concurrent.EsAbortPolicy.rejectedExecution(EsAbortPolicy.java:48) ~[elasticsearch-6.8.7.jar:6.8.7]

es_rejected_execution_exception[bulk] 是批量队列错误。当对 Elasticsearch 集群的请求数超过批量队列大小 (threadpool.bulk.queue_size) 时,会发生此问题。每个节点上的批量队列可以容纳 50 到 200 个请求,具体取决于您使用的 Elasticsearch 版本。队列已满时,将拒绝新请求。

其实,Elasticsearch 分别对不同的操作【例如:index、bulk、get 等】提供不同的线程池,并设置线程池的线程个数与排队任务上限。可以在数据索引所在节点的 settings 中查看

这里面,有两种类型的线程池,一种是 fixing,一种是 scaling,其中 fixing 是固定大小的线程池,默认是 core 个数的 5 倍,也可以指定大小,scaling 是动态变化的线程池,可以设置最大值、最小值。

解决:
在不增加节点的情况下,把节点的线程池设置大一点、队列上限设置大一点,就可以处理更多的请求了。这个方法需要改变 Elasticsearch 集群的配置,然后重启集群,但是一般情况下会有风险,因为节点的硬件配置【内存、CPU】没有变化,单纯增加线程池,会给节点带来压力,可能会宕机,谨慎采用。配置信息参考如下:

-- 修改 elasticsearch.yml 配置文件
threadpool.bulk.type: fixed
threadpool.bulk.size: 64
threadpool.bulk.queue_size: 1500

Elasticsearch 中的 429 错误 es_rejected_execution_exception
https://www.playpi.org/2017042601.html


上一篇 LeetCode.215.Kth Largest Element in an Array 寻找数组中第K大的数

下一篇 LeetCode.092.Reverse Linked List II 反转链表的第m到n个结点

阅读
评论
10,061
阅读预计45分钟
创建日期 2020-02-20
修改日期 2020-05-22
类别
目录
  1. docker 部署 Elasticsearch 7.6.0
    1. 拉取Elasticsearch 7.6.0官方docker镜像
    2. docker中以single模式启动Elasticsearch 7.6.0
    3. curl localhost:9200 测试
  2. Elasticsearch安装ik分词器插件
    1. 打包集成ik分词器的Elasticsearch镜像
    2. 启动包含ik分词器的自定义镜像
    3. plugin requires additional permissions
    4. vm.max_map_count [65530] is too low
  3. 配置elasticsearch
    1. elasticsearch.yml
    2. log4j2.properties 日志配置
      1. elasticsearch 慢日志
  4. docker启动kibana 7.6.0
    1. 拉取kibana 7.6.0官方镜像
    2. docker启动kibana 7.6.0
    3. 配置index
  5. elasticsearch-head
    1. docker部署elasticsearch-head:5
    2. elasticsearch-head连接es集群
  6. Elasticsearch基础
    1. Index索引
      1. PUT创建索引
      2. DELETE删除索引
      3. 查看所有索引
    2. Document文档
      1. 写入文档
        1. PUT指定id创建文档
        2. POST不指定id创建文档
      2. 更新文档
        1. index 插入或更新文档
        2. _update 指定文档id更新
        3. _update_by_query 根据查询更新
      3. URI参数查询
        1. 根据ID查询文档
        2. 查询index下全部文档
    3. Type(7.x后废弃)
    4. Mapping
      1. 动态Mapping
    5. bulk批量操作
      1. Malformed action/metadata line
    6. 乐观并发控制
      1. 409/Conflict
  7. 分词器
    1. standard 默认分词器
    2. ik_max_word/ik_smart
  8. Query DSL
    1. query 和 filter
    2. Elasticsearch DSL 查询优化
      1. 使用 query-bool-filter 组合取代普通 query
    3. Match query
    4. 分页
      1. from/size 分页
      2. 分布式系统中的深度分页问题
      3. scroll分页(缓存快照,无法跳页)
      4. search after(实时分页,无法跳页)
    5. 排序
    6. _source 指定返回字段
    7. nested嵌套查询
  9. SpringBoot 集成 Elasticsearch
    1. REST/Transport
    2. Low Level Rest Api/High Leve Rest Api
    3. Java High Level REST Client
      1. 初始化Client
      2. 判断索引是否存在
      3. Bulk Request操作文档
      4. Java High Level REST Client操作文档完整实例
  10. Circuit Breaker断路器
    1. Request circuit breaker请求断路器
      1. circuit_breaking_exception Data too large
  11. Elasticsearch 压测
    1. 压测点
    2. Elasticsearch 压测方案对比
    3. Rally 官方ES压测工具
    4. JMeter压测 ES HTTP 接口
  12. Elasticsearch 线程池设置
    1. elasticsearch 的3种线程池类型
    2. search 线程池
    3. write 线程池
    4. processors 处理器个数设置
    5. 429 es_rejected_execution_exception

页面信息

location:
protocol:
host:
hostname:
origin:
pathname:
href:
document:
referrer:
navigator:
platform:
userAgent:

评论