ElasticSearch总结

SpringData

1、ElasticSearch

0、入门与安装

1、安装ES

Elasticsearch 的官方地址:https://www.elastic.co/cn
下载地址:https://www.elastic.co/cn/downloads/past-releases#elasticsearch

我们主要学习 Elasticsearch 的 Java 客户端的使用,所以课程中使用的是安装较为简便的 Windows 版本。

我们只要在官网下载Windows的安装包,解压后,进入 bin 文件目录,点击 elasticsearch.bat 文件即可启动 ES 服务。

成功启动后,9300 端口为 Elasticsearch 集群间组件的通信端口,9200 端口为浏览器访问的 http 协议 RESTful 端口。

我们访问时输入地址:http://localhost:9200即可。

2、安装 Kibana

Kibana 是一个免费且开放的用户界面,能够让你对 Elasticsearch 数据进行可视化,并 让你在 Elastic Stack 中进行导航。

下载地址:https://artifacts.elastic.co/downloads/kibana/kibana-7.8.0-windows-x86_64.zip

我们解压下载的zip文件,然后修改 config/kibana.yml 文件:

# 默认端口
server.port: 5601
# ES 服务器的地址
elasticsearch.hosts: ["http://localhost:9200"]
# 索引名
kibana.index: ".kibana"
# 支持中文
i18n.locale: "zh-CN"

然后执行bin/kibana.bat 文件即可启动;启动成功后访问:

http://localhost:5601

3、安装分词器

IK 中文分词器,下载地址为:

https://github.com/medcl/elasticsearch-analysis-ik/releases/tag/v7.8.0

将解压后的后的文件夹放入 ES 根目录下的 plugins 目录下,重启ES即可使用。

1、概念理解

5、核心必懂

读、写、更新和多文档操作流程

3、分片原理

1、倒排索引

Elasticsearch 使用一种称为倒排索引的结构,它适用于快速的全文搜索。所谓的正向索引,就是搜索引擎会将待搜索的文件都对应一个文件 ID,搜索时将这个 ID 和搜索关键字进行对应,形成 K-V 对,然后对关键字进行统计计数;而倒排索引则是把文件 ID对应到关键词的映射转换为关键词到文件ID的映射,每个关键词都对应着一系列的文件, 这些文件中都出现这个关键词。

倒排索引举例:一个倒排索引由文档中所有不重复词的列表构成,对于其中每个词,有一个包含它的文 档列表。例如,假设我们有两个文档,每个文档的 content 域包含如下内容:

  • The quick brown fox jumped over the lazy dog

  • Quick brown foxes leap over lazy dogs in summer

为了创建倒排索引,我们首先将每个文档的 content 域拆分成单独的词(我们称它为 词条 或 tokens ),创建一个包含所有不重复词条的排序列表,然后列出每个词条出现在哪个文档。如下所示:

正在上传…重新上传取消

现在,如果我们想搜索 quick brown ,我们只需要查找包含每个词条的文档:明显Doc_2比Doc_1匹配度更高。注意这里的倒排索引是包含在我们的索引库里的,倒排索引有利于我们在索引库里快速找到相关文档。

2、文档搜索

版本一:早期文档搜索:早期的全文检索会为整个文档集合建立一个很大的倒排索引并将其写入到磁盘。 一旦 新的索引就绪,旧的就会被其替换,这样最近的变化便可以被检索到。

倒排索引被写入磁盘后是 不可改变 的:它永远不会修改。所以它有如下特性:

  • 不需要锁,不需要担心多进程同时修改数据的问题;

  • 一旦索引被读入内核的文件系统缓存,便会留在哪里,由于其不变性。只要文件系统缓存中还有足够 的空间,那么大部分读请求会直接请求内存,而不会命中磁盘。这提供了很大的性能提升。

版本二:动态更新索引:为了在保留不变性的前提下实现倒排索引的更新,我们选择用更多的索引,通过增加新的补充索引来反映新近的修改,而不是直接重写整 个倒排索引。每一个倒排索引都会被轮流查询到,从最早的开始查询完后再对结果进行合并。

