[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"book:Mg87g8Gk":3,"book:recs:Mg87g8Gk":127},{"id":4,"created_at":5,"updated_at":5,"code":6,"author_id":4,"title":7,"slug":8,"cover_theme_key":9,"visibility":10,"status":11,"sort_order":4,"favorite_count":12,"view_count":13,"author":14,"chapter_count":25,"word_count":26,"free_count":12,"is_favorite":22,"book_status":11,"unparted":27},1,"2023-12-27T13:08:44.239+08:00","Mg87g8Gk","ElasticSearch","elasticsearch-1","default","public","published",0,6,{"id":4,"created_at":15,"updated_at":16,"code":6,"username":17,"display_name":18,"role":19,"avatar_url":20,"is_active":21,"is_banned":22,"follower_count":12,"following_count":23,"last_login_at":24},"2023-11-30T23:53:12.952+08:00","2026-10-20T16:16:54.352+08:00","1701359592888","大苹果","verified_author","\u002Fcuts\u002F17021220625513129.png",true,false,3,"2026-10-20T16:12:35+08:00",13,198054,[28,34,42,49,57,65,72,80,88,96,104,112,120],{"id":4,"created_at":29,"updated_at":29,"code":6,"author_id":4,"book_id":4,"slug":30,"article_id":31,"title":32,"sort_order":4,"content":33,"status":11},"2023-12-27T13:08:55.643+08:00","elasticsearch概念-1",151,"ElasticSearch概念","## Elasticsearch 是什么\n\n![ElasticSearch](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Fdownload\u002Feditor\u002F1703682645734es1.png)\n\nThe Elastic Stack, 包括 Elasticsearch、Kibana、Beats 和 Logstash（也称为 ELK Stack）。能够安全可靠地获取任何来源、任何格式的数据，然后实时地对数据进行搜索、分析和可视化。Elaticsearch，简称为 ES，ES 是一个**开源的高扩展的分布式全文搜索引擎**，是整个 Elastic Stack 技术栈的核心。它可以近乎实时的存储、检索数据；本身扩展性很好，可以扩展到上百台服务器，处理 PB 级别的数据。\n\n## 全文搜索引擎\n\nGoogle，百度类的网站搜索，它们都是根据网页中的关键字生成索引，我们在搜索的时候输入关键字，它们会将该关键字即索引匹配到的所有网页返回；还有常见的项目中应用日志的搜索等等。对于这些非结构化的数据文本，关系型数据库搜索不是能很好的支持。\n\n一般传统数据库，全文检索都实现的很鸡肋，因为一般也没人用数据库存文本字段。进行全文检索需要扫描整个表，如果数据量大的话即使对 SQL 的语法优化，也收效甚微。建立了索引，但是维护起来也很麻烦，对于 insert 和 update 操作都会重新构建索引。\n\n基于以上原因可以分析得出，在一些生产环境中，使用常规的搜索方式，性能是非常差的：\n\n* 搜索的数据对象是大量的非结构化的文本数据。\n* 文件记录量达到数十万或数百万个甚至更多。\n* 支持大量基于交互式文本的查询。\n* 需求非常灵活的全文搜索查询。\n* 对高度相关的搜索结果的有特殊需求，但是没有可用的关系数据库可以满足。\n* 对不同记录类型、非文本数据操作或安全事务处理的需求相对较少的情况。为了解决结构化数据搜索和非结构化数据搜索性能问题，我们就需要专业，健壮，强大的全文搜索引擎。\n\n这里说到的全文搜索引擎指的是目前广泛应用的主流搜索引擎。它的工作原理是计算机索引程序通过扫描文章中的每一个词，对每一个词建立一个索引，指明该词在文章中出现的次数和位置，当用户查询时，检索程序就根据事先建立的索引进行查找，并将查找的结果反馈给用户的检索方式。这个过程类似于通过字典中的检索字表查字的过程。\n\n## Elasticsearch And Solr\n\nLucene 是 Apache 软件基金会 Jakarta 项目组的一个子项目，提供了一个简单却强大的应用程式接口，能够做全文索引和搜寻。在 Java 开发环境里 Lucene 是一个成熟的免费开源工具。就其本身而言，Lucene 是当前以及最近几年最受欢迎的免费 Java 信息检索程序库。但 Lucene 只是一个提供全文搜索功能类库的核心工具包，而真正使用它还需要一个完善的服务框架搭建起来进行应用。\n\n目前市面上流行的搜索引擎软件，主流的就两款：Elasticsearch 和 Solr,这两款都是基于 Lucene 搭建的，可以独立部署启动的搜索引擎服务软件。由于内核相同，所以两者除了服务器安装、部署、管理、集群以外，对于数据的操作 修改、添加、保存、查询等等都十分类似。\n\n在使用过程中，一般都会将 Elasticsearch 和 Solr 这两个软件对比，然后进行选型。这两个搜索引擎都是流行的，先进的的开源搜索引擎。它们都是围绕核心底层搜索库 - Lucene构建的 - 但它们又是不同的。像所有东西一样，每个都有其优点和缺点：\n\n![ElasticSearch](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Fdownload\u002Feditor\u002F1703682625166es2.png)\n\n## Elasticsearch Or Solr\n\nElasticsearch 和 Solr 都是开源搜索引擎，那么我们在使用时该如何选择呢？\n\n* Google 搜索趋势结果表明，与 Solr 相比，Elasticsearch 具有很大的吸引力，但这并不意味着 Apache Solr 已经死亡。虽然有些人可能不这么认为，但 Solr 仍然是最受欢迎的搜索引擎之一，拥有强大的社区和开源支持。\n* 与 Solr 相比，Elasticsearch 易于安装且非常轻巧。此外，你可以在几分钟内安装并运行Elasticsearch。但是，如果 Elasticsearch 管理不当，这种易于部署和使用可能会成为一个问题。基于 JSON 的配置很简单，但如果要为文件中的每个配置指定注释，那么它不适合您。总的来说，如果你的应用使用的是 JSON，那么 Elasticsearch 是一个更好的选择。否则，请使用 Solr，因为它的 schema.xml 和 solrconfig.xml 都有很好的文档记录。\n* Solr 拥有更大，更成熟的用户，开发者和贡献者社区。ES 虽拥有的规模较小但活跃的 用户社区以及不断增长的贡献者社区。\n* Solr 贡献者和提交者来自许多不同的组织，而 Elasticsearch 提交者来自单个公司。\n* Solr 更成熟，但 ES 增长迅速，更稳定。\n* Solr 是一个非常有据可查的产品，具有清晰的示例和 API 用例场景。 Elasticsearch 的文档组织良好，但它缺乏好的示例和清晰的配置说明。\n\n**那么，到底是 Solr 还是 Elasticsearch？**\n\n无论选择 Solr 还是 Elasticsearch，首先需要了解正确的用例和未来需求。\n\n* 由于易于使用，Elasticsearch 在新开发者中更受欢迎。一个下载和一个命令就可以启动一切。\n* 如果除了搜索文本之外还需要它来处理分析查询，Elasticsearch 是更好的选择\n* 如果需要分布式索引，则需要选择 Elasticsearch。对于需要良好可伸缩性和以及性能分布式环境，Elasticsearch 是更好的选择。\n* Elasticsearch 在开源日志管理用例中占据主导地位，许多组织在 Elasticsearch 中索引它们的日志以使其可搜索。\n* 如果你喜欢监控和指标，那么请使用 Elasticsearch，因为相对于 Solr，Elasticsearch 暴露了更多的关键指标\n",{"id":35,"created_at":36,"updated_at":36,"code":37,"author_id":4,"book_id":4,"slug":38,"article_id":39,"title":40,"sort_order":35,"content":41,"status":11},2,"2023-12-27T13:14:00.355+08:00","yj6y7X92","elasticsearch安装-2",152,"ElasticSearch安装","## Windows上安装ES\n\n### 下载软件\n\n[Elasticsearch 的官方地址 **(opens new window)**](https:\u002F\u002Fwww.elastic.co\u002Fcn\u002F)\n\n[下载地址 **(opens new window)**](https:\u002F\u002Fwww.elastic.co\u002Fcn\u002Fdownloads\u002Fpast-releases#elasticsearch)\n\n![ElasticSearch](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Fdownload\u002Feditor\u002F1703682923177es3.png)\n\n### 安装软件\n\nWindows 版的 Elasticsearch 的安装很简单，解压即安装完毕，这里我下载的目前最新版8.3.3，解压后的 Elasticsearch 的目录结构如下\n\n![ElasticSearch](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Fdownload\u002Feditor\u002F1703682940870es4.png)\n\n\n| 目录    | 含义        |\n| ------- | ----------- |\n| bin     | 可执行脚本  |\n| config  | 配置目录    |\n| jdk     | 内置JDK目录 |\n| lib     | 类库        |\n| logs    | 日志目录    |\n| modules | 模块目录    |\n| plugins | 插件目录    |\n\n**解压后，我们先来修改一些配置：**\n\n* 1、配置启动内存，修改配置文件 `conf\u002Fjvm.options`\n\n```xml\n################################################################\n## IMPORTANT: JVM heap size\n################################################################\n## 配置启动内存，默认是4G\n-Xms1g\n-Xmx1g\n################################################################\n```\n\n* 2、暂时禁止掉再次启动时更新地图的一些数据库操作，修改 `conf\u002Felasticsearch.yml` 配置\n\n```xml\n# 添加配置：暂时禁止掉再次启动时更新地图的一些数据库操作\ningest.geoip.downloader.enabled: false\n```\n\n* 3、测试时可以暂时关闭用户密码和SSL，将下图中的`true`全部改为`false`：\n  ![ElasticSearch](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Fdownload\u002Feditor\u002F1703683005794es5.png)\n* 4、进入 `bin` 文件目录，点击 `elasticsearch.bat` 文件启动服务 启动完成后，打开浏览器，输入：[http:\u002F\u002Flocalhost:9200\u002F](javascrit:;)\n  ![ElasticSearch](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Fdownload\u002Feditor\u002F1703683021933es6.png)\n\n### 重置密码\n\n* 如果安装步骤中第三步，你没有修改`elasticsearch.yml`配置为`false`，那么你第一次启动ES服务的时候，控制台会首先出现一些用户名及密码，**切记要先保存下来**！ 启动完成后，访问：[https:\u002F\u002F127.0.0.1:9200\u002F](javascrit:;)，输入用户名及密码，用户名为`elastic`，密码就是第一次启动时控制台显示的密码\n* 如果你第一次启动控制台，忘记保存密码了，那么也别担心，进入es安装目录的bin文件中执行一下命令会返回最新的密码\n\n```shell\nG:\\java\\ElasticSearch\\elasticsearch-8.3.3\\bin>elasticsearch-reset-password -u elastic\nwarning: ignoring JAVA_HOME=G:\\java\\java8\\jdk; using bundled JDK\nThis tool will reset the password of the [elastic] user to an autogenerated value.\nThe password will be printed in the console.\nPlease confirm that you would like to continue [y\u002FN]y\n\nPassword for the [elastic] user successfully reset.\nNew value: -PPR2=1zsthCZ0gCYz3I\n```\n\n![ElasticSearch](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Fdownload\u002Feditor\u002F1703683116720es6.png)\n\n## Linux安装ES\n\n### 准备工作\n\n* 首先Linux上需要先安装了 `jdk 1.8` 以上版本\n* 前往[官网下载 Linux 版本 **(opens new window)**](https:\u002F\u002Fwww.elastic.co\u002Fcn\u002Fdownloads\u002Felasticsearch)\n* 在 Linux 里，进入 `\u002Fusr\u002Flocal\u002F` 目录，创建一个目录 `elastic-stack`\n* 将下载好的文件上传到新建目录下\n\n### 安装\n\n* 解压ES文件\n\n```shell\ncd elastic-stack\ntar -zxvf elasticsearch-8.3.3-linux-x86_64.tar.gz\nmv elasticsearch-8.3.3 es\n```\n\n* 解压完成后，先不要运行，因为**不可使用root用户直接启动elasticsearch**，我们需要添加用户，并赋予对 `es` 目录的权限\n\n```shell\nuseradd es\n# 设置的密码\npasswd es\n# 赋予新用户权限\nchown -R es \u002Fusr\u002Flocal\u002Felastic-stack\u002Fes\n```\n\n* 修改`conf\u002Felasticsearch.yml`\n\n```xml\n# ----------------------------------- Paths ------------------------------------\n#\n# Path to directory where to store the data (separate multiple locations by comma):\n#\npath.data: \u002Fdata\u002Fes\n#\n# Path to log files:\n#\npath.logs: \u002Fvar\u002Flog\u002Fes\n#\n# 集群名\ncluster.name: elasticsearch\n# 节点名\nnode.name: node-1\n# 允许外界访问的 ip\nnetwork.host: 0.0.0.0\n# http 访问端口\nhttp.port: 9200\n# 集群节点的 master\ncluster.initial_master_nodes: [\"node-1\"]\n```\n\n* 修改`\u002Fetc\u002Fsecurity\u002Flimits.conf`，在文件末尾添加\n\n修改系统中允许应用最多创建多少文件等的限制权限。Linux 默认来说，一般限制应用最多创建的文件是 65535 个。但是 ES 至少需要 65536 的文件创建权限。\n\n```xml\n# 每个进程可以打开的文件数的限制\nes soft nofile 65536\nes hard nofile 65536\n# 操作系统级别对每个用户创建的进程数的限制\n* hard nproc 4096\n# 注：* 带表 Linux 所有用户名称\n```\n\n* 修改 `\u002Fetc\u002Fsysctl.conf`，在文件末尾中增加下面内容：\n\n```xml\n# 一个进程可以拥有的 VMA (虚拟内存区域)的数量,默认值为 65536\nvm.max_map_count=655360\n```\n\n* 配置修改完成后，重新加载\n\n```shell\nsysctl -p\n```\n\n### 切换用户并启动\n\n```shell\n#\t切换用户\nsu es\n# 进入安装目录\ncd \u002Fusr\u002Flocal\u002Felastic-stack\u002Fes\n# -d 代表后台启动\n.\u002Fbin\u002Felasticsearch -d\n```\n\n## Windows上安装Kibana\n\nKibana 是一个免费且开放的用户界面，能够让你对 Elasticsearch 数据进行可视化，并 让你在 Elastic Stack 中进行导航。你可以进行各种操作，从跟踪查询负载，到理解请求如 何流经你的整个应用，都能轻松完成。\n\n**下载时尽量下载与 ElasicSearch 一致的版本。**\n\n### Windows版本\n\n前往[官网下载 Windows 版本 **(opens new window)**](https:\u002F\u002Fwww.elastic.co\u002Fcn\u002Fdownloads\u002Fkibana)\n\n下载后进行解压，目录如图：\n\n![ElasticSearch](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Fdownload\u002Feditor\u002F1703683142527es8.png)\n\n### 启动kibana\n\n* 启动 `Kibana` 之前要启动 `Elasticsearch`\n* 进入 `bin` 目录，双击 `kibana.bat` 启动服务\n\n![ElasticSearch](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Fdownload\u002Feditor\u002F1703683160233es9.png)\n\n* 启动成功后，复制控制台的地址进行访问:[http:\u002F\u002Flocalhost:5601\u002F?code=905726](javascrit:;)\n\n![ElasticSearch](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Fdownload\u002Feditor\u002F1703683180800es10.png)\n\n* 8.x版本访问时，需要先生成一个`token`，我们在 `ES` 的 `bin` 目录下执行以下命令生成 `token`\n\n```shell\nG:\\java\\ElasticSearch\\elasticsearch-8.3.3\\bin>elasticsearch-create-enrollment-token.bat --scope kibana\nwarning: ignoring JAVA_HOME=G:\\java\\java8\\jdk; using bundled JDK\neyJ2ZXIiOiI4LjMuMyIsImFkciI6WyIxNzIuMjAuMTAuNDo5MjAwIl0sImZnciI6ImUzOWZlYzRmM2MyMDg2ZTEyOGIxOTU3OTAyOTVlZTc5OTlkZmI1MDlmMzM5NjNiZWE2Y2Q1ZTRjMWVhOTdlNmMiLCJrZXkiOiJfbHdsYklJQkpWZm9XX1lLNlY5NTo2czNnMFJZYVRfYXNCX19BZVZ6MDVnIn0=\n```\n\n* 将 `token` 复制到 `kibana`登录页中![ElasticSearch](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Fdownload\u002Feditor\u002F1703683493305es11.png)\n* 登录 `ES` 的 `elastic` 用户![ElasticSearch](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Fdownload\u002Feditor\u002F1703683503579es12.png)\n\n![ElasticSearch](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Fdownload\u002Feditor\u002F1703683515826es13.png)\n\n### 修改界面语言\n\n访问界面是英文，可修改成中文，进入根目录下的 `config` 目录，打开 `kibana.yml` 文件，在最末尾处加入以下配置，添加完成后，重新启动即可\n\n```yaml\n# 默认端口\n# server.port: 5601\n# ES 服务器的地址，经过上一步的操作，这些会自动生成，所以不需要改动\n# elasticsearch.hosts: [\"https:\u002F\u002F127.0.0.1:9200\"]\n# 索引名：可改可不改\n# kibana.index: \".kibana\"\n# 支持中文\ni18n.locale: \"zh-CN\"\n```\n\n## Linux上安装Kibana\n\n### 通过文件安装\n\n* 前往[官网下载 Linux 版本 **(opens new window)**](https:\u002F\u002Fwww.elastic.co\u002Fcn\u002Fdownloads\u002Fkibana)\n* 同样将压缩包上传到Linux指定目录下\n\n### 通过wget\n\n```shell\nwget https:\u002F\u002Fartifacts.elastic.co\u002Fdownloads\u002Fkibana\u002Fkibana-8.3.3-linux-x86_64.tar.gz\ntar -xvf kibana-8.0.0-linux-x86_64.tar.gz\nmv kibana-8.3.3 kibana\ncd kibana\n\n# 赋予es用户对kibana目录的权限\nchown -R es \u002Fusr\u002Flocal\u002Fkibana\n\n# 切换至es用户\nsu es\n\n#启动\n.\u002Fbin\u002Fkibana\n```\n\n### 修改配置\n\n修改 `kibana.yml` 配置，允许远程访问\n\n```yaml\nserver.host: \"0.0.0.0\"\n```\n",{"id":23,"created_at":43,"updated_at":43,"code":44,"author_id":4,"book_id":4,"slug":45,"article_id":46,"title":47,"sort_order":23,"content":48,"status":11},"2023-12-27T13:31:52.175+08:00","r2Xr2XOR","elasticsearch基本操作-3",153,"ElasticSearch基本操作","## 数据格式\n\n`Elasticsearch` 是面向文档型数据库，一条数据在这里就是一个文档。我们可以把 `Elasticsearch` 里存储文档数据和关系型数据库 `MySQL` 存储数据的概念进行一个类比。 `ES` 里的 `Index` 可以看做一个库，而 `Types` 相当于表，`Documents` 则相当于表的行。\n\n![ElasticSearch](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Fdownload\u002Feditor\u002F1703684300406es14.png)\n\n这里 Types 的概念已经被逐渐弱化，Elasticsearch 6.X 中，一个 index 下已经只能包含一个type，Elasticsearch 7.X 中, Type 的概念已经被删除了。\n\n用 JSON 作为文档序列化的格式，比如一条用户信息：\n\n```json\n{\n \"name\" : \"John\",\n \"sex\" : \"Male\",\n \"age\" : 25,\n \"birthDate\": \"1990\u002F05\u002F01\",\n \"about\" : \"I love to go rock climbing\",\n \"interests\": [ \"sports\", \"music\" ]\n}\n\n```\n\n## HTTP 操作\n\n* 打开 `kibana` 的 `Dev Tools` 菜单\n\n### 索引操作\n\n### 创建索引-PUT\n\n* 对比关系型数据库，创建索引就等同于创建数据库\n* PUT 索引名\n  ![ElasticSearch](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Fdownload\u002Feditor\u002F1703684347554es15.png)\n* 发送请求后，服务器返回了这样的响应\n\n```json\n{\n  \"acknowledged\": true,  # true 操作成功\n  \"shards_acknowledged\": true, # 分片操作成功\n  \"index\": \"user\" # 索引名称\n}\n```\n\n\n* 如果重复添加索引呢？重复添加则会返回索引已经存在的错误信息 ![ElasticSearch](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Fdownload\u002Feditor\u002F1703684376891es16.png)\n\n### 查看全部索引-GET\n\n* GET \\_cat\u002Findices?v\n  ![ElasticSearch](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Fdownload\u002Feditor\u002F1703684127810es17.png)\n* `_cat`：表示查看的意思；\n* `indices`： 表示索引\n* `health`：当前服务器健康状态：`green`(集群完整)、`yellow`(单点正常、集群不完整)、`red`(单点不正常)\n* `status`：索引打开、关闭状态\n* `index`：索引名\n* `uuid`：索引统一编号\n* `pri`：主分片数量\n* `rep`：副本数量\n* `docs.count`：可用文档数量\n* `docs.deleted`：文档删除状态（逻辑删除）\n* `store.size`：主分片和副分片整体占空间大小\n* `pri.store.size`：主分片占空间大小\n\n### 查看单个索引-GET\n\n* GET 索引名 ![ElasticSearch](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Fdownload\u002Feditor\u002F1703684420339es18.png)\n\n```json\n{\n  \"user\"【索引名】: {\n    \"aliases\"【别名】: {},\n    \"mappings\"【映射】: {},\n    \"settings\"【设置】: {\n      \"index\"【设置 - 索引】: {\n        \"routing\"【设置 - 索引路由】: {\n          \"allocation\": {\n            \"include\": {\n              \"_tier_preference\": \"data_content\"\n            }\n          }\n        },\n        \"number_of_shards\"【设置 - 索引 - 主分片数量】: \"1\",\n        \"provided_name\"【设置 - 索引 - 名称】: \"user\",\n        \"creation_date\"【设置 - 索引 - 创建时间】: \"1659678930693\",\n        \"number_of_replicas\"【设置 - 索引 - 副分片数量】: \"1\",\n        \"uuid\"【设置 - 索引 - 唯一标识】: \"P0pIpPyTSa-zS7kJCeE7Ng\",\n        \"version\"【设置 - 索引版本号】: {\n          \"created\": \"8030399\"\n        }\n      }\n    }\n  }\n}\n```\n\n\n### 删除索引-DELETE\n\n* delete 索引名 ![ElasticSearch](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Fdownload\u002Feditor\u002F1703684436821es19.png)\n\n## 文档操作\n\n### 创建文档-POST\n\n先创建好一个索引，接下来我们来创建文档，并添加数据。这里的文档可以类比为关系型数据库中的表数据，添加的数据格式为`JSON`格式\n\n* POST 索引名\u002F\\_doc ![ElasticSearch](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Fdownload\u002Feditor\u002F1703684485715es20.png)\n\n```json\n{\n  \"_index\"【索引】: \"user\",\n  \"_id\"【唯一标识，支持自定义】: \"C1yqbIIBJVfoW_YKu2D5\",\n  \"_version\"【版本】: 1,\n  \"result\"【结果】: \"created\",#这里的 create 表示创建成功\n  \"_shards\"【分片】: {\n    \"total\"【分片 - 总数】: 2,\n    \"successful\"【分片 - 成功】: 1,\n    \"failed\"【分片 - 失败】: 0\n  },\n  \"_seq_no\": 0,\n  \"_primary_term\": 1\n}\n```\n\n\n* 自定义唯一标识：POST 索引名\u002F\\_doc\u002F(自定义ID)\n\n### 查看文档-GET\n\n* 查看文档时，需要指明文档的唯一性标识，类似于 MySQL 中数据的主键查询\n* 查看指定文档：GET 索引名\u002F\\_doc\u002F(唯一标识)\n* 查看所有数据：GET 索引名\u002F\\_search ![ElasticSearch](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Fdownload\u002Feditor\u002F1703684514295es21.png)\n\n```json\n{\n  \"_index\"【索引】: \"user\",\n  \"_id\": \"C1yqbIIBJVfoW_YKu2D5\",\n  \"_version\": 1,\n  \"_seq_no\": 0,\n  \"_primary_term\": 1,\n  \"found\"【查询结果】: true,\n  \"_source\"【文档源信息】: {\n    \"userName\": \"A佳技术\",\n    \"age\": \"28\"\n  }\n}\n```\n\n\n### 修改文档-POST\n\n**全局修改**\n\n* POST 索引名\u002F\\_doc\u002F(唯一标识) ![ElasticSearch](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Fdownload\u002Feditor\u002F1703684533052es22.png)\n\n```json\n{\n  \"_index\": \"user\",\n  \"_id\": \"C1yqbIIBJVfoW_YKu2D5\",\n  \"_version\": 2,\n  \"result\"【更新操作】: \"updated\",\n  \"_shards\": {\n    \"total\": 2,\n    \"successful\": 1,\n    \"failed\": 0\n  },\n  \"_seq_no\": 1,\n  \"_primary_term\": 1\n}\n```\n\n\n**局部修改**\n\n局部修改需要注意一下，在**8.x版本**的语法变化\n\n* 在**ES7.X版本**中，局部修改语法是：POST 索引名\u002F\\_updata\u002F(唯一标识)\n* 在**ES8.x版本**中，局部修改语法如下图所示： ![ElasticSearch](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Fdownload\u002Feditor\u002F1703684545086es23.png)\n\n![ElasticSearch](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Fdownload\u002Feditor\u002F1703684552261es24.png)\n\n### 删除文档-DELETE\n\n* 删除一个文档不会立即从磁盘上移除，它只是被标记成已删除（逻辑删除）\n* DELETE  索引名\u002F\\_doc\u002F(唯一标识) ![ElasticSearch](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Fdownload\u002Feditor\u002F1703684565647es25.png)\n\n```json\n{\n  \"_index\": \"user\",\n  \"_id\": \"C1yqbIIBJVfoW_YKu2D5\",\n  \"_version\"【版本:对数据的操作，都会更新版本】: 5,\n  \"result\"【结果】: \"deleted\",# deleted 表示数据被标记为删除\n  \"_shards\": {\n    \"total\": 2,\n    \"successful\": 1,\n    \"failed\": 0\n  },\n  \"_seq_no\": 4,\n  \"_primary_term\": 1\n}\n```\n\n\n![ElasticSearch](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Fdownload\u002Feditor\u002F1703684576484es26.png)\n\n### 条件删除文档-POST\n\n* 首先分别增加多条数据，这里我添加了3条数据\n  ![ElasticSearch](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Fdownload\u002Feditor\u002F1703684585992es27.png)\n* POST 索引名\u002F\\_delete\\_by\\_query + 请求体条件\n  ![ElasticSearch](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Fdownload\u002Feditor\u002F1703684601831es28.png)\n\n```json\n{\n  \"took\"【耗时】: 17,\n  \"timed_out\"【是否超时】: false,\n  \"total\"【总数】: 1,\n  \"deleted\"【删除数量】: 1,\n  \"batches\": 1,\n  \"version_conflicts\": 0,\n  \"noops\": 0,\n  \"retries\": {\n    \"bulk\": 0,\n    \"search\": 0\n  },\n  \"throttled_millis\": 0,\n  \"requests_per_second\": -1,\n  \"throttled_until_millis\": 0,\n  \"failures\": []\n}\n```\n\n\n* 查看文档数据情况，年龄20已经删除 ![ElasticSearch](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Fdownload\u002Feditor\u002F1703684611541es29.png)\n\n## 映射基本操作\n\n### 原理\n\n有了索引库，等于有了数据库中的 `database`。\n\n接下来就需要建索引库(`index`)中的映射了，类似于数据库(`database`)中的表结构(`table`)。创建数据库表需要设置字段名称，类型，长度，约束等；索引库也一样，需要知道这个类型下有哪些字段，每个字段有哪些约束信息，这就叫做映射(`mapping`)。\n\n### 先创建索引-PUT\n\n![ElasticSearch](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Fdownload\u002Feditor\u002F1703684637430es30.png)\n\n### 创建映射-PUT\n\n* PUT 索引名\u002F\\_mapping + 请求体内容 ![ElasticSearch](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Fdownload\u002Feditor\u002F1703684646242es31.png)\n\n**映射数据说明：**\n\n* 字段名：任意填写\n* `type`：类型，`Elasticsearch` 中支持的数据类型非常丰富，说几个关键的：\n  * `String类型`，又分两种\n    * `text`：可分词，支持模糊查询，支持准确查询，不支持聚合查询\n    * `keyword`：不可分词，数据会作为完整字段进行匹配，支持模糊查询，支持准确查询，支持聚合查询。\n  * `Numerical`：数值类型，分两类\n    * 基本数据类型：`long`、`integer`、`short`、`byte`、`double`、`float`、`half_float`\n    * 浮点数的高精度类型：`scaled_float`\n  * `Date`：日期类型\n  * `Array`：数组类型\n  * `Object`：对象\n* `index`：是否索引，默认为 true，也就是说你不进行任何配置，所有字段都会被索引。\n  * true：字段会被索引，则可以用来进行搜索\n  * false：字段不会被索引，不能用来搜索\n* `store`：是否将数据进行独立存储，默认为 false。\n\n原始的文本会存储在`_source` 里面，默认情况下其他提取出来的字段都不是独立存储的，是从`_source`里面提取出来的。当然你也可以独立的存储某个字段，只要设置 `\"store\": true` 即可，获取独立存储的字段要比从 `_source` 中解析快得多，但是也会占用更多的空间，所以要根据实际业务需求来设置。\n\n* `analyzer`：分词器，这里的 `ik_max_word` 即使用 `ik 分词器`\n\n### 查看映射-GET\n\n* GET 索引名\u002F\\_mapping![ElasticSearch](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Fdownload\u002Feditor\u002F1703684205391es32.png)\n\n### 索引映射关联-PUT\n\n* 创建新的索引 student1，与之前的 student 进行映射关联\n* PUT student1 + 请求体\n\n```json\n{\n\"settings\": {},\n\"mappings\": {\n    \"properties\": {\n        \"name\":{\n            \"type\": \"text\",\n            \"index\": true\n        },\n        \"sex\":{\n            \"type\": \"text\",\n            \"index\": false\n        },\n        \"age\":{\n            \"type\": \"long\",\n            \"index\": false\n        }\n    }\n}\n}\n\n```\n\n\n![ElasticSearch](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Fdownload\u002Feditor\u002F1703684711042es33.png)\n",{"id":50,"created_at":51,"updated_at":51,"code":52,"author_id":4,"book_id":4,"slug":53,"article_id":54,"title":55,"sort_order":50,"content":56,"status":11},4,"2023-12-27T13:46:36.662+08:00","zJXMbKwb","elasticsearch高级操作-4",154,"ElasticSearch高级操作","## 数据准备\n\n* 将ES中的索引全部删除\n* 添加索引： `PUT people`\n* 添加几个文档 ![ElasticSearch](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Fdownload\u002Feditor\u002F1703684867954es34.png)\n\n## 高级查询\n\n### 分词查询:\\_search\n\n* 请求方式：GET  索引名\u002F\\_search\n* 请求体内容\n\n```json\n{\n    \"query\": {\n        \"match\": {\n            \"name\": \"lisi\"\n        }\n    }\n}\n```\n\n\n![ElasticSearch](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Fdownload\u002Feditor\u002F1703684895864es35.png)\n\n### 查询所有文档:\\_search\n\n* 请求方式：GET 索引名\u002F\\_search\n* 请求体内容\n\n```json\n{\n    \"query\": {\n        \"match_all\": {}\n    }\n}\n# \"query\"：这里的 query 代表一个查询对象，里面可以有不同的查询属性\n# \"match_all\"：查询类型，例如：match_all(代表查询所有)，match，term，range 等等\n# {查询条件}：查询条件会根据类型的不同，写法也有差异\n```\n\n\n![ElasticSearch](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Fdownload\u002Feditor\u002F1703685055297es36.png)\n\n```json\n{\n  \"took\": 0,\n  \"timed_out\": false,\n  \"_shards\": {\n    \"total\": 1,\n    \"successful\": 1,\n    \"skipped\": 0,\n    \"failed\": 0\n  },\n  \"hits\": {\n    \"total\": {\n      \"value\": 6,\n      \"relation\": \"eq\"\n    },\n    \"max_score\": 1,\n    \"hits\": [\n      {\n        \"_index\": \"people\",\n        \"_id\": \"1001\",\n        \"_score\": 1,\n        \"_source\": {\n          \"name\": \"zhangsan\",\n          \"nickname\": \"zhangsan\",\n          \"sex\": \"男\",\n          \"age\": 30\n        }\n      },\n      {\n        \"_index\": \"people\",\n        \"_id\": \"1002\",\n        \"_score\": 1,\n        \"_source\": {\n          \"name\": \"lisi\",\n          \"nickname\": \"lisi\",\n          \"sex\": \"男\",\n          \"age\": 20\n        }\n      },\n      {\n        \"_index\": \"people\",\n        \"_id\": \"1003\",\n        \"_score\": 1,\n        \"_source\": {\n          \"name\": \"wangwu\",\n          \"nickname\": \"wangwu\",\n          \"sex\": \"女\",\n          \"age\": 40\n        }\n      },\n      {\n        \"_index\": \"people\",\n        \"_id\": \"1004\",\n        \"_score\": 1,\n        \"_source\": {\n          \"name\": \"zhangsan1\",\n          \"nickname\": \"zhangsan1\",\n          \"sex\": \"女\",\n          \"age\": 50\n        }\n      },\n      {\n        \"_index\": \"people\",\n        \"_id\": \"1005\",\n        \"_score\": 1,\n        \"_source\": {\n          \"name\": \"zhangsan2\",\n          \"nickname\": \"zhangsan2\",\n          \"sex\": \"女\",\n          \"age\": 30\n        }\n      },\n      {\n        \"_index\": \"people\",\n        \"_id\": \"1006\",\n        \"_score\": 1,\n        \"_source\": {\n          \"name\": \"zhangsan222\",\n          \"nickname\": \"zhangsan222\",\n          \"sex\": \"女\",\n          \"age\": 30\n        }\n      }\n    ]\n  }\n}\n```\n\n\n### 字段匹配查询:multi\\_match\n\n* `multi_match` 与 `match` 类似，不同的是它可以在多个字段中查询。\n* 请求方式：GET 索引名\u002F\\_search\n* 请求体内容\n\n```json\n{\n    \"query\": {\n        \"multi_match\": {\n            \"query\": \"zhangsan\",\n            \"fields\": [\"name\",\"nickname\"]\n        }\n    }\n}\n```\n\n\n查询 key 为 `name` 和 `nickname`，value 为 `zhangsan` 的数据\n\n![ElasticSearch](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Fdownload\u002Feditor\u002F1703685079762es37.png)\n\n### 单关键字精确查询:term\n\n* `term` 查询，精确的关键词匹配查询，不对查询条件进行分词，即只能单关键字精确查询。\n* 请求方式：GET 索引名\u002F\\_search\n* 请求体内容\n\n```json\n{\n\"query\": {\n    \"term\": {\n        \"name\": {\n            \"value\": \"zhangsan\"\n        }\n    }\n}\n}\n```\n\n\n![ElasticSearch](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Fdownload\u002Feditor\u002F1703685090141es38.png)\n\n### 多关键字精确查询:terms\n\n* terms 查询和 term 查询一样，但它允许你指定多值进行匹配。如果这个字段包含了指定值中的任何一个值，那么这个文档满足条件，类似于 mysql 的 in\n* 请求方式：GET 索引名\u002F\\_search\n* 请求体内容\n\n```json\n{\n    \"query\": {\n        \"terms\": {\n            \"name\": [\"zhangsan\",\"lisi\"]\n        }\n    }\n}\n```\n\n\n![ElasticSearch](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Fdownload\u002Feditor\u002F1703685099748es39.png)\n\n### 指定字段查询:\\_source\n\n* 默认情况下，Elasticsearch 在搜索的结果中，会把文档中保存在 `_source` 的所有字段都返回。\n* 如果我们只想获取其中的部分字段，我们可以添加 `_source` 的过滤\n* 请求方式：GET 索引名\u002F\\_search\n* 请求体内容\n\n```json\n{\n    \"_source\": [\"name\",\"nickname\"], \n    \"query\": {\n        \"terms\": {\n            \"nickname\": [\"zhangsan\"]\n        }\n    }\n}\n```\n\n\n![ElasticSearch](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Fdownload\u002Feditor\u002F1703685110300es40.png)\n\n### 过滤字段:includes&excludes\n\n* includes：来指定想要显示的字段\n* excludes：来指定不想要显示的字段\n* 请求方式：GET 索引名\u002F\\_search\n* 请求体内容\n\n```json\n{\n    \"_source\": {\n        \"includes\": [\"name\",\"nickname\"]\n    }, \n    \"query\": {\n        \"terms\": {\n            \"nickname\": [\"zhangsan\"]\n        }\n    }\n}\n```\n\n\n![ElasticSearch](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Fdownload\u002Feditor\u002F1703685123761es41.png)\n\n![ElasticSearch](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Fdownload\u002Feditor\u002F1703685132169es42.png)\n\n### 组合查询:bool\n\n* `bool` 把各种其它查询通过 `must`（必须，类似 and）、`must_not`（必须不，类似 not）、`should`（应该 类似 or）的方式进行组合\n* 请求方式：GET 索引名\u002F\\_search\n* 请求体内容\n\n```json\n{\n    \"query\": {\n        \"bool\": {\n            \"must\": [\n                {\n                    \"match\": {\n                        \"name\": \"zhangsan\"\n                    }\n                }\n            ],\n            \"must_not\": [\n                {\n                    \"match\": {\n                        \"age\": \"40\"\n                    }\n                }\n            ],\n            \"should\": [\n                {\n                    \"match\": {\n                        \"sex\": \"男\"\n                    }\n                }\n            ]\n        }\n    }\n}\n```\n\n\n![ElasticSearch](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Fdownload\u002Feditor\u002F1703685163805es43.png)\n\n### 范围查询:range\n\n* `range` 查询找出那些落在指定区间内的数字或者时间。`range` 查询允许以下字符\n\n\n| **操作符** | **说明** |\n| ---------- | -------- |\n| gt         | >        |\n| gte        | >=       |\n| lt         | \u003C        |\n| lte        | \u003C=       |\n\n* 请求方式：GET 索引名\u002F\\_search\n* 请求体内容\n\n```json\n{\n    \"query\": {\n        \"range\": {\n            \"age\": {\n                \"gte\": 30,\n                \"lte\": 35\n            }\n        }\n    }\n}\n```\n\n\n![ElasticSearch](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Fdownload\u002Feditor\u002F1703685181188es44.png)\n\n### 模糊查询:fuzzy\n\n* `fuzzy`：返回包含与搜索字词相似的字词的文档，更多`fuzzy`有关解释请查看[官方文档 **(opens new window)**](https:\u002F\u002Fwww.elastic.co\u002Fguide\u002Fcn\u002Felasticsearch\u002Fguide\u002Fcurrent\u002Ffuzziness.html) -编辑距离是将一个术语转换为另一个术语所需的一个字符更改的次数。这些更改可以包括：\n* 更改字符（box → fox）\n* 删除字符（black → lack）\n* 插入字符（sic → sick）\n* 转置两个相邻字符（act → cat）\n* 为了找到相似的术语，fuzzy 查询会在指定的编辑距离内创建一组搜索词的所有可能的变体或扩展。然后查询返回每个扩展的完全匹配。\n* 通过 `fuzziness` 修改编辑距离。一般使用默认值 `AUTO`，根据术语的长度生成编辑距离。\n* 请求方式：GET 索引名\u002F\\_search\n* 请求体内容\n\n```json\n{\n    \"query\": {\n        \"fuzzy\": {\n            \"name\": {\n                \"value\": \"zhangsan\"\n            }\n        }\n    }\n}\n```\n\n\n![ElasticSearch](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Fdownload\u002Feditor\u002F1703685195564es45.png)\n\n* 结果JSON\n\n```json\n{\n  \"took\": 15,\n  \"timed_out\": false,\n  \"_shards\": {\n    \"total\": 1,\n    \"successful\": 1,\n    \"skipped\": 0,\n    \"failed\": 0\n  },\n  \"hits\": {\n    \"total\": {\n      \"value\": 3,\n      \"relation\": \"eq\"\n    },\n    \"max_score\": 1.540445,\n    \"hits\": [\n      {\n        \"_index\": \"people\",\n        \"_id\": \"1001\",\n        \"_score\": 1.540445,\n        \"_source\": {\n          \"name\": \"zhangsan\",\n          \"nickname\": \"zhangsan\",\n          \"sex\": \"男\",\n          \"age\": 30\n        }\n      },\n      {\n        \"_index\": \"people\",\n        \"_id\": \"1004\",\n        \"_score\": 1.3478894,\n        \"_source\": {\n          \"name\": \"zhangsan1\",\n          \"nickname\": \"zhangsan1\",\n          \"sex\": \"女\",\n          \"age\": 50\n        }\n      },\n      {\n        \"_index\": \"people\",\n        \"_id\": \"1005\",\n        \"_score\": 1.3478894,\n        \"_source\": {\n          \"name\": \"zhangsan2\",\n          \"nickname\": \"zhangsan2\",\n          \"sex\": \"女\",\n          \"age\": 30\n        }\n      }\n    ]\n  }\n}\n```\n\n\n![ElasticSearch](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Fdownload\u002Feditor\u002F1703685205992es46.png)\n\n* 结果JSON\n\n```json\n{\n  \"took\": 2,\n  \"timed_out\": false,\n  \"_shards\": {\n    \"total\": 1,\n    \"successful\": 1,\n    \"skipped\": 0,\n    \"failed\": 0\n  },\n  \"hits\": {\n    \"total\": {\n      \"value\": 3,\n      \"relation\": \"eq\"\n    },\n    \"max_score\": 1.540445,\n    \"hits\": [\n      {\n        \"_index\": \"people\",\n        \"_id\": \"1001\",\n        \"_score\": 1.540445,\n        \"_source\": {\n          \"name\": \"zhangsan\",\n          \"nickname\": \"zhangsan\",\n          \"sex\": \"男\",\n          \"age\": 30\n        }\n      },\n      {\n        \"_index\": \"people\",\n        \"_id\": \"1004\",\n        \"_score\": 1.3478894,\n        \"_source\": {\n          \"name\": \"zhangsan1\",\n          \"nickname\": \"zhangsan1\",\n          \"sex\": \"女\",\n          \"age\": 50\n        }\n      },\n      {\n        \"_index\": \"people\",\n        \"_id\": \"1005\",\n        \"_score\": 1.3478894,\n        \"_source\": {\n          \"name\": \"zhangsan2\",\n          \"nickname\": \"zhangsan2\",\n          \"sex\": \"女\",\n          \"age\": 30\n        }\n      }\n    ]\n  }\n}\n```\n\n\n### 多IDs查询:ids\n\n* 请求方式：GET 索引名\u002F\\_search\n* 请求体内容\n\n```json\n{\n    \"query\": {\n        \"ids\" : {\n            \"values\" : [\"1001\", \"1004\", \"1006\"]\n        }\n    }\n}\n```\n\n\n![ElasticSearch](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Fdownload\u002Feditor\u002F1703685220511es47.png)\n\n### 前缀查询:prefix\n\n* 请求方式：GET 索引名\u002F\\_search\n* 请求体内容\n\n```json\n{\n    \"query\": {\n        \"prefix\": {\n            \"name\": {\n                \"value\": \"zhangsan\"\n            }\n        }\n    }\n}\n```\n\n\n![ElasticSearch](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Fdownload\u002Feditor\u002F1703685231381es48.png)\n\n### 单字段排序:sort\n\n* `sort` 可以让我们按照不同的字段进行排序，并且通过 `order` 指定排序的方式。`desc` 降序，`asc` 升序\n* 请求方式：GET 索引名\u002F\\_search\n* 请求体内容\n\n```json\n{\n    \"query\": {\n        \"match\": {\n            \"name\":\"zhangsan\"\n        }\n    },\n    \"sort\": [{\n        \"age\": {\n            \"order\":\"desc\"\n        }\n    }]\n}\n```\n\n\n![ElasticSearch](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Fdownload\u002Feditor\u002F1703685245078es49.png)\n\n### 多字段排序\n\n* 假设我们想要结合使用 age 和 \\_score 进行查询，并且匹配的结果首先按照年龄排序，然后按照相关性得分排序\n* 请求方式：GET 索引名\u002F\\_search\n* 请求体内容\n\n```json\n{\n    \"query\": {\n        \"match_all\": {}\n    },\n    \"sort\": [\n        {\n            \"age\": {\n                \"order\": \"desc\"\n            }\n        },\n        {\n            \"_score\":{\n                \"order\": \"desc\"\n            }\n        }\n    ]\n}\n```\n\n\n![ElasticSearch](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Fdownload\u002Feditor\u002F1703685255955es50.png)\n\n* 结果JSON\n\n```json\n{\n  \"took\": 1,\n  \"timed_out\": false,\n  \"_shards\": {\n    \"total\": 1,\n    \"successful\": 1,\n    \"skipped\": 0,\n    \"failed\": 0\n  },\n  \"hits\": {\n    \"total\": {\n      \"value\": 6,\n      \"relation\": \"eq\"\n    },\n    \"max_score\": null,\n    \"hits\": [\n      {\n        \"_index\": \"people\",\n        \"_id\": \"1004\",\n        \"_score\": 1,\n        \"_source\": {\n          \"name\": \"zhangsan1\",\n          \"nickname\": \"zhangsan1\",\n          \"sex\": \"女\",\n          \"age\": 50\n        },\n        \"sort\": [\n          50,\n          1\n        ]\n      },\n      {\n        \"_index\": \"people\",\n        \"_id\": \"1003\",\n        \"_score\": 1,\n        \"_source\": {\n          \"name\": \"wangwu\",\n          \"nickname\": \"wangwu\",\n          \"sex\": \"女\",\n          \"age\": 40\n        },\n        \"sort\": [\n          40,\n          1\n        ]\n      },\n      {\n        \"_index\": \"people\",\n        \"_id\": \"1001\",\n        \"_score\": 1,\n        \"_source\": {\n          \"name\": \"zhangsan\",\n          \"nickname\": \"zhangsan\",\n          \"sex\": \"男\",\n          \"age\": 30\n        },\n        \"sort\": [\n          30,\n          1\n        ]\n      },\n      {\n        \"_index\": \"people\",\n        \"_id\": \"1005\",\n        \"_score\": 1,\n        \"_source\": {\n          \"name\": \"zhangsan2\",\n          \"nickname\": \"zhangsan2\",\n          \"sex\": \"女\",\n          \"age\": 30\n        },\n        \"sort\": [\n          30,\n          1\n        ]\n      },\n      {\n        \"_index\": \"people\",\n        \"_id\": \"1006\",\n        \"_score\": 1,\n        \"_source\": {\n          \"name\": \"zhangsan222\",\n          \"nickname\": \"zhangsan222\",\n          \"sex\": \"女\",\n          \"age\": 30\n        },\n        \"sort\": [\n          30,\n          1\n        ]\n      },\n      {\n        \"_index\": \"people\",\n        \"_id\": \"1002\",\n        \"_score\": 1,\n        \"_source\": {\n          \"name\": \"lisi\",\n          \"nickname\": \"lisi\",\n          \"sex\": \"男\",\n          \"age\": 20\n        },\n        \"sort\": [\n          20,\n          1\n        ]\n      }\n    ]\n  }\n}\n```\n\n\n### 高亮查询:highlight\n\n* 在进行关键字搜索时，搜索出的内容中的关键字会显示不同的颜色，称之为高亮。\n* `Elasticsearch` 可以对查询内容中的关键字部分，进行标签和样式(高亮)的设置。\n\n在使用 `match` 查询的同时，加上一个 `highlight` 属性：\n\n* `pre_tags`：前置标签\n* `post_tags`：后置标签\n* `fields`：需要高亮的字段\n* `title`：这里声明 `title` 字段需要高亮，后面可以为这个字段设置特有配置，也可以为空\n* 请求方式：GET 索引名\u002F\\_search\n* 请求体内容\n\n```json\n{\n    \"query\": {\n        \"match\": {\n            \"name\": \"zhangsan\"\n        }\n    },\n    \"highlight\": {\n        \"pre_tags\": \"\u003Cfont color='red'>\",\n        \"post_tags\": \"\u003C\u002Ffont>\",\n        \"fields\": {\n            \"name\": {}\n        }\n    }\n}\n```\n\n\n![ElasticSearch](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Fdownload\u002Feditor\u002F1703685273178es51.png)\n\n### 分页查询:from&size\n\n* `from`：当前页的起始索引，默认从 0 开始。 **from = (pageNum - 1) \\* size**\n* `size`：每页显示多少条\n* 请求方式：GET 索引名\u002F\\_search\n* 请求体内容\n\n```json\n{\n    \"query\": {\n        \"match_all\": {}\n    },\n    \"sort\": [\n        {\n            \"age\": {\n                \"order\": \"desc\"\n            }\n        }\n    ],\n    \"from\": 0,\n    \"size\": 2\n}\n```\n\n\n![ElasticSearch](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Fdownload\u002Feditor\u002F1703685283707es52.png)\n\n![ElasticSearch](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Fdownload\u002Feditor\u002F1703685291472es53.png)\n\n## 聚合查询:aggs\n\n聚合允许使用者对 es 文档进行统计分析，类似与关系型数据库中的 group by，当然还有很多其他的聚合，例如取最大值、平均值等等。\n\n聚合查询 `aggs` 字段，该字段里的第一个字段是自定义名字，一个聚合\u002F分组需要另一个聚合\u002F分组需要用到自定义名字（嵌套查询）。第二个字段是聚合查询类型。查询结果不仅有聚合结果，也有设计到的详细数据。\n\n结果长度 `size` 字段和 `aggs` 字段同级，代表只获取聚合结果，不获取涉及到的详细数据。\n\n* 请求方式：GET 索引名\u002F\\_search\n* 请求体内容\n\n```json\n{\n    \"aggs\" : {\u002F\u002F聚合操作\n        \"price_group\":{ \u002F\u002F名称，随意起名\n            \"terms\":{   \u002F\u002F分组操作\n                \"field\":\"age\" \u002F\u002F分组字段\n            }\n        }\n    },\n    \"size\":0\n}\n```\n\n\n* 结果JSON\n\n```json\n{\n  \"took\": 2,\n  \"timed_out\": false,\n  \"_shards\": {\n    \"total\": 1,\n    \"successful\": 1,\n    \"skipped\": 0,\n    \"failed\": 0\n  },\n  \"hits\": {\n    \"total\": {\n      \"value\": 6,\n      \"relation\": \"eq\"\n    },\n    \"max_score\": null,\n    \"hits\": []\n  },\n  \"aggregations\": {\n    \"price_group\": {\n      \"doc_count_error_upper_bound\": 0,\n      \"sum_other_doc_count\": 0,\n      \"buckets\": [\n        {\n          \"key\": 30,\n          \"doc_count\": 3\n        },\n        {\n          \"key\": 20,\n          \"doc_count\": 1\n        },\n        {\n          \"key\": 40,\n          \"doc_count\": 1\n        },\n        {\n          \"key\": 50,\n          \"doc_count\": 1\n        }\n      ]\n    }\n  }\n}\n```\n\n\n![ElasticSearch](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Fdownload\u002Feditor\u002F1703685303873es54.png)\n\n* 如果想求`price`的平均值,将`terms`改为`avg`\n* 结果JSON\n\n```json\n{\n  \"took\": 0,\n  \"timed_out\": false,\n  \"_shards\": {\n    \"total\": 1,\n    \"successful\": 1,\n    \"skipped\": 0,\n    \"failed\": 0\n  },\n  \"hits\": {\n    \"total\": {\n      \"value\": 6,\n      \"relation\": \"eq\"\n    },\n    \"max_score\": null,\n    \"hits\": []\n  },\n  \"aggregations\": {\n    \"price_group\": {\n      \"value\": 33.333333333333336\n    }\n  }\n}\n```\n\n\n### 简单聚合\n\n* 对某个字段取最大值 max\n\n**请求体内容**\n\n```json\n{\n    \"aggs\":{\n        \"max_age\":{   \u002F\u002F 自定义名字\n            \"max\":{\"field\":\"age\"}\n        }\n    },\n    \"size\":0  \u002F\u002F 只获取聚合结果，不获取每一个数据\n}\n```\n\n\n* 对某个字段取最小值 min\n\n**请求体内容**\n\n```json\n{\n    \"aggs\":{\n        \"min_age\":{   \u002F\u002F 自定义名字\n            \"min\":{\"field\":\"age\"}\n        }\n    },\n    \"size\":0  \u002F\u002F 只获取聚合结果，不获取每一个数据\n}\n```\n\n\n* 对某个字段求和 sum\n\n**请求体内容**\n\n```json\n{\n    \"aggs\":{\n        \"sum_age\":{   \u002F\u002F 自定义名字\n            \"sum\":{\"field\":\"age\"}\n        }\n    },\n    \"size\":0  \u002F\u002F 只获取聚合结果，不获取每一个数据\n}\n```\n\n\n* 对某个字段取平均值 avg\n\n**请求体内容**\n\n```json\n{\n    \"aggs\":{\n        \"avg_age\":{ \u002F\u002F 自定义名字\n            \"avg\":{\"field\":\"age\"}\n        }\n    },\n    \"size\":0 \u002F\u002F 只获取聚合结果，不获取每一个数据\n}\n```\n\n\n* 对某个字段的值进行去重之后再取总数\n\n**请求体内容**\n\n```json\n{\n    \"aggs\":{\n        \"distinct_age\":{ \u002F\u002F 自定义名字\n            \"cardinality\":{\"field\":\"age\"}\n        }\n    },\n    \"size\":0 \u002F\u002F 只获取聚合结果，不获取每一个数据\n}\n```\n\n\n* State 聚合\n\n`stats` 聚合，对某个字段一次性返回 `count`，`max`，`min`，`avg` 和 `sum` 五个指标\n\n**请求体内容**\n\n```json\n{\n    \"aggs\":{\n        \"stats_age\":{ \u002F\u002F 自定义名字a\n            \"stats\":{\"field\":\"age\"}\n        }\n    },\n    \"size\":0 \u002F\u002F 只获取聚合结果，不获取每一个数据\n}\n```\n\n\n### 桶聚合查询:terms\n\n**桶聚和相当于 `sql` 中的 `group by` 语句**\n\n* `terms` 聚合，分组统计\n\n**请求体内容**\n\n```json\n{\n    \"aggs\":{\n        \"age_groupby\":{ \u002F\u002F 自定义名字\n            \"terms\":{\"field\":\"age\"}\n        }\n    },\n    \"size\":0 \u002F\u002F 只获取聚合结果，不获取每一个数据\n}\n```\n\n\n* 嵌套查询\n\n**请求体内容**\n\n```json\n{\n    \"aggs\":{\n        \"age_groupby\":{ \u002F\u002F 自定义名字\n            \"terms\":{\n                \"field\": \"age\",\n            },\n            \"aggs\": {\n                \"average_age\": {\n                \"avg\": {\n                    \"field\": \"age\"\n                }\n            }\n      }\n        }\n    },\n    \"size\":0 \u002F\u002F 只获取聚合结果，不获取每一个数据\n}\n```\n\n\n* 在 `terms` 分组下再进行聚合和排序\n\n**请求体内容**\n\n```json\n{\n    \"aggs\":{\n        \"age_groupby\":{ \u002F\u002F 自定义名字\n            \"terms\":{\n                \"field\": \"age\",\n                \"order\": {\n                \t\"average_age\": \"desc\"\n                }\n            },\n            \"aggs\": {\n                \"average_age\": {\n                \"avg\": {\n                    \"field\": \"age\"\n                }\n            }\n      }\n        }\n    },\n    \"size\":0 \u002F\u002F 只获取聚合结果，不获取每一个数据\n}\n```\n",{"id":58,"created_at":59,"updated_at":59,"code":60,"author_id":4,"book_id":4,"slug":61,"article_id":62,"title":63,"sort_order":58,"content":64,"status":11},5,"2023-12-27T13:58:26.408+08:00","bn8oMKe7","elasticsearch集成ik分词器-5",155,"ElasticSearch集成IK分词器","## 前述\n\n为了能够更好地对中文进行搜索和查询，就需要在Elasticsearch中集成好的分词器插件， 而 `IK` 分词器就是用于对中文提供支持得插件。\n\n## 集成IK分词器\n\n### 下载\n\n* [IK分词器下载 **(opens new window)**](https:\u002F\u002Fgithub.com\u002Fmedcl\u002Felasticsearch-analysis-ik\u002Freleases)\n* 注意版本需要对应，目前`IK`分词器还没有`8.3.3`版本，所以就先重新下一个`8.2.3`版本的`ES`\n\n### 安装\n\n* 将下载的IK压缩包直接解压到 `elasticsearch-8.2.3` 的 `plugins`目录下，重启`ES`\n\n### 使用 IK 分词器\n\nIK 分词器提供了两个分词算法：\n\n▶️ `ik_smart`: 会做最粗粒度的拆分，适合 Phrase 查询\n\n▶️ `Ik_max_word`:会将文本做最细粒度的拆分,会穷尽各种可能的组合,适合 Term Query\n\n* 为索引指定默认IK分词器\n\n**这样我们在索引中就不用创建每一个字段，可以通过动态字段映射，将String类型的字段映射为text类型，同时分词器指定为ik\\_max\\_word**\n\n```json\nPUT ik_index\n{\n  \"settings\": {\n    \"analysis\": {\n      \"analyzer\": {\n        \"default\": {\n          \"type\": \"ik_max_word\"\n        }\n      }\n    }\n  }\n}\n```\n\n\n* 使用\n\n```shell\nGET 索引名\u002F_analyze\n{\n    \"analyzer\": \"ik_smart\",\n    \"text\": [\"分词词语测试\"]\n}\n```\n\n\n### 自定义分词效果\n\n* 我们在使用 `IK` 分词器时会发现其实有时候分词的效果也并不是我们所期待的,有时一些特 殊得术语会被拆开，但实际上我们希望不要拆开。\n* `IK` 插件给我们提供了自定义分词字典，我们可以添加自己想要保留得字了。\n\n**自定义分词**\n\n* 首先在 `elasticsearch-analysis-ik-8.2.3`的`config`目录下新建一个`my_self.dic`文件，输入自己希望不想被拆开的术语，比如`分词词语`\n* 接下来我们修改配置文件：`config\u002FIKAnalyzer.cfg.xml`\n\n```xml\n\u003C?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\u003C!DOCTYPE properties SYSTEM \"http:\u002F\u002Fjava.sun.com\u002Fdtd\u002Fproperties.dtd\">\n\u003Cproperties>\n\t\u003Ccomment>IK Analyzer 扩展配置\u003C\u002Fcomment>\n\t\u003C!--用户可以在这里配置自己的扩展字典多个文件用';'分隔开 -->\n\t\u003Centry key=\"ext_dict\">my_self.dic\u003C\u002Fentry>\n\t \u003C!--用户可以在这里配置自己的扩展停止词字典-->\n\t\u003Centry key=\"ext_stopwords\">\u003C\u002Fentry>\n\t\u003C!--用户可以在这里配置远程扩展字典 -->\n\t\u003C!-- \u003Centry key=\"remote_ext_dict\">words_location\u003C\u002Fentry> -->\n\t\u003C!--用户可以在这里配置远程扩展停止词字典-->\n\t\u003C!-- \u003Centry key=\"remote_ext_stopwords\">words_location\u003C\u002Fentry> -->\n\u003C\u002Fproperties>\n```\n\n\n### 热更新 IK 分词使用方法\n\n```xml\n\u003C!--用户可以在这里配置远程扩展字典 -->\n\u003Centry key=\"remote_ext_dict\">location\u003C\u002Fentry>\n\u003C!--用户可以在这里配置远程扩展停止词字典-->\n\u003Centry key=\"remote_ext_stopwords\">location\u003C\u002Fentry>\n```\n\n\n其中 `location` 是指一个 url，比如 `http:\u002F\u002Fyoursite.com\u002FgetCustomDict`，该请求只需满足以下两点即可完成分词热更新。\n\n* 1、该 http 请求需要返回两个头部(header)，一个是 `Last-Modified`，一个是 `ETag`，这两者都是字符串类型，只要有一个发生变化，该插件就会去抓取新的分词进而更新词库。\n* 2、该 http 请求返回的内容格式是一行一个分词，换行符用 `\\n`即可。\n\n满足上面两点要求就可以实现热更新分词了，不需要重启 ES 实例。\n\n可以将需自动更新的热词放在一个 `UTF-8` 编码的 `.txt` 文件里，放在 `nginx` 或其他简易 `http server` 下，当 .`txt` 文件修改时，`http server` 会在客户端请求该文件时自动返回相应的 `Last-Modified` 和 `ETag`。可以另外做一个工具来从业务系统提取相关词汇，并更新这个 `.txt` 文件。\n\n### 常见问题\n\n**1.自定义词典为什么没有生效？**\n\n* 请确保扩展词典的文本格式为 UTF8 编码\n\n**2.分词测试失败，请在某个索引下调用analyze接口测试,而不是直接调用analyze接口 如:**\n\n```shell\ncurl -XGET \"http:\u002F\u002Flocalhost:9200\u002Fyour_index\u002F_analyze\" -H 'Content-Type: application\u002Fjson' -d'\n{\n   \"text\":\"分词词语测试\",\"tokenizer\": \"my_ik\"\n}'\n```\n\n\n## JAVA-API\n\n* 本实例在[ElasticSearch-8.X的JAVA-API](\u002Fdocs\u002Fmiddleware\u002FelasticSearch\u002Fes8-java-api.html) 基础上进行再扩展\n\n```java\n\u002F**\n * @version 1.0.0\n * @className: IndexTest\n * @description: 创建索引并设置String列使用IK分词器\n * @author: smartzeng\n * @create: 2022\u002F8\u002F8 10:03\n *\u002F\n@SpringBootTest\n@Slf4j\npublic class IndexTest\n{\n    @Autowired\n    private ElasticsearchClient elasticsearchClient;\n\n    \u002F**\n     * 创建索引并设置字段使用IK分词器\n     *\n     * @throws IOException ioexception\n     *\u002F\n    @Test\n    void createIndexAndIk() throws IOException {\n        Map\u003CString, Property> documentMap = new HashMap\u003C>();\n        documentMap.put(\"userName\",Property.of(p -> p\n                .text(TextProperty.of(textProperty ->\n                        textProperty.index(true).analyzer(\"ik_max_word\")))));\n\n        documentMap.put(\"age\", Property.of(property ->\n                        property.integer(IntegerNumberProperty.of(integerNumberProperty\n                                -> integerNumberProperty.index(true))\n                        )\n                )\n        );\n        CreateIndexResponse response = elasticsearchClient.indices().create(createIndexBuilder ->\n                createIndexBuilder.index(\"user\").mappings(mappings ->\n                                mappings.properties(documentMap))\n                        .aliases(\"User\",aliases ->\n                                aliases.isWriteIndex(true))\n        );\n        \u002F\u002F响应状态\n        boolean acknowledged = response.acknowledged();\n        boolean shardsAcknowledged = response.shardsAcknowledged();\n        String index = response.index();\n        log.info(\"创建索引状态:{}\",acknowledged);\n        log.info(\"已确认的分片:{}\",shardsAcknowledged);\n        log.info(\"索引名称:{}\",index);\n    }\n}\n```\n",{"id":13,"created_at":66,"updated_at":66,"code":67,"author_id":4,"book_id":4,"slug":68,"article_id":69,"title":70,"sort_order":13,"content":71,"status":11},"2023-12-27T14:02:27.729+08:00","Rv8JoXm5","elasticsearch-7x的java-api-6",156,"ElasticSearch-7.X的JAVA-API","## 前述\n\n* 虽然现在`ES`已经到了`8.x版本`，但是目前实际运用中，`7.X版本`的还是比较多的，所以关于`ES`在`Java`中的使用，会区分`7.x版本` 和 `8.x版本`\n* **7版本**以`7.8.0`为例，**8版本**以`8.3.3`为例\n* 当前章节为`7.8.0`版本在Java中的使用\n* 8.3.3版本[请查看🔎](\u002Fuser\u002FspecialColumnArticleView?type=1&specialColumnId=1)\n\n## 项目配置\n\n### 依赖引入\n\n```xml\n\u003Cdependencies>\n    \u003Cdependency>\n        \u003CgroupId>org.elasticsearch\u003C\u002FgroupId>\n        \u003CartifactId>elasticsearch\u003C\u002FartifactId>\n        \u003Cversion>7.8.0\u003C\u002Fversion>\n    \u003C\u002Fdependency>\n    \u003C!-- elasticsearch 的客户端 -->\n    \u003Cdependency>\n        \u003CgroupId>org.elasticsearch.client\u003C\u002FgroupId>\n        \u003CartifactId>elasticsearch-rest-high-level-client\u003C\u002FartifactId>\n        \u003Cversion>7.8.0\u003C\u002Fversion>\n    \u003C\u002Fdependency>\n    \u003C!-- elasticsearch 依赖 2.x 的 log4j -->\n    \u003Cdependency>\n        \u003CgroupId>org.apache.logging.log4j\u003C\u002FgroupId>\n        \u003CartifactId>log4j-api\u003C\u002FartifactId>\n        \u003Cversion>2.8.2\u003C\u002Fversion>\n    \u003C\u002Fdependency>\n    \u003Cdependency>\n        \u003CgroupId>org.apache.logging.log4j\u003C\u002FgroupId>\n        \u003CartifactId>log4j-core\u003C\u002FartifactId>\n        \u003Cversion>2.8.2\u003C\u002Fversion>\n    \u003C\u002Fdependency>\n    \u003Cdependency>\n        \u003CgroupId>com.fasterxml.jackson.core\u003C\u002FgroupId>\n        \u003CartifactId>jackson-databind\u003C\u002FartifactId>\n        \u003Cversion>2.9.3\u003C\u002Fversion>\n    \u003C\u002Fdependency>\n    \u003C!-- junit 单元测试 -->\n    \u003Cdependency>\n        \u003CgroupId>junit\u003C\u002FgroupId>\n        \u003CartifactId>junit\u003C\u002FartifactId>\n        \u003Cversion>4.12\u003C\u002Fversion>\n    \u003C\u002Fdependency>\n\u003C\u002Fdependencies>\n```\n\n注意：elasticsearch 的两个依赖要和 elasticsearch 服务器版本一致。\n\n### 客户端对象\n\n```java\n\u002F\u002F 运行后控制台如果不报错，则代表连接成功。\npublic class ESTest_Client {\n    public static void main(String[] args) throws IOException {\n\n        \u002F\u002F 创建ES客户端\n        \u002F\u002F 9200 端口为 Elasticsearch 的 Web 通信端口，localhost 为启动 ES 服务的主机名\n        RestHighLevelClient esClient = new RestHighLevelClient(\n                RestClient.builder(new HttpHost(\"localhost\",9200,\"http\"))\n        );\n        \u002F\u002F 关闭ES客户端\n        esClient.close();\n\n    }\n}\n```\n\n## 索引操作\n\n### 创建索引\n\n```java\npublic class ESTest_Index_Create {\n    public static void main(String[] args) throws IOException {\n\n        \u002F\u002F创建ES客户端\n        RestHighLevelClient esClient = new RestHighLevelClient(\n                RestClient.builder(new HttpHost(\"localhost\",9200,\"http\"))\n        );\n\n        \u002F\u002F创建索引\n        CreateIndexRequest request = new CreateIndexRequest(\"user\");\n        CreateIndexResponse createIndexResponse =\n                esClient.indices().create(request, RequestOptions.DEFAULT);\n\n        \u002F\u002F响应状态\n        boolean acknowledged = createIndexResponse.isAcknowledged();\n        System.out.println(\"创建索引状态: \"+acknowledged);\n        esClient.close();\n\n    }\n}\n```\n\n### 查看索引\n\n```java\npublic class ESTest_Index_Search {\n    public static void main(String[] args) throws IOException {\n\n        \u002F\u002F创建ES客户端\n        RestHighLevelClient esClient = new RestHighLevelClient(\n                RestClient.builder(new HttpHost(\"localhost\",9200,\"http\"))\n        );\n\n        \u002F\u002F查询索引\n        GetIndexRequest request = new GetIndexRequest(\"user\");\n        GetIndexResponse getIndexResponse =\n                esClient.indices().get(request, RequestOptions.DEFAULT);\n\n        \u002F\u002F响应状态\n        System.out.println(getIndexResponse.getAliases());\n        System.out.println(getIndexResponse.getMappings());\n        System.out.println(getIndexResponse.getSettings());\n        esClient.close();\n\n    }\n}\n```\n\n### 删除索引\n\n```java\npublic class ESTest_Index_Delete {\n    public static void main(String[] args) throws IOException {\n\n        \u002F\u002F创建ES客户端\n        RestHighLevelClient esClient = new RestHighLevelClient(\n                RestClient.builder(new HttpHost(\"localhost\",9200,\"http\"))\n        );\n\n        \u002F\u002F查询索引\n        DeleteIndexRequest request = new DeleteIndexRequest(\"user\");\n\n        AcknowledgedResponse response =\n                esClient.indices().delete(request, RequestOptions.DEFAULT);\n\n        \u002F\u002F响应状态\n        System.out.println(response.isAcknowledged());\n        esClient.close();\n    }\n}\n```\n\n### 总结\n\n关于索引的请求，用到 `xxxIndexRequest` 以及 `.indices().xxx`，其中 xxx 代表增删查\n\n## 文章操作\n\n### 新增实体类:User\n\n```java\npublic class User \n{\n    private String name;\n  \n    private Integer age;\n  \n    private String sex;\n   \n    public String getName() {\n        return name;\n    }\n\n    public void setName(String name) {\n        this.name = name;\n    }\n\n    public String getSex() {\n        return sex;\n    }\n\n    public void setSex(String sex) {\n        this.sex = sex;\n    }\n\n    public Integer getAge() {\n        return age;\n    }\n\n    public void setAge(Integer age) {\n        this.age = age;\n    }\n}\n```\n\n### 新增文档\n\n```java\npublic class ESTest_Doc_Create {\n    public static void main(String[] args) throws IOException {\n\n        \u002F\u002F创建ES客户端\n        RestHighLevelClient esClient = new RestHighLevelClient(\n                RestClient.builder(new HttpHost(\"localhost\",9200,\"http\"))\n        );\n\n        \u002F\u002F插入数据\n        IndexRequest request = new IndexRequest();\n        request.index(\"user\").id(\"1001\");\n\n        User user = new User();\n        user.setName(\"zhangsan\");\n        user.setAge(20);\n        user.setSex(\"男\");\n\n        \u002F\u002F向ES插入数据，必须将数据转换为JSON格式\n        ObjectMapper mapper = new ObjectMapper();\n        String userJson = mapper.writeValueAsString(user);\n        request.source(userJson, XContentType.JSON);\n\n\n        IndexResponse response = esClient.index(request, RequestOptions.DEFAULT);\n        System.out.println(response.getResult());\n        esClient.close();\n    }\n}\n```\n\n### 修改文档\n\n```java\npublic class ESTest_Doc_Update {\n    public static void main(String[] args) throws IOException {\n\n        \u002F\u002F创建ES客户端\n        RestHighLevelClient esClient = new RestHighLevelClient(\n                RestClient.builder(new HttpHost(\"localhost\",9200,\"http\"))\n        );\n\n        \u002F\u002F修改数据\n        UpdateRequest request = new UpdateRequest();\n        request.index(\"user\").id(\"1001\");\n        request.doc(XContentType.JSON,\"sex\",\"女\");\n\n        UpdateResponse response = esClient.update(request, RequestOptions.DEFAULT);\n\n        System.out.println(response.getResult());\n\n        esClient.close();\n    }\n}\n```\n\n### 查询文档\n\n```java\npublic class ESTest_Doc_Get {\n    public static void main(String[] args) throws IOException {\n\n        \u002F\u002F创建ES客户端\n        RestHighLevelClient esClient = new RestHighLevelClient(\n                RestClient.builder(new HttpHost(\"localhost\",9200,\"http\"))\n        );\n\n        \u002F\u002F查询数据\n        GetRequest request = new GetRequest();\n        request.index(\"user\").id(\"1001\");\n        GetResponse response = esClient.get(request, RequestOptions.DEFAULT);\n        System.out.println(response.getSourceAsString());\n\n        esClient.close();\n    }\n}\n```\n\n### 删除文档\n\n```java\npublic class ESTest_Doc_Delete {\n    public static void main(String[] args) throws IOException {\n\n        \u002F\u002F创建ES客户端\n        RestHighLevelClient esClient = new RestHighLevelClient(\n                RestClient.builder(new HttpHost(\"localhost\",9200,\"http\"))\n        );\n\n        \u002F\u002F删除数据\n\n        DeleteRequest request = new DeleteRequest();\n        request.index(\"user\").id(\"1001\");\n        DeleteResponse response = esClient.delete(request, RequestOptions.DEFAULT);\n        System.out.println(response.toString());\n\n        esClient.close();\n    }\n}\n```\n\n### 批量新增\n\n* 非实体类新增\n\n```java\npublic class ESTest_Doc_Insert_Batch {\n    public static void main(String[] args) throws IOException {\n\n        \u002F\u002F创建ES客户端\n        RestHighLevelClient esClient = new RestHighLevelClient(\n                RestClient.builder(new HttpHost(\"localhost\",9200,\"http\"))\n        );\n\n        \u002F\u002F批量插入数据\n        BulkRequest request = new BulkRequest();\n        request.add(new IndexRequest().index(\"user\").id(\"1001\").source(XContentType.JSON,\"name\",\"zhangsan\"));\n        request.add(new IndexRequest().index(\"user\").id(\"1002\").source(XContentType.JSON,\"name\",\"lisi\"));\n        request.add(new IndexRequest().index(\"user\").id(\"1003\").source(XContentType.JSON,\"name\",\"wangwu\"));\n        request.add(new IndexRequest().index(\"user\").id(\"1004\").source(XContentType.JSON,\"name\",\"lucy\"));\n\n\n        BulkResponse response = esClient.bulk(request, RequestOptions.DEFAULT);\n        System.out.println(response.getTook());\n        System.out.println(response.getItems());\n\n        esClient.close();\n    }\n}\n```\n\n* 实体类新增\n\n```java\npublic class EsDocCreateBatch {\n\n    public static void main(String[] args) throws IOException {\n        \u002F\u002F 连接 ES 客户端\n        RestHighLevelClient esClient = new RestHighLevelClient(\n                RestClient.builder(new HttpHost(\"localhost\",9200,\"http\")));\n\n        BulkRequest request = new BulkRequest();\n        \u002F\u002F 创建数据对象\n        User user = new User(\"可乐\",\"18\",\"男\");\n        request.add(new IndexRequest().index(\"user\").id(\"1001\").source(XContentType.JSON, \"username\",\"可乐\",\"age\",18,\"sex\",\"男\"));\n        request.add(new IndexRequest().index(\"user\").id(\"1002\").source(XContentType.JSON, \"username\",\"冰糖\",\"age\",20,\"sex\",\"女\"));\n        request.add(new IndexRequest().index(\"user\").id(\"1003\").source(XContentType.JSON, \"username\",\"雪梨\",\"age\",22,\"sex\",\"女\"));\n        request.add(new IndexRequest().index(\"user\").id(\"1004\").source(XContentType.JSON, \"username\",\"酸橙\",\"age\",24,\"sex\",\"男\"));\n        request.add(new IndexRequest().index(\"user\").id(\"1005\").source(XContentType.JSON, \"username\",\"蜜桃\",\"age\",26,\"sex\",\"女\"));\n        \u002F\u002F 客户端发送请求，获取响应对象\n        BulkResponse response = esClient.bulk(request, RequestOptions.DEFAULT);\n        System.out.println(\"响应时间：\" + response.getTook());\n        System.out.println(\"创建的内容：\" + Arrays.toString(response.getItems()));\n\n\n        \u002F\u002F 关闭 ES 客户端\n        esClient.close();\n    }\n}\n```\n\n\n### 批量删除\n\n```java\npublic class ESTest_Doc_Delete_Batch {\n    public static void main(String[] args) throws IOException {\n\n        \u002F\u002F创建ES客户端\n        RestHighLevelClient esClient = new RestHighLevelClient(\n                RestClient.builder(new HttpHost(\"localhost\",9200,\"http\"))\n        );\n\n        \u002F\u002F批量插入数据\n        BulkRequest request = new BulkRequest();\n        \u002F\u002F配置修改参数\n        request.add(new DeleteRequest().index(\"user\").id(\"1001\"));\n        request.add(new DeleteRequest().index(\"user\").id(\"1002\"));\n        request.add(new DeleteRequest().index(\"user\").id(\"1003\"));\n        request.add(new DeleteRequest().index(\"user\").id(\"1004\"));\n        request.add(new DeleteRequest().index(\"user\").id(\"1005\"));\n        \u002F\u002F客户端发送请求，获取响应对象\n        BulkResponse response = esClient.bulk(request, RequestOptions.DEFAULT);\n        \u002F\u002F打印结果信息\n        System.out.println(\"响应时间：\"+response.getTook());\n        esClient.close();\n    }\n}\n```\n\n\n### 总结\n\n增删改查操作格式：\n\n* 连接 ES 客户端\n* 创建一个 `XXXRequest` 对象，其中 XXX 代表增删改查\n* 给该对象设置索引和文档\n* 调用 `.XXX`请求，传入参数，其中 XXX 代表增删改查\n\n## 高级查询\n\n### 查询准备\n\n需要事先插入几条数据\n\n```java\npublic class EsDocCreateBatch {\n\n    public static void main(String[] args) throws IOException {\n        \u002F\u002F 连接 ES 客户端\n        RestHighLevelClient esClient = new RestHighLevelClient(\n                RestClient.builder(new HttpHost(\"localhost\",9200,\"http\")));\n\n        BulkRequest request = new BulkRequest();\n        ObjectMapper objectMapper = new ObjectMapper();\n        \u002F\u002F 创建数据对象\n        request.add(new IndexRequest().index(\"user\").id(\"1001\").source(objectMapper.writeValueAsString(new User(\"可乐\", 18, \"男\")), XContentType.JSON));\n        request.add(new IndexRequest().index(\"user\").id(\"1002\").source(objectMapper.writeValueAsString(new User(\"冰糖\", 20, \"女\")), XContentType.JSON));\n        request.add(new IndexRequest().index(\"user\").id(\"1003\").source(objectMapper.writeValueAsString(new User(\"雪梨\", 22, \"女\")), XContentType.JSON));\n        request.add(new IndexRequest().index(\"user\").id(\"1004\").source(objectMapper.writeValueAsString(new User(\"酸橙\", 24, \"男\")), XContentType.JSON));\n        request.add(new IndexRequest().index(\"user\").id(\"1005\").source(objectMapper.writeValueAsString(new User(\"蜜桃\", 30, \"女\")), XContentType.JSON));\n        \u002F\u002F 客户端发送请求，获取响应对象\n        BulkResponse response = esClient.bulk(request, RequestOptions.DEFAULT);\n        System.out.println(\"响应时间：\" + response.getTook());\n\n        \u002F\u002F 关闭 ES 客户端\n        esClient.close();\n    }\n}\n```\n\n\n### 查询所有索引数据\n\n```java\npublic class ESTest_Doc_QueryAll {\n    public static void main(String[] args) throws IOException {\n\n        \u002F\u002F创建ES客户端\n        RestHighLevelClient esClient = new RestHighLevelClient(\n                RestClient.builder(new HttpHost(\"localhost\",9200,\"http\"))\n        );\n\n        \u002F\u002F1.查询索引中全部的数据\n        SearchRequest request = new SearchRequest();\n        \u002F\u002F !!! 参数可以是多个索引\n        request.indices(\"user\");\n  \n        SearchSourceBuilder query = new SearchSourceBuilder().query(QueryBuilders.matchAllQuery());\n        request.source(query);\n        SearchResponse response = esClient.search(request, RequestOptions.DEFAULT);\n\n        SearchHits hits = response.getHits();\n        System.out.println(\"总共的查询条数:\"+hits.getTotalHits());\n        System.out.println(\"查询时间:\"+response.getTook());\n        System.out.println(\"详细数据；\");\n        for (SearchHit hit : hits) {\n            \u002F\u002F 输出每条查询的结果信息\n            System.out.println(hit.getSourceAsString());\n        }\n        esClient.close();\n    }\n}\n```\n\n\n### 条件查询:term\n\n```java\npublic class ESTermQuery {\n    public static void main(String[] args) throws IOException {\n        \u002F\u002F连接ES客户端\n        RestHighLevelClient esClient =\n                new RestHighLevelClient(RestClient.builder(new HttpHost(\"localhost\", 9200, \"http\")));\n\n        SearchRequest request = new SearchRequest();\n        request.indices(\"user\");\n\n        \u002F\u002F构建查询的请求体\n        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();\n        sourceBuilder.query(QueryBuilders.termQuery(\"age\",\"18\"));\n        SearchResponse response = esClient.search(request.source(sourceBuilder), RequestOptions.DEFAULT);\n\n        \u002F\u002F查询匹配\n        SearchHits hits = response.getHits();\n        System.out.println(\"响应时间:\"+response.getTook());\n        System.out.println(\"是否超时:\"+response.isTimedOut());\n        System.out.println(\"命中数量:\"+hits.getTotalHits());\n        System.out.println(\"MaxScore:\"+hits.getMaxScore());\n        System.out.println(\"详细数据:\");\n        for (SearchHit hit : hits) {\n            \u002F\u002F输出每条查询的结果信息\n            System.out.println(hit.getSourceAsString());\n        }\n\n        \u002F\u002F关闭ES客户端\n        esClient.close();\n    }\n}\n```\n\n\n### 条件查询:terms\n\n```java\npublic class ESTermQuery {\n    public static void main(String[] args) throws IOException {\n        \u002F\u002F连接ES客户端\n        RestHighLevelClient esClient =\n                new RestHighLevelClient(RestClient.builder(new HttpHost(\"localhost\", 9200, \"http\")));\n\n        SearchRequest request = new SearchRequest();\n        request.indices(\"user\");\n\n        \u002F\u002F构建查询的请求体\n        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();\n        sourceBuilder.query(QueryBuilders.termsQuery(\"age\",\"18\",\"20\",\"23\"));\n        SearchResponse response = esClient.search(request.source(sourceBuilder), RequestOptions.DEFAULT);\n\n        \u002F\u002F查询匹配\n        SearchHits hits = response.getHits();\n        System.out.println(\"响应时间:\"+response.getTook());\n        System.out.println(\"是否超时:\"+response.isTimedOut());\n        System.out.println(\"命中数量:\"+hits.getTotalHits());\n        System.out.println(\"MaxScore:\"+hits.getMaxScore());\n        System.out.println(\"详细数据:\");\n        for (SearchHit hit : hits) {\n            \u002F\u002F输出每条查询的结果信息\n            System.out.println(hit.getSourceAsString());\n        }\n\n        \u002F\u002F关闭ES客户端\n        esClient.close();\n    }\n}\n```\n\n\n### 分页查询\n\n```java\npublic class ESTermQuery {\n    public static void main(String[] args) throws IOException {\n        \u002F\u002F连接ES客户端\n        RestHighLevelClient esClient =\n                new RestHighLevelClient(RestClient.builder(new HttpHost(\"localhost\", 9200, \"http\")));   \n\t\t\u002F\u002F分页查询\n        SearchRequest request = new SearchRequest();\n        request.indices(\"user\");\n\n        \u002F\u002F构建查询的请求体\n        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();\n        sourceBuilder.query(QueryBuilders.matchAllQuery());\n        sourceBuilder.from(0);\n        sourceBuilder.size(2);\n        SearchResponse response = esClient.search(request.source(sourceBuilder), RequestOptions.DEFAULT);\n\n        \u002F\u002F查询匹配\n        SearchHits hits = response.getHits();\n        System.out.println(\"响应时间:\"+response.getTook());\n        System.out.println(\"是否超时:\"+response.isTimedOut());\n        System.out.println(\"命中数量:\"+hits.getTotalHits());\n        System.out.println(\"MaxScore:\"+hits.getMaxScore());\n        System.out.println(\"详细数据:\");\n        for (SearchHit hit : hits) {\n            \u002F\u002F输出每条查询的结果信息\n            System.out.println(hit.getSourceAsString());\n        }\n\n        \u002F\u002F关闭ES客户端\n        esClient.close();\n    }\n}\n```\n\n\n### 排序查询\n\n```java\npublic class ESTermQuery {\n    public static void main(String[] args) throws IOException {\n        \u002F\u002F连接ES客户端\n        RestHighLevelClient esClient =\n                new RestHighLevelClient(RestClient.builder(new HttpHost(\"localhost\", 9200, \"http\")));  \n\t\t\u002F\u002F查询排序\n        SearchRequest request = new SearchRequest();\n        request.indices(\"user\");\n\n        \u002F\u002F构建查询的请求体\n        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();\n        sourceBuilder.query(QueryBuilders.matchAllQuery());\n        sourceBuilder.sort(\"age\", SortOrder.DESC);\n        SearchResponse response = esClient.search(request.source(sourceBuilder), RequestOptions.DEFAULT);\n\n        \u002F\u002F查询匹配\n        SearchHits hits = response.getHits();\n        System.out.println(\"响应时间:\"+response.getTook());\n        System.out.println(\"是否超时:\"+response.isTimedOut());\n        System.out.println(\"命中数量:\"+hits.getTotalHits());\n        System.out.println(\"MaxScore:\"+hits.getMaxScore());\n        System.out.println(\"详细数据:\");\n        for (SearchHit hit : hits) {\n            \u002F\u002F输出每条查询的结果信息\n            System.out.println(hit.getSourceAsString());\n        }\n\n        \u002F\u002F关闭ES客户端\n        esClient.close();\n    }\n}\n```\n\n\n### 过滤字段\n\n```java\npublic class ESTermQuery {\n    public static void main(String[] args) throws IOException {\n        \u002F\u002F连接ES客户端\n        RestHighLevelClient esClient =\n                new RestHighLevelClient(RestClient.builder(new HttpHost(\"localhost\", 9200, \"http\")));  \n\t\t\u002F\u002F过滤字段\n        SearchRequest request = new SearchRequest();\n        request.indices(\"user\");\n\n        \u002F\u002F构建查询的请求体\n        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();\n        sourceBuilder.query(QueryBuilders.matchAllQuery());\n\n        String[] excludes={\"age\"};\n        String[] includes={\"name\"};\n        sourceBuilder.fetchSource(includes,excludes);\n        SearchResponse response = esClient.search(request.source(sourceBuilder), RequestOptions.DEFAULT);\n\n        \u002F\u002F查询匹配\n        SearchHits hits = response.getHits();\n        System.out.println(\"响应时间:\"+response.getTook());\n        System.out.println(\"是否超时:\"+response.isTimedOut());\n        System.out.println(\"命中数量:\"+hits.getTotalHits());\n        System.out.println(\"MaxScore:\"+hits.getMaxScore());\n        System.out.println(\"详细数据:\");\n        for (SearchHit hit : hits) {\n            \u002F\u002F输出每条查询的结果信息\n            System.out.println(hit.getSourceAsString());\n        }\n\n        \u002F\u002F关闭ES客户端\n        esClient.close();\n    }\n}\n```\n\n\n### Bool查询\n\n```java\npublic class ESTermQuery {\n    public static void main(String[] args) throws IOException {\n        \u002F\u002F连接ES客户端\n        RestHighLevelClient esClient =\n                new RestHighLevelClient(RestClient.builder(new HttpHost(\"localhost\", 9200, \"http\")));   \n\t\t\u002F\u002F组合查询\n        SearchRequest request = new SearchRequest();\n        request.indices(\"user\");\n\n        \u002F\u002F构建查询的请求体\n        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();\n        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();\n        \u002F\u002F必须包含\n        boolQueryBuilder.must(QueryBuilders.matchQuery(\"age\",\"30\"));\n        \u002F\u002F一定不含\n        boolQueryBuilder.mustNot(QueryBuilders.matchQuery(\"username\",\"可乐\"));\n        \u002F\u002F可能包含\n        boolQueryBuilder.should(QueryBuilders.matchQuery(\"sex\",\"男\"));\n        sourceBuilder.query(boolQueryBuilder);\n        SearchResponse response = esClient.search(request.source(sourceBuilder), RequestOptions.DEFAULT);\n\n        \u002F\u002F查询匹配\n        SearchHits hits = response.getHits();\n        System.out.println(\"响应时间:\"+response.getTook());\n        System.out.println(\"是否超时:\"+response.isTimedOut());\n        System.out.println(\"命中数量:\"+hits.getTotalHits());\n        System.out.println(\"MaxScore:\"+hits.getMaxScore());\n        System.out.println(\"详细数据:\");\n        for (SearchHit hit : hits) {\n            \u002F\u002F输出每条查询的结果信息\n            System.out.println(hit.getSourceAsString());\n        }\n\n        \u002F\u002F关闭ES客户端\n        esClient.close();\n    }\n}\n```\n\n\n### 范围查询\n\n```java\npublic class ESTermQuery {\n    public static void main(String[] args) throws IOException {\n        \u002F\u002F连接ES客户端\n        RestHighLevelClient esClient =\n                new RestHighLevelClient(RestClient.builder(new HttpHost(\"localhost\", 9200, \"http\")));   \n\t\t\u002F\u002F范围查询\n        SearchRequest request = new SearchRequest();\n        request.indices(\"user\");\n\n        \u002F\u002F构建查询的请求体\n        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();\n        RangeQueryBuilder rangeQuery = QueryBuilders.rangeQuery(\"age\");\n        rangeQuery.gte(30);\n        rangeQuery.lte(40);\n        sourceBuilder.query(rangeQuery);\n\n        request.source(sourceBuilder);\n        SearchResponse response = esClient.search(request, RequestOptions.DEFAULT);\n\n        \u002F\u002F查询匹配\n        SearchHits hits = response.getHits();\n        System.out.println(\"took:\" + response.getTook());\n        System.out.println(\"timeout:\" + response.isTimedOut());\n        System.out.println(\"total:\" + hits.getTotalHits());\n        System.out.println(\"MaxScore:\" + hits.getMaxScore());\n        for (SearchHit hit : hits) {\n            \u002F\u002F 输出每条查询的结果信息\n            System.out.println(hit.getSourceAsString());\n        }\n        \u002F\u002F关闭ES客户端\n        esClient.close();\n    }\n}\n```\n\n\n### 模糊查询\n\n```java\npublic class ESTermQuery {\n    public static void main(String[] args) throws IOException {\n        \u002F\u002F连接ES客户端\n        RestHighLevelClient esClient =\n                new RestHighLevelClient(RestClient.builder(new HttpHost(\"localhost\", 9200, \"http\")));\n        \u002F\u002F模糊查询\n\t\tSearchRequest request = new SearchRequest();\n        request.indices(\"user\");\n\n        \u002F\u002F构建查询的请求体\n        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();\n        QueryBuilders.fuzzyQuery(\"name\",\"wangwu\").fuzziness(Fuzziness.ONE);\n\n\n        request.source(sourceBuilder);\n        SearchResponse response = esClient.search(request, RequestOptions.DEFAULT);\n\n        \u002F\u002F查询匹配\n        SearchHits hits = response.getHits();\n        System.out.println(\"took:\" + response.getTook());\n        System.out.println(\"timeout:\" + response.isTimedOut());\n        System.out.println(\"total:\" + hits.getTotalHits());\n        System.out.println(\"MaxScore:\" + hits.getMaxScore());\n        for (SearchHit hit : hits) {\n            \u002F\u002F 输出每条查询的结果信息\n            System.out.println(hit.getSourceAsString());\n        }\n        \u002F\u002F关闭ES客户端\n        esClient.close();\n    }\n}\n```\n\n\n### 高亮查询\n\n```java\npublic class ESTermQuery {\n    public static void main(String[] args) throws IOException {\n        \u002F\u002F连接ES客户端\n        RestHighLevelClient esClient =\n                new RestHighLevelClient(RestClient.builder(new HttpHost(\"localhost\", 9200, \"http\"))); \n\t\t\u002F\u002F高亮查询\n        SearchRequest request = new SearchRequest();\n        request.indices(\"user\");\n\n        \u002F\u002F构建查询的请求体\n        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();\n        TermsQueryBuilder termsQueryBuilder = QueryBuilders.termsQuery(\"age\", \"18\",\"20\");\n\n        sourceBuilder.query(termsQueryBuilder);\n\n        HighlightBuilder highlightBuilder = new HighlightBuilder();\n        highlightBuilder.preTags(\"\u003Cfont color='red'>\");\n        highlightBuilder.postTags(\"\u003C\u002Ffont>\");\n        highlightBuilder.field(\"age\");\n\n        request.source(sourceBuilder);\n        SearchResponse response = esClient.search(request, RequestOptions.DEFAULT);\n\n        \u002F\u002F查询匹配\n        SearchHits hits = response.getHits();\n        System.out.println(\"took:\" + response.getTook());\n        System.out.println(\"timeout:\" + response.isTimedOut());\n        System.out.println(\"total:\" + hits.getTotalHits());\n        System.out.println(\"MaxScore:\" + hits.getMaxScore());\n        System.out.println(\"详细数据：\");\n        for (SearchHit hit : hits) {\n            \u002F\u002F 输出每条查询的结果信息\n            System.out.println(hit.getSourceAsString());\n        }\n        \u002F\u002F关闭ES客户端\n        esClient.close();\n    }\n}\n```\n\n\n## 聚合查询\n\n### 最大值\n\n```java\npublic class EsArrgQuery {\n\n    public static void main(String[] args) throws IOException {\n        \u002F\u002F 连接 ES 客户端 \n        RestHighLevelClient esClient = new RestHighLevelClient(\n                RestClient.builder(new HttpHost(\"localhost\",9200,\"http\")));\n\n        \u002F\u002F 高亮查询\n        SearchRequest request = new SearchRequest().indices(\"user\");\n        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();\n        sourceBuilder.aggregation(AggregationBuilders.max(\"maxAge\").field(\"age\"));\n        \u002F\u002F 3.客户端发送请求，获取响应对象\n        SearchResponse response = esClient.search(request.source(sourceBuilder), RequestOptions.DEFAULT);\n        \u002F\u002F 4.打印响应结果\n        System.out.println(response);\n\n        \u002F\u002F 关闭 ES 客户端\n        esClient.close();\n    }\n}\n```\n\n\n### 分组统计\n\n```java\npublic class EsArrgQuery {\n\n    public static void main(String[] args) throws IOException {\n        \u002F\u002F 连接 ES 客户端 \n        RestHighLevelClient esClient = new RestHighLevelClient(\n                RestClient.builder(new HttpHost(\"localhost\",9200,\"http\")));\n\n        \u002F\u002F 高亮查询\n        SearchRequest request = new SearchRequest().indices(\"user\");\n        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();\n        sourceBuilder.aggregation(AggregationBuilders.terms(\"ageGroupby\").field(\"age\"));\n        \u002F\u002F 3.客户端发送请求，获取响应对象\n        SearchResponse response = esClient.search(request.source(sourceBuilder), RequestOptions.DEFAULT);\n        \u002F\u002F 4.打印响应结果\n        SearchHits hits = response.getHits();\n        System.out.println(response);\n\n        \u002F\u002F 关闭 ES 客户端\n        esClient.close();\n    }\n}\n```\n",{"id":73,"created_at":74,"updated_at":74,"code":75,"author_id":4,"book_id":4,"slug":76,"article_id":77,"title":78,"sort_order":73,"content":79,"status":11},7,"2023-12-27T14:03:21.322+08:00","4N8m18A2","elasticsearch-8x的java-api-7",157,"ElasticSearch-8.X的JAVA-API","## 前述\n\n* 在`8.x`版本中，ES出了一个新的`Elasticsearch Java API Client`，[点击查看 **(opens new window)**](https:\u002F\u002Fwww.elastic.co\u002Fguide\u002Fen\u002Felasticsearch\u002Fclient\u002Fjava-api-client\u002F8.3\u002Findex.html)，如果你要在`Springboot`高版本中使用，可以直接使用该API。\n* `ES7.X`版本的JAVA-API[请查看🔎](\u002Fuser\u002FspecialColumnArticleView?type=1&specialColumnId=1)\n* 以下示例，首先需要将`elasticsearch.yml`中的有关`ssl`的配置全部改为`false`，本地测试就不搞`HTTPS`那么麻烦了\n* 示例代码已经上传到[gitee **(opens new window)**](https:\u002F\u002Fgitee.com\u002Flijunyi622\u002Felastic-search8.x)\n\n## 项目配置\n\n### 依赖引入\n\n```xml\n\u003Cdependency>\n    \u003CgroupId>co.elastic.clients\u003C\u002FgroupId>\n    \u003CartifactId>elasticsearch-java\u003C\u002FartifactId>\n    \u003Cversion>8.3.3\u003C\u002Fversion>\n\u003C\u002Fdependency>\n\u003Cdependency>\n    \u003CgroupId>com.fasterxml.jackson.core\u003C\u002FgroupId>\n    \u003CartifactId>jackson-databind\u003C\u002FartifactId>\n    \u003Cversion>2.13.3\u003C\u002Fversion>\n\u003C\u002Fdependency>\n\u003C!--It may happen that after setting up the dependencies, your application fails with ClassNotFoundException: jakarta.json.spi.JsonProvider.-->\n\u003Cdependency>\n    \u003CgroupId>jakarta.json\u003C\u002FgroupId>\n    \u003CartifactId>jakarta.json-api\u003C\u002FartifactId>\n    \u003Cversion>2.1.1\u003C\u002Fversion>\n\u003C\u002Fdependency>\n```\n\n注意：elasticsearch 的两个依赖要和 elasticsearch 服务器版本一致。\n\n### 客户端对象\n\n```java\n@Test\nvoid ElasticsearchClientBuild() throws IOException {\n    \u002F\u002F Create the low-level client\n    RestClient restClient = RestClient.builder(\n            new HttpHost(\"127.0.0.1\", 9200)).build();\n\n    \u002F\u002F Create the transport with a Jackson mapper\n    ElasticsearchTransport transport = new RestClientTransport(\n            restClient, new JacksonJsonpMapper());\n\n    \u002F\u002F And create the API client\n    ElasticsearchClient elasticsearchClient  = new ElasticsearchClient(transport);\n    System.out.println(\"elasticsearchClient = \" + elasticsearchClient);\n    restClient.close();\n}\n```\n\n### 结果输出\n\n```shell\nelasticsearchClient = co.elastic.clients.elasticsearch.ElasticsearchClient@6e4f263e\n```\n\n### 配置获取客户端对象\n\n* yml配置\n\n```yaml\nelasticsearch:\n  host: 127.0.0.1\n  port: 9200\n  http: http\n```\n\n* 配置 ElasticClient\n\n```java\n\u002F**\n * @version 1.0.0\n * @className: ElasticClient\n * @description: 配置获取ES客户端对象\n * @author: smartzeng\n * @create: 2022\u002F8\u002F8 9:58\n *\u002F\n@Component\npublic class ElasticClient {\n    @Value(\"${elasticsearch.host}\")\n    private String host;\n    @Value(\"${elasticsearch.port}\")\n    private Integer port;\n\n    @Value(\"${elasticsearch.http}\")\n    private String http;\n\n    \u002F**\n     * 获取elasticsearch客户端\n     *\n     * @return {@link ElasticsearchClient}\n     *\u002F\n    @Bean\n    public ElasticsearchClient getElasticsearchClient() {\n        RestClient restClient = RestClient.builder(\n                new HttpHost(host, port,http)).build();\n        ElasticsearchTransport transport = new RestClientTransport(\n                restClient, new JacksonJsonpMapper());\n        return new ElasticsearchClient(transport);\n    }\n}\n```\n\n## 索引操作\n\n### 创建索引\n\n```java\n@SpringBootTest\n@Slf4j\npublic class IndexTest \n{\n    @Autowired\n    private ElasticsearchClient elasticsearchClient;\n\n    \u002F**\n     * 创建索引\n     *\n     * @throws IOException ioexception\n     *\u002F\n    @Test\n    void createIndex() throws IOException {\n        CreateIndexResponse response = elasticsearchClient.indices().create(c -> c.index(\"products\"));\n        \u002F\u002F响应状态\n        boolean acknowledged = response.acknowledged();\n        boolean shardsAcknowledged = response.shardsAcknowledged();\n        String index = response.index();\n        log.info(\"创建索引状态:{}\",acknowledged);\n        log.info(\"已确认的分片:{}\",shardsAcknowledged);\n        log.info(\"索引名称:{}\",index);\n    }\n}\n```\n\n**结果**\n\n```shell\n[main] c.e.e.ElasticsearchApplicationTests      : elasticsearchClient:co.elastic.clients.elasticsearch.ElasticsearchClient@6f911326\n[main] c.e.e.ElasticsearchApplicationTests      : 创建索引状态:true\n[main] c.e.e.ElasticsearchApplicationTests      : 已确认的分片:true\n[main] c.e.e.ElasticsearchApplicationTests      : 索引名称:products\n```\n\n### 查看索引\n\n```java\n\u002F**\n * @version 1.0.0\n * @className: IndexTest\n * @description: 索引测试\n * @author: smartzeng\n * @create: 2022\u002F8\u002F8 10:03\n *\u002F\n@SpringBootTest\n@Slf4j\npublic class IndexTest\n{\n    @Autowired\n    private ElasticsearchClient elasticsearchClient;\n\n    \u002F**\n     * 获取索引\n     *\u002F\n    @Test\n    void getIndex() throws IOException\n    {\n        \u002F\u002F 查看指定索引\n        GetIndexResponse getIndexResponse = elasticsearchClient.indices().get(s -> s.index(\"products\"));\n        Map\u003CString, IndexState> result = getIndexResponse.result();\n        result.forEach((k, v) -> log.info(\"key = {},value = {}\",k ,v));\n\n        \u002F\u002F 查看全部索引\n        IndicesResponse indicesResponse = elasticsearchClient.cat().indices();\n        \u002F\u002F 返回对象具体查看 co.elastic.clients.elasticsearch.cat.indices.IndicesRecord\n        indicesResponse.valueBody().forEach(\n                info -> log.info(\"health:{}\\n status:{} \\n uuid:{} \\n \",info.health(),info.status(),info.uuid())\n        );   \n    }\n}\n```\n\n**结果**\n\n```json\nkey = products,\nvalue = IndexState: \n{\n    \"aliases\":{\n\n    },\n    \"mappings\":{\n\n    },\n    \"settings\":{\n        \"index\":{\n            \"number_of_shards\":\"1\",\n            \"number_of_replicas\":\"1\",\n            \"routing\":{\n                \"allocation\":{\n                    \"include\":{\n                        \"_tier_preference\":\"data_content\"\n                    }\n                }\n            },\n            \"provided_name\":\"products\",\n            \"creation_date\":\"1659923692276\",\n            \"uuid\":\"WN0uQLLvQ1SdFsWE2bhlgw\",\n            \"version\":{\n                \"created\":\"8030399\"\n            }\n        }\n    }\n}\n```\n\n### 删除索引\n\n```java\n\u002F**\n * @version 1.0.0\n * @className: IndexTest\n * @description: 索引测试\n * @author: smartzeng\n * @create: 2022\u002F8\u002F8 10:03\n *\u002F\n@SpringBootTest\n@Slf4j\npublic class IndexTest\n{\n    @Autowired\n    private ElasticsearchClient elasticsearchClient;\n\n    \u002F**\n     * 删除索引\n     *\n     * @throws IOException ioexception\n     *\u002F\n    @Test\n    void deleteIndex() throws IOException {\n        DeleteIndexResponse deleteIndexResponse = elasticsearchClient.indices().delete(s -> s.index(\"products\"));\n        log.info(\"删除索引操作结果：{}\",deleteIndexResponse.acknowledged());\n    }\n}\n```\n\n**结果**\n\n```shell\n[main] com.example.elasticsearch.IndexTest      : 删除索引操作结果：true\n```\n\n### 总结\n\n关于索引的请求，用到 `elasticsearchClient.indices().xxx(s -> s.index(\"索引名\"))` ，其中 xxx 代表增删查\n\n## 文章操作\n\n### 新增实体类:User\n\n```java\npublic class User\n{\n    private String id;\n    private String name;\n\n    private Integer age;\n\n    private String sex;\n\n    public String getId() {\n        return id;\n    }\n\n    public void setId(String id) {\n        this.id = id;\n    }\n\n    public String getName() {\n        return name;\n    }\n\n    public void setName(String name) {\n        this.name = name;\n    }\n\n    public Integer getAge() {\n        return age;\n    }\n\n    public void setAge(Integer age) {\n        this.age = age;\n    }\n\n    public String getSex() {\n        return sex;\n    }\n\n    public void setSex(String sex) {\n        this.sex = sex;\n    }\n\n    public User()\n    {\n\n    }\n\n    public User(String id, String name, Integer age, String sex) {\n        this.id = id;\n        this.name = name;\n        this.age = age;\n        this.sex = sex;\n    }\n\n    @Override\n    public String toString() {\n        return \"User{\" +\n                \"id='\" + id + '\\'' +\n                \", name='\" + name + '\\'' +\n                \", age=\" + age +\n                \", sex='\" + sex + '\\'' +\n                '}';\n    }\n}\n```\n\n### 新增文档\n\n```java\n\u002F**\n * @version 1.0.0\n * @className: DocTest\n * @description: 文档操作测试\n * @author: smartzeng\n * @create: 2022\u002F8\u002F8 10:19\n *\u002F\n@SpringBootTest\n@Slf4j\npublic class DocTest\n{\n    @Autowired\n    private ElasticsearchClient elasticsearchClient;\n\n    \u002F**\n     * 添加一个文档\n     * @see: https:\u002F\u002Fwww.elastic.co\u002Fguide\u002Fen\u002Felasticsearch\u002Fclient\u002Fjava-api-client\u002F8.3\u002Findexing.html#indexing\n     * @throws IOException ioexception\n     *\u002F\n    @Test\n    void addOneDocument () throws IOException\n    {\n        \u002F\u002F 1、using the fluent DSL\n        User user = new User(\"1\",\"王五\",28,\"男\");\n        IndexResponse indexResponse = elasticsearchClient.index(s ->\n                \u002F\u002F 索引\n                s.index(\"users\")\n                        \u002F\u002F ID\n                        .id(user.getId())\n                        \u002F\u002F 文档\n                        .document(user)\n        );\n        log.info(\"result:{}\",indexResponse.result().jsonValue());\n\n\n        \u002F\u002F 2、You can also assign objects created with the DSL to variables. Java API Client classes have a static of() method for this, that creates an object with the DSL syntax.\n        IndexRequest\u003CUser> request = IndexRequest.of(i -> i\n                .index(\"users\")\n                .id(user.getId())\n                .document(user));\n        IndexResponse response = elasticsearchClient.index(request);\n        log.info(\"Indexed with version \" + response.version());\n\n  \n        \u002F\u002F 3、Using classic builders\n        IndexRequest.Builder\u003CUser> indexReqBuilder = new IndexRequest.Builder\u003C>();\n        indexReqBuilder.index(\"users\");\n        indexReqBuilder.id(user.getId());\n        indexReqBuilder.document(user);\n        IndexResponse responseTwo = elasticsearchClient.index(indexReqBuilder.build());\n        log.info(\"Indexed with version \" + responseTwo.version());\n    }\n}\n```\n\n**结果**\n\n```xml\n[main] com.example.elasticsearch.DocTest        : user.id:e051445c-ae8c-47ef-ab18-97b34025d49a\n[main] com.example.elasticsearch.DocTest        : result:created\n```\n\n### 查询文档\n\n```java\n\u002F**\n * @version 1.0.0\n * @className: DocTest\n * @description: 文档操作测试\n * @author: smartzeng\n * @create: 2022\u002F8\u002F8 10:19\n *\u002F\n@SpringBootTest\n@Slf4j\npublic class DocTest\n{\n    @Autowired\n    private ElasticsearchClient elasticsearchClient;\n\n    \u002F**\n     * 获取文档\n     * https:\u002F\u002Fwww.elastic.co\u002Fguide\u002Fen\u002Felasticsearch\u002Fclient\u002Fjava-api-client\u002F8.3\u002Freading.html#reading\n     * @throws IOException ioexception\n     *\u002F\n    @Test\n    void getDocument () throws IOException\n    {\n        \u002F\u002F co.elastic.clients.elasticsearch.core.get.GetResult\u003CTDocument>\n        GetResponse\u003CUser> getResponse = elasticsearchClient.get(s -> s.index(\"users\").id(\"e051445c-ae8c-47ef-ab18-97b34025d49a\"),User.class);\n        log.info(\"getResponse:{}\",getResponse.source());\n\n        \u002F\u002F Reading a domain object\n        if (getResponse.found())\n        {\n            User user = getResponse.source();\n            assert user != null;\n            log.info(\"user name={}\",user.getName());\n        }\n\n        \u002F\u002F Reading raw JSON\n        \u002F\u002F if (getResponse.found())\n        \u002F\u002F {\n        \u002F\u002F    ObjectNode json = getResponse.source();\n        \u002F\u002F    String name = json.get(\"name\").asText();\n        \u002F\u002F    log.info(\"Product name \" + name);\n        \u002F\u002F }\n\n        \u002F\u002F 判断文档是否存在\n        BooleanResponse booleanResponse = elasticsearchClient.exists(s -> s.index(\"users\").id(\"e051445c-ae8c-47ef-ab18-97b34025d49a\"));\n        log.info(\"判断Document是否存在:{}\",booleanResponse.value());\n    }\n}\n```\n\n**结果**\n\n```xml\ngetResponse:User{id='e051445c-ae8c-47ef-ab18-97b34025d49a', name='王五', age=28, sex='男'}\n```\n\n### 更新文档\n\n```java\n\u002F**\n * @version 1.0.0\n * @className: DocTest\n * @description: 文档操作测试\n * @author: smartzeng\n * @create: 2022\u002F8\u002F8 10:19\n *\u002F\n@SpringBootTest\n@Slf4j\npublic class DocTest\n{\n    @Autowired\n    private ElasticsearchClient elasticsearchClient;\n\n    \u002F**\n     * 更新文档\n     *\n     * @throws IOException ioexception\n     *\u002F\n    @Test\n    void updateDocument () throws IOException\n    {\n        \u002F\u002F 构建需要修改的内容，这里使用了Map\n        Map\u003CString, Object> map = new HashMap\u003C>();\n        map.put(\"name\", \"liuyife\");\n        \u002F\u002F 构建修改文档的请求\n        UpdateResponse\u003CTest> response = elasticsearchClient.update(e -> e\n                .index(\"users\")\n                .id(\"33\")\n                .doc(map),\n                Test.class\n        );\n        \u002F\u002F 打印请求结果\n        log.info(String.valueOf(response.result()));\n    }\n}\n```\n\n**结果**\n\n```xml\n[main] com.example.elasticsearch.DocTest        : Updated\n```\n\n### 删除文档\n\n```java\n\u002F**\n * @version 1.0.0\n * @className: DocTest\n * @description: 文档操作测试\n * @author: smartzeng\n * @create: 2022\u002F8\u002F8 10:19\n *\u002F\n@SpringBootTest\n@Slf4j\npublic class DocTest\n{\n    @Autowired\n    private ElasticsearchClient elasticsearchClient;\n\n    \u002F**\n     * 删除文档\n     *\n     * @throws IOException ioexception\n     *\u002F\n    @Test\n    void deleteDocument () throws IOException\n    {\n        DeleteResponse deleteResponse = elasticsearchClient.delete(s -> s.index(\"users\").id(\"e051445c-ae8c-47ef-ab18-97b34025d49a\"));\n        log.info(\"删除文档操作结果:{}\",deleteResponse.result());\n    }\n}\n```\n\n**结果**\n\n```xml\n[main] com.example.elasticsearch.DocTest: 删除文档操作结果:Deleted\n```\n\n### 批量新增\n\n```java\n\u002F**\n * @version 1.0.0\n * @className: DocTest\n * @description: 文档操作测试\n * @author: smartzeng\n * @create: 2022\u002F8\u002F8 10:19\n *\u002F\n@SpringBootTest\n@Slf4j\npublic class DocTest\n{\n    @Autowired\n    private ElasticsearchClient elasticsearchClient;\n\n    \u002F**\n     * 批量添加文档\n     *\n     * @throws IOException ioexception\n     *\u002F\n    @Test\n    void batchAddDocument () throws IOException\n    {\n        \u002F\u002F 1、use BulkOperation\n        List\u003CUser> users = new ArrayList\u003C>();\n        users.add(new User(\"1\",\"赵四\",20,\"男\"));\n        users.add(new User(\"2\",\"阿旺\",25,\"男\"));\n        users.add(new User(\"3\",\"刘菲\",22,\"女\"));\n        users.add(new User(\"4\",\"冬梅\",20,\"女\"));\n\n        List\u003CBulkOperation> bulkOperations = new ArrayList\u003C>();\n        users.forEach(u ->\n                        bulkOperations.add(BulkOperation.of(b ->\n                                b.index(\n                                        c ->\n                                                c.id(u.getId()).document(u)\n                                )))\n                );\n        BulkResponse bulkResponse = elasticsearchClient.bulk(s -> s.index(\"users\").operations(bulkOperations));\n        bulkResponse.items().forEach(i ->\n               log.info(\"i = {}\" , i.result()));\n        log.error(\"bulkResponse.errors() = {}\" , bulkResponse.errors());\n\n        \u002F\u002F 2、use BulkRequest\n        BulkRequest.Builder br = new BulkRequest.Builder();\n        for (User user : users) {\n            br.operations(op -> op\n                    .index(idx -> idx\n                            .index(\"users\")\n                            .id(user.getId())\n                            .document(user)));\n        }\n        BulkResponse result = elasticsearchClient.bulk(br.build());\n        \u002F\u002F Log errors, if any\n        if (result.errors()) {\n            log.error(\"Bulk had errors\");\n            for (BulkResponseItem item: result.items()) {\n                if (item.error() != null) {\n                    log.error(item.error().reason());\n                }\n            }\n        }\n    }\n}\n```\n\n**结果**\n\n```xml\n[main] com.example.elasticsearch.DocTest        : i = created\n[main] com.example.elasticsearch.DocTest        : i = created\n[main] com.example.elasticsearch.DocTest        : i = created\n[main] com.example.elasticsearch.DocTest        : i = created\n[main] com.example.elasticsearch.DocTest        : bulkResponse.errors() = false\n```\n\n### 批量删除\n\n```java\n\u002F**\n * @version 1.0.0\n * @className: DocTest\n * @description: 文档操作测试\n * @author: smartzeng\n * @create: 2022\u002F8\u002F8 10:19\n *\u002F\n@SpringBootTest\n@Slf4j\npublic class DocTest\n{\n    @Autowired\n    private ElasticsearchClient elasticsearchClient;\n\n    \u002F**\n     * 批量删除文档\n     *\n     * @throws IOException ioexception\n     *\u002F\n    @Test\n    void batchDeleteDocument () throws IOException\n    {\n        \u002F\u002F 1、use BulkOperation\n        List\u003CString> list = new ArrayList\u003C>();\n        list.add(\"1\");\n        list.add(\"2\");\n        list.add(\"3\");\n        list.add(\"4\");\n        List\u003CBulkOperation> bulkOperations = new ArrayList\u003C>();\n        list.forEach(a ->\n                bulkOperations.add(BulkOperation.of(b ->\n                        b.delete(c -> c.id(a))\n                ))\n        );\n        BulkResponse bulkResponse = elasticsearchClient.bulk(a -> a.index(\"users\").operations(bulkOperations));\n        bulkResponse.items().forEach(a ->\n                log.info(\"result = {}\" , a.result()));\n        log.error(\"bulkResponse.errors() = {}\" , bulkResponse.errors());\n\n        \u002F\u002F 2、use BulkRequest\n        BulkRequest.Builder br = new BulkRequest.Builder();\n        for (String s : list) {\n            br.operations(op -> op\n                    .delete(c -> c.id(s)));\n        }\n        BulkResponse bulkResponseTwo = elasticsearchClient.bulk(br.build());\n        bulkResponseTwo.items().forEach(a ->\n                log.info(\"result = {}\" , a.result()));\n        log.error(\"bulkResponse.errors() = {}\" , bulkResponseTwo.errors());\n    }\n}\n```\n\n**结果**\n\n```xml\n[main] com.example.elasticsearch.DocTest        : result = deleted\n[main] com.example.elasticsearch.DocTest        : result = deleted\n[main] com.example.elasticsearch.DocTest        : result = deleted\n[main] com.example.elasticsearch.DocTest        : result = deleted\n[main] com.example.elasticsearch.DocTest        : bulkResponse.errors() = false\n```\n\n## 高级查询\n\n### 查询准备\n\n* 通过批量添加文档准备几条数据先\n\n```java\n\u002F**\n * @version 1.0.0\n * @className: SearchTest\n * @description: 查询测试\n * @author: smartzeng\n * @create: 2022\u002F8\u002F8 11:04\n *\u002F\n@SpringBootTest\n@Slf4j\npublic class SearchTest\n{\n    @Autowired\n    private ElasticsearchClient elasticsearchClient;\n\n    \u002F**\n     * 批量添加准备数据\n     *\n     * @throws IOException ioexception\n     *\u002F\n    @Test\n    void batchAddDocument () throws IOException\n    {\n        List\u003CUser> users = new ArrayList\u003C>();\n        users.add(new User(\"11\",\"zhaosi\",20,\"男\"));\n        users.add(new User(\"22\",\"awang\",25,\"男\"));\n        users.add(new User(\"33\",\"liuyifei\",22,\"女\"));\n        users.add(new User(\"44\",\"dongmei\",20,\"女\"));\n        users.add(new User(\"55\",\"zhangya\",30,\"女\"));\n        users.add(new User(\"66\",\"liuyihu\",32,\"男\"));\n\n        BulkRequest.Builder br = new BulkRequest.Builder();\n        for (User user : users) {\n            br.operations(op -> op\n                    .index(idx -> idx\n                            .index(\"users\")\n                            .id(user.getId())\n                            .document(user)));\n        }\n        BulkResponse result = elasticsearchClient.bulk(br.build());\n        \u002F\u002F Log errors, if any\n        if (result.errors()) {\n            log.error(\"Bulk had errors\");\n            for (BulkResponseItem item: result.items()) {\n                if (item.error() != null) {\n                    log.error(item.error().reason());\n                }\n            }\n        }\n    }\n}\n```\n\n### 简单的搜索查询\n\n```java\n\u002F**\n * @version 1.0.0\n * @className: SearchTest\n * @description: 查询测试\n * @author: smartzeng\n * @create: 2022\u002F8\u002F8 11:04\n *\u002F\n@SpringBootTest\n@Slf4j\npublic class SearchTest\n{\n    @Autowired\n    private ElasticsearchClient elasticsearchClient;\n\n    \u002F**\n     * 条件查询方式1\n     *\n     * @throws IOException ioexception\n     *\u002F\n    @Test\n    void searchOne() throws IOException {\n        String searchText = \"liuyihu\";\n        SearchResponse\u003CUser> response = elasticsearchClient.search(s -> s\n                \u002F\u002F 我们要搜索的索引的名称\n                .index(\"users\")\n                \u002F\u002F 搜索请求的查询部分（搜索请求也可以有其他组件，如聚合）\n                .query(q -> q\n                        \u002F\u002F 在众多可用的查询变体中选择一个。我们在这里选择匹配查询（全文搜索）\n                        .match(t -> t\n                                \u002F\u002F name配置匹配查询：我们在字段中搜索一个词\n                                .field(\"name\")\n                                .query(searchText)\n                        )\n                ),\n                \u002F\u002F 匹配文档的目标类\n                User.class\n        );\n        TotalHits total = response.hits().total();\n        boolean isExactResult = total.relation() == TotalHitsRelation.Eq;\n\n        if (isExactResult) {\n            log.info(\"There are \" + total.value() + \" results\");\n        } else {\n            log.info(\"There are more than \" + total.value() + \" results\");\n        }\n\n        List\u003CHit\u003CUser>> hits = response.hits().hits();\n        for (Hit\u003CUser> hit: hits) {\n            User user = hit.source();\n            assert user != null;\n            log.info(\"Found userId \" + user.getId() + \", name \" + user.getName());\n        }\n    }\n}\n```\n\n### 嵌套搜索查询\n\n```java\n\u002F**\n * @version 1.0.0\n * @className: SearchTest\n * @description: 查询测试\n * @author: smartzeng\n * @create: 2022\u002F8\u002F8 11:04\n *\u002F\n@SpringBootTest\n@Slf4j\npublic class SearchTest\n{\n    @Autowired\n    private ElasticsearchClient elasticsearchClient;\n\n    \u002F**\n     * 嵌套搜索查询\n     *\u002F\n    @Test\n    void searchTwo() throws IOException {\n        String searchText = \"liuyihu\";\n        int maxAge = 30;\n        \u002F\u002F byName、byMaxAge：分别为各个条件创建查询\n        Query byName = MatchQuery.of(m -> m\n                .field(\"name\")\n                .query(searchText)\n        )\n                \u002F\u002FMatchQuery是一个查询变体，我们必须将其转换为 Query 联合类型\n                ._toQuery();\n        Query byMaxAge = RangeQuery.of(m -> m\n                .field(\"age\")\n                \u002F\u002F Elasticsearch 范围查询接受大范围的值类型。我们在这里创建最高价格的 JSON 表示。\n                .gte(JsonData.of(maxAge))\n        )._toQuery();\n        SearchResponse\u003CUser> response = elasticsearchClient.search(s -> s\n                        .index(\"users\")\n                        .query(q -> q\n                                .bool(b -> b\n                                        \u002F\u002F 搜索查询是结合了文本搜索和最高价格查询的布尔查询\n                                        .must(byName)\n                                        \u002F\u002F .should(byMaxAge)\n                                        .must(byMaxAge)\n                                )\n                        ),\n                User.class\n        );\n        List\u003CHit\u003CUser>> hits = response.hits().hits();\n        for (Hit\u003CUser> hit: hits) {\n            User user = hit.source();\n            assert user != null;\n            log.info(\"Found userId \" + user.getId() + \", name \" + user.getName());\n        }\n    }\n}\n```\n\n### 模板化搜索\n\n* 模板化搜索是存储的搜索，可以使用不同的变量运行它。搜索模板让您无需修改应用程序代码即可更改搜索。\n* 在运行模板搜索之前，首先必须创建模板。这是一个返回搜索请求正文的存储脚本，通常定义为 Mustache 模板\n\n```java\n\u002F**\n * @version 1.0.0\n * @className: SearchTest\n * @description: 查询测试\n * @author: smartzeng\n * @create: 2022\u002F8\u002F8 11:04\n *\u002F\n@SpringBootTest\n@Slf4j\npublic class SearchTest\n{\n    @Autowired\n    private ElasticsearchClient elasticsearchClient;\n\n    \u002F**\n     * 模板化搜索\n     * 模板化搜索是存储的搜索，可以使用不同的变量运行它。搜索模板让您无需修改应用程序代码即可更改搜索。\n     * 在运行模板搜索之前，首先必须创建模板。这是一个返回搜索请求正文的存储脚本，通常定义为 Mustache 模板\n     *\u002F\n    @Test\n    void templatedSearch() throws IOException {\n        \u002F\u002F 事先创建搜索模板\n        elasticsearchClient.putScript(r -> r\n                \u002F\u002F 要创建的模板脚本的标识符\n                .id(\"query-script\")\n                .script(s -> s\n                        .lang(\"mustache\")\n                        .source(\"{\\\"query\\\":{\\\"match\\\":{\\\"{{field}}\\\":\\\"{{value}}\\\"}}}\")\n                ));\n        \u002F\u002F 开始使用模板搜索\n        String field = \"name\";\n        String value = \"liuyifei\";\n        SearchTemplateResponse\u003CUser> response = elasticsearchClient.searchTemplate(r -> r\n                        .index(\"users\")\n                        \u002F\u002F 要使用的模板脚本的标识符\n                        .id(\"query-script\")\n                        \u002F\u002F 模板参数值\n                        .params(\"field\", JsonData.of(field))\n                        .params(\"value\", JsonData.of(value)),\n                User.class\n        );\n\n        List\u003CHit\u003CUser>> hits = response.hits().hits();\n        for (Hit\u003CUser> hit: hits) {\n            User user = hit.source();\n            assert user != null;\n            log.info(\"Found userId \" + user.getId() + \", name \" + user.getName());\n        }\n    }\n}\n```\n\n### 分页&排序查询\n\n* 根据条件搜索的分页&排序\n\n```java\n\u002F**\n * @version 1.0.0\n * @className: SearchTest\n * @description: 查询测试\n * @author: smartzeng\n * @create: 2022\u002F8\u002F8 11:04\n *\u002F\n@SpringBootTest\n@Slf4j\npublic class SearchTest\n{\n    @Autowired\n    private ElasticsearchClient elasticsearchClient;\n\n    \u002F**\n     * 分页+排序条件搜索\n     *\n     * @throws IOException ioexception\n     *\u002F\n    @Test\n    void paginationSearch() throws IOException\n    {\n        int maxAge = 20;\n        Query byMaxAge = RangeQuery.of(m -> m\n                .field(\"age\")\n                .gte(JsonData.of(maxAge))\n        )._toQuery();\n        SearchResponse\u003CUser> response = elasticsearchClient.search(s -> s\n                        .index(\"users\")\n                        .query(q -> q\n                                .bool(b -> b\n                                        .must(byMaxAge)\n                                )\n                        )\n                        \u002F\u002F分页查询，从第0页开始查询4个document\n                        .from(0)\n                        .size(4)\n                         \u002F\u002F按age降序排序\n                        .sort(f -> f.field(o -> o.field(\"age\")\n                                .order(SortOrder.Desc))),\n                User.class\n        );\n        List\u003CHit\u003CUser>> hits = response.hits().hits();\n        for (Hit\u003CUser> hit: hits) {\n            User user = hit.source();\n            assert user != null;\n            log.info(\"Found userId \" + user.getId() + \", name \" + user.getName());\n        }\n    }\n}\n```\n\n**结果**\n\n```json\n[\n    {\n        \"id\":\"66\",\n        \"name\":\"liuyihu\",\n        \"age\":32,\n        \"sex\":\"男\"\n    },\n    {\n        \"id\":\"55\",\n        \"name\":\"zhangya\",\n        \"age\":30,\n        \"sex\":\"女\"\n    },\n    {\n        \"id\":\"c49fa7a2-f8dc-45a4-8ea8-340f10a6162e\",\n        \"name\":\"zhangSan\",\n        \"age\":28,\n        \"sex\":\"男\"\n    },\n    {\n        \"id\":\"1\",\n        \"name\":\"王五\",\n        \"age\":28,\n        \"sex\":\"男\"\n    }\n]\n```\n\n* 查询所有并进行分页&排序\n\n```java\n\u002F**\n * @version 1.0.0\n * @className: SearchTest\n * @description: 查询测试\n * @author: smartzeng\n * @create: 2022\u002F8\u002F8 11:04\n *\u002F\n@SpringBootTest\n@Slf4j\npublic class SearchTest\n{\n    @Autowired\n    private ElasticsearchClient elasticsearchClient;\n\n    \u002F**\n     * 分页+排序条件搜索\n     *\n     * @throws IOException ioexception\n     *\u002F\n    @Test\n    void paginationSearch() throws IOException\n    {\n        int maxAge = 20;\n        Query byMaxAge = RangeQuery.of(m -> m\n                .field(\"age\")\n                .gte(JsonData.of(maxAge))\n        )._toQuery();\n        SearchResponse\u003CUser> response = elasticsearchClient.search(s -> s\n                        .index(\"users\")\n                        .query(q -> q\n                                .matchAll( m -> m)\n                        )\n                        .from(0)\n                        .size(6)\n                        .sort(f -> f.field(o -> o.field(\"age\")\n                                .order(SortOrder.Desc))),\n                User.class\n        );\n        List\u003CHit\u003CUser>> hits = response.hits().hits();\n        for (Hit\u003CUser> hit: hits) {\n            User user = hit.source();\n            assert user != null;\n            log.info(\"Found userId \" + user.getId() + \", name \" + user.getName());\n        }\n    }\n}\n```\n\n**结果**\n\n```json\n[\n    {\n        \"id\":\"66\",\n        \"name\":\"liuyihu\",\n        \"age\":32,\n        \"sex\":\"男\"\n    },\n    {\n        \"id\":\"55\",\n        \"name\":\"zhangya\",\n        \"age\":30,\n        \"sex\":\"女\"\n    },\n    {\n        \"id\":\"c49fa7a2-f8dc-45a4-8ea8-340f10a6162e\",\n        \"name\":\"zhangSan\",\n        \"age\":28,\n        \"sex\":\"男\"\n    },\n    {\n        \"id\":\"1\",\n        \"name\":\"王五\",\n        \"age\":28,\n        \"sex\":\"男\"\n    },\n    {\n        \"id\":\"22\",\n        \"name\":\"awang\",\n        \"age\":25,\n        \"sex\":\"男\"\n    },\n    {\n        \"id\":\"33\",\n        \"name\":\"liuyifei\",\n        \"age\":22,\n        \"sex\":\"女\"\n    },\n    {\n        \"id\":\"11\",\n        \"name\":\"zhaosi\",\n        \"age\":20,\n        \"sex\":\"男\"\n    },\n    {\n        \"id\":\"44\",\n        \"name\":\"dongmei\",\n        \"age\":20,\n        \"sex\":\"女\"\n    }\n]\n```\n\n### 过滤字段\n\n```java\n\u002F**\n * @version 1.0.0\n * @className: SearchTest\n * @description: 查询测试\n * @author: smartzeng\n * @create: 2022\u002F8\u002F8 11:04\n *\u002F\n@SpringBootTest\n@Slf4j\npublic class SearchTest\n{\n    @Autowired\n    private ElasticsearchClient elasticsearchClient;\n\n    \u002F**\n     * 过滤字段\n     *\n     * @throws IOException ioexception\n     *\u002F\n    @Test\n    void filterFieldSearch() throws IOException\n    {\n        SearchResponse\u003CUser> response = elasticsearchClient.search(s -> s\n                        .index(\"users\")\n                        .query(q -> q\n                                .matchAll( m -> m)\n                        )\n                        .sort(f -> f\n                                .field(o -> o\n                                        .field(\"age\")\n                                        .order(SortOrder.Desc)\n                                )\n                        )\n                        .source(source -> source\n                                .filter(f -> f\n                                        .includes(\"name\",\"id\")\n                                        .excludes(\"\"))),\n                User.class\n        );\n        List\u003CHit\u003CUser>> hits = response.hits().hits();\n        List\u003CUser> userList = new ArrayList\u003C>(hits.size());\n        for (Hit\u003CUser> hit: hits) {\n            User user = hit.source();\n            userList.add(user);\n        }\n        log.info(\"过滤字段后：{}\",JSONUtil.toJsonStr(userList));\n    }\n}\n```\n\n**结果**\n\n```java\n[\n    {\n        \"id\":\"66\",\n        \"name\":\"liuyihu\"\n    },\n    {\n        \"id\":\"55\",\n        \"name\":\"zhangya\"\n    },\n    {\n        \"id\":\"c49fa7a2-f8dc-45a4-8ea8-340f10a6162e\",\n        \"name\":\"zhangSan\"\n    },\n    {\n        \"id\":\"1\",\n        \"name\":\"王五\"\n    },\n    {\n        \"id\":\"22\",\n        \"name\":\"awang\"\n    },\n    {\n        \"id\":\"33\",\n        \"name\":\"liuyifei\"\n    },\n    {\n        \"id\":\"11\",\n        \"name\":\"zhaosi\"\n    },\n    {\n        \"id\":\"44\",\n        \"name\":\"dongmei\"\n    }\n]\n```\n\n### 模糊查询\n\n```java\n\u002F**\n * @version 1.0.0\n * @className: SearchTest\n * @description: 查询测试\n * @author: smartzeng\n * @create: 2022\u002F8\u002F8 11:04\n *\u002F\n@SpringBootTest\n@Slf4j\npublic class SearchTest\n{\n    @Autowired\n    private ElasticsearchClient elasticsearchClient;\n\n    \u002F**\n     * 模糊查询\n     *\n     * @throws IOException ioexception\n     *\u002F\n    @Test\n    void fuzzyQuerySearch() throws IOException\n    {\n        SearchResponse\u003CUser> response = elasticsearchClient.search(s -> s\n                        .index(\"users\")\n                        .query(q -> q\n                                \u002F\u002F 模糊查询\n                                .fuzzy(f -> f\n                                        \u002F\u002F 需要判断的字段名称\n                                        .field(\"name\")\n                                        \u002F\u002F 需要模糊查询的关键词\n                                        \u002F\u002F 目前文档中没有liuyi这个用户名\n                                        .value(\"liuyi\")\n                                        \u002F\u002F fuzziness代表可以与关键词有误差的字数，可选值为0、1、2这三项\n                                        .fuzziness(\"2\")\n                                )\n                        )\n                        .source(source -> source\n                                .filter(f -> f\n                                        .includes(\"name\",\"id\")\n                                        .excludes(\"\"))),\n                User.class\n        );\n        List\u003CHit\u003CUser>> hits = response.hits().hits();\n        List\u003CUser> userList = new ArrayList\u003C>(hits.size());\n        for (Hit\u003CUser> hit: hits) {\n            User user = hit.source();\n            userList.add(user);\n        }\n        log.info(\"过滤字段后：{}\",JSONUtil.toJsonStr(userList));\n    }\n}\n```\n\n**结果**\n\n```json\n[\n    {\n        \"id\":\"66\",\n        \"name\":\"liuyihu\"\n    }\n]\n```\n\n### 高亮查询\n\n```java\n\u002F**\n * @version 1.0.0\n * @className: SearchTest\n * @description: 查询测试\n * @author: smartzeng\n * @create: 2022\u002F8\u002F8 11:04\n *\u002F\n@SpringBootTest\n@Slf4j\npublic class SearchTest\n{\n    @Autowired\n    private ElasticsearchClient elasticsearchClient;\n\n    \u002F**\n     * 高亮查询\n     *\n     * @throws IOException ioexception\n     *\u002F\n    @Test\n    void highlightQueryQuery() throws IOException\n    {\n        SearchResponse\u003CUser> response = elasticsearchClient.search(s -> s\n                        .index(\"users\")\n                        .query(q -> q\n                                .term(t -> t\n                                        .field(\"name\")\n                                        .value(\"zhaosi\"))\n                        )\n                        .highlight(h -> h\n                                .fields(\"name\", f -> f\n                                        .preTags(\"\u003Cfont color='red'>\")\n                                        .postTags(\"\u003C\u002Ffont>\")))\n                        .source(source -> source\n                                .filter(f -> f\n                                        .includes(\"name\",\"id\")\n                                        .excludes(\"\"))),\n                User.class\n        );\n        List\u003CHit\u003CUser>> hits = response.hits().hits();\n        List\u003CUser> userList = new ArrayList\u003C>(hits.size());\n        for (Hit\u003CUser> hit: hits) {\n            User user = hit.source();\n            userList.add(user);\n            for(Map.Entry\u003CString, List\u003CString>> entry : hit.highlight().entrySet())\n            {\n                System.out.println(\"Key = \" + entry.getKey());\n                entry.getValue().forEach(System.out::println);\n            }\n        }\n        log.info(\"模糊查询结果：{}\",JSONUtil.toJsonStr(userList));\n    }\n}\n```\n\n**结果**\n\n```xml\nKey = name\n\u003Cfont color='red'>zhaosi\u003C\u002Ffont>\n```\n\n## 聚合查询\n\n### 最大值\n\n```java\n\u002F**\n * @version 1.0.0\n * @className: SearchTest\n * @description: 聚合查询测试\n * @author: smartzeng\n * @create: 2022\u002F8\u002F8 11:04\n *\u002F\n@SpringBootTest\n@Slf4j\npublic class PartyTest\n{\n    @Autowired\n    private ElasticsearchClient elasticsearchClient;\n\n    \u002F**\n     * 获取最大年龄用户\n     *\u002F\n    @Test\n    void getMaxAgeUserTest() throws IOException {\n        SearchResponse\u003CVoid> response = elasticsearchClient.search(b -> b\n                        .index(\"users\")\n                        .size(0)\n                        .aggregations(\"maxAge\", a -> a\n                                .max(MaxAggregation.of(s -> s\n                                        .field(\"age\"))\n                                )\n                        ),\n                Void.class\n        );\n        MaxAggregate maxAge = response.aggregations()\n                .get(\"maxAge\")\n                .max();\n        log.info(\"maxAge.value:{}\",maxAge.value());\n    }\n}\n```\n\n### 分组统计\n\n* `LongTermsAggregate`类型\n\n```java\n\u002F**\n * @version 1.0.0\n * @className: SearchTest\n * @description: 聚合查询测试\n * @author: smartzeng\n * @create: 2022\u002F8\u002F8 11:04\n *\u002F\n@SpringBootTest\n@Slf4j\npublic class PartyTest\n{\n    @Autowired\n    private ElasticsearchClient elasticsearchClient;\n\n    \u002F**\n     * 年龄分组\n     *\n     * @throws IOException ioexception\n     *\u002F\n    @Test\n    void groupingTest() throws IOException {\n        SearchResponse\u003CVoid> response = elasticsearchClient.search(b -> b\n                        .index(\"users\")\n                        .size(0)\n                        .aggregations(\"groupName\", a -> a\n                                .terms(TermsAggregation.of(s -> s\n                                        .field(\"age\")))\n                        ),\n                Void.class\n        );\n        LongTermsAggregate longTermsAggregate = response.aggregations()\n                .get(\"groupName\")\n                .lterms();\n        log.info(\"multiTermsAggregate:{}\",longTermsAggregate.buckets());\n    }\n}\n```\n\n**结果**\n\n```json\n[\n    {\n        \"doc_count\":2,\n        \"key\":20\n    },\n    {\n        \"doc_count\":2,\n        \"key\":28\n    },\n    {\n        \"doc_count\":1,\n        \"key\":22\n    },\n    {\n        \"doc_count\":1,\n        \"key\":25\n    },\n    {\n        \"doc_count\":1,\n        \"key\":30\n    },\n    {\n        \"doc_count\":1,\n        \"key\":32\n    }\n]\n```\n\n* `StringTermsAggregate`类型\n\n```java\n\u002F**\n * @version 1.0.0\n * @className: SearchTest\n * @description: 聚合查询测试\n * @author: smartzeng\n * @create: 2022\u002F8\u002F8 11:04\n *\u002F\n@SpringBootTest\n@Slf4j\npublic class PartyTest\n{\n    @Autowired\n    private ElasticsearchClient elasticsearchClient;\n\n    \u002F**\n     * 性别分组\n     *\n     * @throws IOException ioexception\n     *\u002F\n    @Test\n    void groupBySexTest() throws IOException {\n        SearchResponse\u003CVoid> response = elasticsearchClient.search(b -> b\n                        .index(\"users\")\n                        .size(0)\n                        .aggregations(\"groupSex\", a -> a\n                                .terms(TermsAggregation.of(s -> s\n                        \u002F\u002F ⚠️特别注意这一块，我们加上了.keyword，下面会说明\n                                    .field(\"sex.keyword\"))) \n                        ),\n                Void.class\n        );\n        StringTermsAggregate stringTermsAggregate = response.aggregations()\n                .get(\"groupSex\")\n                .sterms();\n        log.info(\"stringTermsAggregate:{}\",stringTermsAggregate.buckets());\n    }\n}\n```\n\n**结果**\n\n```json\n[\n    {\n        \"doc_count\":5,\n        \"key\":\"男\"\n    },\n    {\n        \"doc_count\":3,\n        \"key\":\"女\"\n    }\n]\n```\n\n**问题**\n\n* 在`性别分组`代码中，有一块地方我们要注意下。我们根据性别分组，按理来说，`field`直接写`sex`就行，但是我们发现，如果不加`.keyword`，运行测试用例会报错，错误如下： ![ElasticSearch](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Fdownload\u002Feditor\u002F1703685972344es55.png)\n\n**原因分析：**\n\n这个错误的原因是因为我的分组聚合查询的字符串`sex`类型是`text`类型。当使用到 `term` 查询的时候，由于是精准匹配，所以查询的关键字在es上的类型，必须是`keyword`而不能是`text`。所以想要分组查询，指定根据分组字段的keyword属性就可以了。\n\n",{"id":81,"created_at":82,"updated_at":82,"code":83,"author_id":4,"book_id":4,"slug":84,"article_id":85,"title":86,"sort_order":81,"content":87,"status":11},8,"2023-12-27T14:08:28.58+08:00","1l63ZKg2","elasticsearch之eql操作-8",158,"ElasticSearch之EQL操作","## 前述\n\n`EQL` 的全名是 `Event Query Language (EQL)`。事件查询语言（EQL）是一种用于基于事件的时间序列数据（例如日志，指标和跟踪）的查询语言。在 `Elastic Security` 平台上，当输入有效的 `EQL` 时，查询会在数据节点上编译，执行查询并返回结果。这一切都快速、并行地发生，让用户立即看到结果。\n\n### EQL优点\n\n* EQL 使你可以表达事件之间的关系：许多查询语言允许您匹配单个事件。EQL 使你可以匹配不同事件类别和时间跨度的一系列事件\n* EQL 的学习曲线很低：EQL 语法看起来像其他常见查询语言，例如  SQL。 EQL 使你可以直观地编写和读取查 询，从而可以进行快速，迭代的搜索。\n* EQL 设计用于安全用例：尽管你可以将其用于任何基于事件的数据，但我们创建了  EQL 来进行威胁搜寻。  EQL 不仅支持危害指标（IOC）搜索，而且可以描述超出 IOC 范围的活动\n\n## 基础语法\n\n### 数据准备\n\n要运行 `EQL` 搜索，搜索到的数据流或索引必须包含时间戳和事件类别字段。默认情况下，`EQL` 使用 `Elastic` 通用模式（ECS）中的 `@timestamp` 和 `event.category` 字段。 `@timestamp` 表示时间戳，`event.category` 表示事件分类。\n\n```shell\n# 创建索引\nPUT \u002Fgmall\n# 批量增加数据\nPUT _bulk\n{\"index\":{\"_index\":\"gmall\"}}\n{\n    \"@timestamp\":\"2022-08-10T15:00:00.00+08:00\",\n    \"event\":{\n        \"category\":\"page\"\n    },\n    \"page\":{\n        \"session_id\":\"42FC7E13-CB3E-5C05-0000-0010A0125101\",\n        \"last_page_id\":\"\",\n        \"page_id\":\"login\",\n        \"user_id\":\"\"\n    }\n}\n{\"index\":{\"_index\":\"gmall\"}}\n{\n    \"@timestamp\":\"2022-08-10T15:02:00.00+08:00\",\n    \"event\":{\n        \"category\":\"page\"\n    },\n    \"page\":{\n        \"session_id\":\"42FC7E13-CB3E-5C05-0000-0010A0125101\",\n        \"last_page_id\":\"login\",\n        \"page_id\":\"good_list\",\n        \"user_id\":\"1\"\n    }\n}\n{\"index\":{\"_index\":\"gmall\"}}\n{\n    \"@timestamp\":\"2022-08-10T15:05:00.00+08:00\",\n    \"event\":{\n        \"category\":\"page\"\n    },\n    \"page\":{\n        \"session_id\":\"42FC7E13-CB3E-5C05-0000-0010A0125101\",\n        \"last_page_id\":\"good_list\",\n        \"page_id\":\"good_detail\",\n        \"user_id\":\"1\"\n    }\n}\n{\"index\":{\"_index\":\"gmall\"}}\n{\n    \"@timestamp\":\"2022-08-10T15:07:00.00+08:00\",\n    \"event\":{\n        \"category\":\"page\"\n    },\n    \"page\":{\n        \"session_id\":\"42FC7E13-CB3E-5C05-0000-0010A0125101\",\n        \"last_page_id\":\"good_detail\",\n        \"page_id\":\"order\",\n        \"user_id\":\"1\"\n    }\n}\n{\"index\":{\"_index\":\"gmall\"}}\n{\n    \"@timestamp\":\"2022-08-10T15:08:00.00+08:00\",\n    \"event\":{\n        \"category\":\"page\"\n    },\n    \"page\":{\n        \"session_id\":\"42FC7E13-CB3E-5C05-0000-0010A0125101\",\n        \"last_page_id\":\"order\",\n        \"page_id\":\"payment\",\n        \"user_id\":\"1\"\n    }\n}\n{\"index\":{\"_index\":\"gmall\"}}\n{\n    \"@timestamp\":\"2022-08-10T15:08:00.00+08:00\",\n    \"event\":{\n        \"category\":\"page\"\n    },\n    \"page\":{\n        \"session_id\":\"42FC7E13-CB3E-5C05-0000-0010A0125102\",\n        \"last_page_id\":\"\",\n        \"page_id\":\"login\",\n        \"user_id\":\"2\"\n    }\n}\n{\"index\":{\"_index\":\"gmall\"}}\n{\n    \"@timestamp\":\"2022-08-10T15:08:00.00+08:00\",\n    \"event\":{\n        \"category\":\"page\"\n    },\n    \"page\":{\n        \"session_id\":\"42FC7E13-CB3E-5C05-0000-0010A0125102\",\n        \"last_page_id\":\"login\",\n        \"page_id\":\"payment\",\n        \"user_id\":\"2\"\n    }\n}\n```\n\n\n### 数据窗口搜索\n\n在事件响应过程中，有很多时候，了解特定时间发生的所有事件是很有用的。使用一种名为`any`的特殊事件类型，针对所有事件进行匹配，如果想要匹配特定事件，就需要指明事件分类名称\n\n```shell\n# \nGET \u002Fgmall\u002F_eql\u002Fsearch \n{ \n    \"query\" : \"\"\" \n        any where page.user_id == \"1\" \n    \"\"\" \n}\n```\n\n\n### 统计符合条件的事件\n\n```shell\nGET \u002Fgmall\u002F_eql\u002Fsearch \n{ \n    \"query\" : \"\"\" \n        any where true \n    \"\"\",\n    \"filter\": { \n        \"range\": { \n            \"@timestamp\": { \n                \"gte\": \"1654056000000\",\n                \"lt\": \"1654056005000\" \n            } \n        } \n    }\n}\n```\n\n\n### 事件序列\n\n* 页面先访问  login,后面又访问了  good\\_detail的页面\n\n```shell\nGET \u002Fgmall\u002F_eql\u002Fsearch \n{ \n    \"query\" : \"\"\" \n      sequence by page.session_id \n        [page where page.page_id==\"login\"] \n        [page where page.page_id==\"good_detail\"] \n    \"\"\" \n}\n```\n\n\n## 安全检测\n\n`EQL`在 `Elastic Securit` 中被广泛使用。实际应用时，我们可以使用 `EQL` 语言来进行检测安全威胁和其他可疑行为。\n\n### 数据准备\n\n`regsvr32.exe` 是一个内置的命令行实用程序，用于在`Windows`中注册`.dll`库。作为本机工具，`regsvr32.exe` 具有受信任的状态，从而使它可以绕过大多数允许列表软件和脚本阻止 程序。有权访问用户命令行的攻击者可以使用 `regsvr32.exe` 通过`.dll`库运行恶意脚本，即使在其他情况下也不允许这些脚本运行。\n\n`regsvr32` 滥用的一种常见变体是`Squfullydoo`攻击。在 `Squfullydoo` 攻击中，`regsvr32.exe` 命令使用 `scrobj.dll` 库注册并运行远程脚本。\n\n测试数据来自 `Atomic Red Team` 的测试数据集，其中包括模仿`Squibledoo`攻击的事件。 数据已映射到`Elastic`通用架构（ECS）字段：`normalized-T1117-AtomicRed-regsvr32.json` 将文件内容导入到`ES`软件中：\n\n```shell\n# 创建索引 \nPUT my-eql-index \n# 导入数据 \nPOST my-eql-index\u002F_bulk?pretty&refresh\n{\"index\":{}}\n{\n    ......\n}\n```\n\n\n* 查看数据导入情况\n\n```shell\nGET \u002F_cat\u002Findices\u002Fmy-eql-index?v=true&h=health,status,index,docs.count\n```\n\n\n### 获取regsvr32事件的计数\n\n* 获取与`regsvr32.exe`进程关联的事件数\n\n```shell\n# 查询数据 \n# ?filter_path=-hits.events 从响应中排除hits.events 属性。此搜索仅用于获取事件计数，而不是匹配事件的列表 \n# query : 匹配任何进程名称为regsvr32.exe的事件 \n# size : 最多返回200个匹配事件的匹配,实际查询结果为143个 \nGET my-eql-index\u002F_eql\u002Fsearch?filter_path=-hits.events \n{ \n    \"query\": \"\"\" \n      any where process.name == \"regsvr32.exe\"          \n    \"\"\", \n    \"size\": 200                                         \n}\n```\n\n\n### 检查命令行参数\n\n该查询将一个事件与创建的`event.type`相匹配，指示`regsvr32.exe`进程的开始。根据事件的 `process.command_line`值，`regsvr32.exe` 使用 `scrobj.dll` 注册了脚本 `RegSvr32.sct`.这符合`Squibledoo`攻击的行为\n\n```shell\n# 增加过滤条件查询数据 \nGET my-eql-index\u002F_eql\u002Fsearch \n{ \n    \"query\": \"\"\" \n       process where process.name == \"regsvr32.exe\" and process.command_line.keyword != null          \n    \"\"\"  \n}\n```\n\n\n### 检查恶意脚本加载\n\n* 检查`regsvr32.exe`以后是否加载`scrobj.dll`库\n\n```shell\n# 增加过滤条件查询数据 \nGET my-eql-index\u002F_eql\u002Fsearch \n{ \n    \"query\": \"\"\" \n        library where process.name == \"regsvr32.exe\" and dll.name == \"scrobj.dll\"\n    \"\"\"                                       \n}\n```\n\n\n### 检查攻击成功可能性\n\n在许多情况下，攻击者使用恶意脚本连接到远程服务器或下载其他文件。 使用`EQL`序列查询来检查以下一系列事件：\n\n* `regsvr32.exe`进程\n* 通过相同的进程加载`scrobj.dll`库\n* 同一过程中的任何网络事件\n\n```shell\n# 增加过滤条件查询数据 \nGET my-eql-index\u002F_eql\u002Fsearch \n{ \n    \"query\": \"\"\" \n        sequence by process.pid \n            [process where process.name == \"regsvr32.exe\"] \n            [library where dll.name == \"scrobj.dll\"] \n            [network where true]          \n    \"\"\"                                   \n}\n```\n",{"id":89,"created_at":90,"updated_at":90,"code":91,"author_id":4,"book_id":4,"slug":92,"article_id":93,"title":94,"sort_order":89,"content":95,"status":11},9,"2023-12-27T14:09:02.256+08:00","7vXP5KZ1","elasticsearch-sql-9",159,"Elasticsearch SQL","## 介绍\n\n![Elasticsearch_SQL](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Fdownload\u002Feditor\u002F1703686218838esql1.png)\n\n* [官方操作文档 **(opens new window)**](https:\u002F\u002Fwww.elastic.co\u002Fguide\u002Fen\u002Felasticsearch\u002Freference\u002Fcurrent\u002Fsql-overview.html#sql-why)\n\n一般使用 `Elasticsearch` 的时候，会使用 `Query DSL` 来查询数据，从 `Elasticsearch6.3` 版本以后，`Elasticsearch` 已经支持`SQL`查询了。\n\n`Elasticsearch SQL` 是一个 `X-Pack` 组件，它允许针对 `Elasticsearch` 实时执行类似`SQL`的 查询。无论使用`REST`接口，命令行还是`JDBC`，任何客户端都可以使用`SQL`对 `Elasticsearch`中的数据进行原生搜索和聚合数据。可以将 `Elasticsearch SQL` 看作是一种翻译器，它可以将 `SQL`翻译成 `Query DSL`。\n\n### Elasticsearch\\_SQL具有如下特性\n\n* 原生集成 `Elasticsearch SQL` 是为 `Elasticsearch` 从头开始构建的。每个查询都根据底层存储有效地针对相关节点执行。\n* 没有外部零件 无需额外的硬件、进程、运行时或库来查询 `Elasticsearch`；`Elasticsearch SQL` 通过在`Elasticsearch` 内部运行消除了额外的移动部件。\n* 轻巧高效 `Elasticsearch SQL`并未抽象化其搜索功能，相反的它拥抱并接受了`SQL`来 实现全文搜索，以简洁的方式实时运行全文搜索。\n\n## SQL和Elasticsearch的对应关系\n\n\n| SQL      | ES           | 说明                                                                                                                                                                                                                                                                                                                                                                                                                                                 |\n| -------- | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |\n| Column   | field        | 对比两个，数据都存储在命名条目中，具有多种数据类型，包含一 个值。        SQL  将此类条目称为列，而 Elasticsearch  称为字段。请注意，在 Elasticsearch  中，一个字段可以包含多个相同类型的值（本质上是一个列表），而在 SQL  中，一个列可以只包含一个所述类型的值。Elasticsearch SQL  将尽最大努力保留    SQL  语义，并根据查询拒 绝那些返回具有多个值的字段的查询                                                                                      |\n| Row      | document     | Columns  和 fields 本身不存在；它们是 row 或 document 的一部分 。两者的语义略有不同：row 趋于严格（并且有更多的强制执行），而 document 趋于更加灵活或松散（同时仍然具有结构）。                                                                                                                                                                                                                                                                      |\n| Table    | Index        | 执行查询的目标                                                                                                                                                                                                                                                                                                                                                                                                                                       |\n| Schema   | Mapping      | 在 RDBMS 中，schem 主要是表的命名空间，通常用作安全边界。 Elasticsearch 没有为它提供等效的概念。但是，当启用安全性时， Elasticsearch 会自动应用安全性强制，以便角色只能看到它被允许访 问的数据                                                                                                                                                                                                                                                       |\n| Database | Cluster 实例 | 在 SQL 中，catalog 或者 database 从概念上可以互换使用，表示一组 模式，即多个表。在 Elasticsearch  中，可用的索引集被分组在一个 cluster，语义也有所不同。database 本质上是另一个命名空间（可能对数据的存储方式有一些影响），而 Elasticsearch  cluster 是一个运行时实例，或者更确切地说是一组至少一个 Elasticsearch  实例（通 常是分布式运行）。在实践中，这意味着虽然在 SQL  中，一个实例中可能有多个目录，但在 Elasticsearch  中，一个目录仅限于一个 |\n\n## 数据准备\n\n* 创建索引并增加数据，等同于创建表和数据\n\n```shell\nPUT my-sql-index\u002F_bulk?refresh \n{\"index\":{\"_id\": \"JAVA\"}} \n{\"name\":  \"JAVA\",  \"author\":  \"zhangsan\",  \"release_date\":  \"2022-08-10\",\"page_count\": 561} \n{\"index\":{\"_id\": \"BIGDATA\"}} \n{\"name\": \"BIGDATA\", \"author\": \"lisi\", \"release_date\": \"2022-08-11\", \"page_count\": 482} \n{\"index\":{\"_id\": \"SCALA\"}}\n{\"name\": \"SCALA\", \"author\": \"wangwu\", \"release_date\": \"2022-08-12\", \"page_count\": 604}\n```\n\n![Elasticsearch_SQL](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Fdownload\u002Feditor\u002F1703686239383esql2.png)\n\n### 第一个SQL查询\n\n```xml\n# SQL \n# 这里的表就是索引 \n# 可以通过 format参数控制返回结果的格式，默认为  json格式 \n#       txt:表示文本格式，看起来更直观点. \n#       csv:使用逗号隔开的数据 \n#       json:JSON格式数据 \n#       tsv: 使用  tab键隔开数据 \n#       yaml:属性配置格式 \nPOST _sql?format=txt \n{ \n    \"query\": \"\"\" \n      SELECT * FROM \"my-sql-index\" \n    \"\"\" \n}\n```\n\n![Elasticsearch_SQL](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Fdownload\u002Feditor\u002F1703686250127esql3.png)\n\n### 条件查询\n\n```shell\n# 条件查询 \nPOST _sql?format=txt \n{ \n    \"query\": \"\"\" \n      SELECT * FROM \"my-sql-index\" where page_count > 500\n    \"\"\" \n}\n```\n\n![Elasticsearch_SQL](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Fdownload\u002Feditor\u002F1703687718376esql4.png)\n\n### SQL转换为DSL使用\n\n当我们需要使用`Query DSL`时，也可以先使用`SQL`来查询，然后通过`Translate API`转换即可，查询的结果为`DSL`方式的结果\n\n```shell\n# 转换  SQL为  DSL进行操作 \nPOST _sql\u002Ftranslate \n{ \n    \"query\": \"\"\" \n      SELECT * FROM \"my-sql-index\" where page_count > 500 \n    \"\"\" \n}\n```\n\n![Elasticsearch_SQL](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Fdownload\u002Feditor\u002F1703686259041esql5.png)\n\n### SQL和DSL混合使用\n\n我们如果在优化`SQL`语句之后还不满足查询需求，可以拿`SQL`和 `DSL` 混用，`ES`会先根据`SQL` 进行查询，然后根据`DSL`语句对`SQL`的执行结果进行二次查询\n\n```shell\n# SQL和  DSL混合使用 \n# 由于索引中含有横线，所以作为表名时需要采用双引号，且外层需要三个引号包含 \nPOST _sql?format=txt \n{ \n    \"query\": \"\"\"SELECT * FROM \"my-sql-index\" \"\"\", \n    \"filter\" : { \n      \"range\": { \n        \"page_count\": { \n          \"gte\": 400, \n          \"lte\": 600 \n        } \n      } \n    }, \n    \"fetch_size\": 2 \n}\n```\n\n![Elasticsearch_SQL](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Fdownload\u002Feditor\u002F1703686270711esql6.png)\n\n## 常用SQL操作\n\n### 查询所有索引\n\n```shell\nGET _sql?format=txt\n{ \n    \"query\": \"\"\" \n      show tables \n    \"\"\" \n}\n```\n\n![Elasticsearch_SQL](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Fdownload\u002Feditor\u002F1703686300224esql7.png)\n\n### 查询指定索引\n\n```shell\nGET _sql?format=txt\n{ \n    \"query\": \"\"\" \n      show tables like 'my-sql-index'\n    \"\"\" \n}\n```\n\n![Elasticsearch_SQL](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Fdownload\u002Feditor\u002F1703687767160esql8.png)\n\n### 模糊查询索引\n\n```shell\nGET _sql?format=txt\n{ \n    \"query\": \"\"\" \n      show tables like 'my-%'\n    \"\"\" \n}\n```\n\n![Elasticsearch_SQL](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Fdownload\u002Feditor\u002F1703687782942esql9.png)\n\n### 查看索引结构\n\n```shell\n# 先创建一个索引\nput myindex\n{\n  \"mappings\":{\n    \"properties\":{\n      \"sku_id\":{\n        \"type\":\"long\"\n      },\n      \"sku_name\":{\n        \"type\":\"text\"\n      },\n      \"sku_url\":{\n        \"type\":\"keyword\"\n      }\n    }\n  }\n}\n```\n\n```shell\nGET _sql?format=txt\n{ \n    \"query\": \"\"\" \n      describe myindex\n    \"\"\" \n}\n```\n\n![Elasticsearch_SQL](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Fdownload\u002Feditor\u002F1703687794677esql10.png)\n\n## 基本查询操作\n\n* 在`ES`中使用`SQL`查询的语法与在数据库中使用基本一致\n\n### where\n\n```shell\n# 条件过滤 \nPOST _sql?format=txt \n{ \n    \"query\": \"\"\" SELECT * FROM \"my-sql-index\" where name = 'JAVA' \"\"\" \n}\n```\n\n![Elasticsearch_SQL](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Fdownload\u002Feditor\u002F1703687805642esql11.png)\n\n### group-by\n\n```shell\n# 按照日期进行分组 \nGET _sql?format=txt \n{ \n    \"query\": \"\"\" \n      SELECT release_date FROM \"my-sql-index\" group by release_date \n    \"\"\" \n}\n```\n\n![Elasticsearch_SQL](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Fdownload\u002Feditor\u002F1703687812588esql12.png)\n\n### having\n\n```shell\n# 对分组后的数据进行过滤 \nGET _sql?format=txt \n{ \n  \"query\": \"\"\" \n    SELECT sum(page_count), release_date as datacnt FROM \"my-sql-index\" group by release_date having sum(page_count) > 1000 \n  \"\"\" \n}\n```\n\n![Elasticsearch_SQL](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Fdownload\u002Feditor\u002F1703687821899esql13.png)\n\n### order-by\n\n```shell\n# 对页面数量进行排序（降序） \nGET _sql?format=txt \n{ \n    \"query\": \"\"\" \n      select * from \"my-sql-index\" order by page_count desc\n    \"\"\" \n}\n```\n\n![Elasticsearch_SQL](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Fdownload\u002Feditor\u002F1703687829166esql14.png)\n\n### limit\n\n```shell\n# 限定查询数量 \nGET _sql?format=txt \n{ \n    \"query\": \"\"\" \n      select * from \"my-sql-index\" limit 3\n    \"\"\" \n}\n```\n\n![Elasticsearch_SQL](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Fdownload\u002Feditor\u002F1703687839181esql15.png)\n\n### cursor\n\n游标（cursor）是系统为用户开设的一个数据缓冲区，存储sql语句的执行结果，每个游标区都有一个名字，用户可以用 sql 语句逐一从游标中获取记录，并赋给主变量，交由主语言进一步处理。就本质而言，游标实际上是一种能从包括多条数据记录的结果集中每次提取一条或多条记录的机制\n\n```shell\n# 查询数据 \n# 因为查询结果较多，但是获取的数据较少，所以为了提高效果，会将数据存储到临时缓冲区中 \n# 此处数据展示格式为   json \nPOST _sql?format=json \n{ \n    \"query\": \"\"\" SELECT * FROM \"my-sql-index\" order by page_count desc \"\"\", \n    \"fetch_size\": 2 \n}\n```\n\n![Elasticsearch_SQL](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Fdownload\u002Feditor\u002F1703687852298esql16.png)\n\n返回结果中的`cursor`就是缓冲区的标识，这就意味着可以从缓冲区中直接获取后续数据，操作上有点类似于迭代器，可多次执行。\n\n```shell\n# 此处游标cursor值就是上图中的结果\nPOST \u002F_sql?format=json \n{ \n    \"cursor\": \"v5HqA0RGTACEkd9OwjAUxnvmQgwx8RF8BVG44IKLDew0YRCkUF1MljI6NigtrOVPeCIfwPfTbUDEK7+Lnu80PV+T34EAQYIsQIW+c92WDuVXUI1TLqahVpmprtiMh5HaSIMqYZxm2gAgsIWSs+N7+IIrZFm5KY4y4eNkUFHBrrCNSVSGLFuyJUfWZaZ1k3HBmebhlBkOd9pkaWTKJlQrkyrJRGjSJQ8lk0pb8AnbxvPa2T35k7eFiR6x7lKxCbxxHS\u002FEfEh7a9pJ1NhrdklnqiORKCKSve9FDhaiPhnt7vsjHI\u002Fmbi2Yu+3goUEGFMdDGjlntZs+DQ4v+76HiX94JUwK\u002FE5XPb\u002Fmpl0SkMHAcf\u002F7y3FaLVQ9crUhPhEA2\u002FC9yZHEJYnf9oLIGS3Ef8lcF0gKEPl4vqofAAAA\u002F\u002F8DAA==\" \n}\n```\n\n![Elasticsearch_SQL](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Fdownload\u002Feditor\u002F1703687872211esql17.png)\n\n* 如果执行后，无任何结果返回，说明数据已经读取完毕\n  ![Elasticsearch_SQL](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Fdownload\u002Feditor\u002F1703687879777esql18.png)\n* 此时再次执行，会返回错误信息\n  ![Elasticsearch_SQL](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Fdownload\u002Feditor\u002F1703687887251esql19.png)\n* 如果关闭缓冲区，执行下面指令即可\n\n```shell\nPOST _sql\u002Fclose\n{ \n  \"cursor\": \"v5HqA0RGTACEkUtuwjAQhj1phCpUqUfoFUoLCxYsEmjSSgREMbjNJjLBJgFjQ2we4kQ9QE\u002FUi7RJAJWu+i9G\u002F4zmIX0DIYIEWYAKfee6LR3KS1DlKRPTSKvMVFd0xqJYbaRBlYinmTYACGyh5OzYD59whSwrN0UoK18ng4rtYFfoxiQqQ5Yt6ZIh63KndZMxwahm0ZQaBnfaZGlsyiRSK5MqSUVk0iWLJJVKW\u002FAB28bz2tk9BZO3hYkfPd0lYhP647q3EPMh6a1JJ1Fjv9nFnamORaKwSPaBHzueEPXJaHffH3l8NHdr4dxthw8NPCAeH5LYOavdDEh4eNn3fQ8Hh1dMpfDeyaoX1Ny0i0M8GDjuf7ccp9VC1SNXG\u002FiJANiG7U2OhJckftMLIme0wP+SuS6QFCDy8fxVPwAAAP\u002F\u002FAwA=\"\n}\n```\n\n## 聚合操作\n\n* Min、Max、Avg、Sum、Count(\\*) 、Distinct\n\n```shell\nGET _sql?format=txt \n{ \n    \"query\": \"\"\" \n        SELECT \n        MIN(page_count) min, \n        MAX(page_count) max, \n        AVG(page_count) avg, \n        SUM(page_count) sum, \n        COUNT(*) count, \n        COUNT(DISTINCT name) dictinct_count \n        FROM \"my-sql-index\"\n    \"\"\"\n}\n```\n\n![Elasticsearch_SQL](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Fdownload\u002Feditor\u002F1703687898559esql20.png)\n\n## 支持的函数和运算\n\n### 比较运算符\n\n```sql\n-- Equality \nSELECT * FROM \"my-sql-index\" WHERE name = 'JAVA' \n\n-- Null Safe Equality \nSELECT 'elastic' \u003C=> null AS \"equals\" \nSELECT null \u003C=> null AS \"equals\" \n\n-- Inequality \nSELECT * FROM \"my-sql-index\" WHERE name \u003C> 'JAVA' \nSELECT * FROM \"my-sql-index\" WHERE name != 'JAVA' \n\n-- Comparison \nSELECT * FROM \"my-sql-index\" WHERE page_count > 500 \nSELECT * FROM \"my-sql-index\" WHERE page_count >= 500 \nSELECT * FROM \"my-sql-index\" WHERE page_count \u003C 500 \nSELECT * FROM \"my-sql-index\" WHERE page_count \u003C= 500 \n\n-- BETWEEN \nSELECT * FROM \"my-sql-index\" WHERE page_count between 100 and 500 \n\n-- Is Null \u002F Is Not Null \nSELECT * FROM \"my-sql-index\" WHERE name is not null \nSELECT * FROM \"my-sql-index\" WHERE name is null \n\n-- IN \nSELECT * FROM \"my-sql-index\" WHERE name in ('JAVA', 'SCALA')\n```\n\n### 逻辑运算符\n\n```sql\n-- AND \nSELECT * FROM \"my-sql-index\" WHERE name = 'JAVA' AND page_count > 100\n\n-- OR \nSELECT * FROM \"my-sql-index\" WHERE name = 'JAVA' OR name = 'SCALA' \n\n-- NOT \nSELECT * FROM \"my-sql-index\" WHERE NOT name = 'JAVA'\n```\n\n### 数学运算符\n\n```sql\n# 加减乘除 \nselect  1 + 1 as x \nselect  1 - 1 as x \nselect  - 1 as x \nselect  6 * 6 as x \nselect  30 \u002F 5 as x \nselect  30 % 7 as x\n```\n\n### 类型转换\n\n```sql\nSELECT '123'::long AS long\n```\n\n### 模糊查询\n\n```sql\n-- LIKE 通配符 \nSELECT * FROM \"my-sql-index\" WHERE name like 'JAVA%' \nSELECT * FROM \"my-sql-index\" WHERE name like 'JAVA_' \n\n-- 如果需要匹配通配符本身,使用转义字符 \nSELECT * FROM \"my-sql-index\" WHERE name like 'JAVA\u002F%' ESCAPE '\u002F' \n\n-- RLIKE 不要误会，这里的  R表示的不是方向，而是正则表示式  Regex \nSELECT * FROM \"my-sql-index\" WHERE name like 'JAV*A' \nSELECT * FROM \"my-sql-index\" WHERE name rlike 'JAV*A' \n\n-- 尽管 LIKE在 Elasticsearch SQL 中搜索或过滤时是一个有效的选项，但全文搜索    MATCH和 QUERY 速度更快、功能更强大，并且是首选替代方案。\n```\n\n### 聚合分析函数\n\n```sql\n-- FIRST \u002F FIRST_VALUE : FIRST(第一个字段，排序字段) \nSELECT  first(name, release_date) FROM \"my-sql-index\" \nSELECT first_value(substring(name,2,1)) FROM \"my-sql-index\" \n\n-- LAST \u002F LAST_VALUE : LAST (第一个字段，排序字段) \nSELECT  last(name, release_date) FROM \"my-sql-index\" \nSELECT  last_value(substring(name,2,1)) FROM \"my-sql-index\" \n\n-- KURTOSIS 量化字段的峰值分布 \nSELECT  KURTOSIS(page_count) FROM \"my-sql-index\" \n\n-- MAD \nSELECT  MAD(page_count) FROM \"my-sql-index\"\n```\n\n### 分组函数\n\n```sql\n-- HISTOGRAM : 直方矩阵 \nSELECT HISTOGRAM(page_count, 100) as c，count(*) FROM \"my-sql-index\" group by c\n```\n\n### 数学通用函数\n\n```sql\n-- ABS：求数字的绝对值 \nselect ABS(page_count) from \"myindex\" limit 5 \n\n-- CBRT：求数字的立方根，返回  double \nselect page_count v,CBRT(page_count) cbrt from \"myindex\" limit 5 \n\n-- CEIL：返回大于或者等于指定表达式最小整数（double） \nselect page_count v,CEIL(page_count) from \"myindex\" limit 5 \n\n-- CEILING：等同于  CEIL \nselect page_count v,CEILING(page_count) from \"myindex\" limit 5 \n\n-- E：返回自然常数  e(2.718281828459045) \nselect  page_count,E(page_count)  from  \"myindex\"  limit  5 \n\n-- ROUND：四舍五入精确到个位 \nselect ROUND(-3.14) \n\n-- FLOOR：向下取整 \nselect FLOOR(3.14)\n\n-- LOG：计算以  2为底的自然对数 \nselect LOG(4) \n\n-- LOG10：计算以  10为底的自然对数 \nselect LOG10(100) \n\n-- SQRT：求一个非负实数的平方根 \nselect  SQRT(9) \n\n-- EXP：此函数返回  e(自然对数的底)的  X次方的值 \nselect  EXP(3)\n```\n\n### 三角函数\n\n```sql\n-- DEGREES：返回  X从弧度转换为度值 \nselect DEGREES(x) \n\n-- RADIANS：返回  X从度转换成弧度的值 \nselect RADIANS(x) \n\n-- SIN：返回  X的正弦 \nselect SIN(x) \n\n-- COS：返回  X，X值是以弧度给出的余弦值 \nselect COS(角度) \n\n-- TAN：返回参数  X，表示以弧度的切线值 \nselect TAN(角度) \n\n-- ASIN：返回  X的反正弦，X的值必须在-1至  1范围内，返回  NULL \nselect ASIN(x) \n\n-- ACOS：返回  X的反正弦，X值必须-1到  1之间范围否则将返回  NULL \nselect ACOS(x) \n\n-- ATAN：返回  X的反正切 \nselect ATAN(x) \n\n-- SINH：返回  X的双曲正弦值 \nselect SINH(x) \n\n-- COSH：返回  X的双曲余弦值 \nselect COSH(x)\n```\n\n### 日期时间函数\n\n```sql\n-- YEAR： \nSELECT YEAR(CAST('2022-08-10T00:00:00Z' AS TIMESTAMP)) AS year \n\n-- MONTH_OF_YEAR() or MONTH()： \nSELECT MONTH(CAST('2022-08-10T00:00:00Z' AS TIMESTAMP)) AS month \n\n-- WEEK_OF_YEAR() or WEEK()： \nSELECT WEEK(CAST('2022-08-10T00:00:00Z' AS TIMESTAMP)) AS week \n\n-- DAY_OF_YEAR() or DOY() ，效果等同于    EXTRACT(\u003Cdatetime_function> FROM \u003Cexpression>)： \nSELECT DOY(CAST('2022-08-10T00:00:00Z' AS TIMESTAMP)) AS day \n\n-- DAY_OF_MONTH(), DOM(), or DAY()： \nSELECT DAY(CAST('2022-08-10T00:00:00Z' AS TIMESTAMP)) AS day\n\n-- DAY_OF_WEEK() or DOW()： \nSELECT DOW(CAST('2022-08-10T00:00:00Z' AS TIMESTAMP)) AS day \n\n-- HOUR_OF_DAY() or HOUR()： \nSELECT HOUR(CAST('2022-08-10T00:00:00Z' AS TIMESTAMP)) AS hour \n\n-- MINUTE_OF_DAY()： \nSELECT MINUTE_OF_DAY(CAST('2022-08-10T00:00:00Z' AS TIMESTAMP)) AS minute \n\n-- MINUTE_OF_HOUR() or MINUTE()\nSELECT MINUTE(CAST('2022-08-10T00:00:00Z' AS TIMESTAMP)) AS minute \n\n-- SECOND_OF_MINUTE() or SECOND()\nSELECT SECOND(CAST('2022-08-10T00:00:00Z' AS TIMESTAMP)) AS second\n```\n\n### 全文检索函数\n\n```sql\n-- MATCH：MATCH(匹配字段，规则, 配置参数(可选)) \nSELECT  * FROM \"my-sql-index\" where MATCH(name, 'JAVA') \nSELECT  * FROM \"my-sql-index\" where MATCH(name, 'java') \n\n-- MATCH：MATCH(('匹配字段^权重  1,匹配字段^权重  2'，规则, 配置参数(可选)) \nSELECT  * FROM \"my-sql-index\" where MATCH('author^2,name^5', 'java') \n\n-- QUERY \nSELECT  * FROM \"my-sql-index\" where QUERY('name:Java') \n\n-- SCORE : 评分 \nSELECT  *, score() FROM \"my-sql-index\" where QUERY('name:Java')\n```\n\n### 字符串检索函数\n\n```sql\n-- ASCII : 字符串转成  ASC码 \nSELECT ASCII('Elastic') \n\n-- BIT_LENGTH ：   位长度 \nSELECT BIT_LENGTH('Elastic') \nSELECT BIT_LENGTH('中国') \n\n-- CHAR ：转换字符 \nSELECT CHAR(69) \n\n-- CHAR_LENGTH ：字符长度 \nSELECT CHAR_LENGTH('Elastic') \n\n-- CONCAT:合并 \nSELECT CONCAT('Elastic', 'search') \n\n-- INSERT : INSERT(字符串，起始位置，长度，插入的内容) \nSELECT INSERT('Elastic', 8, 1, 'search') \nSELECT INSERT('Elastic', 7, 1, 'search') \n\n-- LCASE ：转换小写 \nSELECT LCASE('Elastic') \n\n-- LEFT : 获取左边最多  N个字符 \nSELECT LEFT('Elastic',3) \n\n-- LENGTH \nSELECT length('Elastic') \nSELECT length('中国') \n\n-- LOCATE : LOCATE(表达式，字符串，起始位置)，获取满足条件的位置\nSELECT LOCATE('a', 'Elasticsearch') \nSELECT LOCATE('a', 'Elasticsearch', 5)\n\n-- LTRIM ：去除左边的空格 \nSELECT LTRIM('  Elastic') \n\n-- OCTET_LENGTH : 字节长度 \nSELECT OCTET_LENGTH('Elastic') \nSELECT OCTET_LENGTH('中国') \n\n-- POSITION ：获取指定字符串的位置 \nSELECT POSITION('Elastic', 'Elasticsearch') \n\n-- REPEAT ：将字符串重复指定次数 \nSELECT REPEAT('Elastic', 3) \n\n-- REPLACE ：替换数据 \nSELECT REPLACE('Elastic','El','Fant') \n\n-- RIGHT ：从右边获取指定数量的数据 \nSELECT RIGHT('Elastic',3) \n\n-- RTRIM ：去除右边的空格 \nSELECT RTRIM('Elastic   ') \n\n-- SPACE : 生成指定数量的空格 \nSELECT concat(SPACE(3),'abc') \n\n-- STARTS_WITH : 判断是否以指定字符串开头 \nSELECT STARTS_WITH('Elasticsearch', 'Elastic') \n\n-- SUBSTRING ：   截取字符串，必须传递三个参数 \nSELECT SUBSTRING('Elasticsearch', 0, 7) \n\n-- TRIM ：去掉首尾空格 \nSELECT TRIM('   Elastic   ') AS trimmed \n\n-- UCASE : 转换大写 \nSELECT UCASE('Elastic')\n\n```\n\n### 条件分支函数\n\n```sql\n-- 多重分支判断 \nSELECT CASE 5 \n    WHEN 1 THEN 'elastic' \n    WHEN 2 THEN 'search' \n    WHEN 3 THEN 'elasticsearch' \n    ELSE 'default' \nEND AS \"case\" \n\n\nSELECT CASE WHEN 1 > 2 THEN 'elastic' \n    WHEN 2 > 10 THEN 'search' \n    ELSE 'default' \nEND AS \"case\" \n\n-- IFNULL \nSELECT IFNULL('elastic', null) AS \"ifnull\" \nSELECT IFNULL(null, 'search') AS \"ifnull\" \n\n-- IIF \nSELECT IIF(1 \u003C 2, 'TRUE', 'FALSE') AS result1, IIF(1 > 2, 'TRUE', 'FALSE') AS result2\n\n-- ISNULL \nSELECT ISNULL('elastic', null) AS \"isnull\" \nSELECT ISNULL(null, 'search') AS \"isnull\" \n\n-- LEAST:获取除  null外的最小值 \nSELECT LEAST(null, 2, 11) AS \"least\" \nSELECT LEAST(null, null, null, null) AS \"least\" \n\n-- NULLIF : 如果两个字符串不相同，则返回第一个字符串，如果相同，返回  null \nSELECT NULLIF('elastic', 'search') AS \"nullif\" \nSELECT NULLIF('elastic', 'elastic') AS \"nullif\" \n\n-- NVL : 返回第一个不是  null的字符串，如果都是  null,那么返回  Null \nSELECT NVL('elastic', null) AS \"nvl\" \nSELECT NVL(null, null) AS \"nvl\"\n```\n\n### 系统函数\n\n```sql\n-- ES集群 \nSELECT DATABASE() \n\n-- 用户 \nSELECT USER()\n```\n",{"id":97,"created_at":98,"updated_at":98,"code":99,"author_id":4,"book_id":4,"slug":100,"article_id":101,"title":102,"sort_order":97,"content":103,"status":11},10,"2023-12-27T14:45:59.688+08:00","Q18bJ8x0","elasticsearch集群搭建-10",160,"ElasticSearch集群搭建","## 概述\n\n### 单机 & 集群\n\n单台 `Elasticsearch` 服务器提供服务，往往都有最大的负载能力，超过这个阈值，服务器性能就会大大降低甚至不可用，所以生产环境中，一般都是运行在指定服务器集群中。\n\n除了负载能力，单点服务器也存在其他问题：\n\n* 单台机器存储容量有限\n* 单服务器容易出现单点故障，无法实现高可用\n* 单服务的并发处理能力有限\n\n配置服务器集群时，集群中节点数量没有限制，大于等于 2 个节点就可以看做是集群了。一般出于高性能及高可用方面来考虑集群中节点数量都是 3 个以上。\n\n### 集群Cluster\n\n一个集群就是由一个或多个服务器节点组织在一起，共同持有整个的数据，并一起提供索引和搜索功能。一个 Elasticsearch 集群有一个唯一的名字标识，这个名字默认就是 `elasticsearch`。这个名字是重要的，因为一个节点只能通过指定某个集群的名字，来加入这个集群。\n\n### 节点Node\n\n集群中包含很多服务器，一个节点就是其中的一个服务器。作为集群的一部分，它存储数据，参与集群的索引和搜索功能。\n\n一个节点也是由一个名字来标识的，默认情况下，这个名字是一个随机的漫威漫画角色的名字，这个名字会在启动的时候赋予节点。这个名字对于管理工作来说挺重要的，因为在这个管理过程中，你会去确定网络中的哪些服务器对应于 Elasticsearch 集群中的哪些节点。\n\n一个节点可以通过配置集群名称的方式来加入一个指定的集群。默认情况下，每个节点都会被安排加入到一个叫做 `elasticsearch` 的集群中，这意味着，如果你在你的网络中启动了若干个节点，并假定它们能够相互发现彼此，它们将会自动地形成并加入到一个叫做 `elasticsearch` 的集群中。\n\n在一个集群里，只要你想，可以拥有任意多个节点。而且，如果当前你的网络中没有运行任何 Elasticsearch 节点，这时启动一个节点，会默认创建并加入一个叫做 `elasticsearch` 的集群。\n\n### 集群搭建说明\n\n* 集群搭建同样会区分 `7.x版本` 及 `8.x 版本`，其中，Windows下的集群搭建，`8.x版本`就不搞证书之类的了，`linux`下我们会使用`https`证书，毕竟实际生产环境大部分是`linux`。\n\n## Windows 集群搭建\n\n## 7.X(7.8.0)集群搭建\n\n### 集群准备\n\n1、首先创建 `es-cluster` 文件夹，将 `elasticsearch-7.8.0` 复制三份，并修改文件名\n\n![ElasticSearch](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Fdownload\u002Feditor\u002F1703688588507es60.png)\n\n2、修改集群文件目录下，每一个节点的 `config\u002Felasticsearch.yml` 配置文件\n\n* [node-1001节点](#node-1001节点)\n* [node-1002节点](#node-1002节点)\n* [node-1003节点](#node-1003节点)\n\n```xml\n# 节点 1 的配置信息：\n# ---------------------------------- Cluster -----------------------------------\n# 集群名称，节点之间要保持一致\ncluster.name: my-elasticsearch\n# ------------------------------------ Node ------------------------------------\n# 节点名称，集群内要唯一\nnode.name: node-1001\nnode.master: true\nnode.data: true\n# ---------------------------------- Network -----------------------------------\n# ip 地址\nnetwork.host: localhost\n# http 端口\nhttp.port: 7001\n# tcp 监听端口\ntransport.tcp.port: 9301\n# --------------------------------- Discovery ----------------------------------\n# 集群内的可以被选为主节点的节点列表\ncluster.initial_master_nodes: [\"node-1001\", \"node-1002\",\"node-1003\"]\n# ---------------------------------- Various -----------------------------------\n# 跨域配置\n#action.destructive_requires_name: true\nhttp.cors.enabled: true\nhttp.cors.allow-origin: \"*\"\n```\n\n\n```xml\n# 节点 2 的配置信息：\n# ---------------------------------- Cluster -----------------------------------\n# 集群名称，节点之间要保持一致\ncluster.name: my-elasticsearch\n# ------------------------------------ Node ------------------------------------\n# 节点名称，集群内要唯一\nnode.name: node-1002\nnode.master: true\nnode.data: true\n# ---------------------------------- Network -----------------------------------\n# ip 地址\nnetwork.host: localhost\n# http 端口\nhttp.port: 7002\n# tcp 监听端口\ntransport.tcp.port: 9302\n# --------------------------------- Discovery ----------------------------------\ndiscovery.seed_hosts: [\"localhost:9301\"]\ndiscovery.zen.fd.ping_timeout: 1m\ndiscovery.zen.fd.ping_retries: 5\n# 集群内的可以被选为主节点的节点列表\ncluster.initial_master_nodes: [\"node-1001\", \"node-1002\",\"node-1003\"]\n# ---------------------------------- Various -----------------------------------\n# 跨域配置\n#action.destructive_requires_name: true\nhttp.cors.enabled: true\nhttp.cors.allow-origin: \"*\"\n```\n\n\n```xml\n# 节点 3 的配置信息：\n# ---------------------------------- Cluster -----------------------------------\n# 集群名称，节点之间要保持一致\ncluster.name: my-elasticsearch\n# ------------------------------------ Node ------------------------------------\n# 节点名称，集群内要唯一\nnode.name: node-1003\nnode.master: true\nnode.data: true\n# ---------------------------------- Network -----------------------------------\n# ip 地址\nnetwork.host: localhost\n# http 端口\nhttp.port: 7003\n# tcp 监听端口\ntransport.tcp.port: 9303\n# --------------------------------- Discovery ----------------------------------\ndiscovery.seed_hosts: [\"localhost:9301\", \"localhost:9302\"]\ndiscovery.zen.fd.ping_timeout: 1m\ndiscovery.zen.fd.ping_retries: 5\n# 集群内的可以被选为主节点的节点列表\ncluster.initial_master_nodes: [\"node-1001\", \"node-10022\",\"node-1003\"]\n# ---------------------------------- Various -----------------------------------\n# 跨域配置\n#action.destructive_requires_name: true\nhttp.cors.enabled: true\nhttp.cors.allow-origin: \"*\"\n```\n\n\n### 启动集群\n\n* 启动前先删除每个节点中的 `data` 目录中所有内容（如果存在）\n* 进入 `bin` 目录，分别双击执行 `bin\u002Felasticsearch.bat`，启动节点服务器，启动后，会自动加入指定名称的集群,谁先启动就会成为 `master`\n\n## 8.X(8.3.3)集群搭建\n\n### 集群准备\n\n1、同样的，我们创建`es-cluster`文件夹，将 `elasticsearch-8.3.3` 复制三份，并修改文件名 2、分别修改每一个节点的 `config\u002Felasticsearch.yml` 配置文件\n\n* [node-1001节点](#node-1001节点)\n* [node-1002节点](#node-1002节点)\n* [node-1003节点](#node-1003节点)\n\n```xml\n#节点 1 的配置信息：\n# ---------------------------------- Cluster -----------------------------------\n# 集群名称，节点之间要保持一致\ncluster.name: my-es-application\n# ------------------------------------ Node ------------------------------------\n# 节点名称，集群内要唯一\nnode.name: node-1001\n# 节点角色 [主节点、数据节点]：注意这一点同7.x版本的区别配置\nnode.roles: [master,data]\n# ---------------------------------- Network -----------------------------------\n# 发布ip\nnetwork.host: localhost\n# http 端口\nhttp.port: 7001\n# tcp 监听端口：注意这一点同7.x版本的区别配置\ntransport.port: 9301\n# --------------------------------- Discovery ----------------------------------\n# 初始主节点\ncluster.initial_master_nodes: [\"node-1001\", \"node-1002\", \"node-1003\"]\n# ---------------------------------- Various -----------------------------------\n# 删除索引是否需要指定索引全名（false就是可以用正则）\n#action.destructive_requires_name: false\n# 跨域配置\nhttp.cors.enabled: true\nhttp.cors.allow-origin: \"*\"\n```\n\n\n```xml\n# 节点 2 的配置信息：\n# ---------------------------------- Cluster -----------------------------------\n# 集群名称，节点之间要保持一致\ncluster.name: my-es-application\n# ------------------------------------ Node ------------------------------------\n# 节点名称，集群内要唯一\nnode.name: node-1002\n# 节点角色 [主节点、数据节点]\nnode.roles: [master,data]\n# ---------------------------------- Network -----------------------------------\n# 发布ip\nnetwork.host: localhost\n# http 端口\nhttp.port: 7002\n# tcp 监听端口\ntransport.port: 9302\n# --------------------------------- Discovery ----------------------------------\n# 自动发现集群节点ip\ndiscovery.seed_hosts: [\"localhost:9301\"]\ndiscovery.zen.fd.ping_timeout: 1m\ndiscovery.zen.fd.ping_retries: 5\n# 集群内的可以被选为主节点的节点列表\ncluster.initial_master_nodes: [\"node-1001\", \"node-1002\",\"node-1003\"]\n# ---------------------------------- Various -----------------------------------\n# 跨域配置\nhttp.cors.enabled: true\nhttp.cors.allow-origin: \"*\"\n```\n\n\n```xml\n#节点 3 的配置信息：\n# ---------------------------------- Cluster -----------------------------------\n# 集群名称，节点之间要保持一致\ncluster.name: my-es-application\n# ------------------------------------ Node ------------------------------------\n# 节点名称，集群内要唯一\nnode.name: node-1003\n# 节点角色 [主节点、数据节点]\nnode.roles: [master,data]\n# ---------------------------------- Network -----------------------------------\n# 发布ip\nnetwork.host: localhost\n# http 端口\nhttp.port: 7003\n# tcp 监听端口\ntransport.port: 9303\n# --------------------------------- Discovery ----------------------------------\n# 自动发现集群节点ip\ndiscovery.seed_hosts: [\"localhost:9301\", \"localhost:9302\"]\ndiscovery.zen.fd.ping_timeout: 1m\ndiscovery.zen.fd.ping_retries: 5\n# 集群内的可以被选为主节点的节点列表\ncluster.initial_master_nodes: [\"node-1001\", \"node-1002\",\"node-1003\"]\n# ---------------------------------- Various -----------------------------------\n# 跨域配置\nhttp.cors.enabled: true\nhttp.cors.allow-origin: \"*\"\n```\n\n\n### 启动集群\n\n* 启动前先删除每个节点中的 `data` 目录中所有内容（如果存在）\n* 进入 `bin` 目录，分别双击执行 `bin\u002Felasticsearch.bat`，启动节点服务器\n  ![ElasticSearch](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Fdownload\u002Feditor\u002F1703688618221es59.png)\n\n### 测试集群\n\n* 查看集群状态：浏览器直接访问：[http:\u002F\u002F127.0.0.1:7001\u002F\\_cluster\u002Fhealth](javascrit:;)\n  ![ElasticSearch](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Fdownload\u002Feditor\u002F1703688621410es56.png)\n* `statu`：当前集群在总体上是否工作正常，有三种颜色\n  * `green`：所有的主分片和副本分片都正常运行\n  * `yellow`：所有的主分片都正常运行，但不是所有副本分片都正常运行\n  * `red`：有主分片没能正常运行\n* `numnber_of_nodes`：集群里多少个节点\n* `numnber_of_data_nodes`：集群里多少个节点可以存储数据\n\n**向集群中的 node-7001 节点增加索引**\n\n![ElasticSearch](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Fdownload\u002Feditor\u002F1703688630595es57.png)\n\n**在集群的node-1002节点查询刚刚添加的索引**\n\n![ElasticSearch](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Fdownload\u002Feditor\u002F1703688641400es58.png)\n\n## Linux集群\n\n## 7.X(7.8.0)集群搭建\n\n### 集群准备\n\n* 1、首先需要安装好 `ElasticSearch`，[Linux安装ES请参考](\u002Fdocs\u002Fmiddleware\u002FelasticSearch\u002Fsetup.html)\n* 2、安装完成后，同样的我们复制三份ES服务，最好是新建一个`es-cluster`文件夹来存放\n\n### 创建用户\n\n因为安全问题，`Elasticsearch` 不允许 `root` 用户直接运行，所以要在每个节点中创建新用户，在 `root` 用户中创建新用户\n\n```shell\nuseradd es # 新增用户\npasswd es # 设置密码\nuserdel -r es # 如果错了，可以删除再加\n# 对文件夹赋权给es用户\nchown -R es \u002Fusr\u002Flocal\u002Fes-cluster\u002Fnode-7001 \nchown -R es \u002Fusr\u002Flocal\u002Fes-cluster\u002Fnode-7002\nchown -R es \u002Fusr\u002Flocal\u002Fes-cluster\u002Fnode-7003\n```\n\n\n### 修改ES配置文件\n\n* 分别修改各节点的 `config\u002Felasticsearch.yml` 文件\n* node-7001\n\n```xml\n# 加入如下配置\n#集群名称\ncluster.name: cluster-es\n#节点名称，每个节点的名称不能重复\nnode.name: node-7001\n#ip 地址，每个节点的地址不能重复\nnetwork.host: linux1\n#是不是有资格主节点\nnode.master: true\nnode.data: true\nhttp.port: 9200\n# head 插件需要这打开这两个配置\nhttp.cors.allow-origin: \"*\"\nhttp.cors.enabled: true\nhttp.max_content_length: 200mb\n#es7.x 之后新增的配置，初始化一个新的集群时需要此配置来选举 master\ncluster.initial_master_nodes: [\"node-7001\"]\n#es7.x 之后新增的配置，节点发现\ndiscovery.seed_hosts: [\"linux1:9300\",\"linux2:9300\",\"linux3:9300\"]\ngateway.recover_after_nodes: 2\nnetwork.tcp.keep_alive: true\nnetwork.tcp.no_delay: true\ntransport.tcp.compress: true\n#集群内同时启动的数据任务个数，默认是 2 个\ncluster.routing.allocation.cluster_concurrent_rebalance: 16\n#添加或删除节点及负载均衡时并发恢复的线程个数，默认 4 个\ncluster.routing.allocation.node_concurrent_recoveries: 16\n#初始化数据恢复时，并发恢复线程的个数，默认 4 个\ncluster.routing.allocation.node_initial_primaries_recoveries: 16\n```\n\n- node-7002\n\n```xml\n# 加入如下配置\n#集群名称\ncluster.name: cluster-es\n#节点名称，每个节点的名称不能重复\nnode.name: node-7002\n#ip 地址，每个节点的地址不能重复\nnetwork.host: linux1\n#是不是有资格主节点\nnode.master: true\nnode.data: true\nhttp.port: 9200\n# head 插件需要这打开这两个配置\nhttp.cors.allow-origin: \"*\"\nhttp.cors.enabled: true\nhttp.max_content_length: 200mb\n#es7.x 之后新增的配置，初始化一个新的集群时需要此配置来选举 master\ncluster.initial_master_nodes: [\"node-7001\"]\n#es7.x 之后新增的配置，节点发现\ndiscovery.seed_hosts: [\"linux1:9300\",\"linux2:9300\",\"linux3:9300\"]\ngateway.recover_after_nodes: 2\nnetwork.tcp.keep_alive: true\nnetwork.tcp.no_delay: true\ntransport.tcp.compress: true\n#集群内同时启动的数据任务个数，默认是 2 个\ncluster.routing.allocation.cluster_concurrent_rebalance: 16\n#添加或删除节点及负载均衡时并发恢复的线程个数，默认 4 个\ncluster.routing.allocation.node_concurrent_recoveries: 16\n#初始化数据恢复时，并发恢复线程的个数，默认 4 个\ncluster.routing.allocation.node_initial_primaries_recoveries: 16\n```\n\n- node-7003\n\n```xml\n# 加入如下配置\n#集群名称\ncluster.name: cluster-es\n#节点名称，每个节点的名称不能重复\nnode.name: node-7003\n#ip 地址，每个节点的地址不能重复\nnetwork.host: linux1\n#是不是有资格主节点\nnode.master: true\nnode.data: true\nhttp.port: 9200\n# head 插件需要这打开这两个配置\nhttp.cors.allow-origin: \"*\"\nhttp.cors.enabled: true\nhttp.max_content_length: 200mb\n#es7.x 之后新增的配置，初始化一个新的集群时需要此配置来选举 master\ncluster.initial_master_nodes: [\"node-7001\"]\n#es7.x 之后新增的配置，节点发现\ndiscovery.seed_hosts: [\"linux1:9300\",\"linux2:9300\",\"linux3:9300\"]\ngateway.recover_after_nodes: 2\nnetwork.tcp.keep_alive: true\nnetwork.tcp.no_delay: true\ntransport.tcp.compress: true\n#集群内同时启动的数据任务个数，默认是 2 个\ncluster.routing.allocation.cluster_concurrent_rebalance: 16\n#添加或删除节点及负载均衡时并发恢复的线程个数，默认 4 个\ncluster.routing.allocation.node_concurrent_recoveries: 16\n#初始化数据恢复时，并发恢复线程的个数，默认 4 个\ncluster.routing.allocation.node_initial_primaries_recoveries: 16\n```\n\n### 修改系统配置文件\n\n1、修改系统中允许应用最多创建多少文件等的限制权限。Linux 默认来说，一般限制应用最多创建的文件是 65535 个。但是 ES 至少需要 65536 的文件创建权限。\n\n2、修改系统中允许用户启动的进程开启多少个线程。默认的 Linux 限制 root 用户开启的进程可以开启任意数量的线程，其他用户开启的进程可以开启 1024个线程。必须修改限制数为 4096+。因为ES至少需要 4096 的线程池预备。ES在 5.x 版本之后，强制要求在 Linux 中不能使用 root 用户启动 ES 进程。所以必须使用其他用户启动 ES 进程才可以。\n\n3、Linux 低版本内核为线程分配的内存是 128K。4.x 版本的内核分配的内存更大。如果虚拟机的内存是 1G，最多只能开启 3000+ 个线程数。至少为虚拟机分配 1.5G 以上的内存。\n\nES 可以对每个进程的文件数进行限制等，如果服务器内存或空间不足，可以通过修改配置文件，进行「裁剪」\n\n* 修改 `\u002Fetc\u002Fsecurity\u002Flimits.conf`：\n\n```shell\nvim \u002Fetcsecurity\u002Flimits.conf\n# 在文件末尾中增加下面内容\n# 每个进程可以打开的文件数的限制\nes soft nofile 65536\nes hard nofile 65536\n```\n\n* 修改 `\u002Fetc\u002Fsecurity\u002Flimits.d\u002F20-nproc.conf`:\n\n```shell\nvim \u002Fetc\u002Fsecurity\u002Flimits.d\u002F20-nproc.conf\n# 在文件末尾中增加下面内容\n# 每个进程可以打开的文件数的限制\nes soft nofile 65536\nes hard nofile 65536\n# 操作系统级别对每个用户创建的进程数的限制\n* hard nproc 4096\n# 注：* 带表 Linux 所有用户名称\n```\n\n* 修改 `\u002Fetc\u002Fsysctl.conf`：\n\n```shell\nvim \u002Fetc\u002Fsysctl.conf\n# 在文件末尾中增加下面内容\n# 一个进程可以拥有的 VMA (虚拟内存区域)的数量,默认值为 65536\nvm.max_map_count=655360\n```\n\n* 重新加载配置\n\n```shell\nsysctl -p\n```\n\n### 启动集群\n\n* 使用 ES 用户分别在不同节点上启动 ES 服务器，-d 代表后台启动\n\n```shell\n# 节点1\ncd \u002Fusr\u002Flocal\u002Fes-cluster\u002Fnode-7001\nbin\u002Felasticsearch -d\n# 节点2\ncd \u002Fusr\u002Flocal\u002Fes-cluster\u002Fnode-7002\nbin\u002Felasticsearch -d\n# 节点3\ncd \u002Fusr\u002Flocal\u002Fes-cluster\u002Fnode-7003\nbin\u002Felasticsearch -d\n```\n\n## 8.X(8.3.3)集群搭建\n\n### 集群准备\n\n* 前期准备同`7.x`一致，这里就不再重复叙述\n* 准备三台机器或者单台都可以\n* 示例中`ES`在 `\u002Fopt\u002Fmodule`目录下\n\n### 集群规划\n\n\n| IP           | 节点名称  | 节点角色    | 节点功能    |\n| ------------ | --------- | ----------- | ----------- |\n| 182.61.xx.43 | es-node-1 | Master,data | 主+数据节点 |\n| 182.61.xx.44 | es-node-2 | Master,data | 主+数据节点 |\n| 182.61.xx.45 | es-node-3 | Master,data | 主+数据节点 |\n\n### 搭建集群\n\n1、创建新用户 `es`, 数据文件，证书目录, 并修改 `Elasticsearch` 文件拥有者\n\n```shell\n# 新增 es 用户\nuseradd es\n\n# 为 es 用户设置密码\npasswd es\n\n# 创建数据文件目录\nmkdir \u002Fopt\u002Fmodule\u002Felasticsearch-8.3.3\u002Fdata\n\n# 创建证书目录\nmkdir \u002Fopt\u002Fmodule\u002Felasticsearch-8.3.3\u002Fconfig\u002Fcerts\n\n#切换目录\ncd \u002Fopt\u002Fmodule\u002Felasticsearch-8.3.3\n\n# 修改文件拥有者\nchown -R es:es \u002Fopt\u002Fmodule\u002Felasticsearch-8.3.3\n```\n\n2、在**第一台**服务器节点 `es-node-1` 设置集群多节点通信密钥\n\n```shell\n# 切换用户\nsu es\n\n# 签发 ca 证书，过程中需按两次回车键\nbin\u002Felasticsearch-certutil ca\n\n# 用 ca 证书签发节点证书，过程中需按三次回车键\nbin\u002Felasticsearch-certutil cert --ca elastic-stack-ca.p12\n\n# 将生成的证书文件移动到 config\u002Fcerts 目录中\nmv elastic-stack-ca.p12 elastic-certificates.p12 config\u002Fcerts\n```\n\n3、在**第一台**服务器节点 `es-node-1` 设置集群多节点 `HTTP 证书`\n\n```shell\n# 签发 Https 证书\nbin\u002Felasticsearch-certutil http\n# 以下是每次要求输入时，需要输入的内容（注意看关键字词）\n1、Generate a CSR? 是否生成CSR，输入`【n】`\n\n2、use an existing CA? 是否使用已经存在的CA证书，输入`【y】`\n\n3、CA Path:certs\u002Felastic-stack-ca.p12 指定证书路径\n\n4、Password for elastic-stack-ca.p12: 为证书输入密码，不用密码直接回车\n\n5、For how long should your certificate be valid? [5y] 20y 输入证书过期时间，输入20年`【20y】`\n\n6、generate a certificate per node? 是否为每个节点单独生成证书,输入`【n】`\n\n7、Is this correct[Y\u002Fn] y 输出连接到第一个节点的所有主机名称`【y】`\n\n8、Is this correct[Y\u002Fn] y 输出连接到第一个节点的所有主机 IP 地址`【y】`\n\n9、Do you wish to change any of these options? [y\u002FN] n 不改变证书选项配置`【n】`\n\n10、What password do you want for you private key(s)? 不给证书加密，按键输入两次回车\n```\n\n4、解压刚刚生成的 `zip` 包\n\n```shell\n# 解压文件\nunzip elasticsearch-ssl-http.zip\n```\n\n* `zip`内容\n\n```xml\n\u002Felasticsearch\n|_ README.txt\n|_ http.p12\n|_ sample-elasticsearch.yml\n\n\u002Fkibana\n|_ README.txt\n|_ elasticsearch-ca.pem\n|_ sample-kibana.yml\n```\n\n5、将解压后的证书文件移动到 config\u002Fcerts 目录中\n\n```shell\n# 移动文件:其中kibana中的证书是提供给kibana通信使用的，具体配置查看目录中的README.txt\nmv elasticsearch\u002Fhttp.p12 kibana\u002Felasticsearch-ca.pem config\u002Fcerts\n```\n\n6、修改主配置文件：`config\u002Felasticsearch.yml`\n\n```yaml\n# 设置 ES 集群名称\ncluster.name: es-cluster\n\n# 设置集群中当前节点名称\nnode.name: es-node-1\n# 节点角色 [主节点、数据节点]\nnode.roles: [ master, data ]\n\n# 设置数据，日志文件路径\npath.data: \u002Fopt\u002Fmodule\u002Felasticsearch-8.3.3\u002Fdata\npath.logs: \u002Fopt\u002Fmodule\u002Felasticsearch-8.3.3\u002Flog\n\n# 设置网络访问节点\nnetwork.host: linux1\n\n# 设置网络访问端口\nhttp.port: 9200\n\n# 初始节点\ndiscovery.seed_hosts: [\"linux1\"]\n\n# 安全认证\nxpack.security.enabled: true\nxpack.security.enrollment.enabled: true\nxpack.security.http.ssl:\n enabled: true\n keystore.path: \u002Fopt\u002Fmodule\u002Felasticsearch-8.3.3\u002Fconfig\u002Fcerts\u002Fhttp.p12\n truststore.path: \u002Fopt\u002Fmodule\u002Felasticsearch-8.3.3\u002Fconfig\u002Fcerts\u002Fhttp.p12\n# 集群间安全通信配置\nxpack.security.transport.ssl:\n enabled: true\n verification_mode: certificate\n keystore.path:\n\u002Fopt\u002Fmodule\u002Felasticsearch-8.3.3\u002Fconfig\u002Fcerts\u002Felastic-certificates.p12\n truststore.path:\n\u002Fopt\u002Fmodule\u002Felasticsearch-8.3.3\u002Fconfig\u002Fcerts\u002Felastic-certificates.p12\n\n# 此处需注意，es-node-1 为上面配置的节点名称\ncluster.initial_master_nodes: [\"es-node-1\"]\nhttp.host: [_local_, _site_]\ningest.geoip.downloader.enabled: false\nxpack.security.http.ssl.client_authentication: none\n```\n\n7、启动 ES 服务\n\n```shell\n# 启动 ES 软件\nbin\u002Felasticsearch\n```\n\n* 注意1：第一次成功启动后，会显示密码，请记住，访问时需要。如果没记住，就通过ES的rest-password重置密码，详细操作请查看[ElasticSearch安装中重置密码一节](\u002Fdocs\u002Fmiddleware\u002FelasticSearch\u002Fsetup.html)\n* 注意2：9300 端口为 Elasticsearch 集群间组件的通信端口，9200 端口为浏览器访问的 http 协议 RESTful 端口。\n\n8、访问服务器节点 https:\u002F\u002F虚拟机地址:9200。因为配置了安全协议，所以使用 https 协议进行访问，但由于证书是自己生成的，并不 可靠，所以会有安全提示\n\n9、修改集群中其他节点的配置文件：`config\u002Felasticsearch.yml`\n\n* linux2: 证书直接拷贝，其他步骤完全相同，配置文件中修改如下内容即可\n\n```shell\n# 设置节点名称\nnode.name: es-node-2\n# 设置网络访问主机\nnetwork.host: linux2\n```\n\n* linux3:证书直接拷贝，其他步骤完全相同，配置文件中修改如下内容即可\n\n```shell\n# 设置节点名称\nnode.name: es-node-3\n# 设置网络访问主机\nnetwork.host: linux3\n```\n\n10、依次启动集群的三台服务器节点, 不要忘记切换用户后再启动\n\n```shell\n#linux1:\n## 后台启动服务\nbin\u002Felasticsearch -d\n\n#linux2:\n## 后台启动服务\nbin\u002Felasticsearch -d\n\n#linux3:\n## 后台启动服务\nbin\u002Felasticsearch -d\n```\n",{"id":105,"created_at":106,"updated_at":106,"code":107,"author_id":4,"book_id":4,"slug":108,"article_id":109,"title":110,"sort_order":105,"content":111,"status":11},11,"2023-12-27T14:46:11.773+08:00","kbXvOKMJ","elasticsearch备份工具-11",161,"ElasticSearch备份工具","## ElasticSearch备份工具介绍\n\n* 一个是由[elasticsearch-dump **(opens new window)**](https:\u002F\u002Fgithub.com\u002Felasticsearch-dump\u002Felasticsearch-dump)实现\n* 另一个是由[logstash **(opens new window)**](https:\u002F\u002Fwww.elastic.co\u002Fcn\u002Flogstash\u002F)实现,`logstash`操作相对而言比较简单。\n\n## 安装elasticsearch-dump\n\n```shell\n[root@elasticsearch ~\u002Fsoft]# git clone https:\u002F\u002Fgithub.com\u002Ftaskrabbit\u002Felasticsearch-dump\n\n# 2.安装elasticsearch-dump\n[root@elasticsearch]# mv elasticsearch-dump\u002F \u002Fdata\u002F\n[root@elasticsearch]# cd \u002Fdata\u002Felasticsearch-dump\n[root@elasticsearch \u002Fdata\u002Felasticsearch-dump]# npm install elasticdump -g\n\n# 3.查看版本\n[root@elasticsearch \u002Fdata\u002Felasticsearch-dump]# elasticdump --version\n6.62.1\n```\n\n\n### 可能报错\n\n```shell\n[root@elasticsearch \u002Fdata\u002Felasticsearch-dump]# npm install elasticdump\nnpm ERR! code ENOSELF\nnpm ERR! Refusing to install package with name \"elasticdump\" under a package\nnpm ERR! also called \"elasticdump\". Did you name your project the same\nnpm ERR! as the dependency you're installing?\nnpm ERR! \nnpm ERR! For more information, see:\nnpm ERR!     \u003Chttps:\u002F\u002Fdocs.npmjs.com\u002Fcli\u002Finstall#limitations-of-npms-install-algorithm>\n\nnpm ERR! A complete log of this run can be found in:\nnpm ERR!     \u002Froot\u002F.npm\u002F_logs\u002F2021-01-13T06_36_56_956Z-debug.log\n\n报错内容翻译如下\n错误的ERR！ 代码ENOSELF\n错误的ERR！ 拒绝在包下安装名为“webpack”的包\n错误的ERR！ 也被称为“webpack”。 你的项目名称是否相同？\n错误的ERR！ 作为您正在安装的依赖项？\n错误的ERR！\n错误的ERR！ 有关更多信息，请参阅：\n错误的ERR！\u003Chttps:\u002F\u002Fdocs.npmjs.com\u002Fcli\u002Finstall#limitations-of-npms-install-algorithm>\n\n错误的ERR！ 可以在以下位置找到此运行的完整日志：\n```\n\n\n### 解决方案\n\n* 修改 `package.json` 中 `name`，然后再次执行`npm install`\n\n```shell\n[root@elasticsearch \u002Fdata\u002Felasticsearch-dump]# vim package.json \n{\n  \"author\": \"Evan Tahler \u003Cevantahler@gmail.com>\",\n  \"name\": \"elasticdump1\",\t\t\t\t#随便改就行\n}\n```\n\n\n## 备份索引库\n\n```shell\n# 语法：elasticdump --input es地址\u002F索引 --output 备份到某个路径\n[root@elasticsearch ~]# mkdir \u002Fdata\u002Fes-backer\n[root@elasticsearch ~]# elasticdump --input http:\u002F\u002F172.31.80.1:9200\u002Fajia --output \u002Fdata\u002Fes-backer\u002Fajia.json\nThu, 14 Jan 2021 02:57:08 GMT | starting dump\nThu, 14 Jan 2021 02:57:09 GMT | got 4 objects from source elasticsearch (offset: 0)\nThu, 14 Jan 2021 02:57:09 GMT | sent 4 objects to destination file, wrote 4\nThu, 14 Jan 2021 02:57:09 GMT | got 0 objects from source elasticsearch (offset: 4)\nThu, 14 Jan 2021 02:57:09 GMT | Total Writes: 4\nThu, 14 Jan 2021 02:57:09 GMT | dump complete\n```\n\n\n## 测试还原\n\n### 删除索引\n\n* 在kibana中删除：`Delete ajia`\n\n### 还原索引库\n\n```shell\n# 语法：elasticdump --input 备份文件路径 --output es地址\u002F索引\n[root@elasticsearch ~]# elasticdump --input \u002Fdata\u002Fes-backer\u002Fajia.json --output http:\u002F\u002F172.31.80.1:9200\u002Fajia\nThu, 14 Jan 2021 03:04:45 GMT | starting dump\nThu, 14 Jan 2021 03:04:45 GMT | got 4 objects from source file (offset: 0)\nThu, 14 Jan 2021 03:04:50 GMT | sent 4 objects to destination elasticsearch, wrote 4\nThu, 14 Jan 2021 03:04:50 GMT | got 0 objects from source file (offset: 4)\nThu, 14 Jan 2021 03:04:50 GMT | Total Writes: 4\nThu, 14 Jan 2021 03:04:50 GMT | dump complete\n```\n\n\n## 两个es之间进行数据迁移\n\n```shell\n# 语法：elasticdump --input 要迁移es1地址\u002F索引 --output 迁移到es2地址\u002F索引\n[root@elasticsearch ~]# elasticdump --input http:\u002F\u002F172.31.80.1:9200\u002Fajia --output http:\u002F\u002F172.31.80.2:9200\u002Fajia\nThu, 14 Jan 2021 03:08:24 GMT | starting dump\nThu, 14 Jan 2021 03:08:24 GMT | got 4 objects from source elasticsearch (offset: 0)\nThu, 14 Jan 2021 03:08:32 GMT | sent 4 objects to destination elasticsearch, wrote 4\nThu, 14 Jan 2021 03:08:32 GMT | got 0 objects from source elasticsearch (offset: 4)\nThu, 14 Jan 2021 03:08:32 GMT | Total Writes: 4\nThu, 14 Jan 2021 03:08:32 GMT | dump complete\n```\n\n\n## logstash\n\n* 下载[logstash **(opens new window)**](https:\u002F\u002Felasticsearch.cn\u002Fdownload\u002F#seg-3)，最好与`ES`版本对应。\n* 在`logstash`的`bin`目录下新建`logstash.conf`文件\n\n### logstash配置迁移全量数据\n\n```xml\ninput{\n    elasticsearch{\n      # 源集群地址\n      hosts => [\"127.0.0.1:9200\",\"127.0.0.1:9201\",\"127.0.0.1:9202\"]\n      # 如果设置了访问ES服务的用户名密码\n      # user => \"yyyy\"\n      # password => \"xxxxxx\"\n      # 表示所有索引,多个索引以英文以逗号（,）分隔。\n      index => \"*\"\n      # 设置为true，将会提取ES文档的元信息，例如index、type和id\n      docinfo => true\n      size => 5000\n      scroll => \"5m\"\n    }\n}\n\nfilter {\n  # 去掉一些Logstash自己加的字段。\n  mutate {\n    remove_field => [\"@timestamp\", \"@version\"]\n  }\n}\n\noutput{\n    elasticsearch{\n      # 目标集群地址\n      hosts => [\"xx.xx.xx.xx:9200\",\"xx.xx.xx.xx:9201\",\"47.xx.xx.xx.xx:9202\"]\n      # 目标端索引名称，以下配置表示索引与源端保持一致。\n      index => \"%{[@metadata][_index]}\"\n      # 目标端索引type，以下配置表示索引类型与源端保持一致。\n      document_type => \"%{[@metadata][_type]}\"\n      # 目标端数据的id，如果不需要保留原id，可以删除以下这行，删除后性能会更好。\n      document_id => \"%{[@metadata][_id]}\"\n    }\n}\n```\n\n\n### 运行\n\n* 使用`logstash -f logstash.conf -t`检测配置是否正确，如果返回`Configuration OK`，则表示配置正确。\n* 运行命令`logstash -f logstash.conf`实现`ES`跨集群同步。\n\n### logstash配置迁移增量数据\n\n* [参考文档 **(opens new window)**](https:\u002F\u002Fhelp.aliyun.com\u002Fdocument_detail\u002F418418.html)\n\n```xml\ninput{\n    elasticsearch{\n        # 源端ES地址。\n        hosts =>  [\"http:\u002F\u002Flocalhost:9200\"]\n        # 安全集群配置登录用户名密码。\n        user => \"xxxxxx\"\n        password => \"xxxxxx\"\n        # 需要迁移的索引列表，多个索引使用英文逗号（,）分隔。\n        index => \"kibana_sample_data_logs\"\n        # 按时间范围查询增量数据，以下配置表示查询最近5分钟的数据。\n        query => '{\"query\":{\"range\":{\"@timestamp\":{\"gte\":\"now-5m\",\"lte\":\"now\u002Fm\"}}}}'\n        # 定时任务，以下配置表示每分钟执行一次。\n        schedule => \"* * * * *\"\n        scroll => \"5m\"\n        docinfo=>true\n        size => 5000\n    }\n}\n\nfilter {\n  # 去掉一些Logstash自己加的字段.\n  mutate {\n    remove_field => [\"@timestamp\", \"@version\"]\n  }\n}\n\n\noutput{\n    elasticsearch{\n        # 目标端ES地址，可在阿里云Elasticsearch实例的基本信息页面获取。\n        hosts => [\"http:\u002F\u002Fes-cn-zvp2m4bko0009****.elasticsearch.aliyuncs.com:9200\"]\n        # 安全集群配置登录用户名密码.\n        user => \"elastic\"\n        password => \"xxxxxx\"\n        # 目标端索引名称，以下配置表示索引与源端保持一致。\n        index => \"%{[@metadata][_index]}\"\n        # 目标端索引type，以下配置表示索引类型与源端保持一致。\n        document_type => \"%{[@metadata][_type]}\"\n        # 目标端数据的id，如果不需要保留原id，可以删除以下这行，删除后性能会更好。\n        document_id => \"%{[@metadata][_id]}\"\n        ilm_enabled => false\n        manage_template => false\n    }\n}\n```\n",{"id":113,"created_at":114,"updated_at":114,"code":115,"author_id":4,"book_id":4,"slug":116,"article_id":117,"title":118,"sort_order":113,"content":119,"status":11},12,"2023-12-27T14:46:20.886+08:00","Lb6GZKer","elasticsearch进阶-12",162,"ElasticSearch进阶","## 索引(Index)\n\n一个索引就是一个拥有几分相似特征的文档的集合。比如说，你可以有一个客户数据的索引，另一个产品目录的索引，还有一个订单数据的索引。一个索引由一个名字来标识（必须全部是小写字母），并且当我们要对这个索引中的文档进行检索、搜索、更新和删除的时候，都要使用到这个名字。在一个集群中，可以定义任意多的索引。\n\n能搜索的数据必须有索引，这样的好处是可以提高查询速度，比如：新华字典前面的目录就是索引的意思，目录可以提高查询速度。\n\nElasticsearch 索引的精髓：一切设计都是为了提高搜索的性能\n\n## 类型(Type)\n\n在一个索引中，你可以定义一种或多种类型。\n\n一个类型是你的索引的一个逻辑上的分类\u002F分区，其语义完全由你来定。通常，会为具有一组共同字段的文档定义一个类型。不同的版本，类型发生了不同的变化\n\n* 7.x之后：默认不再支持自定义索引类型（默认类型为：\\_doc）\n\n## 文档(Document)\n\n一个文档是一个可被检索的基础信息单元，也就是一条数据。\n\n比如：你可以拥有某一个客户的文档，某一个产品的一个文档，当然，也可以拥有某个订单的一个文档。文档以 JSON（Javascript Object Notation）格式来表示，而 JSON 是一个到处存在的互联网数据交互格式。\n\n在一个 index\u002Ftype 里面，你可以存储任意多的文档。\n\n## 字段(Field)\n\n相当于是数据表的字段，对文档数据根据不同属性进行的分类标识\n\n## 映射(Mapping)\n\nmapping 是处理数据的方式和规则方面做一些限制，如：某个字段的数据类型、默认值、分析器、是否被检索等等。这些都是映射里面可以设置的，其它就是处理 ES 里面数据的一些使用规则设置也叫做映射，按着最优规则处理数据对性能提高很大，因此才需要建立映射，并且需要思考如何建立映射才能对性能更好\n\n## 分片(Shards)\n\n一个索引可以存储超出单个节点硬件限制的大量数据。比如，一个具有 10 亿文档数据的索引占据 1TB 的磁盘空间，而任一节点都可能没有这样大的磁盘空间。或者单个节点处理搜索请求，响应太慢。为了解决这个问题，Elasticsearch 提供了将索引划分成多份的能力，每一份就称之为分片。当你创建一个索引的时候，你可以指定你想要的分片的数量。每个分片本身也是一个功能完善并且独立的「索引」，这个「索引」可以被放置到集群中的任何节点上。\n\n分片很重要，主要有两方面的原因：\n\n* 允许你水平分割\u002F扩展你的内容容量\n* 允许你在分片之上进行分布式的、并行的操作，进而提高性能\u002F吞吐量\n\n至于一个分片怎样分布，它的文档怎样聚合和搜索请求，是完全由 Elasticsearch 管理的，对于作为用户的你来说，这些都是透明的，无需过分关心。\n\n被混淆的概念是：一个 Lucene 索引，我们在 Elasticsearch 称作分片。一个 Elasticsearch 索引是分片的集合。当 Elasticsearch 在索引中搜索的时候，他发送查询到每一个属于索引的分片(Lucene 索引)，然后合并每个分片的结果到一个全局的结果集\n\n## 副本(Replicas)\n\n在一个网络\u002F云的环境里，失败随时都可能发生，在某个分片\u002F节点不知怎么的就处于离线状态，或者由于任何原因消失了，这种情况下，有一个故障转移机制是非常有用并且是 强烈推荐的。为此目的，Elasticsearch 允许你创建分片的一份或多份拷贝，这些拷贝叫做`复制分片(副本)`。\n\n复制分片之所以重要，有两个主要原因：\n\n* 在分片\u002F节点失败的情况下，提供了高可用性。因为这个原因，注意到复制分片从不与 原\u002F主要（original\u002Fprimary）分片置于同一节点上是非常重要的\n* 扩展你的搜索量\u002F吞吐量，因为搜索可以在所有的副本上并行运行\n\n总之，每个索引可以被分成多个分片。一个索引也可以被复制 0 次（意思是没有复制）或多次。一旦复制了，每个索引就有了主分片（作为复制源的原来的分片）和复制分片（主分片的拷贝）之别。分片和复制的数量可以在索引创建的时候指定。在索引创建之后，你可以在`任何时候动态地改变复制的数量`，但是你事后`不能改变分片的数量`。默认情况下，Elasticsearch 中的每个索引被分片 1 个主分片和 1 个复制，这意味着，如果你的集群中至少有两个节点，你的索引将会有 1 个主分片和另外 1 个复制分片（1 个完全拷贝），这样的话每个索引总共就有 2 个分片，我们需要根据索引需要确定分片个数.\n\n## 分配(Allocation)\n\n将分片分配给某个节点的过程，包括分配主分片或者副本。如果是副本，还包含从主分片复制数据的过程。这个过程是由 master 节点完成的。\n\n## 系统架构\n\n一个运行中的 Elasticsearch 实例称为一个节点，而集群是由一个或者多个拥有相同 cluster.name 配置的节点组成，它们共同承担数据和负载的压力。当有节点加入集群中或者从集群中移除节点时，集群将会重新平均分布所有的数据。\n\n当一个节点被选举成为主节点时， 它将负责管理集群范围内的所有变更，例如增加、删除索引，或者增加、删除节点等。而主节点并不需要涉及到文档级别的变更和搜索等操作，所以当集群只拥有一个主节点的情况下，即使流量的增加它也不会成为瓶颈。任何节点都可以成为主节点。我们的示例集群就只有一个节点，所以它同时也成为了主节点。\n\n作为用户，我们可以将请求发送到集群中的任何节点，包括主节点。每个节点都知道任意文档所处的位置，并且能够将我们的请求直接转发到存储我们所需文档的节点。无论我们将请求发送到哪个节点，它都能负责从各个包含我们所需文档的节点收集回数据，并将最终结果返回給客户端。Elasticsearch 对这一切的管理都是透明的。\n",{"id":25,"created_at":121,"updated_at":121,"code":122,"author_id":4,"book_id":4,"slug":123,"article_id":124,"title":125,"sort_order":25,"content":126,"status":11},"2023-12-27T14:46:28.369+08:00","lMK9dX2w","elasticsearch面试题-13",163,"ElasticSearch面试题","## 为什么要使用Elasticsearch\n\n系统中的数据，随着业务的发展，时间的推移，将会非常多，而业务中往往采用模糊查询进行数据的搜索，而模糊查询会导致查询引擎放弃索引，导致系统查询数据时都是全表扫描，在百万级别的数据库中，查询效率是非常低下的，而我们使用 ES 做一个全文索引，将经常查询的系统功能的某些字段，比如说电商系统的商品表中商品名，描述、价格还有 id 这些字段我们放入 ES 索引库里，可以提高查询速度。\n\n## Elasticsearch的master选举流程\n\n* `Elasticsearch` 的选主是 `ZenDiscovery` 模块负责的，主要包含 `Ping`（节点之间通过这个 `RPC` 来发现彼此）和 `Unicast`（单播模块包含一个主机列表以控制哪些节点需要 `ping` 通）这两部分\n* 对所有可以成为 `master` 的节点（`node.master: true`、`node.roles: [master,data]`）根据 `nodeId` 字典排序，每次选举每个节点都把自己所知道节点排一次序，然后选出第一个（第 `0`位）节点，暂且认为它是 `master` 节点\n* 如果对某个节点的投票数达到一定的值（可以成为 `master` 节点数 `n\u002F2+1`）并且该节点自己也选举自己，那这个节点就是 `master`。否则重新选举一直到满足上述条件\n* `master` 节点的职责主要包括集群、节点和索引的管理，不负责文档级别的管理；`data` 节点可以关闭 `http` 功能\n\n## Elasticsearch集群脑裂问题\n\n### 「脑裂」问题可能的成因\n\n* 网络问题：集群间的网络延迟导致一些节点访问不到 `master`，认为 `master` 挂掉了从而选举出新的 `master`，并对 `master` 上的分片和副本标红，分配新的主分片\n* 节点负载：主节点的角色既为 `master` 又为 `data`，访问量较大时可能会导致 `ES` 停止响应造成大面积延迟，此时其他节点得不到主节点的响应认为主节点挂掉了，会重新选取主节点\n* 内存回收：`data` 节点上的 `ES` 进程占用的内存较大，引发 `JVM` 的大规模内存回收，造成 `ES` 进程失去响应\n\n### 脑裂问题解决方案\n\n* 减少误判：`discovery.zen.ping_timeout` 节点状态的响应时间，默认为 `3s`，可以适当调大\n  如果 `master` 在该响应时间的范围内没有做出响应应答，判断该节点已经挂掉了。调大参数（如 6s，`discovery.zen.ping_timeout:6`），可适当减少误判\n* 选举触发: 修改参数为1，`discovery.zen.minimum_master_nodes:1`\n  该参数是用于控制选举行为发生的最小集群主节点数量。当备选主节点的个数大于等于该参数的值，且备选主节点中有该参数个节点认为主节点挂了，进行选举。官方建议为 `（n\u002F2）+1`，`n` 为主节点个数（即有资格成为主节点的节点个数）\n* 角色分离：即 `master` 节点与 `data` 节点分离，限制角色\n  主节点配置为：`node.master: true node.data: false` \u002F `node.roles: [master]`\n  从节点配置为：`node.master: false node.data: true` \u002F `node.roles: [data]`\n\n## Elasticsearch索引文档的流程\n\n![ElasticSearch](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Fdownload\u002Feditor\u002F1703688424940es61.png)\n\n* 协调节点默认使用文档 `ID` 参与计算（也支持通过 `routing`），以便为路由提供合适的分片：\n\n```shell\nshard = hash(document_id) % (num_of_primary_shards)\n```\n\n\n* 当分片所在的节点接收到来自协调节点的请求后，会将请求写入到 `Memory Buffer`，然后定时（默认是每隔 1 秒）写入到 `Filesystem Cache`，这个从 `Memory Buffer` 到 `Filesystem Cache` 的过程就叫做 `refresh`\n* 当然在某些情况下，存在 `Momery Buffer` 和 `Filesystem Cache` 的数据可能会丢失，ES 是通过 `translog` 的机制来保证数据的可靠性的。其实现机制是接收到请求后，同时也会写入到 `translog` 中，当 `Filesystem cache` 中的数据写入到磁盘中时，才会清除掉，这个过程叫做 `flush`\n* 在 `flush` 过程中，内存中的缓冲将被清除，内容被写入一个新段，段的 `fsync` 将创建一个新的提交点，并将内容刷新到磁盘，旧的 `translog` 将被删除并开始一个新的 `translog`\n* `flush` 触发的时机是定时触发（默认 `30` 分钟）或者 `translog` 变得太大（默认为 `512M`）时\n\n## Elasticsearch更新和删除文档的流程\n\n* 删除和更新也都是写操作，但是 Elasticsearch 中的文档是不可变的，因此不能被删除或者改动以展示其变更\n* 磁盘上的每个段都有一个相应的 `.del` 文件。当删除请求发送后，文档并没有真的被删除，而是在 `.del` 文件中被标记为删除。该文档依然能匹配查询，但是会在结果中被过滤掉。当段合并时，在 `.del` 文件中被标记为删除的文档将不会被写入新段\n* 在新的文档被创建时，Elasticsearch 会为该文档指定一个版本号，当执行更新时，旧版本的文档在 `.del` 文件中被标记为删除，新版本的文档被索引到一个新段。旧版本的文档依然能匹配查询，但是会在结果中被过滤掉\n\n## Elasticsearch搜索的流程\n\n![ElasticSearch](http:\u002F\u002Fe.cdn.go90.cn\u002Fokgoes\u002Fblog\u002Fdownload\u002Feditor\u002F1703688437230es62.png)\n\n* 搜索被执行成一个两阶段过程，我们称之为 `Query Then Fetch`\n* 在初始查询阶段时，查询会广播到索引中每一个分片拷贝（主分片或者副本分片）。 每个分片在本地执行搜索并构建一个匹配文档的大小为 `from + size` 的优先队列。PS：在搜索的时候是会查询 `Filesystem Cache` 的，但是有部分数据还在 `Memory Buffer`，所以搜索是近实时的\n* 每个分片返回各自优先队列中 所有文档的 `ID` 和排序值 给协调节点，它合并这些值到自己的优先队列中来产生一个全局排序后的结果列表\n* 接下来就是取回阶段，协调节点辨别出哪些文档需要被取回并向相关的分片提交多个 `GET` 请求。每个分片加载并丰富文档，如果有需要的话，接着返回文档给协调节点。一旦所有的文档都被取回了，协调节点返回结果给客户端\n* `Query Then Fetch` 的搜索类型在文档相关性打分的时候参考的是本分片的数据，这样在文档数量较少的时候可能不够准确，`DFS Query Then Fetch` 增加了一个预查询的处理，询问 `Term` 和 `Document frequency`，这个评分更准确，但是性能会变差\n\n## Elasticsearch在部署时，对Linux的设置有哪些优化方法\n\n* **64GB** 内存的机器是非常理想的，但是 **32GB** 和 **16GB** 机器也是很常见的。少于 **8GB** 会适得其反\n* 如果你要在更快的 **CPUs** 和更多的核心之间选择，选择更多的核心更好。多个内核提供的额外并发远胜过稍微快一点点的时钟频率\n* 如果你负担得起 **SSD**，它将远远超出任何旋转介质。基于 **SSD** 的节点，查询和索引性能都有提升。如果你负担得起，**SSD** 是一个好的选择\n* 即使数据中心们近在咫尺，也要避免集群跨越多个数据中心。绝对要避免集群跨越大的地理距离\n* 请确保运行你应用程序的 **JVM** 和服务器的 **JVM** 是完全一样的。在 Elasticsearch 的几个地方，使用 Java 的本地序列化\n* 通过设置 `gateway.recover_after_nodes`、`gateway.expected_nodes`、`gateway.recover_after_time` 可以在集群重启的时候避免过多的分片交换，这可能会让数据恢复从数个小时缩短为几秒钟\n* Elasticsearch 默认被配置为使用单播发现，以防止节点无意中加入集群。只有在同一台机器上运行的节点才会自动组成集群。最好使用单播代替组播\n* 不要随意修改垃圾回收器`（CMS）`和各个线程池的大小\n* 把你的内存的（少于）一半给 `Lucene`（但不要超过 32GB），通过 `ES_HEAP_SIZE` 环境变量设置\n* 内存交换到磁盘对服务器性能来说是致命的。如果内存交换到磁盘上，一个 **100** 微秒的操作可能变成 **10** 毫秒。 再想想那么多 **10** 微秒的操作时延累加起来。 不难看出 `swapping` 对于性能是多么可怕\n* `Lucene` 使用了大量的文件。同时，Elasticsearch 在节点和 HTTP 客户端之间进行通信也使用了大量的套接字。 所有这一切都需要足够的文件描述符。你应该增加你的文件描述符，设置一个很大的值，如 `64000`\n\n**补充：索引阶段性能提升方法**\n\n* 使用批量请求并调整其大小：每次批量数据 `5MB – 15MB` 大是个不错的起始点\n* 存储：使用 `SSD`\n* 段和合并：Elasticsearch 默认值是 `20MB\u002Fs`，对机械磁盘应该是个不错的设置。如果你用的是 `SSD`，可以考虑提高到 `100 – 200MB\u002Fs`。如果你在做批量导入，完全不在意搜索，你可以彻底关掉合并限流。另外还可以增加 `index.translog.flush_threshold_size` 设置，从默认的 `512MB` 到更大一些的值，比如 `1GB`，这可以在一次清空触发的时候在事务日志里积累出更大的段\n* 如果你的搜索结果不需要近实时的准确度，考虑把每个索引的 `index.refresh_interval` 改到 `30s`\n* 如果你在做大批量导入，考虑通过设置 `index.number_of_replicas: 0` 关闭副本\n\n## GC方面，在使用Elasticsearch时要注意什么\n\n* 倒排词典的索引需要常驻内存，无法 GC，需要监控 `data node` 上 `segment memory` 增长趋势\n* 各类缓存，`field cache`, `filter cache`, `indexing cache`, `bulk queue` 等等，要设置合理的大小，并且要应该根据最坏的情况来看 `heap` 是否够用，也就是各类缓存全部占满的时候，还有 `heap` 空间可以分配给其他任务吗？避免采用 `clear cache` 等「自欺欺人」的方式来释放内存\n* 避免返回大量结果集的搜索与聚合。确实需要大量拉取数据的场景，可以采用 `scan` & `scroll api` 来实现\n* `cluster stats` 驻留内存并无法水平扩展，超大规模集群可以考虑分拆成多个集群通过 `tribe node` 连接\n* 想知道 `heap` 够不够，必须结合实际应用场景，并对集群的 `heap` 使用情况做持续的监控\n\n## Elasticsearch对于大数据量（上亿量级）的聚合如何实现\n\nElasticsearch 提供的首个近似聚合是 `cardinality` 度量。它提供一个字段的基数，即该字段的 `distinct` 或者 `unique` 值的数目。它是基于 `HLL算法`的。`HLL` 会先对我们的输入作哈希运算，然后根据哈希运算的结果中的 `bits` 做概率估算从而得到基数。其特点是：可配置的精度，用来控制内存的使用（更精确 ＝ 更多内存）；小的数据集精度是非常高的；我们可以通过配置参数，来设置去重需要的固定内存使用量。无论数千还是数十亿的唯一值，内存使用量只与你配置的精确度相关。\n\n## 在并发情况下，Elasticsearch如果保证读写一致\n\n* 可以通过版本号使用乐观并发控制，以确保新版本不会被旧版本覆盖，由应用层来处理具体的冲突\n* 另外对于写操作，一致性级别支持`quorum\u002Fone\u002Fall`，默认为 `quorum`，即只有当大多数分片可用时才允许写操作。但即使大多数可用，也可能存在因为网络等原因导致写入副本失败，这样该副本被认为故障，分片将会在一个不同的节点上重建\n* 对于读操作，可以设置 `replication` 为 `sync(默认)`，这使得操作在主分片和副本分片都完成后才会返回；如果设置 `replication` 为 `async` 时，也可以通过设置搜索请求参数 `_preference` 为 `primary` 来查询主分片，确保文档是最新版本\n\n## 如何监控Elasticsearch集群状态\n\n`elasticsearch-head` 插件通过 `Kibana` 监控 `Elasticsearch`。你可以实时查看你的集群健康状态和性能，也可以分析过去的集群、索引和节点指标。\n\n## Elasticsearch中的集群、节点、索引、文档、类型是什么\n\n* 集群是一个或多个节点（服务器）的集合，它们共同保存您的整个数据，并提供跨所有节点的联合索引和搜索功能。群集由唯一名称标识，默认情况下为 `my-es-application`。此名称很重要，因为如果节点设置为按名称加入群集，则该节点只能是群集的一部分\n* 节点是属于集群一部分的单个服务器。它存储数据并参与群集索引和搜索功能\n* 索引就像关系数据库中的「数据库」。它有一个定义多种类型的映射。索引是逻辑名称空间，映射到一个或多个主分片，并且可以有零个或多个副本分片。 `MySQL` => 数据库; `Elasticsearch` => 索引\n* 文档类似于关系数据库中的一行。不同之处在于索引中的每个文档可以具有不同的结构（字段），但是对于通用字段应该具有相同的数据类型。 MySQL => Databases => Tables => Columns \u002F Rows ; Elasticsearch => Indices => Types => 具有属性的文档\n* 类型是索引的逻辑类别\u002F分区，其语义完全取决于用户\n\n## Elasticsearch中的倒排索引是什么\n\n倒排索引是搜索引擎的核心。搜索引擎的主要目标是在查找发生搜索条件的文档时提供快速搜索。ES 中的倒排索引其实就是 lucene 的倒排索引，区别于传统的正向索引，倒排索引会在存储数据时将关键词和数据进行关联，保存到倒排表中，然后查询时，将查询内容进行分词后在倒排表中进行查询，最后匹配数据即可。\n",[128,133,138],{"id":35,"created_at":129,"updated_at":129,"code":37,"author_id":4,"title":130,"slug":131,"cover_theme_key":9,"visibility":10,"status":11,"sort_order":35,"favorite_count":12,"view_count":23,"author":132},"2023-12-30T11:23:15.697+08:00","分布式事务Seata","分布式事务seata-2",{"id":4,"created_at":15,"updated_at":16,"code":6,"username":17,"display_name":18,"role":19,"avatar_url":20,"is_active":21,"is_banned":22,"follower_count":12,"following_count":23,"last_login_at":24},{"id":23,"created_at":134,"updated_at":134,"code":44,"author_id":4,"title":135,"slug":136,"cover_theme_key":9,"visibility":10,"status":11,"sort_order":23,"favorite_count":12,"view_count":23,"author":137},"2024-01-06T03:24:09.349+08:00","Electron Forge","electron-forge-3",{"id":4,"created_at":15,"updated_at":16,"code":6,"username":17,"display_name":18,"role":19,"avatar_url":20,"is_active":21,"is_banned":22,"follower_count":12,"following_count":23,"last_login_at":24},{"id":50,"created_at":139,"updated_at":140,"code":52,"author_id":4,"title":141,"slug":142,"cover_theme_key":9,"visibility":10,"status":11,"sort_order":50,"favorite_count":12,"view_count":35,"author":143},"2024-01-06T03:31:06.026+08:00","2024-01-08T15:22:03.316+08:00","企业微信开发","企业微信开发-4",{"id":4,"created_at":15,"updated_at":16,"code":6,"username":17,"display_name":18,"role":19,"avatar_url":20,"is_active":21,"is_banned":22,"follower_count":12,"following_count":23,"last_login_at":24}]