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

Elasticsearch

Elasticsearch 笔记

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

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

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


Elasticsearch 基础

倒排索引
普通索引是通过id,标题等标识建立索引,只有知道这些标识才能搜索到内容;倒排索引对内容进行分词,反向建立词项->标题->id的索引,可以根据内容搜索。

index 索引(名词):一个 索引 类似于传统关系数据库中的一个 数据库 ,是一个存储关系型文档的地方。 索引 (index) 的复数词为 indices 或 indexes 。

索引(动词):索引一个文档 就是存储一个文档到一个 索引 (名词)中以便被检索和查询。类似于 SQL 语句中的 INSERT,除了文档已存在时,新文档会替换旧文档情况之外。

Document 文档:index 中的一条记录,等于 MySQL 中的行数据,被序列化成 JSON 并存储到 Elasticsearch 中


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 镜像

从 Elasticsearch 官方仓库 拉取 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/search/es/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml \
-v /data/es:/usr/share/elasticsearch/data \
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 仓库中,方便保存,也可以记录修改历史。
-v /data/es:/usr/share/elasticsearch/data 把本地目录映射到容器,一遍容器销毁后能保存es数据

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/search/es/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml \
-v /data/es:/usr/share/elasticsearch/data \
es

AccessDeniedException /usr/share/elasticsearch/data/nodes

想把宿主机目录绑定到 es 容器的 /usr/share/elasticsearch/data 目录,但宿主机目录权限不够,导致es无法启动

org.elasticsearch.bootstrap.StartupException: ElasticsearchException[failed to bind service]; nested: AccessDeniedException[/usr/share/elasticsearch/data/nodes];
ElasticsearchException[failed to bind service]; nested: AccessDeniedException[/usr/share/elasticsearch/data/nodes];
Likely root cause: java.nio.file.AccessDeniedException: /usr/share/elasticsearch/data/nodes

原因: docker 容器对宿主机的 /data/es 目录没有写权限
解决:修改目录权限即可
chmod 777 /data/es

chown -R 1000:1000 /data/es

在使用docker来部署elasticsearch服务时,通常需要把elasticsearch的索引数据和日志数据映射到本地进行持久化存储,但是经常会遇到权限问题:elasticsearch无法读取数据或者无法写入日志文件,主要有两种处理方法:
1、将外部文件权限全部打开,有一定安全风险
2、将镜像内文件与宿主机文件的用户及组id统一,因为在校验权限时通过uid和gid来验证,官方elasticsearch镜像内部为elasticsearch用户,uid和gid为1000,因此我们也将外部用户及组id修改为1000

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


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

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

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

Docker 安装 elasticsearch-head:5

1、docker 拉取 elasticsearch-head 官方镜像

docker pull mobz/elasticsearch-head:5

2、启动 es-head

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

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

Chrome 扩展安装 elasticsearch-head

打开链接安装插件
https://chrome.google.com/webstore/detail/elasticsearch-head/ffmkiejjmecolpfloofpjologoblkegm
或 Chrome 应用商店中搜索 ElasticSearch Head,提供方:travistx

通过 Chrome 扩展安装的好处是无需启用 es 的 CORS 跨域策略就能连接 es 集群。

elasticsearch-head 连接 ES 集群

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


elasticsearch-head 初始界面

连接上es集群后如图:


elasticsearch-head 查看ES结点状态

elasticsearch-head 无法连接 ES(ES 未开启跨域)

除了通过 Chrome 插件的形式安装 elasticsearch-head,其他的安装方式都需要 es 本身开启 CORS 跨域才能连接,否则连接不上。

修改elasticsearch.yml,增加如下字段

http.cors.enabled: true
http.cors.allow-origin: "*"

elasticsearch-head 连接高版本 ES 报错 406

问题:
elasticsearch-head:5 连接 es 7.16,概览和索引信息都可以查看,但数据浏览中看不到数据,f12 后台看到返回错误

{
    "error":"Content-Type header [application/x-www-form-urlencoded] is not supported",
    "status":406
}

原因:
高版本 es 增加了严格的 Content-Type 内容类型检查,也是防止 CSRF 攻击的一层保护

解决:
进 es-head 容器,修改 /usr/src/app/_site/vendor.js 文件,将
contentType: “application/x-www-form-urlencoded” 替换为 contentType: “application/json”
有两处要修改,但 es-head 容器内没 vi 编辑器,无法编辑文件。

将 /usr/src/app/_site/vendor.js 从容器中拷贝出来,编辑后再拷贝进容器
docker cp es-head:/usr/src/app/_site/vendor.js ./
docker cp vendor.js es-head:/usr/src/app/_site/vendor.js


文档插入性能调优

Elasticsearch Guide [7.17] » How to » Tune for indexing speed
https://www.elastic.co/guide/en/elasticsearch/reference/current/tune-for-indexing-speed.html

使用批量操作API

多线程插入

修改或关闭刷新间隔(refresh_interval)

index.refresh_interval
https://www.elastic.co/guide/en/elasticsearch/reference/current/index-modules.html#index-refresh-interval-setting

默认值 1 秒,可设为 -1 来禁用 refresh。
未显式设置时,分片在 index.search.idle.after 秒未收到 search 请求后会停止后台的定时 refresh 任务,直到再次收到 search 请求。这么做的目的是为了加快批量索引的速度。
refresh_interval 可以在既存索引上进行动态更新。

1、当正在建立一个大的新索引时,可以将刷新间隔临时调整为较大的值,创建完再调整为默认值。
注意:refresh_interval 需要一个 持续时间 值, 例如 1s (1 秒) 或 2m (2 分钟)。 一个绝对值 1 表示的是 1 毫秒。

PUT /index/_settings
{ "refresh_interval": "30s" } 30s自动刷新。
 
PUT /index/_settings
{ "refresh_interval": "1s" }  每秒自动刷新。

2、当正在建立一个大的新索引时,可以先关闭自动刷新,待开始使用该索引时,再把它们调回来:

PUT /index/_settings
{ "refresh_interval": -1 }    关闭自动刷新。
 
PUT /index/_settings
{ "refresh_interval": "1s" }  每秒自动刷新。

禁用副本

在做批量索引时,可以考虑把副本数 index.number_of_replicas 设置成0,因为document从主分片(primary shard)复制到从分片(replica shard)时,从分片也要执行相同的分析、索引和合并过程,这样的开销比较大,你可以在构建索引之后再开启副本,这样只需要把数据从主分片拷贝到从分片

curl -XPUT 'localhost:9200/my_index/_settings' -d ' {
    "index" : {
        "number_of_replicas" : 0
    }
}'

禁用操作系统swap

增加操作系统文件cache

使用自动生成的id

如果指定 id 创建文档,es 需要先检查此 id 是否已存在,较为耗时。

使用SSD磁盘

增加索引buffer size

读写分离


检索性能调优

Elasticsearch Guide [7.17] » How to » Tune for search speed
https://www.elastic.co/guide/en/elasticsearch/reference/current/tune-for-search-speed.html

增加系统缓存

Elasticsearch 重度依赖操作系统的文件系统缓存,为了加快检索速度,要保证至少一半可用内存用于系统缓存,以保证 ES 可将热点索引放到物理内存中。
这也是为什么官方建议堆内存不要超过可用内存的一半。

使用更快的硬件

1、保证文件系统缓存足够
2、使用 SSD 磁盘

文档模型

避免 nested 嵌套字段,避免 join 关系字段。
nested 字段有几倍的性能损失,join 关系字段会带来几百倍的性能损失。

使用copy_to合并字段

将多个字段的内容 copy_to 到一个字段中,方便搜索

提前计算中间结果

比如经常需要对 price 字段进行聚合查询,聚合为 0-200,200-500,500+,可以在插入文档时就计算好这个聚合分类

使用keyword代替数字类型

ES 对 integer, long 等数字类型做了优化更适合 range 查询,但 keyword 类型适合 term 级别查询。
并不是所有的数字类型数据都要存储为数字类型(integer, long等),例如数字类型的 ISBN 号、产品ID等,很少会用做范围 range 查询,更多的是 term 查询,所以更适合保存为 keyword 类型。

避免脚本搜索

强制合并只读索引

将只读索引强制合并为单个 Segment 可加快查询。对于 log 等基于时间写入的索引尤其适用,只保留当天的日志可写,将之前的日志合并为一个索引。

预热全局序号

全局序号用于加速聚合查询,是 fielddata 缓存结构的一部分,位于 JVM 堆内存中,在查询时触发。
对于常用的聚合字段,告诉 ES 提前预热该字段的全局序号缓存,可加快聚合查询速度。

预热文件系统缓存

机器重启后操作系统文件缓存会丢失。
对于常用的索引,配置到 "index.store.preload": ["index1", "index2"],以便启动时自动预热数据

索引数据排序

Elasticsearch 从 6.0 版本开始引入了一个新的特征,叫 Index Sorting(索引排序)。用户可以将索引数据按照指定的顺序存储在硬盘上,这样在搜索排序取前 N 条时,不需要访问所有的匹配中的记录再进行排序,只需要访问前 N 条记录即可。


Index 索引 API

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


PUT /index 创建索引

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

PUT /<index> 创建索引

例1、使用默认配置、不指定mapping创建索引 article
curl -X PUT 'http://localhost:9200/article'
返回如下

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

例2、指定分片、副本数创建索引

PUT /my-index-000001
{
  "settings": {
    "index": {
      "number_of_shards": 3,  
      "number_of_replicas": 2 
    }
  }
}

body 请求体可以简化,无需指定 index 块,如下:

PUT /my-index-000001
{
  "settings": {
    "number_of_shards": 3,
    "number_of_replicas": 2
  }
}

例3、指定分片数、mapping创建索引

PUT /test
{
  "settings": {
    "number_of_shards": 1
  },
  "mappings": {
    "properties": {
      "field1": { "type": "text" }
    }
  }
}

GET /index 查询索引

Elasticsearch Guide [7.16] » REST APIs » Index APIs » Get index API
https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-get-index.html

curl -X GET 'http://localhost:9200/article'
返回

{
    "article": {
        "aliases": {},
        "mappings": {},
        "settings": {
            "index": {
                "routing": {
                    "allocation": {
                        "include": {
                            "_tier_preference": "data_content"
                        }
                    }
                },
                "number_of_shards": "1",
                "provided_name": "article",
                "creation_date": "1643005891480",
                "number_of_replicas": "1",
                "uuid": "phMOfBkAT8yE6lE1WHQniA",
                "version": {
                    "created": "7160299"
                }
            }
        }
    }
}

DELETE /index 删除索引

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

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

{
    "acknowledged": true
}

GET /index/_mapping 查询mapping

Elasticsearch Guide [7.17] » REST APIs » Index APIs » Get mapping API
https://www.elastic.co/guide/en/elasticsearch/reference/7.17/indices-get-mapping.html

curl -X GET 'http://localhost:9200/article/_mapping'
无 mapping 结果如下:

{
    "user_1.24.14": {
        "mappings": {}
    }
}

PUT /index/_mapping 修改mapping

Elasticsearch Guide [7.17] » REST APIs » Index APIs » Update mapping API
https://www.elastic.co/guide/en/elasticsearch/reference/7.17/indices-put-mapping.html

PUT /my-index-000001/_mapping
{
  "properties": {
    "email": {
      "type": "keyword"
    }
  }
}

GET /index/_settings 查询索引的配置参数

Elasticsearch Guide [7.17] » REST APIs » Index APIs » Get index settings API
https://www.elastic.co/guide/en/elasticsearch/reference/7.17/indices-get-settings.html

GET /<target>/_settings 查询指定索引的全部配置项
GET /<target>/_settings/<setting> 查询指定索引的指定配置项

查询 index.number_of_shards 配置项

GET /my_blog_3shards/_settings/index.number_of_shards
{
    "my_blog_3shards": {
        "settings": {
            "index": {
                "number_of_shards": "3"
            }
        }
    }
}

查询索引的全部配置项

GET /my_blog_3shards/_settings
{
    "my_blog_3shards": {
        "settings": {
            "index": {
                "routing": {
                    "allocation": {
                        "include": {
                            "_tier_preference": "data_content"
                        }
                    }
                },
                "number_of_shards": "3",
                "provided_name": "my_blog_3shards",
                "creation_date": "1645156600073",
                "sort": {
                    "field": "timestamp",
                    "order": "desc"
                },
                "number_of_replicas": "1",
                "uuid": "6uNlMASdSAGPNjHUNo9JiA",
                "version": {
                    "created": "7160299"
                }
            }
        }
    }
}

POST /index/_settings 修改索引的动态配置

Elasticsearch Guide [7.17] » REST APIs » Index APIs » Update index settings API
https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-update-settings.html

PUT /<target>/_settings 实时修改索引的动态配置参数

这个API经常被用来打开/关闭 index.refresh_interval 自动刷新,以便快速批量索引大量数据。

例如动态修改索引的副本数

PUT /my-index-000001/_settings
{
  "index" : {
    "number_of_replicas" : 2
  }
}

POST /index/_close 关闭索引

Elasticsearch Guide [7.17] » REST APIs » Index APIs » Close index API
https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-close.html

POST /<index>/_close 关闭索引

索引可以被关闭,关闭的索引不可读写数据,只能查看元数据信息。


POST /index/_open 打开索引

Elasticsearch Guide [7.17] » REST APIs » Index APIs » Open index API
https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-open-close.html

POST /<target>/_open 打开索引

重新打开关闭的索引使之再次可读写数据。


POST /index/_refresh 刷新索引

Elasticsearch Guide [7.17] » REST APIs » Index APIs » Refresh API
https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-refresh.html

刷新指定索引:

POST <target>/_refresh
GET <target>/_refresh

刷新全部索引:

POST /_refresh
GET /_refresh

refresh 操作将内存缓冲区中的数据写入 Lucene segment 使之可读
refresh 可以使最近的操作对 search 可见,比如新插入的文档在 refresh 操作后才可被检索到
默认情况下 es 每隔一秒钟执行一次 refresh,可以通过参数 index.refresh_interval 来修改这个刷新间隔

refresh 操作包括:
1、所有在内存缓冲区中的文档被写入到一个新的segment中,但是没有调用fsync,因此内存中的数据可能丢失
2、segment被打开使得里面的文档能够被搜索到
3、清空内存缓冲区


POST /index/_flush 刷入磁盘

Elasticsearch Guide [7.17] » REST APIs » Index APIs » Flush API
https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-flush.html

刷指定索引的:

POST /<target>/_flush
GET /<target>/_flush

刷全部索引的:

POST /_flush
GET /_flush

flush 操作将 translog 中的操作记录刷入磁盘,默认5s一次

flush 过程主要做了如下操作:
1、通过refresh操作把所有在内存缓冲区中的文档写入到一个新的segment中
2、清空内存缓冲区
3、往磁盘里写入commit point信息
4、文件系统的page cache(segments) fsync到磁盘
5、删除旧的translog文件,因此此时内存中的segments已经写入到磁盘中,就不需要translog来保障数据安全了


POST /index/_forcemerge 强制段合并

Elasticsearch Guide [7.17] » REST APIs » Index APIs » Force merge API
https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-forcemerge.html

POST /<target>/_forcemerge 强制合并指定的索引
POST /_forcemerge 强制合并全部索引