按段搜索:Elasticsearch 基于 Lucene引入了按段搜索的概念。每一段本身都是一个倒排索引,另外索引还增加了提交点的概念,提交点是一个列出了所有已知段的文件。按段搜索的流程如下:

  1. 新文档被收集到内存索引缓存;

  2. 不时地, 缓存被 提交:

    1. 一个新的段—一个追加的倒排索引—被写入磁盘;

    2. 一个新的包含新段名字的 提交点 被写入磁盘;

    3. 磁盘进行 同步 — 所有在文件系统缓存中等待的写入都刷新到磁盘,以确保它们 被写入物理文件;

  3. 然后新的段被开启,让它包含的文档可见以被搜索;

  4. 紧接着,内存缓存被清空,等待接收新的文档。

文档的查询、删除和修改原理

当一个查询被触发,所有已知的段按顺序被查询。词项统计会对所有段的结果进行聚合,以保证每个词和每个文档的关联都被准确计算。 这种方式可以用相对较低的成本将新文档添加到索引;

段是不可改变的,所以既不能从把文档从旧的段中移除,也不能修改旧的段来进行反映文档 的更新。 取而代之的是,每个提交点会包含一个 .del 文件,文件中会列出这些被删除文档 的段信息。 当一个文档被 “删除” 时,它实际上只是在 .del 文件中被 标记 删除。一个被标记删除的文档仍然可以被查询匹配到, 但它会在最终结果被返回前从结果集中移除;

文档更新也是类似的操作方式:当一个文档被更新时,旧版本文档被标记删除,文档的新版 本被索引到一个新的段中。 可能两个版本的文档都会被一个查询匹配到,但被删除的那个 旧版本文档在结果集返回前就已经被移除。

版本三:近实时搜索

随着按段(per-segment)搜索的发展,一个新的文档从索引到可被搜索需要几分钟,但这样不够快。磁盘在这里成为了瓶颈。提交 (Commiting)一个新的段到磁盘需要一个 fsync 来确保段被物理性地写入磁盘,这样在断电的时候就不会丢失数据。 但是 fsync 操作代价很大; 如果每次索引一个文档都去执行一 次的话会造成很大的性能问题。

我们需要的是一个更轻量的方式来使一个文档可被搜索,这意味着 fsync 要从整个过程中被移除。在 Elasticsearch 和磁盘之间是文件系统缓存。 像之前描述的一样, 在内存索引缓冲区中的文档会被写入到一个新的段中。 但是这里新段会被先写入到文件系统缓存—这一 步代价会比较低,稍后再被刷新到磁盘—这一步代价比较高。不过只要文件已经在缓存中, 就可以像其它文件一样被打开和读取了。

在 Elasticsearch 中,写入和打开一个新段的轻量的过程(即写入文件缓存且可悲搜索的过程,不包括写入磁盘的过程)叫做 refresh 。 默认情况下每个分 片会每秒自动刷新一次。这就是为什么我们说 Elasticsearch 是 近 实时搜索: 文档的变化 并不是立即对搜索可见,但会在一秒之内变为可见。

持久化变更

实时搜索的问题解决了,但如果没有用 fsync 把数据从文件系统缓存刷(flush)到硬盘,我们不能保证数据在断电甚至是程序正常退出之后依然存在。我们仍然需要经常进行完整提交来确保能从失败中恢复。但在两次提交之间发生变化的文档怎么办?我们也不希望丢失掉这些数据。Elasticsearch 增加了一个 translog ,或者叫事务日志,在每一次对 Elasticsearch 进行操作时均进行了日志记录。

整体流程如下:

  • 一个文档被索引之后,就会被添加到内存缓冲区,并且追加到了 translog;

  • 刷新(refresh)使分片每秒被刷新(refresh)一次:

    • 这些在内存缓冲区的文档被写入到一个新的段中,且没有进行 fsync 操作;

    • 这个段被打开,使其可被搜索;

    • 内存缓冲区被清空,但事务日志不会清空。

  • 这个进程继续工作,更多的文档被添加到内存缓冲区和追加到事务日志;

  • 每隔一段时间或translog变得越来越大时,索引被刷新,一个新的 translog 被创建,并且一个全量提交被执行:

    • 所有在内存缓冲区的文档都被写入一个新的段;

    • 缓冲区被清空;

    • 一个提交点被写入硬盘;

    • 文件系统缓存通过 fsync 被刷新(flush);

    • 老的 translog 被删除。

段合并

由于自动刷新流程每秒会创建一个新的段 ,这样会导致短时间内的段数量暴增。而段数目太多会带来较大的麻烦。 每一个段都会消耗文件句柄、内存和 cpu 运行周期。更重要的是,每个搜索请求都必须轮流检查每个段;所以段越多,搜索也就越慢。