POST /index/_split/new_index 拆分索引

Elasticsearch Guide [7.17] » REST APIs » Index APIs » Split index API
https://www.elastic.co/guide/en/elasticsearch/reference/7.17/indices-split-index.html

POST /<index>/_split/<target-index>
PUT /<index>/_split/<target-index>

将已有的索引拆分为分片数更多的新索引,原索引的每个主分片会拆分为多个目标索引上的新分片

例如将 my_source_index 拆分为新索引 my_target_index

POST /my_source_index/_split/my_target_index
{
  "settings": {
    "index.number_of_shards": 2
  }
}

上述操作在新索引创建后会立即返回,并不会等待索引分割操作完成。

索引拆分前提条件

  • 原索引必须是只读的,防止操作时有新数据写入
  • 集群健康状态必须是绿色
  • 目标索引必须不存在
  • 原索引的主分片数必须少于目标索引
  • 目标索引的主分片数必须是原索引主分片数的整数倍
  • 处理拆分过程的节点必须有足够的磁盘空间来容纳原索引的一份数据拷贝

可通过 index.blocks.write 设为 true 将索引设为数据只读,此时依然允许元数据操作,比如删除索引

PUT /my_source_index/_settings
{
  "settings": {
    "index.blocks.write": true 
  }
}

索引可拆分的倍数

索引可拆分的倍数由 index.number_of_routing_shards 静态配置项决定。
例如,索引分片数为 5, number_of_routing_shards 设为 30(5 × 2 × 3),30 可被因子 2 和 3 整除,所以可进行下面的拆分:
5 -> 10 -> 30 先1分2,再1分3
5 -> 15 -> 2 先1分3,再1分2
5 -> 30 1分6

index.number_of_routing_shards 是静态配置项,必须在创建索引时指定,或在关闭的索引上修改。

index.number_of_routing_shards 的默认值依赖于主分片的个数,目的是为了允许将索引以 2 为倍数拆分为最多 1024 个分片。例如索引有 5 个主分片,可以以 2 倍一次或多次拆分为 10,20,40,80,160,320,640 个分片,则 index.number_of_routing_shards 默认值为 640。

如果原索引只有一个主分片(或者多分片索引被 收缩 为一个主分片),则可被拆分为任意个分片,拆分后 index.number_of_routing_shards 的默认值也会随之变化。

索引拆分过程

1、创建一个新索引,和原索引定义相同,主分片数更多。
2、将原索引的段数据 硬链接(Hard Link) 到新索引的段数据上,Linux 中只是 inode 链接数的变化,很快。如果文件系统不支持硬链接,会执行数据拷贝,耗时会很长。
3、对全部文档进行重新哈希,之后删除不需要的段数据。
4、恢复目标索引,类似一个关闭的索引刚被打开一样。

为什么ES不支持增量reshard

监控拆分过程


POST /index/_shrink/new_index 收缩索引

Elasticsearch Guide [7.17] » REST APIs » Index APIs » Shrink index API
https://www.elastic.co/guide/en/elasticsearch/reference/7.17/indices-shrink-index.html

POST /<index>/_shrink/<target-index>
PUT /<index>/_shrink/<target-index>

将已有的索引收缩为分片数更少的新索引,目标索引的主分片数必须是原索引主分片数的整数因子

例如原索引的主分片数是 8,可以收缩为主分片数为 4, 2, 1 的新索引。如果原索引的主分片数是素数,则只能收缩为单分片的索引。

索引收缩过程:
1、创建一个新索引,和原索引定义相同,主分片数更少。
2、将原索引的段数据 硬链接(Hard Link) 到新索引的段数据上。如果文件系统不支持硬链接,会执行数据拷贝,耗时会很长。或者如果使用多数据目录(多磁盘分区),不同数据目录间的数据也需要完全拷贝,因为硬链接无法跨越磁盘。
3、恢复目标索引,类似一个关闭的索引刚被打开一样。


POST /index/_cache/clear 清理缓存

Elasticsearch Guide [7.17] » REST APIs » Index APIs » Clear cache API
https://www.elastic.co/guide/en/elasticsearch/reference/7.17/indices-clearcache.html

POST /<target>/_cache/clear 清理指定索引的缓存
POST /_cache/clear 清理全部缓存

默认清理全部缓存,可以指定清理 query, request, fielddata 三种缓存之一

POST /my-index-000001/_cache/clear?fielddata=true  // 只清理 fielddata 缓存
POST /my-index-000001/_cache/clear?query=true      // 只清理 query 缓存
POST /my-index-000001/_cache/clear?request=true    // 只清理 request 缓存

GET /index/_stats 查询索引统计信息

Elasticsearch Guide [7.17] » REST APIs » Index APIs » Index stats API
https://www.elastic.co/guide/en/elasticsearch/reference/7.17/indices-stats.html

GET /<target>/_stats/<index-metric> 查询指定索引的指定统计指标
GET /<target>/_stats 查询指定索引的全部统计指标
GET /_stats 查询全部索引的全部统计指标


索引模块

Elasticsearch Guide [7.17] » Index modules
https://www.elastic.co/guide/en/elasticsearch/reference/7.17/index-modules.html

索引的配置参数分为静态配置和动态配置:

  • 静态配置只能在创建索引时设置,或在已关闭的索引上设置
  • 动态配置可在打开的索引上通过 API /index/_settings 动态修改

静态配置

https://www.elastic.co/guide/en/elasticsearch/reference/current/index-modules.html#_static_index_settings

index.number_of_shards 主分片数

index.number_of_shards 索引的主分片(primary shards)数,默认值是 1
主分片数只能在创建索引时设定,已关闭的索引也无法修改分片数

默认分片数最大为 1024,可通过在每个节点上设置 export ES_JAVA_OPTS="-Des.index.max_number_of_shards=128" 来修改最大分片数限制

index.number_of_routing_shards 路由分片数

index.number_of_routing_shardsindex.number_of_shards 一起决定数据如何被路由到主分片上,具体分片公式在 Mapping 映射 -> 元数据字段 -> _routing 字段的解释中。
index.number_of_routing_shards 没有默认值,默认是空的。


动态配置

https://www.elastic.co/guide/en/elasticsearch/reference/current/index-modules.html#dynamic-index-settings

index.number_of_replicas 副本数

index.number_of_replicas 每个主分片的副本(replicas)数,默认值是 1

index.refresh_interval 刷新间隔

index.refresh_interval 后台定期自动 refresh 操作的间隔,默认值 1 秒,可设为 -1 来禁用 refresh
refresh 操作将内存缓冲区中的数据写入 Lucene segment 使之可读
未显式设置时,分片在 index.search.idle.after 秒未收到 search 请求后会停止后台的定时 refresh 任务,直到再次收到 search 请求。这么做的目的是为了加快批量索引的速度。

index.max_result_window 最大分页数据量

from + size 分页可获取的最大结果数,默认值 10000


Translog

Elasticsearch Guide [7.17] » Index modules » Translog
https://www.elastic.co/guide/en/elasticsearch/reference/current/index-modules-translog.html

插入文档时,ES 将文档写入内存缓冲区中,并将此次操作写入 translog,内存缓冲区的数据会以同步/异步方式刷入文件系统缓冲区(形成 Segment 段数据),之后可被检索到。文件系统缓冲区中的 Segment 以同步/异步方式 fysnc 到磁盘,形成 Lucene commit point,同时清理对应的 translog,完成持久化。

ES 使用 translog(transaction log) 事务日志来记录所有的操作,增删改一条记录时会把数据写到 translog 中。这样一旦发生崩溃,数据可以从 Translog 中恢复。但如果系统崩溃时 translog 内容在系统缓冲区而没写入磁盘,也会造成数据丢失。

Lucene Segment 内部是 LSM Tree 结构,这种数据结构充分利用磁盘顺序写速度大于随机写的特点,将随机写的操作都在内存里进行,当内存达到一定阈值后,使用顺序写一次性刷到磁盘里。

translog 中存储还未在 Lucene 中被安全持久化(即还不是 Lucene commit point 一部分,即还未 fsync 刷入磁盘)的操作记录,这部分操作虽然可被搜索到,但当出现系统掉电、OS崩溃、JVM崩溃等故障时这部分数据会丢失

写入 translog 的数据并不一定立即被写入磁盘,一般情况下,对磁盘文件的 write 操作,更新的只是内存中的页缓存,而脏页面不会立即更新到磁盘中,而是由操作系统统一调度,如由专门的 flusher 内核线程在满足一定条件时(如一定时间间隔、内存中的脏页面达到一定比例)将脏页面同步到磁盘上。因此如果服务器在 write 之后、磁盘同步之前宕机,则数据会丢失。

大量的segment 和 commit point 在磁盘中存在,会影响数据的读性能。因此 Lucene 会按照一定的策略将磁盘中的 segment 和 commit point 合并,多个小的文件合并成一个大的文件并删除小文件,从而减少磁盘中文件数据,提升数据的读性能。

相关可动态更新的配置参数:
index.translog.durability 控制 translog 是每 5 秒钟刷新一次还是每次请求都 fsync,这个参数有 2 个取值:

  • request 默认值,同步刷盘,每次请求(增、删、改、bulk批量操作)都要等fsync到磁盘且在主/从分片上提交后才会返回成功,所有已返回成功的数据操作都不会丢失
  • async 异步刷盘,translog每隔 index.translog.sync_interval 时间(默认5秒钟)fsync一次,写入性能会有提升,但期间出故障则此部分数据丢失

index.translog.sync_interval 控制 translog 多久 fsync 到磁盘,默认为 5 秒,允许的最小为 100ms

index.translog.flush_threshold_size translog 的大小超过这个参数后 flush 然后生成一个新的 translog,默认 512mb


ES 持久化

es 为了保证高可用,会定期将全部数据持久化到磁盘上。

Elasticsearch持久化过程详解
https://blog.csdn.net/aa1215018028/article/details/108746679


Elasticsearch 持久化存储过程

这个描述是 异步持久化的过程。

1、数据写入内存缓存区和 Translog 日志文件中。
当写一条数据 doc 的时候,一方面写入到内存缓冲区中,一方面同时写入到 Translog 日志文件中。

2、如果 index.translog.durability=async,内存缓存区满了或者每隔1秒(默认1秒),refresh 将内存缓存区的数据生成 index segment 文件并写入文件系统缓存区,此时 index segment 可被打开以供 search 查询读取,这样文档就可以被搜索到了(注意,此时文档还没有写到磁盘上);然后清空内存缓存区供后续使用。可见,refresh 实现的是文档从内存缓存区移到文件系统缓存区的过程。
如果 index.translog.durability=request 每次文档 CRUD 请求都要等 refresh 并 fsync 后才返回。

3、重复上两个步骤,新的 segment 不断添加到文件系统缓存区,内存缓存区不断被清空,而 translog 的数据不断增加,随着时间的推移,Translog 文件会越来越大。

4、当 Translog 长度达到一定程度的时候,会触发 flush 操作,否则默认每隔 30 分钟也会定时 flush,其主要过程:
4.1、执行 refresh 操作将内存缓存区中的数据写入到新的 segment 并写入文件系统缓存区,然后打开本 segment 以供 search 使用,最后再次清空内存缓存区。
4.2、一个 commit point 被写入磁盘,这个 commit point 中标明所有的 index segment。
4.3、文件系统中缓存的所有的 index segment 文件被 fsync 强制刷到磁盘,当 index segment 被 fsync 强制刷到磁盘上以后,就会被打开,供查询使用。
4.4、translog 被清空和删除,创建一个新的translog。


段合并

Elasticsearch Guide [7.17] » Index modules » Merge
https://www.elastic.co/guide/en/elasticsearch/reference/current/index-modules-merge.html

如果使用默认的 index.translog.durability=request 同步刷新方式,每个请求都会创建一个新的 Lucene Segment,这样会导致短时间内的段数量暴增。
段数目太多会带来较大的麻烦,每一个段都会消耗文件句柄、内存和cpu运行周期。此外,每个搜索请求都必须轮流检查每个段;所以段越多,搜索也就越慢。
Elasticsearch 通过在后台进行段合并来解决这个问题。

段合并的时候会将标记为已删除的文档和文档的旧版本从文件系统中清除

ES 中有后台线程根据 Lucene 的合并规则定期进行段合并操作,一般不需要用户担心或者采取任何行动。

通过 /index/_forcemerge API 可以手动强制段合并


存储类型(mmap内存映射)

Elasticsearch Guide [7.17] » Index modules » Store
https://www.elastic.co/guide/en/elasticsearch/reference/7.17/index-modules-store.html

存储类型用于配置索引数据在磁盘上的存储和访问方式。

默认情况下,Elasticsearch 会基于操作系统选择最优存储类型。

支持的存储类型:
fs 基于操作系统选择最优存储类型,当前在全部系统上都是 hybridfs
simplefs 在 7.15 版本中废弃,在 8.0 版本中会删除,使用 niofs 代替。对应 Lucene 的 SimpleFsDirectory 类型,直接随机访问文件,并发性能较差,
niofs 对应 Lucene 的 NIOFSDirectory 类型,使用 Java NIO 读写索引数据文件。允许多线程并发读取同一个文件。
mmapfs 对应 Lucene 的 MMapDirectory 类型,通过 mmap 零拷贝内存映射读写索引文件,内存映射会占用进程的 virt 虚拟地址空间,virt 大小等于被 mmap 映射的索引文件大小。使用这个存储类型要保证系统有足够的虚拟地址空间。对应到文件扩展名,就是 nvd(norms),dvd(doc values),tim(term dictionary),tip(term index),cfs(compound) 类型的文件使用 mmap 方式加载,其余使用 nio。
hybridfsniofsmmapfs 的混合,基于每种文件的读写模式选择最合适的文件系统类型。对于 Lucene term dictionary, norms, doc values 使用 mmap 内存映射打开,其他使用 NIOFSDirectory 打开。

node.store.allow_mmap 使用 mmapfshybridfs 存储类型时,是否允许开启 mmap 内存映射,默认值是允许。

操作系统的 mmap 虚拟内存区域个数会影响 Elasticsearch 读写索引数据的性能,sysctl -w vm.max_map_count=262144 提高进程的虚拟内存区域个数,sysctl vm.max_map_count 查看改后的值。

预加载文件系统缓存

Elasticsearch Guide [7.17] » Index modules » Store » Preloading data into the file system cache
https://www.elastic.co/guide/en/elasticsearch/reference/7.17/preload-data-to-file-system-cache.html

默认情况下,Elasticsearch 完全依赖操作系统的文件系统缓存来进行 I/O 操作。可以将常用的索引配置在 index.store.preload 中,实现 ES 启动时就预加载索引数据到系统缓存。
注意索引预加载可能会减慢索引的打开速度,并且只有当索引数据加载到物理内存后才可用。

索引预加载只是尽最大努力进行,具体是否生效依赖存储类型以及操作系统类型。

index.store.preload 静态参数,配置逗号分割的索引列表,默认为空,即不预加载任何索引到系统缓存。
例如 index.store.preload: ["nvd", "dvd"]
支持通配符,例如 index.store.preload: ["*"]


Index blocks 索引限制(锁)

Elasticsearch Guide [7.17] » Index modules » Index blocks
https://www.elastic.co/guide/en/elasticsearch/reference/7.17/index-modules-blocks.html