Elasticsearch 通过在后台进行段合并来解决这个问题。小的段被合并到大的段,然后这些大 的段再被合并到更大的段。

段合并的时候会将那些旧的已删除文档从文件系统中清除。被删除的文档(或被更新文档的 旧版本)不会被拷贝到新的大段中。

启动段合并不需要你做任何事。进行索引和搜索时会自动进行。

3、文档分析

1、基本分析过程:

  • 将一块文本分成适合于倒排索引的独立的词条;

  • 将这些词条统一化为标准格式以提高它们的“可搜索性”。

文档分析器包括三个功能:

  • 字符过滤器:首先,字符串按顺序通过每个字符过滤器 。他们的任务是在分词前整理字符串。一个字符过滤器可以用来去掉 HTML,或者将 & 转化成 and;

  • 分词器:其次,字符串被 分词器 分为单个的词条。一个简单的分词器遇到空格和标点的时候, 可能会将文本拆分成词条;

  • Token 过滤器:最后,词条按顺序通过每个 token 过滤器 。这个过程可能会改变词条(例如,小写化 Quick ),删除词条(例如, 像 a, and, the 等无用词),或者增加词条(例如,像 jump 和 leap 这种同义词)。

2、ES内置的词条分析器

  • 标准分析器:标准分析器是 Elasticsearch 默认使用的分析器。它是分析各种语言文本最常用的选择。 它根据 Unicode 联盟 定义的 单词边界 划分文本。删除绝大部分标点。最后,将词条小写;

  • 简单分析器:简单分析器在任何不是字母的地方分隔文本,将词条小写;

  • 空格分析器:空格分析器在空格的地方划分文本;

  • 语言分析器:特定语言分析器可用于 很多语言。它们可以考虑指定语言的特点。例如, 英语 分析 器附带了一组英语无用词(常用单词,例如 and 或者 the ,它们对相关性没有多少影响), 它们会被删除。

中文分词器

ES 的默认分词器无法识别中文中测试、单词这样的词汇,而是简单的将每个字拆完分为一 个词,ES 的默认分词器无法识别中文中测试、单词这样的词汇,而是简单的将每个字拆完分为一个词。

我们在查询的时候可以指定分词器:

# GET http://localhost:9200/_analyze
{
"text":"测试单词",
"analyzer":"ik_max_word"  // 指定分词器,ik_max_word会将文本做最细粒度的拆分(一个字就是一个词);ik_smart:会将文本做最粗粒度的拆分,结合语义。
}

而当遇到特殊中文时,如人名:弗雷尔卓德,IK分词器会将其分成一个字对应一个词;解决办法是:

首先进入 ES 根目录中的 plugins 文件夹下的 ik 文件夹,进入 config 目录,创建 custom.dic 文件,写入弗雷尔卓德。同时打开 IKAnalyzer.cfg.xml 文件,将新建的 custom.dic 配置其中, 重启 ES 服务器。

正在上传…重新上传取消

虽然 Elasticsearch 带有一些现成的分析器,然而在分析器上 Elasticsearch 真正的强大之 处在于,你可以通过在一个适合你的特定数据的设置之中组合字符过滤器、分词器、词汇单 元过滤器来创建自定义的分析器。

具体不在这里赘述!!!

6、典型面试题

1、为什么要使用 ES?

系统中的数据,随着业务的发展,时间的推移,将会非常多,而业务中往往采用模糊查询进行数据的 搜索,而模糊查询会导致查询引擎放弃索引,导致系统查询数据时都是全表扫描,在百万级别的数据库中, 查询效率是非常低下的,而我们使用 ES 做一个全文索引,将经常查询的系统功能的某些字段,比如说电 商系统的商品表中商品名,描述、价格还有 id 这些字段我们放入 ES 索引库里,可以提高查询速度。

2、 ES的 master 选举流程?

  • 对所有可以成为 master 的节点(node.master: true)根据 nodeId 字典排序,每次选举每个节点都把自 己所知道节点排一次序,然后选出第一个(第 0 位)节点,暂且认为它是 master 节点;

  • 如果对某个节点的投票数达到一定的值(可以成为 master 节点数 n/2+1)并且该节点自己也选举自己, 那这个节点就是 master。否则重新选举一直到满足上述条件;

  • 如果对某个节点的投票数达到一定的值(可以成为 master 节点数 n/2+1)并且该节点自己也选举自己, 那这个节点就是 master。否则重新选举一直到满足上述条件。