索引 block 用于限制在指定索引上的操作,用于限制读、写、元数据操作。

index.blocks.read_only

index.blocks.read_only 动态参数,设为 true 时索引的数据和元数据变为只读,设为 false 时索引数据和元数据可写。

index.blocks.read_only_allow_delete

index.blocks.read_only_allow_delete 动态参数,设为 true 时索引只读且 允许删除索引本身来释放空间,磁盘分配器在节点的磁盘使用率超过洪水位时会自动添加这个block,低于高水位时会自动解除此block

注意:**index.blocks.read_only_allow_delete 为 true 时允许删的是索引本身,而不是索引内的文档**,删除索引内文档可能还会导致空间占用更大。所以 index.blocks.read_only_allow_delete 为 true 时是不允许删除索引内的文档的。

index.blocks.read

index.blocks.read 动态参数,设为 true 时禁用索引上的读操作

index.blocks.write

index.blocks.write 动态参数,设为 true 时禁用索引上的写操作,这个设置不影响元数据操作,期间依然可读写索引元数据。

index.blocks.metadata

index.blocks.metadata 动态参数,设为 true 时禁用索引元数据读写。


索引排序

Elasticsearch Guide [7.17] » Index modules » Index Sorting
https://www.elastic.co/guide/en/elasticsearch/reference/current/index-modules-index-sorting.html

Elasticsearch 从 6.0 版本开始引入了一个新的特征,叫 Index Sorting(索引排序)。用户可以将索引数据按照指定的顺序存储在硬盘上,这样在搜索排序取前 N 条时,不需要访问所有的匹配中的记录再进行排序,只需要访问前 N 条记录即可。

默认情况下, Lucene 没有任何排序字段,可通过配置 index.sort.* 来设置 Segment 内文档的排序字段。

例1、创建索引时指定单个排序字段,按 date 倒序排序:

PUT my-index-000001
{
  "settings": {
    "index": {
      "sort.field": "date", 
      "sort.order": "desc"  
    }
  },
  "mappings": {
    "properties": {
      "date": {
        "type": "date"
      }
    }
  }
}

例2、创建索引时指定多个排序字段,按 username 正序、date 倒序排序:

PUT my-index-000001
{
  "settings": {
    "index": {
      "sort.field": [ "username", "date" ], 
      "sort.order": [ "asc", "desc" ]       
    }
  },
  "mappings": {
    "properties": {
      "username": {
        "type": "keyword",
        "doc_values": true
      },
      "date": {
        "type": "date"
      }
    }
  }
}

文档会先按 username 正序排序,username 相同的按 date 倒序排序。

早期中断

假如要搜索按日期倒序排序的前 N 条数据,无其他条件:

  • 如果索引中的文档没有排序,需要遍历索引中的全部文档后找出排序的 TopN 数据,开销巨大。
  • 如果索引中的文档已经按日期倒序排好序了,只需要访问每个 segment 中的前 N 条数据即可中断请求,这就是 早期中断(Early termination)

例如有上文中按 date 倒序排序的索引,执行下面的检索时,es发现检索条件的排序字段与索引中文档存储的排序字段一致,只需访问每个 segment 中的前 10 条数据,可更快的返回。如果完全不需要计数,可以将 track_total_hits 设为 false,进一步加速检索。

GET /my-index-000001/_search
{
  "size": 10,
  "sort": [
    { "date": "desc" }
  ],
  "track_total_hits": false
}

索引压力

Elasticsearch Guide [7.17] » Index modules » Indexing pressure
https://www.elastic.co/guide/en/elasticsearch/reference/7.17/index-modules-indexing-pressure.html

indexing_pressure.memory.limit 外部索引请求可占用的堆内存最大值,默认 JVM 堆内存的 10%。当超出次阈值时,节点会拒绝执行新的协调和主分片操作。当副本操作超过 1.5 被次阈值时,节点会拒绝执行副本操作。

FST 索引前缀

FST(Finite State Transducer)
Lucene 会为每个词都生成倒排索引,数据量较大。所以倒排索引对应的倒排表被存放在磁盘上。这样如果每次查询都直接读取磁盘上的倒排表,再查询目标关键词,会有很多次磁盘 IO,严重影响查询性能。为优化磁盘 IO,Lucene 引入倒排索引的二级索引 FST(Finite State Transducer),原理类似 前缀树/字典树/Trie树,加速查询。

用户查询时,先通过关键词(Term)查询内存中的 FST,找到该 Term 对应的 Block 首地址。再读磁盘上的分词表,将该 Block 加载到内存,遍历该 Block,查找到目标 Term 对应的 DocID。再按照一定的排序规则,生成 DocID 的优先级队列,再按该队列的顺序读取磁盘中的原始数据(行存或列存)。

Lucene 使用 FST 实现 Term Index,Term Index 是 Term Dictionary 的索引,可以快速查找一个 Term 是否在 Dictionary 中;并且能够快速定位 Block 的位置。


elasticsearch-fst

elasticsearch-fst2

7.7 开始将 FST 通过mmap加载

从 ES 7.7 版本开始,将 tip(term index) 文件修改为通过 mmap 的方式加载,这使 FST 占据的内存从堆内转移到了堆外由操作系统的 pagecache 管理。
7.7 之前 FST 永驻堆内存,无法被 GC 回收,FST 约占堆内存总量的 50% - 70%,每 GB 索引大约需要几 MB 的 FST,则 10TB 索引数据需要 10-15 GB 的 FST。
将数据结构从 JVM 堆移动到磁盘,并依赖文件系统缓存(通常称为页面缓存或 OS 缓存)将热数据保存在内存中。

7.7 版本中的新改进:显著降低 Elasticsearch 堆内存使用量
https://www.elastic.co/cn/blog/significantly-decrease-your-elasticsearch-heap-memory-usage

Term 词条: 索引里面最小的存储和查询单元,对于英文来说是一个单词,对于中文来说一般指分词后的一个词。
Term Dictionary 词典:是词条 Term 的集合。搜索引擎的通常索引单位是单词,单词词典是由文档集合中出现过的所有单词构成的字符串集合,单词词典内每条索引项记载单词本身的一些信息以及指向“倒排列表”的指针。
Postings List 倒排表:一个文档通常由多个词组成,倒排表记录的是某个词在哪些文档里出现过以及出现的位置。每条记录称为一个倒排项(Posting)。倒排表记录的不单是文档编号,还存储了词频等信息。
Inverted File 倒排文件:所有单词的倒排列表往往顺序地存储在磁盘的某个文件里,这个文件被称之为倒排文件,倒排文件是存储倒排索引的物理文件。

Lucene 段文件内容

Lucene 一个 Index 会包含多个 Segment,一个 Segment 又由多个文件共同组成:
xx.tip:存储 Term Index
xx.tim:存储 Term Dictionary
xx.doc:存储 Postings 的 DocId 信息和 Term 的词频
xx.fnm:存储文档 Field 的元信息
xx.fdx:存储文档的索引,使用 SkipList 来实现
xx.fdt:存储具体的文档
xx.dvm:存储 DocValues 元信息
xx.dvd:存储具体 DocValues 数据
Lucene 没有更新跟删除逻辑,所有对 Lucene 的更新都是 Append 一个新 Doc 到 Segment。


Mapping 映射

Elasticsearch Guide [7.17] » Mapping
https://www.elastic.co/guide/en/elasticsearch/reference/7.17/mapping.html

mapping 用来规定 index 中的字段数据类型,类似 metadata 元数据、schema 等概念。


动态 Mapping

Elasticsearch Guide [7.17] » Mapping » Dynamic mapping
https://www.elastic.co/guide/en/elasticsearch/reference/7.17/dynamic-mapping.html

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

字段类型自动映射

Elasticsearch Guide [7.17] » Mapping » Dynamic mapping » Dynamic field mapping
https://www.elastic.co/guide/en/elasticsearch/reference/7.17/dynamic-field-mapping.html

JSON 字段类型的自动识别如下:

JSON数据类型 “dynamic”:”true” “dynamic”:”runtime”
null 不添加字段 不添加字段
true 或 false boolean boolean
double float double
integer long long
object object 不添加字段
array 取决于第一个非空元素的类型 取决于第一个非空元素的类型
日期格式的string date date
数字格式的string float 或 long double 或 long
非日期且非数字格式的string 带 .keyword 的 text keyword

显式 Mapping

Elasticsearch Guide [7.17] » Mapping » Explicit mapping
https://www.elastic.co/guide/en/elasticsearch/reference/7.17/explicit-mapping.html

为什么需要自定义mapping?

虽然 elasticsearch 中已经有动态 mapping(Dynamic Mapping),而且新增字段默认也会添加新的 mapping,但是毕竟是机器,有时会推算的不对,比如地理位置信息,特殊格式化的日期类型等。这时,如果需要 es 提供排序、聚合等查询功能,就不能满足我们的需求。

通过手动设置 mapping,我们可以
指定哪个字段作为全文索引
指定哪个字段包含数字、日志、地理位置信息
日期的格式
定义动态 mapping 的规则

显式自定义 mapping 和 动态 mapping 可以结合使用,例如对于想明确指定类型的字段使用自定义 mapping,其他字段使用动态 mapping

mapping 会把 JSON 文档文档映射成 Lucene 所需要的扁平格式

一个 mapping 属于一个索引的 type,每个文档都属于一个 Type,一个 type 有一个 mapping 定义
从 es 7.0 开始,不需要在 mapping 定义中指定 type 信息,因为默认每个索引只有一个 type 叫 _doc


创建index时指定mapping

创建 index 的同时可指定 mapping,例如

PUT /my-index-000001
{
    "mappings":{
        "properties":{
            "firstName":{
                "type":"text", //text类型全文搜索
                "fields":{
                    "keyword":{
                        "type":"keyword", //keyword支持聚合查询
                        "ignore_above":256
                    }
                }
            },
            "lastName":{
                "type":"keyword",
                "null_value":"NULL" //支持字段为null,只有keyword类型支持
            },
            "mobile":{
                "type":"text",
                "index":false //此字段不被索引
            },
            "address":{
                "type":"text",
                "index_options":"offsets" //控制倒排索引记录的内容。offsets最多,记录四个
            }
        }
    }
}

添加字段到已有mapping中

可以使用 Update mapping API 向已有 index 的 mapping 中添加一个或多个字段,例如:

PUT /my-index-000001/_mapping
{
  "properties": {
    "employee-id": {
      "type": "keyword",
      "index": false
    }
  }
}

通过上面的调用,添加了一个 employee-id 字段,类型为 keyword,但不被索引。


更新已有index的mapping

不能修改已有字段的数据类型,否则索引数据会失效,只能修改 字段属性

如果想修改索引的字段类型,可以创建一个新索引,然后将已有索引的数据重新索引(reindex)过去。
https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-reindex.html


元数据字段

Elasticsearch Guide [7.17] » Mapping » Metadata fields
https://www.elastic.co/guide/en/elasticsearch/reference/7.17/mapping-fields.html


_id 字段

Elasticsearch Guide [7.17] » Mapping » Metadata fields » _id field
https://www.elastic.co/guide/en/elasticsearch/reference/7.17/mapping-id-field.html

每个文档都有唯一的一个 _id 字段,可以通过 GET /index/_doc/_id 来查询。_id 可在插入文档时指定,也可以由 ES 自动生成,_id 字段的类型无法在 mapping 中配置。


_routing 字段(Elasticsearch 分片策略/片键)

Elasticsearch Guide [7.17] » Mapping » Metadata fields » _routing field
https://www.elastic.co/guide/en/elasticsearch/reference/7.17/mapping-routing-field.html

文档根据下面的公式被路由到指定的分片中:

routing_factor = num_routing_shards / num_primary_shards
shard_num = (hash(_routing) % num_routing_shards) / routing_factor

num_routing_shards 是索引的 index.number_of_routing_shards 配置值
num_primary_shards 是索引的 index.number_of_shards 配置值

默认的 _routing 路由字段(片键)是 _id,可在文档级别自定义路由字段,插入文档时指定路由字段后,搜索时也要指定路由字段。


字段数据类型

Elasticsearch Guide [7.17] » Mapping » Field data types
https://www.elastic.co/guide/en/elasticsearch/reference/7.17/mapping-types.html

text

https://www.elastic.co/guide/en/elasticsearch/reference/current/text.html

text 类型被用来索引长文本,在建立索引前会将这些文本进行分词,转化为词的组合,建立索引。允许 es 来检索这些词语。text 类型不能用来排序和聚合。

keyword

https://www.elastic.co/guide/en/elasticsearch/reference/current/keyword.html

Keyword 类型不需要进行分词,可以被用来检索过滤、排序和聚合。keyword 类型字段只能用本身来进行检索

text和keyword的区别

text/keyword,对应 json 中的 String,一般会设置字段为 text,然后新建个 keyword 子字段,设置为 keyword 类型

date

https://www.elastic.co/guide/en/elasticsearch/reference/current/date.html

long, integer, short, byte, double, float
boolean
IPv4&IPv6

object

https://www.elastic.co/guide/en/elasticsearch/reference/current/object.html

创建一个包含 object 类型字段 location 的索引 user

PUT /user
{
    "mappings": {
        "properties": {
            "name": {
                "type": "text"
            },
            "location": {
                "properties": {
                    "left": {
                        "type": "float"
                    },
                    "top": {
                        "type": "float"
                    },
                    "width": {
                        "type": "float"
                    },
                    "height": {
                        "type": "float"
                    }
                }
            }
        }
    }
}

nested

https://www.elastic.co/guide/en/elasticsearch/reference/current/nested.html
复杂类型 - 对象和嵌套对象
对象类型 / 嵌套类型 (nested)

nested和object区别

特殊类型
geo_point&geo_shape/percolator


字段映射参数

dynamic

Elasticsearch Guide [7.17] » Mapping » Mapping parameters » dynamic
https://www.elastic.co/guide/en/elasticsearch/reference/7.17/dynamic.html

dynamic 参数控制是否可自动添加字段,可取值如下,默认值是 true

  • true 自动添加新字段到 mapping
  • runtime 新字段作为 运行时字段 被添加到 mapping,这些字段不会被索引,但查询时会在 _source 字段中返回。
  • false 新字段会被忽略。这些字段不被索引,不可搜索,不会被添加到 mapping,但数据依然会被存储,依然会在查询结果的 _source 字段中返回。
  • strict 如果检测到新字段,会抛出异常,文档无法插入,新字段必须显式添加到 mapping 中。

动态 dynamic 属性设置

PUT dynamic_mapping_test/_mapping
{
  "dynamic": false
}

PUT dynamic_mapping_test/_mapping
{
  "dynamic": strict
}

index

index 控制当前字段是否被索引,默认为 true,如果设置成 false,该字段不可被搜索

index_options

index_options 控制倒排索引记录的内容

  • docs 记录 doc id
  • freqs 记录 doc id 和 term frequencies
  • positions 记录 doc id/term frequencies/term position
  • offsets 记录 doc id/term frequencies/term position/character offects

null_value

null_value 需要对字段为 null 值实现搜索
只有 keyword 类型支持设定为 null_value

copy_to

Elasticsearch Guide [7.17] » Mapping » Mapping parameters » copy_to
https://www.elastic.co/guide/en/elasticsearch/reference/7.17/copy-to.html

copy_to 可以将字段的值拷贝到另一个字段中,常用于将多个字段的值合并到同一个字段方便检索
将字段内容拷贝到目标字段,查询时可以用目标字段作为查询条件,但是不会出现 _source 中

例如,将 first_name 和 last_name 字段的内容拷贝到 full_name 中,然后可直接在 full_name 字段上搜索全名。

PUT my-index-000001
{
  "mappings": {
    "properties": {
      "first_name": {
        "type": "text",
        "copy_to": "full_name" 
      },
      "last_name": {
        "type": "text",
        "copy_to": "full_name" 
      },
      "full_name": {
        "type": "text"
      }
    }
  }
}

fields

fields
在字段下新增一个字段,可以自定义类型,使用不同的 analyzer
可以用来实现以拼音方式搜索中文字段

analyzer

analyzer 分词器
standard 默认分词器,按词切分,小写处理
simple 按照非字母切分(符号被过滤),小写处理
stop 小写处理,停用词过滤(the、a、is)
whitespace 按照空格切分,不转小写
keyword 不分词,直接将输入当作输出
patter 正则表达式,默认 \W+(非字符分隔)
language 提供了 30 多种常见语言的分词器(english、german)
中文分词 icu_analyzer、ik、thulac

doc_values 用于加速聚合/排序的正排索引

Elasticsearch Guide [7.17] » Mapping » Mapping parameters » doc_values
https://www.elastic.co/guide/en/elasticsearch/reference/7.17/doc-values.html

排序、聚合、脚本等查询需要通过访问文档的字段值来进行,这是和使用倒排索引查询不同的一种数据访问模式。这类查询不使用倒排索引,无法直接根据 term 找到对应的文档,而是需要通过访问文档来找到他包含的 term 值。

Doc Values 是磁盘上的一种数据结构,在索引文档时创建。
Doc Values 是一种类似 doc -> field value 的正排索引映射关系,可快速找到文档包含的 term,可使得排序和聚合查询更高效
除了 textannotated_text,全部字段类型都支持 Doc values。

倒排索引示例,方便查找某个 term 在哪些文档中,每个字段都有倒排索引,这里只示意 Field1 的:
| Field1 | Doc_1 | Doc_2 | Doc_3 |
|——–|——-|——-|——-|
| brown | X | X | |
| color | | X | X |
| dog | | | X |

Doc Values 正排索引示例,方便查找某个 Doc 中包含哪些 term:
| Doc | Field1 | Field2 |
|——-|————–|————-|
| Doc_1 | brown, color | meat |
| Doc_2 | brown | fruit, meat |
| Doc_3 | dog, color | meat |

之后的查询如果需要按 Field2 聚合,通过查 Doc Values 可以知道 Doc_1 和 Doc_3 的 Field2 字段值相同,可快速聚合。

支持 Doc values 的字段默认开启 doc_values 功能,如果确认某个字段不需要用来做排序、聚合、脚本查询,可以通过 "doc_values": false 关闭 doc_values 支持,以便节省磁盘空间。
注意:"doc_values": false 的字段不支持排序、聚合、脚本查询。
例如:

PUT my-index-000001
{
  "mappings": {
    "properties": {
      "status_code": { 
        "type":       "keyword"
      },
      "session_id": { 
        "type":       "keyword",
        "doc_values": false
      }
    }
  }
}

eager_global_ordinals 全局序号

Elasticsearch Guide [7.17] » Mapping » Mapping parameters » eager_global_ordinals
https://www.elastic.co/guide/en/elasticsearch/reference/7.17/eager-global-ordinals.html

ES 使用 Doc Values 正排索引结构来支持聚合、排序、脚本查询等需要访问文档字段值的操作。
ES 使用全局序号代替真正的 term 值来压缩优化 Doc Values 的存储,可提高聚合查询的性能、节省存储 Doc Values 使用的磁盘空间、节省 fielddata 缓存使用的内存空间。

Global Ordinals 是 Shard 级别的,因此当一个 Shard 的 Segment 发生变动时就需要重新构建 Global Ordinals,比如有新数据写入导致产生新的Segment、Segment Merge等情况。当然,如果Segment没有变动,那么构建一次后就可以一直利用缓存了(适用于历史数据)。

默认情况下,Global Ordinals 是在收到聚合查询请求并且该查询会命中相关字段时构建,而构建动作是在查询最开始做的,即在Filter之前。在遇到某个字段的值种类很多时会变的非常慢,严重影响聚合查询速度。在追求查询的场景下很影响查询性能。可以使用 eager_global_ordinals,即在每次refresh以后即可更新字典,字典常驻内存,减少了查询的时候构建字典的耗时。


7.x 开始移除 Type

Removal of mapping types
https://www.elastic.co/guide/en/elasticsearch/reference/current/removal-of-types.html

index 中的 document 可以分组,这种分组就叫做 Type,比如 twitter 索引中可以有 user 类型的数据和 tweet 类型的数据。

ES 6.x 之前的版本,可在一个索引库下创建多个 type
ES 6.x 版只允许每个 Index 包含一个 Type,并预告 7.x 版将会彻底移除 Type
ES 7.x 开始,彻底废弃一个 index 下多个 type 支持,包括 api 层面

为什么 ES 要移除 Type?

一开始 es 发布时,声明 index 对应关系数据库中的 database,type 对应 table,document 对应 row 数据行。
但其实并不是这样,关系数据库中不同 table 间的同名字段是互相独立互不影响的,但 es 中同一个 index 下不同 type 间的同名字段是互相影响的,其实在 Lucene 内部是存储在同一字段中的。
还以 twitter 索引中有 user 和 tweet 两种 type 的数据为例,比如两类数据中都有 user_name 字段,则 Lucene 内部都使用 user_name 字段索引,所以两类数据中 user_name 字段的类型必须一致。
某些情况下我们可能想要不同 type 中的同名字段是不同类型,比如一个 type 中 deleted 是 date 类型,另一个 type 中是 boolean 类型,这种是实现不了的。
此外,在同一个 index 中存储不同类型的文档会导致数据稀疏,和 Lucene 的文档压缩能力冲突。


Document 文档 API

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

读写文档

Elasticsearch Guide [7.17] » REST APIs » Document APIs » Reading and Writing documents
https://www.elastic.co/guide/en/elasticsearch/reference/7.17/docs-replication.html

基本写模型

1、协调阶段(coordinating):根据路由规则将文档路由到主分片
2、主分片处理阶段(primary):验证文档,在主分片执行操作,转发到 in-sync 副本

PUT /index/_doc/_id 指定id创建文档

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

向指定的 /Index/Type/ID 发送 PUT 请求,就可以在 Index 里面新增一条记录。
ID 是调用方指定的唯一ID,如果已存在,则会完全替换更新文档并增加其版本 version
**在 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
}

外部版本号

https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-index_.html#index-versioning
ES 允许不使用内置的 version 进行版本控制,可以自定义使用外部的 version,此时将 version_type 设为 external,此时可传入一个大于 0 小于 9.2e+18 的 long 型 version 参数。
使用外部版本号时,只有当你提供的 version 比当前文档的 _version 大的时候,才能完成修改(包括删除)。

例如常见的双写方案,MySQL 和 ES 各存一份数据,ES 用于加速查询,此时可以将 version 维护在 MySQL 中。
例如:

PUT my-index-000001/_doc/1?version=2&version_type=external
{
  "user": {
    "id": "elkbee"
  }
}

POST /index/_doc 不指定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


POST /index/_update/_id 指定id更新文档

Elasticsearch Guide [7.16] » REST APIs » Document APIs » Update API
https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-update.html

POST /<index>/_update/<_id>

通过脚本更新文档。脚本可更新、删除或跳过文档。更新 API 还支持传入一部分文档内容,最终合并到已存在文档中。如果想完全替换更新已存在文档,使用带 ID 的文档创建 API 即可。

update API 实现的逻辑中,其实可以理解为三步操作:
1、qeury:通过文档 ID 去 GET 文档,此时可获取文档的 _version 版本
2、update:根据 script 脚本来更新 document;
3、reindex:将更新后的 document 重新写回到索引,

如果在 GET 和 Reindex 期间,文档被更新,_version 值发生变化,则更新失败。可以使用 retry_on_conflict 参数来设置当发生更新上述情况更新失败时,自动重试的次数。retry_on_conflict 的默认值为0,即不重试。

因此,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 来实现

之前版本的api是 POST /index/_doc/_id/_update

POST test/_update/1
{
  "script" : {
    "source": "ctx._source.counter += params.count",
    "lang": "painless",
    "params" : {
      "count" : 4
    }
  }
}

POST /index/_update_by_query 根据查询更新

Elasticsearch Guide [7.16] » REST APIs » Document APIs » Update By Query API
https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-update-by-query.html


GET /index/_doc/_id 根据id查询文档

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

GET <index>/_doc/<_id> 根据 ID 查询文档

ES 文档上的每一次写操作,包括删除,都会使文档的 _version 递增,已删除文档的 version 会在一小段时间内保持可见,时间由配置项 index.gc_deletes 决定,默认是 60 秒。

例如 GET 'http://localhost:9200/article/_doc/uv_ZjXEBrN9oq5tgVMuj' 返回

{
    "_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
}

DELETE /index/_doc/_id 删除文档

Elasticsearch Guide [8.1] » REST APIs » Document APIs » Delete API
https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-delete.html

DELETE /<index>/_doc/<_id>

通过修改 version 进行删除,异步合并 Segment 时才真正删除。


POST /index/_delete_by_query 根据条件删除

Elasticsearch Guide [7.17] » REST APIs » Document APIs » Delete by query API
https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-delete-by-query.html

POST /<target>/_delete_by_query 根据条件删除文档,使用和 search 接口相同的查询条件语法,可使用 URI 条件或 body 条件。

例如

POST /my-index-000001/_delete_by_query
{
  "query": {
    "match": {
      "user.id": "elkbee"
    }
  }
}

删除索引中的全部文档(清空索引)

使用空条件删除即可

POST /my-index-000001/_delete_by_query
{
    "query": {
        "match_all": {}
    }
}

条件删除原理

_delete_by_query 并不是真正意义上物理文档删除,而是只是版本变化并且对文档增加了删除标记。当我们再次搜索的时候,会搜索全部然后过滤掉有删除标记的文档。因此,该索引所占的空间并不会随着该 API 的操作磁盘空间会马上释放掉,只有等到下一次段合并的时候才真正被物理删除,这个时候磁盘空间才会释放。相反,在被查询到的文档标记删除过程同样需要占用磁盘空间,这个时候,你会发现触发该 API 操作的时候磁盘不但没有被释放,反而磁盘使用率上升了。


POST /_bulk 批量操作

Elasticsearch Guide [7.17] » REST APIs » Document APIs » Bulk API
https://www.elastic.co/guide/en/elasticsearch/reference/current/docs-bulk.html

POST /_bulk
POST /<target>/_bulk

例如

POST _bulk
{ "index" : { "_index" : "test", "_id" : "1" } }
{ "field1" : "value1" }
{ "index" : { "_index" : "test"} }
{ "field1" : "value1" }
{ "delete" : { "_index" : "test", "_id" : "2" } }
{ "create" : { "_index" : "test", "_id" : "3" } }
{ "field1" : "value3" }
{ "update" : {"_id" : "1", "_index" : "test"} }
{ "doc" : {"field2" : "value2"} }

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


乐观并发控制

Elasticsearch Guide [7.17] » REST APIs » Document APIs » Optimistic concurrency control
https://www.elastic.co/guide/en/elasticsearch/reference/7.17/optimistic-concurrency-control.html

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

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

409/Conflict

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


Search 搜索 API

Search APIs
https://www.elastic.co/guide/en/elasticsearch/reference/current/search.html


GET /index/_doc/_search 搜索

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

通过 _search 接口可在一个或多个索引或数据流上搜索数据,可通过 q 查询串来指定查询条件,也可以通过 body 参数指定查询条件。

在指定的索引上搜索
GET /<target>/_search
POST /<target>/_search
<target> 路径参数 target 可以是索引名、索引别名、数据流名,还 可以是逗号分割的多个索引/数据流,并且支持通配符 *

如果想在全部索引上搜索,可以使用 *_all,或者忽略 target 参数,例如:
GET /_search
POST /_search

_search 接口的一些参数既可以放在 query string 上,也可以放在 body 中,如果两个地方同时指定了,将以 query string 中的为准

seq_no_primary_term Query 或 Body 参数,boolean 类型,为 true 时返回 _seq_no_primary_term 字段。
version Query 或 Body 参数,boolean 类型,为 true 时返回 _version 字段。

如果不带任何查询参数就会返回 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基础介绍"
                }
            }
        ]
    }
}

track_total_hits

track_total_hits query 参数,integer 或 bool 类型。

  • 如果设为一个整数,表示精确计算命中个数的最大值,默认值 10000,查询条件命中的文档个数超过此值时不再精确计算命中个数。
  • 如果设为 true 将每次都精确计算命中个数,会比较耗性能。
  • 如果设为 false 将完全不计算命中个数,返回结果无 hits.total 字段。

默认 _search 接口返回的 hits.total.value 值最大为 10000,搜索条件命中的文档个数超过 10000 时就不准了
命中个数大于 10000 时,hits.total.relation=gte 表示实际命中个数是大于 10000 的。
命中个数小于 10000 时,hits.total.relation=eq value 是准确的命中数。

{
    "hits":{
        "total":{
            "value":10000,
            "relation":"gte"
        }
    }
}

_source 指定返回字段

_source 参数指定返回结果 hits._source 中的字段数,默认值为 true

_source 参数可放在 query string,也可以放在 body 中。

放在 query string 中时,支持以下值:

  • true 返回文档的全部字段
  • false 不返回任何字段
  • <string> 逗号分割的字段名列表,支持 * 通配符

放在 body 中时可内嵌 excludesexcludes 字段

比如只返回文档的 _iduser_id 字段

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

指定返回的和排除的

GET /_search
{
    "_source": {
        "includes": [ "obj1.*", "obj2.*" ],
        "excludes": [ "*.description" ]
    },
    "query" : {
        "term" : { "user" : "kimchy" }
    }
}

The _source option
https://www.elastic.co/guide/en/elasticsearch/reference/current/search-fields.html#source-filtering


q查询(URI搜索/lucene语法查询)

https://www.elastic.co/guide/en/elasticsearch/reference/7.17/search-search.html#search-api-query-params-q

q 搜索就是 URI 搜索,通过 URI 参数 q 来指定查询相关参数。q 搜索使用 Lucene 语法,不支持完整的 ES DSL 语法,但让我们可以快速做一个查询。
q 搜索会覆盖 body 中的 query 查询参数,如果同时指定,只会使用路径中的 q 参数。

例1、从索引 tweet 里面搜索字段 user 为 kimchy 的记录
GET /twitter/_search?q=user:kimchy

例2、从索引 tweet,user 里面搜索字段 user 为 kimchy的记录
GET /twitter/tweet,user/_search?q=user:kimchy
GET /kimchy,elasticsearch/_search?q=tag:wow

例3、从所有索引里面搜索字段 tag 为 wow 的记录
GET /_all/_search?q=tag:wow
GET /_search?q=tag:wow


sort 排序