3、ES 集群脑裂问题?

“脑裂”问题可能的成因

  • 网络问题:集群间的网络延迟导致一些节点访问不到 master,认为 master 挂掉了从而选举出新的 master,并对 master 上的分片和副本标红,分配新的主分片;

  • 节点负载:主节点的角色既为 master 又为 data,访问量较大时可能会导致 ES 停止响应造成大面积延 迟,此时其他节点得不到主节点的响应认为主节点挂掉了,会重新选取主节点;

  • 内存回收:data 节点上的 ES 进程占用的内存较大,引发 JVM 的大规模内存回收,造成 ES 进程失去 响应。

解决方案

  • 减少误判:discovery.zen.ping_timeout 节点状态的响应时间,默认为 3s,可以适当调大,如果 master 在该响应时间的范围内没有做出响应应答,判断该节点已经挂掉了。调大参数(如 6s, discovery.zen.ping_timeout:6),可适当减少误判;

  • 设置选举触发参数:discovery.zen.minimum_master_nodes:1,该参数是用于控制选举行为发生的最小集群主节点数量。当备选主节点的个数大于等于该参数的值, 且备选主节点中有该参数个节点认为主节点挂了,进行选举。官方建议为(n/2)+1,n 为主节点个数 (即有资格成为主节点的节点个数);

  • 角色分离:即 master 节点与 data 节点分离,限制角色功能,使主节点不存储数据,降低主节点负载:

    主节点配置为:node.master: true node.data: false

4、ES 索引文档的流程?

正在上传…重新上传取消

  1. 请求到达某一节点后,该节点就作为协调节点,默认使用文档ID计算为路由提供合适的分片:计算公式如下:

    shard = hash(document_id) % (num_of_primary_shards)
  2. 当分片所在的节点接收到来自协调节点的请求后,会将请求写入到 Memory Buffer,然后定时(默认 是每隔 1 秒)写入到 Filesystem Cache,这个从 Memory Buffer 到 Filesystem Cache 的过程就叫做 refresh;

  3. 当然在某些情况下,存在 Momery Buffer 和 Filesystem Cache 的数据可能会丢失,ES 是通过 translog 的机制来保证数据的可靠性的。其实现机制是接收到请求后,同时也会写入到 translog 中,当 Filesystem cache 中的数据写入到磁盘中时,才会清除掉,这个过程叫做 flush;

  4. 在 flush 过程中,内存中的缓冲将被清除,内容被写入一个新段,段的 fsync 将创建一个新的提交点, 并将内容刷新到磁盘,旧的 translog 将被删除并开始一个新的 translog;

  5. 在 flush 过程中,内存中的缓冲将被清除,内容被写入一个新段,段的 fsync 将创建一个新的提交点, 并将内容刷新到磁盘,旧的 translog 将被删除并开始一个新的 translog。

索引的流程具体演变见分片原理部分!!!

5、ES更新和删除文档的流程

  • 删除和更新也都是写操作,但是 Elasticsearch 中的文档是不可变的,因此不能被删除或者改动以展示其变更;

  • 磁盘上的每个段都有一个相应的.del 文件。当删除请求发送后,文档并没有真的被删除,而是在.del 文件中被标记为删除。该文档依然能匹配查询,但是会在结果中被过滤掉。当段合并时,在.del 文件中被标记为删除的文档将不会被写入新段;

  • 在新的文档被创建时,Elasticsearch 会为该文档指定一个版本号,当执行更新时,旧版本的文档在.del 文件中被标记为删除,新版本的文档被索引到一个新段。旧版本的文档依然能匹配查询,但是会在结 果中被过滤掉。

6、ES搜索的流程?

正在上传…重新上传取消

  1. 搜索被执行成一个两阶段过程,我们称之为 Query Then Fetch;

  2. 在初始查询阶段时,查询会广播到索引中每一个分片拷贝(主分片或者副本分片)。 每个分片在本 地执行搜索并构建一个匹配文档的大小为 from + size 的优先队列;

  3. 每个分片返回各自优先队列中 所有文档的 ID 和排序值 给协调节点,它合并这些值到自己的优先队 列中来产生一个全局排序后的结果列表;

  4. 接下来就是取回阶段,协调节点辨别出哪些文档需要被取回并向相关的分片提交多个 GET 请求。每 个分片加载并丰富文档,如果有需要的话,接着返回文档给协调节点。一旦所有的文档都被取回了, 协调节点返回结果给客户端;

  5. Query Then Fetch 的搜索类型在文档相关性打分的时候参考的是本分片的数据,这样在文档数量较少 的时候可能不够准确。

7、ES中的集群、节点、索引、文档、类型是什么?

  • 集群是一个或多个节点(服务器)的集合,它们共同保存您的整个数据,并提供跨所有节点的联合索 引和搜索功能。集群由唯一名称标识,默认情况下为“elasticsearch”。此名称很重要,因为如果节点设 置为按名称加入集群,则该节点只能是集群的一部分;

  • 节点是属于集群一部分的单个服务器。它存储数据并参与群集索引和搜索功能;

  • 索引就像关系数据库中的“数据库”。它有一个定义多种类型的映射。索引是逻辑名称空间,映射到一 个或多个主分片,并且可以有零个或多个副本分片;

  • 文档类似于关系数据库中的一行。不同之处在于索引中的每个文档可以具有不同的结构(字段),但 是对于通用字段应该具有相同的数据类型。

8、ES 中的倒排索引是什么?

倒排索引是搜索引擎的核心。搜索引擎的主要目标是在查找发生搜索条件的文档时提供快速搜索。ES 中的倒排索引其实就是 lucene 的倒排索引,倒排索引会再存储数据时将关键词和数据进行关联,保存到倒排表中,然后查询时,将查询内容进行分词后在倒排表中进行查询,最后匹配数据即可。

2、实际运用

1、基本工具Kibana

1、介绍

  • 下载一个与ES版本对应的Kibana工具(对应关系CSDN一大堆),修改配置文件使其连接上ES服务器,访问Kibana:http://ip:port/app/home#/,选择Management -> Dev Tools;

  • 可根据它的界面写出原始的ES查询语句,这些语句是 JSON格式的,kibana提供了很友好的语句检测和语句提示功能,方便我们书写这些语句,还可以执行语句,看结果是否正确。

2、基本操作

1、基本增删改

// 创建索引,创建过后不能修改
PUT /索引名
{
  "mappings": {
    "properties": {
      "name":{
        "type": "text"
      },
      "age":{
        "type": "long"
      },
      "birthday":{
        "type": "date"
      },
       "tag":{
        "type": "text"
      }
    }
  }
}
// 获取索引信息
GET test1
// 删除索引
DELETE test1
// 新增文档  指定id
PUT /test1/_doc/id
{
  "name":"张益达",
  "age":11,
  "birthday": "1990-08-09"
}
// 新增文档,随机id
POST /test1/_doc/
{
  "name":"张益达",
  "age":11,
  "birthday": "1990-08-09"
}
// 修改文档  post 修改,不会覆盖它的其他字段
POST /test1/_update/1/
{
  "doc": {
  "name" : "张益达2"
  }
}

2、基本查询

// 查询所有
GET es_user/_search
{
  "query": {
    "match_all": {}
  }
}
// term 精确查询,不分词直接匹配词条
GET es_user/_search
{
  "query": {
    "term": {
      "address": {
        "value": "杭州好地方"
      }
    }
  }
}
// prefix查询:词条以指定的value为前缀的
GET es_user/_search
{
  "query": {
    "prefix": {
      "address": {
        "value": "杭"
      }
    }
  }
}
// wildcard模糊查询:不分词通配符的方式匹配词条 ,*指任意内容,?指任意一个内容
GET es_user/_search    
{
  "query": {
    "wildcard": {
      "address": {
        "value": "杭州*"
      }
    }
  }
}
//range:范围查询,gte为大于等于,lte为小于等于
POST es_user/_search
{
  "query": {
    "range": {
      "age": {
        "gte": 32,
        "lte": 36
      }
    }
  }
}
// 分页查询:使用分页插件pageable

3、复合查询