Elasticsearch Guide [7.17] » Search your data » Sort search results
https://www.elastic.co/guide/en/elasticsearch/reference/7.17/sort-search-results.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"
    }
  }
}

分页

Elasticsearch Guide [7.17] » Search your data » Paginate search results
https://www.elastic.co/guide/en/elasticsearch/reference/7.17/paginate-search-results.html

from/size 分页

和 SQL 使用 LIMIT 关键字返回单个 page 结果的方法相同,Elasticsearch 接受 from 和 size 参数:
from 参数,可放在路径中,也可放在 body 中,指定应该跳过的初始结果数量,默认是 0
size 参数,可放在路径中,也可放在 body 中,指定应该返回的结果数量,默认是 10
默认 from + size 的值不可超过 10000,可通过参数 index.max_result_window 修改这个限制值,如果需要对多于 10000 的数据进行分页,可以用 search_after 参数

如果每页展示 5 条结果,下面 3 个请求分别查询第 1 到 3 页的结果:

GET /_search?size=5
GET /_search?size=5&from=5
GET /_search?size=5&from=10
分布式系统中的深度分页问题

假设在一个有 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分页(缓存快照,无法跳页,不再推荐)

https://www.elastic.co/guide/en/elasticsearch/reference/7.17/paginate-search-results.html#scroll-search-results

ES 不再推荐 scroll 分页,深度分页应使用 search_after 代替

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

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

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

初始化的时候就像是普通的 search 一样
其中的 scroll=3m 代表当前查询的数据缓存 3 分钟
结果会返回一个 _scroll_id 用于之后使用

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(实时分页,无法跳页,推荐)

https://www.elastic.co/guide/en/elasticsearch/reference/7.17/paginate-search-results.html#search-after

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

search_after 分页使用前一页中的一组排序值来检索匹配的下一页数据。
使用 search_after 分页需要多次请求的 query 和 sort 参数完全一致,如果在处理多个请求期间发生了 refresh 操作(也就是有新数据写入),则结果可能不一致。为避免这种情况发生,可以使用 PIT 时间点生成数据快照

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

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


GET /index/_count 条件计数

Count API
https://www.elastic.co/guide/en/elasticsearch/reference/current/search-count.html

使用和 _search 接口相同的查询条件,可用于查询符合条件的文档个数。


PIT 时间点

Elasticsearch Guide [7.17] » REST APIs » Search APIs » Point in time API
https://www.elastic.co/guide/en/elasticsearch/reference/7.17/point-in-time-api.html

在 Elasticsearch 中搜索数据时默认都是在最新实时数据上进行,有些情况下,比如 search_after 分页中需要将 ES 数据固定在一个数据快照上,此时可用 时间点(point in time, PIT) 解决。

POST /index/_pit 时间点

搜索请求前需要先显式地创建时间点,keep_alive 参数指定时间点的有效时间,例如:

POST /my-index-000001/_pit?keep_alive=1m

返回一个 id 字段如下:

{
    "id": "64O1AwESdmVoaWNsZV9mb3VyX3doZWVsFmhMNWNLdnJ0U0NDV2tRUGtWTXdSd1EAFk9LV3JSdzEwUTVPRW1RZksyYjd2bUEAAAAAAAAAAAEWa0p4SnloRFFRTEttVDRmOUJnN0ttZwABFmhMNWNLdnJ0U0NDV2tRUGtWTXdSd1EAAA=="
}

下次请求时将 id 的值放入 pit.id 参数中传过去,例如

POST /_search 
{
    "size": 100,
    "query": {
        "match" : {
            "title" : "elasticsearch"
        }
    },
    "pit": {
        "id":  "46ToAwMDaWR5BXV1aWQyKwZub2RlXzMAAAAAAAAAACoBYwADaWR4BXV1aWQxAgZub2RlXzEAAAAAAAAAAAEBYQADaWR5BXV1aWQyKgZub2RlXzIAAAAAAAAAAAwBYgACBXV1aWQyAAAFdXVpZDEAAQltYXRjaF9hbGw_gAAAAA==", 
        "keep_alive": "1m"  
    }
}

注意:

  • 带有 pit 参数的 search 请求不能指定 index, routing 和 preference 参数,这些参数都从 PIT 时间点中获取。
  • pit.id 告诉 es 在指定的 PIT 时间点上下文中进行查询。
  • keep_alive 参数告诉 es 本次查询需要将 PIT 时间点的有效期延长多长时间。这个参数不需要太大,只需要够本次请求执行即可。

PIT 时间点会消耗磁盘和内存资源

PIT 时间点会消耗磁盘和内存资源:

  • 打开 PIT 时间点会阻止删除不需要的 Lucene Segment,因为这些 segment 还要用到,所以会占用磁盘空间。
  • 打开过多的 PIT 时间点会占用堆内存,尤其在频繁删除、更新的索引上。

DELETE /_pit 删除时间点

超过 keep_alive 时间的 PIT 时间点会自动删除。
为了节省资源,不用的时间点应该及时主动关闭:

DELETE /_pit
{
    "id" : "46ToAwMDaWR5BXV1aWQyKwZub2RlXzMAAAAAAAAAACoBYwADaWR4BXV1aWQxAgZub2RlXzEAAAAAAAAAAAEBYQADaWR5BXV1aWQyKgZub2RlXzIAAAAAAAAAAAwBYgACBXV1aWQyAAAFdXVpZDEAAQltYXRjaF9hbGw_gAAAAA=="
}

返回:

{
   "succeeded": true, 
   "num_freed": 3     
}

“succeeded”: true 表示和此时间点相关的所有搜索上下文都已被清除,num_freed 是删除的搜索上下文的个数。


slice 搜索分片

https://www.elastic.co/guide/en/elasticsearch/reference/7.17/point-in-time-api.html#search-slicing


Query DSL

Elasticsearch Guide [7.17] » Query DSL
https://www.elastic.co/guide/en/elasticsearch/reference/7.17/query-dsl.html

DSL(Domain Specific Language) 领域特定语言,Elasticsearch 里描述检索条件的一种语言,格式为 JSON,包括两种类型的语句:

  • Leaf query clauses 叶子查询,叶子查询匹配具体字段的值,例如 match, term, range,叶子查询可单独使用。
  • Compound query clauses 复合查询,以逻辑方式组合多个叶子、复合查询为一个查询,例如 bool

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


查询(query)和过滤(filter)上下文

Query and filter context
https://www.elastic.co/guide/en/elasticsearch/reference/current/query-filter-context.html

默认情况下,es 会根据相关性对每个匹配结果进行排序,相关性描述每个文档和查询条件的匹配度。
相关性分值是个正的浮点数,在检索结果每个文档的 _score 字段返回,这个值越大,表示此文档和查询条件越相关。
相关性分值计算取决于查询子句是在 query 上下文中还是在 filter 上下文中。

查询(query)上下文

query 上下文中的查询语句解决 “这个文档有多匹配这个查询条件?” 的问题,所以除了决定文档是否匹配,还会计算一个相关性分值放到 _score 字段返回。
查询条件中有 query 参数时,就是 query 上下文。

过滤(filter)上下文

filter 上下文中的查询语句解决 “这个文档是否匹配这个查询条件?” 的问题,答案只有“是”或“不是”,不需要评分。filter 上下文经常用于结构化数据的过滤,例如:
timestamp 是否在 2015 ~ 2016 范围内?
status 是否等于 published?

频繁使用的过滤查询会被 es 自动缓存,用于提高查询性能。

查询条件中有 filter 参数时,就是 filter 上下文。例如:

  • bool 查询中的 filter 或 must_not 参数
  • constant_score 查询中的 filter 参数
  • filter 聚合查询

query和filter的区别

query 需要计算相关性,按照分数进行排序,而且无法cache结果
filter 不需要计算相关性,且会缓存频繁查询的 filter 结果,所以 filter 会更快

如果是用于搜索,需要将最相关的数据先返回,那么用 query
如果只是要根据一些条件筛选出一部分数据,不关注其相关性排序,那么用 filter

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


复合查询(Compound Query)

Compound queries
https://www.elastic.co/guide/en/elasticsearch/reference/current/compound-queries.html

复合查询以逻辑的方式将多个叶子、复合查询组合为一个查询,可能是对查询结果和分数的合并,可能改变其行为,或者从 query 上下文切换为 filter 上下文。


bool 查询

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

bool 查询将多个查询子句组合到一起,多个子查询之间的逻辑关系是与(and),返回的文档必须匹配全部子查询。
支持的查询类型有:

  • must 返回文档必须严格匹配 must 条件,会影响相关性得分。
  • filter 返回文档必须严格匹配 filter 条件,在 filter 上下文中执行,和 must 唯一的区别是不影响相关性得分。
  • should 返回文档应当匹配 should 条件,会影响相关性得分。
  • must_not 返回文档必须不匹配 must_not 条件,也在 filter 上下文中执行,不影响相关性得分。

查询语句同时包含 must 和 should 时,返回的文档可以满足 must 条件但不满足 should 条件,因为 must 条件优先级高于 should,但是如果也满足 should 条件,则会提高相关性得分。

通常 should 子句是个数组,返回的文档必须匹配 minimum_should_match 个 should 条件。
通过 minimum_should_match 参数指定返回文档需要匹配的 should 子句的个数或百分比,如果 bool 查询包含至少一个 should 子句且没有 must 和 filter 子句,则 minimum_should_match 默认值为 1,否则默认值为 0

例1、

POST _search
{
  "query": {
    "bool" : {
      "must" : {
        "term" : { "user.id" : "kimchy" }
      },
      "filter": {
        "term" : { "tags" : "production" }
      },
      "must_not" : {
        "range" : {
          "age" : { "gte" : 10, "lte" : 20 }
        }
      },
      "should" : [
        { "term" : { "tags" : "env1" } },
        { "term" : { "tags" : "deployed" } }
      ],
      "minimum_should_match" : 1,
      "boost" : 1.0
    }
  }
}

例2、3种不同的 filter 条件:时间范围+颜色多选+名字单选

{
    "query": {
        "bool": {
            "filter": [
                {
                    "range": {
                        "timestamp": {
                            "gte": 1645157343381,
                            "lte": 1646053743440
                        }
                    }
                },
                {
                    "terms": {
                        "color": ["青色","黄色"]
                    }
                },
                {
                    "term": {
                        "name": "小明"
                    }
                }
            ]
        }
    },
    "sort": {
        "timestamp": {
            "order": "desc"
        }
    },
    "from": 500,
    "size": 20
}

boosting 查询

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


全文查询

Elasticsearch Guide [7.17] » Query DSL » Full text queries
https://www.elastic.co/guide/en/elasticsearch/reference/7.17/full-text-queries.html


match 分词查询

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

match 查询会对入参的 text 进行分词后再和文档匹配。

例如

GET /_search
{
  "query": {
    "match": {
      "message": {
        "query": "this is a test"
      }
    }
  }
}

可以将 match 查询的 <field>query 合并简化,例如:

GET /_search
{
  "query": {
    "match": {
      "message": "this is a test"
    }
  }
}

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

{
    "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基础介绍"
                }
            }
        ]
    }
}

match_phrase 短语查询

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

Match phrase 查询会先对入参文本进行分词,之后匹配包含全部分词的文档,并且顺序要和入参一致。

slop 参数告诉 match_phrase 分词相隔多远时仍然能将文档视为匹配,默认值是 0
例如入参 I like riding 想匹配文档 I like swimming and riding,需要将 riding 向前移动两个位置,所以 slop 设为 2 时可匹配到。

{
  "query": {
    "match_phrase": {
      "message": {
        "query": "I like riding",
        "slop": 2
      }
    }
  }
}

注意:Match phrase 也会先对入参分词,并不是有的博客中说的 Match phrase 查询不对入参分词。


match_phrase_prefix 短语前缀查询

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

和 Match phrase 查询类似,Match phrase prefix 查询会先对入参文本进行分词,之后匹配包含全部分词的文档,并且顺序要和入参一致,只不过最后一个单词会当做前缀进行匹配。

例如入参 “quick brown f” 可匹配 “quick brown fox” 或 “two quick brown ferrets”,但不会匹配 “the fox is quick and brown”

GET /_search
{
  "query": {
    "match_phrase_prefix": {
      "message": {
        "query": "quick brown f"
      }
    }
  }
}

query_string 查询(类Lucene语法查询)

Elasticsearch Guide [7.17] » Query DSL » Full text queries » Query string query
https://www.elastic.co/guide/en/elasticsearch/reference/7.17/query-dsl-query-string-query.html

query_string 查询会严格校验查询串的语法,语法错误时会报错。
由于 query_string 查询语法错误时会报错,不建议在给用户输入的搜索框中使用,可选方案:

  • 如果需要支持 Lucene 语法,使用 simple_query_string 查询
  • 如果不需要支持 Lucene 语法,使用 match 查询
GET /_search
{
    "query": {
        "query_string" : {
            "query" : "(new york city) OR (big apple)",
            "default_field" : "content"
        }
    }
}

simple_query_string 简化lucene查询

Elasticsearch Guide [7.17] » Query DSL » Full text queries » Simple query string query
https://www.elastic.co/guide/en/elasticsearch/reference/7.17/query-dsl-simple-query-string-query.html


Term查询

Term-level queries
https://www.elastic.co/guide/en/elasticsearch/reference/current/term-level-queries.html

Term 查询(等值查询)

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

Terms 查询(In查询)

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

Range 范围查询

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


Wildcard 通配符查询

Elasticsearch Guide [7.17] » Query DSL » Term-level queries » Wildcard query
https://www.elastic.co/guide/en/elasticsearch/reference/7.17/query-dsl-wildcard-query.html

* 匹配任何字符序列(包括空字符)
? 匹配任何单个字符

注意:避免使用以 *? 开头的查询串,速度会很慢

例如查询姓张的

GET /_search
{
    "query": {
        "wildcard" : { "name": "张*" }
    }
}

highlight高亮搜索

GET /_search
{
    "query" : {
        "match": { "content": "kimchy" }
    },
    "highlight" : {
        "fields" : {
            "content" : {}
        }
    }
}

Request Body Search
https://www.elastic.co/guide/en/elasticsearch/reference/7.6/search-request-body.html#request-body-search-highlighting


nested嵌套查询

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


脚本查询

Elasticsearch Guide [7.17] » Query DSL » Specialized queries » Script score query
https://www.elastic.co/guide/en/elasticsearch/reference/current/query-dsl-script-score-query.html

cosineSimilarity 余弦相似度

向量字段的类型需设置为 dense_vector,其中 dims 是向量维度

PUT my-index-000001
{
  "mappings": {
    "properties": {
      "my_dense_vector": {
        "type": "dense_vector",
        "dims": 3
      },
      "status" : {
        "type" : "keyword"
      }
    }
  }
}

插入数据:

PUT my-index-000001/_doc/1
{
  "my_dense_vector": [0.5, 10, 6],
  "status" : "published"
}

检索,用 status 字段做了筛选,用入参的 query_vector 向量和文档的 my_dense_vector 向量做相似度计算,结果再加 1.0

GET my-index-000001/_search
{
  "query": {
    "script_score": {
      "query" : {
        "bool" : {
          "filter" : {
            "term" : {
              "status" : "published" 
            }
          }
        }
      },
      "script": {
        "source": "cosineSimilarity(params.query_vector, 'my_dense_vector') + 1.0", 
        "params": {
          "query_vector": [4, 3.4, -0.2]  
        }
      }
    }
  }
}

搜索你的数据

Elasticsearch Guide [7.17] » Search your data
https://www.elastic.co/guide/en/elasticsearch/reference/7.17/search-your-data.html

准实时(near-realtime)搜索

Elasticsearch Guide [7.17] » Search your data » Near real-time search
https://www.elastic.co/guide/en/elasticsearch/reference/current/near-real-time.html

elastic 底层采用的是 lucene 这个库来实现倒排索引的功能,在 lucene 的概念里每一条记录称为 document(文档),lucene 使用 segment(分段) 来存储数据。Segment 是最小的数据存储单元。其中包含了文档中的词汇字典、词汇字典的倒排索引以及 Document 的字段数据。因此 Segment 直接提供了搜索功能。但是 Segment 能提供搜索的前提是数据必须被提交(Lucene commit),即文档经过一系列的处理之后生成倒排索引等一系列数据。

由于 Lucene commit 非常耗时,es 并不会每收到一条数据就提交一次,而是维护一个 Lucene 内部的 in-memory buffer(内存缓存区),新增一条记录时,es 只是将数据写入内存缓存区,每隔一段时间(默认为1秒),Elasticsearch会执行一次refresh操作:lucene中所有的缓存数据都被写入到一个新的Segment,清空缓存数据。此时数据就可以被搜索。当然,每次执行refresh操作都会生成一个新的Segment文件,这样一来Segment文件有大有小,相当碎片化。Elasticsearch内部会开启一个线程将小的Segment合并(Merge)成大的Segment,减少碎片化,降低文件打开数,提升IO性能。

新增一条记录时,es 会把数据写到 translog 和 in-memory buffer(内存缓存区) 中,数据写入内存缓冲区后并不能立即被搜索到,需要等 refresh 操作(默认1秒一次)执行后刷入 segment 才能被搜索到,所以 es 搜索是 near-realtime 准实时的,而不是实时的。


CAT 运维 API

Compact and aligned text (CAT) APIs
https://www.elastic.co/guide/en/elasticsearch/reference/current/cat.html

JSON 参数格式的接口方便程序处理,但对人眼来说,我们熟悉的分行分列的 Linux 命令行输出格式更加友好,Elasticsearch 提供的 CAT(compact and aligned text) 接口就是以命令行输出格式返回的。

CAT 接口返回的结果只适合人眼阅读,如果需要程序处理,建议使用 JSON 返回格式的接口。


公共参数

https://www.elastic.co/guide/en/elasticsearch/reference/current/cat.html#common-parameters

?help 可用列

全部 CAT 接口都有 help 参数(query string),可用来查询此接口提供的列信息及解释,例如 http://localhost:8200/_cat/nodes?help
返回的三列从左到右分别是: 字段名、别名(缩写)、解释

curl "http://10.92.54.76:8200/_cat/indices?help"
health                           | h                              | current health status
status                           | s                              | open/close status
index                            | i,idx                          | index name
uuid                             | id,uuid                        | index uuid
pri                              | p,shards.primary,shardsPrimary | number of primary shards
rep                              | r,shards.replica,shardsReplica | number of replica shards
docs.count                       | dc,docsCount                   | available docs
docs.deleted                     | dd,docsDeleted                 | deleted docs
store.size                       | ss,storeSize                   | store size of primaries & replicas
pri.store.size                   |                                | store size of primaries

?v 详情

全部 CAT 接口都有 v 参数(query string),用来开启详情输出,例如 localhost:9200/_cat/indices?v

?h 指定输出列

全部 CAT 接口都有 h 参数(query string),用于指定输出列,例如 _cat/nodes?h=ip,port,heapPercent,name
h 支持通配符,例如 /_cat/thread_pool?h=ip,queue*

curl "http://localhost:8200/_cat/nodes?h=ip,port,heapPercent,name"
127.0.0.97 9300 17 es-7-master-2
127.0.0.95 9300 50 es-7-master-1
127.0.0.96 9300 38 es-7-master-0

?s 指定排序列

CAT 接口支持通过 ?s 设置排序字段,可通过列名或别名指定,可通过逗号分割指定多个排序列,默认是升序排序,列名后加 :desc 可指定降序排序,例如 s=column1,column2:desc,column3

GET _cat/templates?v=true&s=order:desc,index_patterns

?format 返回格式

CAT 接口支持通过 ?format 指定返回格式,默认是 text,支持的格式有:text, json, smile, yaml, cbor

curl "http://10.92.54.76:8200/_cat/indices"
green open user_0124 fRqr86C7QDWp2q1JzNL9DQ 1 1 104772294 2540905 760.7gb 379.7gb

curl "http://10.92.54.76:8200/_cat/indices?format=json"
[{"health":"green","status":"open","index":"user_0124","uuid":"fRqr86C7QDWp2q1JzNL9DQ","pri":"1","rep":"1","docs.count":"104772294","docs.deleted":"2540905","store.size":"760.7gb","pri.store.size":"379.7gb"}]

curl "http://10.92.54.76:8200/_cat/indices?format=yaml"
---
- health: "green"
  status: "open"
  index: "user_0124"
  uuid: "fRqr86C7QDWp2q1JzNL9DQ"
  pri: "1"
  rep: "1"
  docs.count: "104772294"
  docs.deleted: "2540905"
  store.size: "760.7gb"
  pri.store.size: "379.7gb"

/_cat 列出全部CAT接口

=^.^=
/_cat/allocation
/_cat/shards
/_cat/shards/{index}
/_cat/master
/_cat/nodes
/_cat/tasks
/_cat/indices
/_cat/indices/{index}
/_cat/segments
/_cat/segments/{index}
/_cat/count
/_cat/count/{index}
/_cat/recovery
/_cat/recovery/{index}
/_cat/health
/_cat/pending_tasks
/_cat/aliases
/_cat/aliases/{alias}
/_cat/thread_pool
/_cat/thread_pool/{thread_pools}
/_cat/plugins
/_cat/fielddata
/_cat/fielddata/{fields}
/_cat/nodeattrs
/_cat/repositories
/_cat/snapshots/{repository}
/_cat/templates
/_cat/ml/anomaly_detectors
/_cat/ml/anomaly_detectors/{job_id}
/_cat/ml/trained_models
/_cat/ml/trained_models/{model_id}
/_cat/ml/datafeeds
/_cat/ml/datafeeds/{datafeed_id}
/_cat/ml/data_frame/analytics
/_cat/ml/data_frame/analytics/{id}
/_cat/transforms
/_cat/transforms/{transform_id}

/_cat/health?v 查看集群状态

https://www.elastic.co/guide/en/elasticsearch/reference/current/cat-health.html

epoch      timestamp cluster  status node.total node.data shards pri relo init unassign pending_tasks max_task_wait_time active_shards_percent
1643354450 07:20:50  det-es-7 green           3         3      2   1    0    0        0             0                  -                100.0%

/_cat/nodes?v 查看全部节点

https://www.elastic.co/guide/en/elasticsearch/reference/current/cat-nodes.html

ip           heap.percent ram.percent cpu load_1m load_5m load_15m node.role   master name
10.233.64.95           61          56   1    1.71    1.52     1.38 cdfhilmrstw *      det-es-7-master-1
10.233.64.97           28          80   0    1.71    1.52     1.38 cdfhilmrstw -      det-es-7-master-2
10.233.64.96           49          80   1    1.71    1.52     1.38 cdfhilmrstw -      det-es-7-master-0

/_cat/indices?v 查看所有索引

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

/_cat/count/index?v 查看索引的文档数

https://www.elastic.co/guide/en/elasticsearch/reference/current/cat-count.html

/_cat/count 查看全部索引的文档总数
/_cat/count/<target> 查看指定索引的文档个数

epoch      timestamp count
1643355132 07:32:12  104772294

/_cat/segments/index?v 查看索引的段数据

Elasticsearch Guide [7.17] » REST APIs » Compact and aligned text (CAT) APIs » cat segments API
https://www.elastic.co/guide/en/elasticsearch/reference/7.17/cat-segments.html

GET /_cat/segments 查看全部段数据
GET /_cat/segments/index 查看指定 index 的段数据

size.memory 是一个 segment 占用的堆内存大小。

字段说明:

GET /_cat/segments?help
index        | i,idx                 | index name                       
shard        | s,sh                  | shard name                       
prirep       | p,pr,primaryOrReplica | primary or replica               
ip           |                       | ip of node where it lives        
id           |                       | unique id of node where it lives 
segment      | seg                   | segment name                     
generation   | g,gen                 | segment generation               
docs.count   | dc,docsCount          | number of docs in segment        
docs.deleted | dd,docsDeleted        | number of deleted docs in segment
size         | si                    | segment size in bytes            
size.memory  | sm,sizeMemory         | segment memory in bytes          
committed    | ic,isCommitted        | is segment committed             
searchable   | is,isSearchable       | is segment searched              
version      | v,ver                 | version                          
compound     | ico,isCompound        | is segment compound              

示例

GET /_cat/segments/index
index           shard prirep ip           segment generation docs.count docs.deleted    size size.memory committed searchable version compound
my_blog_3shards 0     p      192.168.1.1  _73            255    1185600            0   4.2gb       18244 true      true       8.10.1  false
my_blog_3shards 0     p      192.168.1.1  _da            478    1124265            0     4gb       18020 true      true       8.10.1  false
my_blog_3shards 0     p      192.168.1.1  _j9            693    1272105            0   4.6gb       18884 true      true       8.10.1  false
my_blog_3shards 0     p      192.168.1.1  _o8            872    1002084            0   3.6gb       17540 true      true       8.10.1  false
my_blog_3shards 0     p      192.168.1.1  _ug           1096    1126064            0     4gb       18404 true      true       8.10.1  false
my_blog_3shards 0     p      192.168.1.1  _zq           1286    1176128            0   4.2gb       18372 true      true       8.10.1  false
my_blog_3shards 0     p      192.168.1.1  _15i          1494     904718            0   3.2gb       17188 true      true       8.10.1  false
my_blog_3shards 0     p      192.168.1.1  _1b8          1700    1081184            0   3.9gb       18148 true      true       8.10.1  false
my_blog_3shards 0     p      192.168.1.1  _1hq          1934     915554            0   3.3gb       17012 true      true       8.10.1  true

/_cat/shards?v 查看分片状态

https://www.elastic.co/guide/en/elasticsearch/reference/current/cat-shards.html

/_cat/shards 查看全部分片信息
/_cat/shards/<target> 查看具体某个分片信息

index                   shard prirep state        docs   store ip           node
my_app_article_inf_0124 0     p      STARTED 104772294 379.7gb 10.233.64.96 det-es-7-master-0
my_app_article_inf_0124 0     r      STARTED 104772294 380.9gb 10.233.64.97 det-es-7-master-2

文本分析

POST /_analyze 分析文本

Elasticsearch Guide [7.17] » REST APIs » Index APIs » Analyze API
https://www.elastic.co/guide/en/elasticsearch/reference/7.17/indices-analyze.html

对一段文本进行分词

GET /_analyze
POST /_analyze
GET /<index>/_analyze
POST /<index>/_analyze

standard 默认分词器

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