// must:求交集,must 多个查询单元必须同时匹配
GET es_user/_search
{
  "query": {
    "bool": {
      "must": [ // 多个并列条件使用[],两者之间使用都好分隔
        {
          "term": {
            "address": {
              "value": "浙江省"
            }
          }
        },
        {
          "range": {
            "balance": {
              "gte": 5600,
              "lte": 6000
            }
          }
        }
      ]
    }
  }
}
// must not:取反,表示多个查询单元必须都不匹配,操作格式同理。
// should:并集,多个查询单元满足其中一个条件即可,操作格式同样类似上述。
​
// 排序sort字段:他和分页字段一样,在使用springdata时是合在pageable对象中的。
// desc:降序 asc:升序
GET /es_user/_search
{  // 这里记录下排序的方法,是因为kibana查询方便,便于观察结果
  "query": {
    "bool": {
      "must":{
          "match": {
            "address": "杭州人"
          }
        }
    }
  },
  "sort": [
    {
      "age": {
        "order": "desc"
      }
    }
  ]
}
​
// 聚合查询:我们可以对查询到的数据进行聚合运算,如求平均值、最大、最小、求和、求百分比、求前几。这些查询语句不能作用于springdata的自定义查询语句中,所以这里不做计算。

4、在Java代码中的运用

以上查询都是为了在Java代码中可以直接使用它们来查询ES:如下:可以直接在Java代码中使用原生的ES查询语句,

@Repository  // 交给Spring管理。所继承的接口,需要传入的泛型参数是:映射对应的实体类、映射的主键类型
public interface LogAnalyseRepo extends ElasticsearchRepository<LogAnalysis,Long > {
    @Query("{\n" +
            "    \"bool\": {\n" +
            "      \"must\": [\n" +
            "        {\n" +
            "          \"range\": {\n" +
            "            \"time\": {\n" +
            "              \"gte\": ?1,\n" +
            "              \"lte\": ?2\n" +
            "            }\n" +
            "          }\n" +
            "        },\n" +
            "        {\n" +
            "          \"bool\": {\n" +
            "            \"must\": [\n" +
            "              {\n" +
            "                \"term\": {\n" +
            "                  \"level.keyword\": {\n" +
            "                    \"value\": \"?0\"\n" +
            "                  }\n" +
            "                }\n" +
            "              }\n" +
            "            ]\n" +
            "          }\n" +
            "        }\n" +
            "      ]\n" +
            "    }\n" +
            "  }")  // 根据异常日志级别、时间区间,分页查询异常日志详情
    Page<LogAnalysis> findErrorLogsWithLevelByTimeBetween(String level,Long start, long end, Pageable pageable); // 这里查询语句中的占位符使用(?数字)填充,
}

3、高级查询

ES Java查询

依赖

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
<!-- 这两个依赖的版本关系一定要对应,一般而言同一个boot下的依赖管理是够用的 -->
<dependency>
    <groupId>org.elasticsearch.client</groupId>
    <artifactId>elasticsearch-rest-high-level-client</artifactId>
</dependency>

客户端配置

@Configuration
public class ElasticRestClientConfig extends AbstractElasticsearchConfiguration {
​
    @Value("${spring.elasticsearch.uris}")
    private String url;
    @Override
    @Bean
    public RestHighLevelClient elasticsearchClient() {
​
        url = url.replaceAll("http://", "");
        String[] urlArr = url.split(",");
        HttpHost[] httpPostArr = new HttpHost[urlArr.length];
        for (int i = 0; i < urlArr.length; i++) {
​
            HttpHost httpHost = new HttpHost(urlArr[i].split(":")[0].trim(),
                    Integer.parseInt(urlArr[i].split(":")[1].trim()), "http");
            httpPostArr[i] = httpHost;
        }
        RestClientBuilder builder = RestClient.builder(httpPostArr)
                .setHttpClientConfigCallback(httpClientBuilder -> {
                // 设置登录的账号密码
    final CredentialsProvider credentialsProvider = new BasicCredentialsProvider();
        credentialsProvider.setCredentials(AuthScope.ANY, new           UsernamePasswordCredentials(username, password));
                    httpClientBuilder.setMaxConnTotal(30);
                    httpClientBuilder.setMaxConnPerRoute(10);
                    httpClientBuilder.setKeepAliveStrategy(((response, context) -> Duration.ofMinutes(5).toMillis()));
                    return httpClientBuilder;
                });
        return new RestHighLevelClient(builder);
    }
}

yml配置文件

spring:
  elasticsearch:
    uris: http://172.23.26.65:30114  # 设置连接的ip和端口
    connection-timeout: 30s  # 设置连接保持时间

查询语句

方法调用部分:

// 日志量查询,构造查询条件
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder(); 
// 根据关键字构造查询条件
WildcardQueryBuilder agentClass = QueryBuilders.wildcardQuery("agentClass", "*" + keyWord + "*");
WildcardQueryBuilder ip = QueryBuilders.wildcardQuery("ip", keyWord + "*");
WildcardQueryBuilder name = QueryBuilders.wildcardQuery("name", "*" + keyWord + "*");
RangeQueryBuilder time = QueryBuilders.rangeQuery("time").gte(start).lte(end);
           searchSourceBuilder.query(QueryBuilders.boolQuery().must(time).must(QueryBuilders.boolQuery().should(agentClass).should(ip).should(name))).size(5000);
return EsStatisticalQuery.rangeCount("log",searchSourceBuilder);  // 传入要查询的索引名称和查询条件

执行查询的方法一:聚合查询

public static long rangeCount(String indexName, SearchSourceBuilder sourceBuilder) {
        try {
            // 设置索引名
            CountRequest countRequest = new CountRequest(indexName); // 这里的CountRequest表示计数查询,查询总数。如果要查其他数据,其他代码不变,把这里改为相应的请求即可。如:SearchRequest查询全部数据等
            countRequest.source(sourceBuilder); // 添加构造条件
            RestHighLevelClient restHighLevelClient = SpringUtil.getBean(RestHighLevelClient.class); // 获取刚才注入到IOC中的客户端
            CountResponse countResponse = restHighLevelClient.count(countRequest, RequestOptions.DEFAULT);
            // 遍历查询结果
            RestStatus status = countResponse.status(); // 获取ES请求状态
            if (status != RestStatus.OK) {
                System.out.println("未查询出结果");
                return 0;
            }
            long count = countResponse.getCount();
            return count;
        }catch (Exception e){
            logger.error("ES查询失败",e);
        }
        return 0;
    }

执行查询的方法二:游标查询

  • 在默认情况下,ES查询每次最多只能查询前1W条;

  • 如果查询时结果超过1W条,在默认情况下就会报异常,提示超过数据窗口大小;

  • 顾名思义,相当于用“一把游标”标记“查询的位置”,下次查询时接着标记的地方往后查。

public static int getLogAmount(String indexName,SearchSourceBuilder searchSourceBuilder){
​
        // 1. 创建一个查询请求 searchRequest
        SearchRequest searchRequest = new SearchRequest(indexName);
        // 2. 设置游标的存活时间,单位是分钟
        searchRequest.scroll(TimeValue.timeValueMinutes(1L));
        // 3. 设置查询条件 searchSourceBuilder
        searchRequest.source(searchSourceBuilder);
        //4. 得到返回值,其中包括游标Id-scrollId, 查到的数据 source
        SearchResponse searchResponse; //Initialize the search context by sending an initial search request
        RestHighLevelClient restHighLevelClient = SpringUtil.getBean(RestHighLevelClient.class);
        try {
            searchResponse = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        String scrollId = searchResponse.getScrollId(); // 查到游标Id
        SearchHit[] searchHits = searchResponse.getHits().getHits(); // 查到的数据
        int amount = searchHits.length; // 得到第一次查询到的总数量
        while (true) { // 直到查到的数据为空,退出循环
            SearchScrollRequest scrollRequest = new SearchScrollRequest(scrollId);
            scrollRequest.scroll(TimeValue.timeValueMinutes(1L));
            SearchResponse scrollResp;
            try {
                //7. 执行查询,得到查询结果
                scrollResp = restHighLevelClient.scroll(scrollRequest, RequestOptions.DEFAULT);
            } catch (IOException  e) {
                throw new RuntimeException(e);
            }
            //8. 判断是否有查到数据
            SearchHit[] hits = scrollResp.getHits().getHits();
            if (hits != null && hits.length > 0) {
                amount += hits.length;
            } else {
                break;
            }
        }
        ClearScrollRequest clearScrollRequest = new ClearScrollRequest(); // 用以清除游标
        //11. Specify scrollId
        clearScrollRequest.addScrollId(scrollId);  // 把需要清除的游标添加进来
        ClearScrollResponse clearScrollResponse;
        try {
            //12. 查询完毕,记得删除游标!
            clearScrollResponse = restHighLevelClient.clearScroll(clearScrollRequest, RequestOptions.DEFAULT);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
        boolean succeeded = clearScrollResponse.isSucceeded();  //打印出来,看一下本次查询情况
        logger.info("delete scrollId: {}", succeeded);
        logger.info("Total number of queries:{}", amount);
        return amount;
    }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值