GET /_analyze
{
  "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 查询。

GET /_analyze
{
    "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


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


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 线程池)队列大小(不建议),或者增加结点。


ES数据迁移

Elastic数据迁移方法及注意事项
https://www.cnblogs.com/zhengchunyuan/p/9957851.html


Cluster 集群 API

Elasticsearch Guide [7.17] » REST APIs » Cluster APIs
https://www.elastic.co/guide/en/elasticsearch/reference/7.17/cluster.html

PUT /_cluster/settings 修改动态配置

Elasticsearch Guide [7.17] » REST APIs » Cluster APIs » Cluster update settings API
https://www.elastic.co/guide/en/elasticsearch/reference/7.17/cluster-update-settings.html

PUT /_cluster/settings

例如

PUT /_cluster/settings
{
  "persistent" : {
    "indices.recovery.max_bytes_per_sec" : "50mb"
  }
}

GET /_cluster/settings 查询集群配置

Elasticsearch Guide [7.17] » REST APIs » Cluster APIs » Cluster get settings API
https://www.elastic.co/guide/en/elasticsearch/reference/7.17/cluster-get-settings.html

GET /_cluster/settings

默认只返回显式修改过的配置,增加 include_defaults=true 参数可返回默认配置。


GET /_nodes/stats 查询节点统计信息

Elasticsearch Guide [7.17] » REST APIs » Cluster APIs » Nodes stats API
https://www.elastic.co/guide/en/elasticsearch/reference/7.17/cluster-nodes-stats.html

GET /_nodes/stats 查询全部节点的全部统计信息
GET /_nodes/<node_id>/stats 查询指定节点的全部统计信息
GET/_nodes/stats/<metric> 查询全部节点的指定统计信息,例如 /_nodes/stats/jvm 查全部节点的 jvm 信息
GET/_nodes/<node_id>/stats/<metric> 查询指定节点的指定统计信息

GET /_nodes/stats/jvm 查询节点的JVM信息

{
    "_nodes": {
        "total": 3,
        "successful": 3,
        "failed": 0
    },
    "cluster_name": "det-es-7",
    "nodes": {
        "JadPfrWATmu3br_YrOogIA": {
            "timestamp": 1648638934312,
            "name": "master-2",
            "transport_address": "127.0.0.1:9300",
            "host": "127.0.0.1",
            "ip": "127.0.0.1:9300",
            "roles": [
                "data",
                "data_cold",
                "data_content",
                "data_frozen",
                "data_hot",
                "data_warm",
                "ingest",
                "master",
                "ml",
                "remote_cluster_client",
                "transform"
            ],
            "attributes": {
                "ml.machine_memory": "17179869184",
                "ml.max_open_jobs": "512",
                "xpack.installed": "true",
                "ml.max_jvm_size": "8589934592",
                "transform.node": "true"
            },
            "jvm": {
                "timestamp": 1648638934312,
                "uptime_in_millis": 3454100628,
                "mem": {
                    "heap_used_in_bytes": 4971640320,
                    "heap_used_percent": 57,
                    "heap_committed_in_bytes": 8589934592,
                    "heap_max_in_bytes": 8589934592,
                    "non_heap_used_in_bytes": 185095632,
                    "non_heap_committed_in_bytes": 188481536,
                    "pools": {
                        "young": {
                            "used_in_bytes": 4160749568,
                            "max_in_bytes": 0,
                            "peak_used_in_bytes": 5146411008,
                            "peak_max_in_bytes": 0
                        },
                        "old": {
                            "used_in_bytes": 806977024,
                            "max_in_bytes": 8589934592,
                            "peak_used_in_bytes": 4854326784,
                            "peak_max_in_bytes": 8589934592
                        },
                        "survivor": {
                            "used_in_bytes": 3913728,
                            "max_in_bytes": 0,
                            "peak_used_in_bytes": 469762048,
                            "peak_max_in_bytes": 0
                        }
                    }
                },
                "threads": {
                    "count": 65,
                    "peak_count": 80
                },
                "gc": {
                    "collectors": {
                        "young": {
                            "collection_count": 24232,
                            "collection_time_in_millis": 698748
                        },
                        "old": {
                            "collection_count": 0,
                            "collection_time_in_millis": 0
                        }
                    }
                },
                "buffer_pools": {
                    "mapped": {
                        "count": 2658,
                        "used_in_bytes": 2496772173825,
                        "total_capacity_in_bytes": 2496772173825
                    },
                    "direct": {
                        "count": 52,
                        "used_in_bytes": 9262228,
                        "total_capacity_in_bytes": 9262227
                    },
                    "mapped - 'non-volatile memory'": {
                        "count": 0,
                        "used_in_bytes": 0,
                        "total_capacity_in_bytes": 0
                    }
                },
                "classes": {
                    "current_loaded_count": 24047,
                    "total_loaded_count": 24085,
                    "total_unloaded_count": 38
                }
            }
        }
    }
}

GET /_nodes/stats/indices 查看节点的索引统计信息

GET /_nodes/stats/indices 查看全部节点的索引统计信息
GET /_nodes/JadPfrWATmu3br_YrOogIA/stats/indices 查看指定节点的索引统计信息

GET /_nodes/JadPfrWATmu3br_YrOogIA/stats/indices
{
    "_nodes": {
        "total": 1,
        "successful": 1,
        "failed": 0
    },
    "cluster_name": "det-es-7",
    "nodes": {
        "JadPfrWATmu3br_YrOogIA": {
            "timestamp": 1649384639759,
            "name": "es-7-master-2",
            "transport_address": "127.0.0.1:9300",
            "host": "127.0.0.1",
            "ip": "127.0.0.1:9300",
            "roles": [
                "data",
                "data_cold",
                "data_content",
                "data_frozen",
                "data_hot",
                "data_warm",
                "ingest",
                "master",
                "ml",
                "remote_cluster_client",
                "transform"
            ],
            "attributes": {
                "ml.machine_memory": "17179869184",
                "ml.max_open_jobs": "512",
                "xpack.installed": "true",
                "ml.max_jvm_size": "8589934592",
                "transform.node": "true"
            },
            "indices": {
                "docs": {
                    "count": 672675191,
                    "deleted": 0
                },
                "shard_stats": {
                    "total_count": 2
                },
                "store": {
                    "size_in_bytes": 2555649098412,
                    "total_data_set_size_in_bytes": 2555649098412,
                    "reserved_in_bytes": 0
                },
                "indexing": {
                    "index_total": 672675191,
                    "index_time_in_millis": 859673193,
                    "index_current": 0,
                    "index_failed": 0,
                    "delete_total": 0,
                    "delete_time_in_millis": 0,
                    "delete_current": 0,
                    "noop_update_total": 0,
                    "is_throttled": false,
                    "throttle_time_in_millis": 8124403
                },
                "get": {
                    "total": 0,
                    "time_in_millis": 0,
                    "exists_total": 0,
                    "exists_time_in_millis": 0,
                    "missing_total": 0,
                    "missing_time_in_millis": 0,
                    "current": 0
                },
                "search": {
                    "open_contexts": 0,
                    "query_total": 557,
                    "query_time_in_millis": 913940,
                    "query_current": 0,
                    "fetch_total": 147,
                    "fetch_time_in_millis": 1528,
                    "fetch_current": 0,
                    "scroll_total": 0,
                    "scroll_time_in_millis": 0,
                    "scroll_current": 0,
                    "suggest_total": 0,
                    "suggest_time_in_millis": 0,
                    "suggest_current": 0
                },
                "merges": {
                    "current": 0,
                    "current_docs": 0,
                    "current_size_in_bytes": 0,
                    "total": 11405,
                    "total_time_in_millis": 2334432062,
                    "total_docs": 1457464133,
                    "total_size_in_bytes": 5626849591443,
                    "total_stopped_time_in_millis": 3821957,
                    "total_throttled_time_in_millis": 848917760,
                    "total_auto_throttle_in_bytes": 31632531
                },
                "refresh": {
                    "total": 9288,
                    "total_time_in_millis": 45898273,
                    "external_total": 643,
                    "external_total_time_in_millis": 8947460,
                    "listeners": 0
                },
                "flush": {
                    "total": 8442,
                    "periodic": 8161,
                    "total_time_in_millis": 373382635
                },
                "warmer": {
                    "current": 0,
                    "total": 641,
                    "total_time_in_millis": 64
                },
                "query_cache": {
                    "memory_size_in_bytes": 0,
                    "total_count": 0,
                    "hit_count": 0,
                    "miss_count": 0,
                    "cache_size": 0,
                    "cache_count": 0,
                    "evictions": 0
                },
                "fielddata": {
                    "memory_size_in_bytes": 0,
                    "evictions": 0
                },
                "completion": {
                    "size_in_bytes": 0
                },
                "segments": {
                    "count": 662,
                    "memory_in_bytes": 12082184,
                    "terms_memory_in_bytes": 7308480,
                    "stored_fields_memory_in_bytes": 4225008,
                    "term_vectors_memory_in_bytes": 0,
                    "norms_memory_in_bytes": 0,
                    "points_memory_in_bytes": 0,
                    "doc_values_memory_in_bytes": 548696,
                    "index_writer_memory_in_bytes": 0,
                    "version_map_memory_in_bytes": 0,
                    "fixed_bit_set_memory_in_bytes": 0,
                    "max_unsafe_auto_id_timestamp": -1,
                    "file_sizes": {}
                },
                "translog": {
                    "operations": 0,
                    "size_in_bytes": 110,
                    "uncommitted_operations": 0,
                    "uncommitted_size_in_bytes": 110,
                    "earliest_last_modified_age": 2840440564
                },
                "request_cache": {
                    "memory_size_in_bytes": 71040,
                    "evictions": 0,
                    "hit_count": 39,
                    "miss_count": 242
                },
                "recovery": {
                    "current_as_source": 0,
                    "current_as_target": 0,
                    "throttle_time_in_millis": 0
                }
            }
        }
    }
}

GET /_cluster/stats 查询集群统计信息

Elasticsearch Guide [7.17] » REST APIs » Cluster APIs » Cluster stats API
https://www.elastic.co/guide/en/elasticsearch/reference/7.17/cluster-stats.html


配置 Elasticsearch

Elasticsearch Guide [7.17] » Set up Elasticsearch » Configuring Elasticsearch
https://www.elastic.co/guide/en/elasticsearch/reference/7.17/settings.html#dynamic-cluster-setting


配置 Elasticsearch

配置文件位置

Elasticsearch 有三个配置文件:

  • elasticsearch.yml 用于配置 Elasticsearch
  • jvm.options 用于配置 Elasticsearch JVM 设置
  • log4j2.properties 用于配置 Elasticsearch 日志记录

我启动 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。


配置文件格式

ES 的配置文件是 yaml 格式


环境变量替换

配置文件中可以通过 ${...} 引用环境变量


集群和节点配置

  • 静态配置,只能在集群启动前在 elasticsearch.yml 中配置
  • 动态配置,可通过集群配置 API PUT /_cluster/settings 在运行中动态修改,也可以在 elasticsearch.yml 中配置
动态配置

动态配置分为:

  • 临时的(transient) 集群重启后失效
  • 持久的(persistent) 集群重启后还在

可通过配置 API 给配置项赋值 null 来重置持久的或临时的配置项。

如果在多处配置了相同的配置项,优先级如下:

  • transient 临时配置项
  • persistent 持久配置项
  • elasticsearch.yml 中的配置
  • 默认值

transient 配置项优先级最高,可以通过 transient 配置项覆盖 persistent 配置项或 elasticsearch.yml 中的配置。

ES 不建议再使用 transient 临时配置项,因为在集群不稳定时临时配置项可能无故消失,导致潜在的问题。

静态配置

静态配置只能集群启动前在 elasticsearch.yml 中配置
静态配置在集群的每个节点都需要配置


集群分片分配和路由配置

Elasticsearch Guide [7.17] » Set up Elasticsearch » Configuring Elasticsearch » Cluster-level shard allocation and routing settings
https://www.elastic.co/guide/en/elasticsearch/reference/7.17/modules-cluster.html#disk-based-shard-allocation

磁盘分片分配设置

磁盘分配器通过 高水位(high watermark)低水位(low watermark) 阈值控制分片数据在磁盘的分配,主要目标是要确保每个节点的磁盘使用率都不超过高水位,或者只是临时超过高水位。如果一个节点的磁盘使用率超过了高水位,ES 会将此节点上的分片数据移动到集群中的其他节点

注意:节点的磁盘使用率临时超过高水位值是正常的。

节点的磁盘使用率超过低水位后,分配器会停止往此节点分配分片数据,以使节点远离高水位。如果全部节点都超过了低水位,ES 就无法再分配分片数据,同时也无法在节点间移动分片数据。所以要始终保证集群中有几个节点的磁盘使用率低于低水位。

如果节点的磁盘填充速度非常快,ES 可能还来不及将分片数据移动到其他节点,可能导致磁盘被完全占满。为了避免这种情况,如果节点的磁盘使用率超过 **洪水位(flood-stage watermark)**,ES 会禁止向有分片在此节点的索引中写入数据。ES 会继续将此节点的分片数据移动到其他节点,一旦磁盘使用率低于高水位,就会解除写阻塞。

cluster.routing.allocation.disk.threshold_enabled 是否开启磁盘分配器水位阈值检查,默认 true,设为 false 禁用检查。
cluster.routing.allocation.disk.watermark.low 磁盘使用率低水位,默认值 85%,即磁盘使用率超过 85% 后 ES 就停止向此节点写入分片数据。也可以设为绝对值,例如 500mb,表示磁盘空间低于 500mb 后就禁止写入。
cluster.routing.allocation.disk.watermark.high 磁盘使用率高水位,默认值 90%,即磁盘使用率超过 90% 后 ES 会尝试将此节点的分片数据移动到其他节点。也可以设为绝对值,例如 500mb,表示磁盘空间低于 500mb 后就尝试向外移动分片数据。
cluster.routing.allocation.disk.watermark.flood_stage 磁盘使用率洪水位,默认值 95%,即磁盘使用率超过 95% 后 ES 将有分片在此节点的索引设为只读可删 index.blocks.read_only_allow_delete,这是防止节点磁盘被占满的最终手段,磁盘使用率低于高水位后,就会自动解除写阻塞。

注意:不能在这几个配置项中混合使用百分比和绝对值,因为 ES 内部要验证其合理性,保证低水位低于高水位,高水位低于洪水位

例如,通过 API 解除指定索引的只读可删限制

PUT /my-index-000001/_settings
{
  "index.blocks.read_only_allow_delete": null
}

cluster.info.update.interval ES 检查磁盘使用率的时间间隔,默认 30s


429 disk usage exceeded flood-stage watermark

问题:
es 插入数据报错

{
    "error":{
        "root_cause":[
            {
                "type":"cluster_block_exception",
                "reason":"index [my_index] blocked by: [TOO_MANY_REQUESTS/12/disk usage exceeded flood-stage watermark, index has read-only-allow-delete block];"
            }
        ],
        "type":"cluster_block_exception",
        "reason":"index [my_index] blocked by: [TOO_MANY_REQUESTS/12/disk usage exceeded flood-stage watermark, index has read-only-allow-delete block];"
    },
    "status":429
}

原因:
磁盘使用率超过 95% 洪水位,es 禁止写入数据到index

解决:
1、关闭磁盘水位阈值检查,可通过 API 不停机动态更新,或停机后修改 elasticsearch.yml

PUT /_cluster/settings
{
    "transient": {
        "cluster.routing.allocation.disk.threshold_enabled": false
    }
}

或者提高各个水位阈值,同样也可以通过 API 更新或配置文件更新,例如更新低水位到 100gb,高水位到 50gb,洪水位到 10gb,设置信息刷新间隔为 1 分钟

PUT _cluster/settings
{
  "transient": {
    "cluster.routing.allocation.disk.watermark.low": "100gb",
    "cluster.routing.allocation.disk.watermark.high": "50gb",
    "cluster.routing.allocation.disk.watermark.flood_stage": "10gb",
    "cluster.info.update.interval": "1m"
  }
}

2、关闭水位检查或者调高水位值后,还是不能写入,因为各个index已经被加上了 只读可删 的 block,需要手动去掉 block,下面的 API 通过 _all 去掉全部索引的 block,也可以指定 index

PUT _all/_settings
{
    "index.blocks.read_only_allow_delete": null
}

https://stackoverflow.com/questions/50609417/elasticsearch-error-cluster-block-exception-forbidden-12-index-read-only-all


Elasticsearch 日志配置

Elasticsearch Guide [7.17] » Set up Elasticsearch » Configuring Elasticsearch » Logging
https://www.elastic.co/guide/en/elasticsearch/reference/7.17/logging.html

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


Circuit Breaker 断路器配置

Elasticsearch Guide [7.17] » Set up Elasticsearch » Configuring Elasticsearch » Circuit breaker settings
https://www.elastic.co/guide/en/elasticsearch/reference/7.17/circuit-breaker.html

Elasticsearch 有很多断路器(circuit breaker),用于阻止各种操作可能导致的 OOM 内存溢出。每个断路器都有一个阈值指定最多可以使用多少内存。此外,还有一个父断路器指定了所有断路器最多可以使用多少内存。

Request circuit breaker 请求断路器

request 断路器用于限制执行单个请求需要的内存,比如一个聚合请求可能会用 JVM 内存来做一些汇总计算。

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

429 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 多分配写内存。


Field data circuit breaker 列缓存断路器

field data 断路器可以估算将一个 field 加载到 列数据缓存 中需要占用多少内存,如果此加载操作会引起内存使用超过预定义的阈值,就会返回错误。

indices.breaker.fielddata.limit Dynamic 参数,列缓存允许使用的最大内存,默认是 JVM 堆内存的 40%


node query cache 节点查询缓存

Elasticsearch Guide [7.17] » Set up Elasticsearch » Configuring Elasticsearch » Node query cache settings
https://www.elastic.co/guide/en/elasticsearch/reference/7.17/query-cache.html

filter 类型的查询结果会缓存在节点的查询缓存中,每个节点都有一个全部索引共用的查询缓存,使用 LRU 淘汰策略:当缓存满的时候最早的查询结果会被删除。此缓存的内容无法被查看。

query cache 属于 node-level 缓存,能够被当前节点的所有 shard 所共享。

从 5.1.1 版本开始,term filter 查询不再被缓存,因为倒排索引本身就是 term 到文档的一种缓存,本身就很快,如果缓存 term 查询反而会冲掉 LRU 中真正需要被缓存的结果
https://www.elastic.co/blog/elasticsearch-5-1-1-released

Term queries are no longer cached. The reason for this is twofold: term queries are almost always fast, and queries for thousands of terms can trash the query cache history, preventing more expensive queries from being cached.

默认节点查询缓存可存储最多 10000 条查询结果,最多占用 10% JVM 堆内存。

indices.queries.cache.size Static 配置,节点级配置,filter 查询缓存的最大值,默认是 JVM 堆内存的 10%。可配置为 JVM 堆内存的百分比如 10%,或者绝对值比如 512mb
index.queries.cache.enabled Static 配置,索引级配置,是否开启索引的查询缓存,默认 true 开启。


shard request chache 分片请求缓存

Elasticsearch Guide [7.17] » Set up Elasticsearch » Configuring Elasticsearch » Shard request cache settings
https://www.elastic.co/guide/en/elasticsearch/reference/7.17/shard-request-cache.html

在一个或多个索引上进行检索时,所涉及的每个分片会在本地执行检索然后将局部结果返回给协调节点,协调节点将这些分片的局部结果组合成完整的全局检索结果

ES 会缓存每个分片上的检索结果,使得高频检索请求可以立即返回。这个缓存也是 LRU 缓存,满的时候最早的结果被删除。

缓存key是这个查询的DSL语句。所以如果要命中缓存查询生成的DSL一定要一样,这里的一样是指DSL这个字符串一样。

当更新文档、更新 mapping 时,缓存会自动失效

indices.requests.cache.size 默认值最大值是 JVM 堆内存的 2%

打开/关闭分片请求缓存

分片请求缓存默认是开启的,创建索引时可指定关闭缓存:

PUT /my-index-000001
{
  "settings": {
    "index.requests.cache.enable": false
  }
}

也可以动态开启/关闭已有索引的缓存:

PUT /my-index-000001/_settings
{ "index.requests.cache.enable": true }

查看请求缓存使用量

/index/_stats
"request_cache": {
    "memory_size_in_bytes": 168128,
    "evictions": 0,
    "hit_count": 64,
    "miss_count": 466
}

fielddata cache 列缓存配置

Elasticsearch Guide [7.17] » Set up Elasticsearch » Configuring Elasticsearch » Field data cache settings
https://www.elastic.co/guide/en/elasticsearch/reference/7.17/modules-fielddata.html

fielddata 缓存包含字段数据和全局序号 global ordinals,用于支持字段的聚合查询,是位于 JVM 堆内存上的一种缓存结构。

indices.fielddata.cache.size Static 配置,列缓存的最大值,默认无限制,比如堆内存的 38%,或者绝对值 12GB,这个值应该小于 indices.breaker.fielddata.limit


Elasticsearch 线程池配置

Elasticsearch Guide [7.17] » Set up Elasticsearch » Configuring Elasticsearch » Thread pools
https://www.elastic.co/guide/en/elasticsearch/reference/7.17/modules-threadpool.html

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

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

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


Elasticsearch 高级配置(JVM配置)

Elasticsearch Guide [7.17] » Set up Elasticsearch » Configuring Elasticsearch » Advanced configuration
https://www.elastic.co/guide/en/elasticsearch/reference/current/advanced-configuration.html

不要直接修改 /usr/share/elasticsearch/config/jvm.options,将自定义 JVM 参数放到 /usr/share/elasticsearch/config/jvm.options.d/ 目录中

ES 默认根据节点的角色和总内存大小自动配置 JVM 堆内存大小,建议直接使用默认值。

-Xms和-Xmx配置必须相同

-Xms和-Xmx配置的内存大小必须相同,避免 resize,否则 es 启动会报错

ERROR: [1] bootstrap checks failed. You must address the points described in the following [1] lines before starting Elasticsearch.
bootstrap check failure [1] of [1]: initial heap size [8589934592] not equal to maximum heap size [17179869184]; this can cause resize pauses

-Xms 和 -Xmx 建议不要超过总内存的 50%,因为除了 JVM,es还有其他占用内存的地方。

young gc 频繁

{"type": "server", "timestamp": "2022-03-05T18:28:59,824Z", "level": "INFO", "component": "o.e.m.j.JvmGcMonitorService", "cluster.name": "det-es-7", "node.name": "det-es-7-master-0", "message": "[gc][1347730] overhead, spent [311ms] collecting in the last [1s]", "cluster.uuid": "-YDlZAJIQxKModujOTof2g", "node.id": "K_g4Ids0Rz-SHHG2jhp9dQ"  }
{"type": "server", "timestamp": "2022-03-05T18:48:00,164Z", "level": "INFO", "component": "o.e.m.j.JvmGcMonitorService", "cluster.name": "det-es-7", "node.name": "det-es-7-master-0", "message": "[gc][1348869] overhead, spent [324ms] collecting in the last [1s]", "cluster.uuid": "-YDlZAJIQxKModujOTof2g", "node.id": "K_g4Ids0Rz-SHHG2jhp9dQ"  }

发现和集群形成

Elasticsearch Guide [7.17] » Set up Elasticsearch » Discovery and cluster formation
https://www.elastic.co/guide/en/elasticsearch/reference/7.17/modules-discovery.html

包括发现节点、选举 master、形成集群、发布集群状态。

ES 7.x 之前,Bully 算法(Zen Discovery 集群协调子系统):根据节点的ID大小来判定谁是leader,简单粗暴,可能出现选举时集群暂时不可用、以及无法选出master的问题
ES 7.x 使用了新的选主算法,类Raft算法

ElasticSearch-新老选主算法对比
https://yemilice.com/2021/06/16/elasticsearch-%E6%96%B0%E8%80%81%E9%80%89%E4%B8%BB%E7%AE%97%E6%B3%95%E5%AF%B9%E6%AF%94/


Quorum(多数派)选举

Elasticsearch Guide [7.17] » Set up Elasticsearch » Discovery and cluster formation » Quorum-based decision making
https://www.elastic.co/guide/en/elasticsearch/reference/7.17/modules-discovery-quorums.html


MasterNotDiscoveredException

es 启动报错:
“type”: “server”, “timestamp”: “2022-03-30T02:08:52,263Z”, “level”: “WARN”, “component”: “r.suppressed”, “cluster.name”: “det-es-7”, “node.name”: “det-es-7-master-0”, “message”: “path: /_cluster/health, params: {wait_for_status=green, timeout=1s}”,
“stacktrace”: [“org.elasticsearch.discovery.MasterNotDiscoveredException: null”

原因:
从 6.x 升级到 7.x 后,需要在环境变量或 elasticsearch.yml 中配置 cluster.initial_master_nodes 指定 master 节点
例如

# 三台实例保证相同
cluster.name: my-cluster
# 设置成对应的 ${HOSTNAME}
node.name: es-01
# 设置成三台实例的 ${HOSTNAME}
discovery.seed_hosts: ["es-01", "es-02", "es-03"]
cluster.initial_master_nodes: ["es-01", "es-02", "es-03"]

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

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

阅读
评论
34.8k
阅读预计155分钟
创建日期 2020-02-20
修改日期 2022-04-18
类别
目录
  1. Elasticsearch 基础
  2. Docker 部署 Elasticsearch 7.6.0
    1. 拉取 Elasticsearch 7.6.0 官方 Docker 镜像
    2. Docker 中以 single 模式启动 Elasticsearch 7.6.0
    3. curl localhost:9200 测试
  3. Elasticsearch 安装 IK 分词器插件
    1. 打包集成 IK 分词器的 Elasticsearch 镜像
    2. 启动包含 IK 分词器的自定义镜像
    3. AccessDeniedException /usr/share/elasticsearch/data/nodes
    4. plugin requires additional permissions
    5. vm.max_map_count [65530] is too low
  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. Chrome 扩展安装 elasticsearch-head
    3. elasticsearch-head 连接 ES 集群
    4. elasticsearch-head 无法连接 ES(ES 未开启跨域)
    5. elasticsearch-head 连接高版本 ES 报错 406
  6. 文档插入性能调优
    1. 使用批量操作API
    2. 多线程插入
    3. 修改或关闭刷新间隔(refresh_interval)
    4. 禁用副本
    5. 禁用操作系统swap
    6. 增加操作系统文件cache
    7. 使用自动生成的id
    8. 使用SSD磁盘
    9. 增加索引buffer size
    10. 读写分离
  7. 检索性能调优
    1. 增加系统缓存
    2. 使用更快的硬件
    3. 文档模型
    4. 使用copy_to合并字段
    5. 提前计算中间结果
    6. 使用keyword代替数字类型
    7. 避免脚本搜索
    8. 强制合并只读索引
    9. 预热全局序号
    10. 预热文件系统缓存
    11. 索引数据排序
  8. Index 索引 API
    1. PUT /index 创建索引
    2. GET /index 查询索引
    3. DELETE /index 删除索引
    4. GET /index/_mapping 查询mapping
    5. PUT /index/_mapping 修改mapping
    6. GET /index/_settings 查询索引的配置参数
    7. POST /index/_settings 修改索引的动态配置
    8. POST /index/_close 关闭索引
    9. POST /index/_open 打开索引
    10. POST /index/_refresh 刷新索引
    11. POST /index/_flush 刷入磁盘
    12. POST /index/_forcemerge 强制段合并
    13. POST /index/_split/new_index 拆分索引
      1. 索引拆分前提条件
      2. 索引可拆分的倍数
      3. 索引拆分过程
      4. 为什么ES不支持增量reshard
      5. 监控拆分过程
    14. POST /index/_shrink/new_index 收缩索引
    15. POST /index/_cache/clear 清理缓存
    16. GET /index/_stats 查询索引统计信息
  9. 索引模块
    1. 静态配置
      1. index.number_of_shards 主分片数
      2. index.number_of_routing_shards 路由分片数
    2. 动态配置
      1. index.number_of_replicas 副本数
      2. index.refresh_interval 刷新间隔
      3. index.max_result_window 最大分页数据量
    3. Translog
    4. ES 持久化
    5. 段合并
    6. 存储类型(mmap内存映射)
      1. 预加载文件系统缓存
    7. Index blocks 索引限制(锁)
      1. index.blocks.read_only
      2. index.blocks.read_only_allow_delete
      3. index.blocks.read
      4. index.blocks.write
      5. index.blocks.metadata
    8. 索引排序
      1. 早期中断
    9. 索引压力
      1. FST 索引前缀
      2. 7.7 开始将 FST 通过mmap加载
      3. Lucene 段文件内容
  10. Mapping 映射
    1. 动态 Mapping
      1. 字段类型自动映射
    2. 显式 Mapping
      1. 为什么需要自定义mapping?
      2. 创建index时指定mapping
      3. 添加字段到已有mapping中
      4. 更新已有index的mapping
    3. 元数据字段
      1. _id 字段
      2. _routing 字段(Elasticsearch 分片策略/片键)
    4. 字段数据类型
      1. text
      2. keyword
        1. text和keyword的区别
      3. date
      4. object
      5. nested
        1. nested和object区别
    5. 字段映射参数
      1. dynamic
      2. index
      3. index_options
      4. null_value
      5. copy_to
      6. fields
      7. analyzer
      8. doc_values 用于加速聚合/排序的正排索引
      9. eager_global_ordinals 全局序号
    6. 7.x 开始移除 Type
      1. 为什么 ES 要移除 Type?
  11. Document 文档 API
    1. 读写文档
      1. 基本写模型
    2. PUT /index/_doc/_id 指定id创建文档
      1. 外部版本号
    3. POST /index/_doc 不指定id创建文档
    4. POST /index/_update/_id 指定id更新文档
    5. POST /index/_update_by_query 根据查询更新
    6. GET /index/_doc/_id 根据id查询文档
    7. DELETE /index/_doc/_id 删除文档
    8. POST /index/_delete_by_query 根据条件删除
      1. 删除索引中的全部文档(清空索引)
      2. 条件删除原理
    9. POST /_bulk 批量操作
      1. Malformed action/metadata line
    10. 乐观并发控制
      1. 409/Conflict
  12. Search 搜索 API
    1. GET /index/_doc/_search 搜索
      1. track_total_hits
      2. _source 指定返回字段
      3. q查询(URI搜索/lucene语法查询)
      4. sort 排序
      5. 分页
        1. from/size 分页
        2. 分布式系统中的深度分页问题
        3. scroll分页(缓存快照,无法跳页,不再推荐)
        4. search_after(实时分页,无法跳页,推荐)
    2. GET /index/_count 条件计数
    3. PIT 时间点
      1. POST /index/_pit 时间点
      2. PIT 时间点会消耗磁盘和内存资源
      3. DELETE /_pit 删除时间点
      4. slice 搜索分片
  13. Query DSL
    1. 查询(query)和过滤(filter)上下文
      1. 查询(query)上下文
      2. 过滤(filter)上下文
      3. query和filter的区别
    2. 复合查询(Compound Query)
      1. bool 查询
      2. boosting 查询
    3. 全文查询
      1. match 分词查询
      2. match_phrase 短语查询
      3. match_phrase_prefix 短语前缀查询
      4. query_string 查询(类Lucene语法查询)
      5. simple_query_string 简化lucene查询
    4. Term查询
      1. Term 查询(等值查询)
      2. Terms 查询(In查询)
      3. Range 范围查询
      4. Wildcard 通配符查询
    5. highlight高亮搜索
    6. nested嵌套查询
    7. 脚本查询
      1. cosineSimilarity 余弦相似度
  14. 搜索你的数据
    1. 准实时(near-realtime)搜索
  15. CAT 运维 API
    1. 公共参数
      1. ?help 可用列
      2. ?v 详情
      3. ?h 指定输出列
      4. ?s 指定排序列
      5. ?format 返回格式
    2. /_cat 列出全部CAT接口
    3. /_cat/health?v 查看集群状态
    4. /_cat/nodes?v 查看全部节点
    5. /_cat/indices?v 查看所有索引
    6. /_cat/count/index?v 查看索引的文档数
    7. /_cat/segments/index?v 查看索引的段数据
    8. /_cat/shards?v 查看分片状态
  16. 文本分析
    1. POST /_analyze 分析文本
    2. standard 默认分词器
    3. ik_max_word/ik_smart
  17. 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操作文档完整实例
  18. Elasticsearch 压测
    1. 压测点
    2. Elasticsearch 压测方案对比
    3. Rally 官方ES压测工具
    4. JMeter压测 ES HTTP 接口
  19. ES数据迁移
  20. Cluster 集群 API
    1. PUT /_cluster/settings 修改动态配置
    2. GET /_cluster/settings 查询集群配置
    3. GET /_nodes/stats 查询节点统计信息
      1. GET /_nodes/stats/jvm 查询节点的JVM信息
      2. GET /_nodes/stats/indices 查看节点的索引统计信息
    4. GET /_cluster/stats 查询集群统计信息
  21. 配置 Elasticsearch
    1. 配置 Elasticsearch
      1. 配置文件位置
      2. 配置文件格式
      3. 环境变量替换
      4. 集群和节点配置
        1. 动态配置
        2. 静态配置
    2. 集群分片分配和路由配置
      1. 磁盘分片分配设置
        1. 429 disk usage exceeded flood-stage watermark
    3. Elasticsearch 日志配置
      1. elasticsearch 慢日志
    4. Circuit Breaker 断路器配置
      1. Request circuit breaker 请求断路器
        1. 429 circuit_breaking_exception Data too large
      2. Field data circuit breaker 列缓存断路器
    5. node query cache 节点查询缓存
    6. shard request chache 分片请求缓存
      1. 打开/关闭分片请求缓存
      2. 查看请求缓存使用量
    7. fielddata cache 列缓存配置
    8. Elasticsearch 线程池配置
      1. elasticsearch 的3种线程池类型
      2. search 线程池
      3. write 线程池
      4. processors 处理器个数设置
      5. 429 es_rejected_execution_exception
    9. Elasticsearch 高级配置(JVM配置)
      1. -Xms和-Xmx配置必须相同
      2. young gc 频繁
  22. 发现和集群形成
    1. Quorum(多数派)选举
    2. MasterNotDiscoveredException

页面信息

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

评论