一文入门elasticsearch

654 阅读12分钟

一、前言

1.1 概述

  • Elaticsearch ,简称es,es是一个开源的高扩展的分布式全文检索引擎,它可以近乎实时的存储、检索数据;本身扩展性很好,可以扩展到上百台服务器,处理PB级别的数据。es也使用Java开发并使用Lucene作为其核心来实现所有索引和搜索的功能,但是它的目的是通过简单的RESTful API来隐藏Lucene的复杂性,从而让全文搜索变的简单。

1.2 使用场景

1、维基百科,类似百度百科,全文检索,高亮,搜索推荐/2 (权重,百度!)

2、The Guardian(国外新闻网站),类似搜狐新闻,用户行为日志(点击,浏览,收藏,评论)+社交 网络数据(对某某新闻的相关看法),数据分析,给到每篇新闻文章的作者,让他知道他的文章的公众 反馈(好,坏,热门,垃圾,鄙视,崇拜)

3、Stack Overflow(国外的程序异常讨论论坛),IT问题,程序的报错,提交上去,有人会跟你讨论和 回答,全文检索,搜索相关问题和答案,程序报错了,就会将报错信息粘贴到里面去,搜索有没有对应 的答案

4、GitHub(开源代码管理),搜索上千亿行代码

5、电商网站,检索商品

6、日志数据分析,logstash采集日志,ES进行复杂的数据分析,ELK技术, elasticsearch+logstash+kibana

7、商品价格监控网站,用户设定某商品的价格阈值,当低于该阈值的时候,发送通知消息给用户,比如 说订阅牙膏的监控,如果高露洁牙膏的家庭套装低于50块钱,就通知我,我就去买。

8、BI系统,商业智能,Business Intelligence。比如说有个大型商场集团,BI,分析一下某某区域最近 3年的用户消费金额的趋势以及用户群体的组成构成,产出相关的数张报表,**区,最近3年,每年消费 金额呈现100%的增长,而且用户群体85%是高级白领,开一个新商场。ES执行数据分析和挖掘, Kibana进行数据可视化

9、国内:站内搜索(电商,招聘,门户,等等),IT系统搜索(OA,CRM,ERP,等等),数据分析 (ES热门的一个使用场景)

二、安装elasticsearch

docker 中安装:

1、下载elasticsearch 和kibaba

docker pull elasticsearch:7.6.2
docker pull kibana:7.6.2

2、配置

可以被远程访问

mkdir -p /mydata/elasticsearch/config
mkdir -p /mydata/elasticsearch/data
echo "http.host: 0.0.0.0" >/mydata/elasticsearch/config/elasticsearch.yml
chmod -R 777 /mydata/elasticsearch/

3、启动Elasticsearch

docker run --name elasticsearch -p 9200:9200 -p 9300:9300 \
-e  "discovery.type=single-node" \
-e ES_JAVA_OPTS="-Xms64m -Xmx512m" \
-v /mydata/elasticsearch/config/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml \
-v /mydata/elasticsearch/data:/usr/share/elasticsearch/data \
-v  /mydata/elasticsearch/plugins:/usr/share/elasticsearch/plugins \
-d elasticsearch:7.6.2 

设置开机启动elasticsearch

docker update elasticsearch --restart=always

4、启动kibana

docker run --name kibana -e ELASTICSEARCH_HOSTS=http://192.168.44.104:9200 -p 5601:5601 -d kibana:7.6.2

设置开机自启动

docker update kibana  --restart=always

5、测试

查看 elasticsearch版本信息:http://192.168.44.104:9200/

{
  "name" : "a0fdde086775",
  "cluster_name" : "elasticsearch",
  "cluster_uuid" : "a-nWkqxuRAaMnE80yD34Mw",
  "version" : {
    "number" : "7.4.2",
    "build_flavor" : "default",
    "build_type" : "docker",
    "build_hash" : "2f90bbf7b93631e52bafb59b3b049cb44ec25e96",
    "build_date" : "2019-10-28T20:40:44.881551Z",
    "build_snapshot" : false,
    "lucene_version" : "8.2.0",
    "minimum_wire_compatibility_version" : "6.8.0",
    "minimum_index_compatibility_version" : "6.0.0-beta1"
  },
  "tagline" : "You Know, for Search"
}

三、初步检索

3.1 _cat

  • GET /_cat/nodes :查看所有节点
请求路径:
http://192.168.44.104:9200/_cat/nodes
请求结果:
127.0.0.1 61 91 11 0.08 0.49 0.87 dilm * 0adeb7852e00
  • GET /_cat/health :查看es健康状况
请求路径:
http://192.168.44.104:9200/_cat/health
返回结果:
1611885437 01:57:17 elasticsearch green 1 1 3 3 0 0 0 0 - 100.0%
  • GET /_cat/master :查看主节点
请求路径:
http://192.168.44.104:9200/_cat/master
返回结果:
LrZMyPXvRhWcoLBpYWh09w 127.0.0.1 127.0.0.1 a0fdde086775
  • GET /_cat/indicies : 查看所有索引,等价于mysql数据库的show databases
请求路径:
http://192.168.44.104:9200/_cat/indices

返回结果:
green open .kibana_task_manager_1   mReiL6afT12fT-cGrh7n8Q 1 0 2 0 12.5kb 12.5kb
green open .apm-agent-configuration PNgY7t61QlqEHsM-Qr4jpg 1 0 0 0   283b   283b
green open .kibana_1                ixGtl_9VQBKGqTCuRkYwzw 1 0 5 0 18.8kb 18.8kb

3.2 索引一个文档

保存一个数据,保存在哪个索引的哪个类型下,指定用那个唯一标识 PUT customer/external/1;在customer索引下的external类型下保存1号数据为”

PUT customer/external/1
{
    "name":"小雷"
}
{
    "_index": "customer",
    "_type": "external",
    "_id": "1",
    "_version": 1,
    "result": "created",
    "_shards": {
        "total": 2,
        "successful": 1,
        "failed": 0
    },
    "_seq_no": 0,
    "_primary_term": 1
}
这些返回的JSON串的含义;这些带有下划线开头的,称为元数据,反映了当前的基本信息。

“_index”: “customer” 表明该数据在哪个数据库下;

“_type”: “external” 表明该数据在哪个类型下;

“_id”: “1” 表明被保存数据的id;

“_version”: 1, 被保存数据的版本

“result”: “created” 这里是创建了一条数据,如果重新put一条数据,则该状态会变为updated,并且版本号也会发生变化。

put 和post都可以,post新增。如果不指定id,会自动生成id。指定id就会修改这个数据,并新增版本号;put可以新增也可以修改。PUT必须指定id;由于PUT需要指定id,外面一般用来做修改操作,不指定id会报错。

3.3 查看文档

GET /customer/external/1

请求路径
http://192.168.44.104:9200/customer/external/1

返回结果
{
    "_index": "customer",
    "_type": "external",
    "_id": "1",
    "_version": 1,
    "_seq_no": 0,
    "_primary_term": 1,
    "found": true,
    "_source": {
        "name": "小雷"
    }
}

3.4 更新文档

两种方式:

第一种:
POST customer/external/1/_update
{
	"doc":{
    	"name":"xiaolei"
    }
}

第二种:
POST customer/external/1
{
	"name" : "xiaolei"
}
或
PUT customer/external/1
{
	"name" : "xiaolei"
}
  • post和put区别:post操作会对比原文档数据,如果相同不会有什么操作,文档version不增加,PUT操作总会将数据重新保存并增加version版本。带_update 对比元数据如果一样就不进行任何操作。看场景:对于大并发更新,不带update。

(1)POST更新文档,带有_update,则会对比原文,如果相同版本号就不变

(2)put不可用于_update,且默认没有使用_update的情况下,它们的版本号都会自增。

3.5 删除文档或索引

语法:

DELETE customer/external/1
DELETE customer

注意:elasticsearch并没有提供删除类型的操作,只提供了删除索引和文档的操作。

实例:删除id=1的数据,删除后继续查询,可以发现类型还在。

{
    "_index": "customer",
    "_type": "external",
    "_id": "1",
    "found": false
}

3.6 eleasticsearch的批量操作——bulk

语法格式:

{action:{metadata}}\n
{request body  }\n

{action:{metadata}}\n
{request body  }\n

这里的批量操作,当发生某一条执行发生失败时,其他的数据仍然能够接着执行,也就是说彼此之间是独立的。

案例,在kibana中测试:

POST /customer/external/_bulk
{"index":{"_id":"1"}}
{"name":"John Doe"}
{"index":{"_id":"2"}}
{"name":"John Doe"}

结果:

#! Deprecation: [types removal] Specifying types in bulk requests is deprecated.
{
  "took" : 5,
  "errors" : false,
  "items" : [
    {
      "index" : {
        "_index" : "customer",
        "_type" : "external",
        "_id" : "1",
        "_version" : 1,
        "result" : "created",
        "_shards" : {
          "total" : 2,
          "successful" : 1,
          "failed" : 0
        },
        "_seq_no" : 10,
        "_primary_term" : 1,
        "status" : 201
      }
    },
    {
      "index" : {
        "_index" : "customer",
        "_type" : "external",
        "_id" : "2",
        "_version" : 1,
        "result" : "created",
        "_shards" : {
          "total" : 2,
          "successful" : 1,
          "failed" : 0
        },
        "_seq_no" : 11,
        "_primary_term" : 1,
        "status" : 201
      }
    }
  ]
}

测试数据链接:

github.com/elastic/ela… ,导入测试数据,

POST bank/account/_bulk

四、进阶检索

4.1 SearchAPI

ES支持两种基本方式检索:

  • 一个是通过使用REST request URI 发送搜索参数 (hri+检索参数)
  • 另一个是通过使用REST request body 来发送它们 (uri+请求体)

4.2 Query DSL

Elasticsearch提供了一个可以执行查询的Json风格的DSL。这个被称为Query DSL,该查询语言非常全面。

4.2.1 基本语法格式

查询语句的典型结构:

QUERY_NAME:{
   ARGUMENT:VALUE,
   ARGUMENT:VALUE,...
}

如果针对于某个字段,那么它的结构如下:

{
  QUERY_NAME:{
     FIELD_NAME:{
       ARGUMENT:VALUE,
       ARGUMENT:VALUE,...
      }   
   }
}
GET bank/_search
{
  "query": {
    "match_all": {}
  },
  "from": 0,
  "size": 5,
  "sort": [
    {
      "account_number": {
        "order": "desc"
      }
    }
  ]
}

query 定义如何查询:

  • match_all 查询类型【代表查询所有的所有】,es中可以在query中组合非常多的查询类型完成复杂查询;
  • 除了query参数之外,外面也可传递其他的参数,例如sort,size;
  • form+size完成分页功能
  • sort 排序,多字段排序,会在前序字段相等时后续字段内部排序。
4.2.2 返回部分字段
GET bank/_search
{
  "query": {
    "match_all": {}
  },
  "from": 0,
  "size": 5,
  "sort": [
    {
      "account_number": {
        "order": "desc"
      }
    }
  ],
  "_source": ["balance","firstname"]
  
}
4.2.3 match 匹配查询
  • 基本类型(非字符串),精确控制,类似于sql的where
GET bank/_search
{
  "query": {
    "match": {
      "account_number": "20"
    }
  }
}
4.2.4 match_phrase[短句匹配]

match_phrase是做短语匹配,只要文本中包含匹配条件,就能匹配到。而match是精确匹配的。

将需要匹配的值当成一整改单词(不分词)进行检索,并给出相关性得分

GET bank/_search
{
  "query": {
    "match_phrase": {
      "address": "mill road"
    }
  }
}
4.2.5 multi_math[多字段匹配]

state 或address中包含mill 就查询出来,还会计算得分

GET bank/_search
{
  "query": {
    "multi_match": {
      "query": "mill",
      "fields": [
        "state",
        "address"
      ]
    }
  }
}
4.2.6 bool用来做复合查询

复合语句可以合并,任何其他查询语句,包括复合语句。复合语句之间可以相互嵌套。

must:必须达到must所列举的所有条件

GET bank/_search
{
   "query":{
        "bool":{
             "must":[
              {"match":{"address":"mill"}},
              {"match":{"gender":"M"}}
             ]
         }
    }

must_not,必须不匹配must_not所列举的所有条件。

should,应该满足should所列举的条件。

4.2.7 Filter [结果过滤]

并不是所有的查询都需要产生分数,特别是哪些仅用于filtering过滤的文档。为了不计算分数,elastic search会自动检查场景并优化查询的执行。

4.2.8 term

和match一样。匹配某个属性的值。全文检索字段用match,其他非text字段匹配用term

4.2.9 Aggregation(执行聚合)

聚合提供了从数据中分组和提取数据的能力。最简单的聚合方法大致等于SQL GROUP by 和SQL聚合函数。在elasticsearch中,执行搜索返回this(命中结果),并且同时返回聚合结果,把以响应中的所有hits(命中结果)分割开的额能力,这是非常强大且有效的,你可以执行查询和多个聚合,并且在一次使用中得到各自的返回结果,避免网络往返。

size=0:不显示搜索数据

聚合语法:

"aggs":{
    "aggs_name这次聚合的名字,方便展示在结果集中":{
        "AGG_TYPE聚合的类型(avg,term,terms)":{}
     }
}

4.3 Mapping

Maping是用来定义一个文档(document),以及它所包含的属性(field)是如何存储和索引的。比如:使用maping来定义:

  • 哪些字符串属性应该被看做全文本属性(full text fields);

  • 哪些属性包含数字,日期或地理位置;

  • 文档中的所有属性是否都嫩被索引(all 配置);

  • 日期的格式;

  • 自定义映射规则来执行动态添加属性;

  • 查看mapping信息

创建索引并指定映射

PUT /my_index
{
  "mappings": {
    "properties": {
      "age": {
        "type": "integer"
      },
      "email": {
        "type": "keyword"
      },
      "name": {
        "type": "text"
      }
    }
  }
}

添加新的字段映射:

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

这里的 “index”: false,表明新增的字段不能被检索,只是一个冗余字段。

更新映射:

对于已经存在的字段映射,外面不能更新。更新必须创建新的索引,进行数据迁移。

数据迁移:

4.4 分词

一个tokenizer(分词器)接收一个字符流,将之分割为独立的tokens,然后输出tokens流 例如:whitespace tokenizer遇到空白字符时分割文本。它会将文本“Quick brown fox!”分割为[Quick,brown,fox!]。

该tokenizer(分词器)还负责记录各个terms(词条)的顺序或position位置(用于phrase短语和word proximity词近邻查询),以及term(词条)所代表的原始word(单词)的start(起始)和end(结束)的character offsets(字符串偏移量)(用于高亮显示搜索的内容)。

elasticsearch提供了很多内置的分词器,可以用来构建custom analyzers(自定义分词器)。

关于分词器: www.elastic.co/guide/en/el…

POST _analyze
{
  "analyzer": "standard",
  "text": "The 2 QUICK Brown-Foxes jumped over the lazy dog's bone."
}

执行结果:

{
  "tokens" : [
    {
      "token" : "the",
      "start_offset" : 0,
      "end_offset" : 3,
      "type" : "<ALPHANUM>",
      "position" : 0
    },
    {
      "token" : "2",
      "start_offset" : 4,
      "end_offset" : 5,
      "type" : "<NUM>",
      "position" : 1
    },
    {
      "token" : "quick",
      "start_offset" : 6,
      "end_offset" : 11,
      "type" : "<ALPHANUM>",
      "position" : 2
    },
    {
      "token" : "brown",
      "start_offset" : 12,
      "end_offset" : 17,
      "type" : "<ALPHANUM>",
      "position" : 3
    },
    {
      "token" : "foxes",
      "start_offset" : 18,
      "end_offset" : 23,
      "type" : "<ALPHANUM>",
      "position" : 4
    },
    {
      "token" : "jumped",
      "start_offset" : 24,
      "end_offset" : 30,
      "type" : "<ALPHANUM>",
      "position" : 5
    },
    {
      "token" : "over",
      "start_offset" : 31,
      "end_offset" : 35,
      "type" : "<ALPHANUM>",
      "position" : 6
    },
    {
      "token" : "the",
      "start_offset" : 36,
      "end_offset" : 39,
      "type" : "<ALPHANUM>",
      "position" : 7
    },
    {
      "token" : "lazy",
      "start_offset" : 40,
      "end_offset" : 44,
      "type" : "<ALPHANUM>",
      "position" : 8
    },
    {
      "token" : "dog's",
      "start_offset" : 45,
      "end_offset" : 50,
      "type" : "<ALPHANUM>",
      "position" : 9
    },
    {
      "token" : "bone",
      "start_offset" : 51,
      "end_offset" : 55,
      "type" : "<ALPHANUM>",
      "position" : 10
    }
  ]
}

进入linux,安装中文ik分词器

[root@hadoop104 plugins]# wget https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.4.2/elasticsearch-analysis-ik-7.4.2.zip
[root@hadoop104 plugins]# ls
elasticsearch-analysis-ik-7.4.2.zip

unzip 下载的文件

案例:

发送内容:
POST _analyze
{
  "analyzer":"ik_smart",
  "text":"自小无父无母的孤儿,成年后成为美国伯克利军方的一位科学家"
}

返回结果:
{
  "tokens" : [
    {
      "token" : "自小",
      "start_offset" : 0,
      "end_offset" : 2,
      "type" : "CN_WORD",
      "position" : 0
    },
    {
      "token" : "无父",
      "start_offset" : 2,
      "end_offset" : 4,
      "type" : "CN_WORD",
      "position" : 1
    },
    {
      "token" : "无",
      "start_offset" : 4,
      "end_offset" : 5,
      "type" : "CN_CHAR",
      "position" : 2
    },
    {
      "token" : "母",
      "start_offset" : 5,
      "end_offset" : 6,
      "type" : "CN_CHAR",
      "position" : 3
    },
    {
      "token" : "的",
      "start_offset" : 6,
      "end_offset" : 7,
      "type" : "CN_CHAR",
      "position" : 4
    },
    {
      "token" : "孤儿",
      "start_offset" : 7,
      "end_offset" : 9,
      "type" : "CN_WORD",
      "position" : 5
    },
    {
      "token" : "成",
      "start_offset" : 10,
      "end_offset" : 11,
      "type" : "CN_CHAR",
      "position" : 6
    },
    {
      "token" : "年后",
      "start_offset" : 11,
      "end_offset" : 13,
      "type" : "CN_WORD",
      "position" : 7
    },
    {
      "token" : "成为",
      "start_offset" : 13,
      "end_offset" : 15,
      "type" : "CN_WORD",
      "position" : 8
    },
    {
      "token" : "美国",
      "start_offset" : 15,
      "end_offset" : 17,
      "type" : "CN_WORD",
      "position" : 9
    },
    {
      "token" : "伯克利",
      "start_offset" : 17,
      "end_offset" : 20,
      "type" : "CN_WORD",
      "position" : 10
    },
    {
      "token" : "军方",
      "start_offset" : 20,
      "end_offset" : 22,
      "type" : "CN_WORD",
      "position" : 11
    },
    {
      "token" : "的",
      "start_offset" : 22,
      "end_offset" : 23,
      "type" : "CN_CHAR",
      "position" : 12
    },
    {
      "token" : "一位",
      "start_offset" : 23,
      "end_offset" : 25,
      "type" : "CN_WORD",
      "position" : 13
    },
    {
      "token" : "科学家",
      "start_offset" : 25,
      "end_offset" : 28,
      "type" : "CN_WORD",
      "position" : 14
    }
  ]
}
发送内容:
GET _analyze
{
   "analyzer": "ik_max_word", 
   "text":"我是中国人"
}

测试结果:
{
  "tokens" : [
    {
      "token" : "我",
      "start_offset" : 0,
      "end_offset" : 1,
      "type" : "CN_CHAR",
      "position" : 0
    },
    {
      "token" : "是",
      "start_offset" : 1,
      "end_offset" : 2,
      "type" : "CN_CHAR",
      "position" : 1
    },
    {
      "token" : "中国人",
      "start_offset" : 2,
      "end_offset" : 5,
      "type" : "CN_WORD",
      "position" : 2
    },
    {
      "token" : "中国",
      "start_offset" : 2,
      "end_offset" : 4,
      "type" : "CN_WORD",
      "position" : 3
    },
    {
      "token" : "国人",
      "start_offset" : 3,
      "end_offset" : 5,
      "type" : "CN_WORD",
      "position" : 4
    }
  ]
}

可以看出这两个分词器还是存在区别的。

五、Elasticsearch-Rest-Client

Elasticsearch-Rest-Client:官方RestClient,封装了ES操作,API层次分明,上手简单;

5.1 导入依赖

这里的版本要和所安装的ELK版本匹配

<dependency>
    <groupId>org.elasticsearch.client</groupId>
    <artifactId>elasticsearch-rest-high-level-client</artifactId>
    <version>7.6.2</version>
</dependency>

ELK 版本位

    <elasticsearch.version>7.6.2</elasticsearch.version>

5.2 测试类

  1. API测试数据
@SpringBootTest
class ElasticsearchStudyApplicationTests {

    @Autowired
    private RestHighLevelClient restHighLevelClient;

    // 创建索引
    @Test
    void createIndex() throws IOException {
        // 1、创建索引请求
        CreateIndexRequest request = new CreateIndexRequest("lei_index");
        // 2、客户端执行请求IndicesClient,请求后获得响应
        CreateIndexResponse createIndexResponse=restHighLevelClient.indices().create(request, RequestOptions.DEFAULT);
        System.out.println(createIndexResponse);
    }

    //测试索引是否存在
    @Test
    void testExistIndex() throws IOException{
        GetIndexRequest request =new GetIndexRequest("lei_index");
        boolean exists=restHighLevelClient.indices().exists(request,RequestOptions.DEFAULT);
        System.out.println(exists);
    }

    //测试删除索引
    @Test
    void testDelete() throws IOException {
        DeleteIndexRequest request =new DeleteIndexRequest("lei");
        AcknowledgedResponse delete = restHighLevelClient.indices().delete(request, RequestOptions.DEFAULT);
        System.out.println(delete.isAcknowledged());
    }


    //================================================================
    //二、文档测试

    // 2.1 测试添加文档
    @Test
    void testAddDocument() throws IOException {
        User user = new User("xiaolei", 24);
        //创建请求
        IndexRequest request =new IndexRequest("lei_index");

        // 规则
        request.id("1");
        request.timeout(TimeValue.timeValueSeconds(1));
        request.timeout("1s");

        //将我们的数据放入请求
        request.source(JSON.toJSONString(user), XContentType.JSON);

        //客户端发送请求,获取返回的结果
        IndexResponse index = restHighLevelClient.index(request, RequestOptions.DEFAULT);
        System.out.println(index.toString());
        System.out.println(index.status());
    }

    //2.2 测试 判断文档是否存在 get /index/doc/1
    @Test
    void testIsExists() throws IOException {
        GetRequest request = new GetRequest("lei_index", "1");
        // 不获取返回的_source的上下文
        request.fetchSourceContext(new FetchSourceContext(false));
        request.storedFields("_none_");

        boolean exists = restHighLevelClient.exists(request, RequestOptions.DEFAULT);
        System.out.println(exists);
    }

    // 2.3 文档信息
    @Test
    void testInfo() throws IOException {
        GetRequest request =new GetRequest("lei_index","1");
        GetResponse documentFields = restHighLevelClient.get(request, RequestOptions.DEFAULT);
        System.out.println(documentFields.getSourceAsString());// 打印文档内容
        System.out.println(documentFields);
    }

    // 2.4 更新文档记录
    @Test
    void testUpdate() throws IOException {
        UpdateRequest request = new UpdateRequest("lei_index", "1");
        request.timeout("1s");

        User user = new User("xxxxxx", 3);

        request.doc(JSON.toJSONString(user),XContentType.JSON);

        UpdateResponse update = restHighLevelClient.update(request, RequestOptions.DEFAULT);
        System.out.println(update.status());
    }

    // 2.5 删除文档记录
    @Test
    void testDeleteDocument() throws IOException {
        DeleteRequest request = new DeleteRequest("lei_index", "1");
        request.timeout("1s");
        DeleteResponse delete = restHighLevelClient.delete(request, RequestOptions.DEFAULT);
        System.out.println(delete.status());
    }

    //2.6 批量插入数据
    @Test
    void testBulkRequest() throws IOException {
        BulkRequest bulkRequest = new BulkRequest();
        bulkRequest.timeout("10s");

        ArrayList<User> list = new ArrayList<>();

        list.add(new User("xiaolei2",23));
        list.add(new User("xiaolei3",23));
        list.add(new User("xiaolei4",23));
        list.add(new User("xiaolei25",23));
        list.add(new User("xiaolei26",23));
        list.add(new User("xiaolei27",23));

        for (int i = 0; i < list.size(); i++) {
            bulkRequest.add(new IndexRequest("lei_index")
            .id(""+(1+i)).source(JSON.toJSONString(list.get(i)),XContentType.JSON));
        }
        BulkResponse bulk = restHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT);
        System.out.println(bulk.status());
        System.out.println(bulk.hasFailures());// 是否失败
    }

    // 2.7 查询
    // SearchRequest 搜索请求
    // SearchSourceBuilder 条件构造
    // QueryBuilders.termQuery 精确
    // HightlightBuilder  高亮
    @Test
    void testSearch() throws IOException {
        SearchRequest request = new SearchRequest("lei_index");
        // 构建搜索条件
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();

        // 查询条件,我们可以使用QueryBuilders 工具来实现
        // QueryBuilders.termQuery 精确
        // QueryBuilders.matchAllQuery 匹配所有
        TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("name", "xiaolei27");
        MatchAllQueryBuilder matchAllQueryBuilder = QueryBuilders.matchAllQuery();


        sourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS));
        sourceBuilder.query(termQueryBuilder);


        request.source(sourceBuilder);
        SearchResponse search = restHighLevelClient.search(request, RequestOptions.DEFAULT);

        System.out.println(JSON.toJSONString(search.getHits()));
        System.out.println("==============================");
        for (SearchHit hit : search.getHits().getHits()) {
            System.out.println(hit.getSourceAsMap());
        }

    }
}

2)java 实现

 /**
     * 复杂检索:在bank中搜索address中包含mill的所有人的年龄分布以及平均年龄,平均薪资
     * @throws IOException
     */
    @Test
    public void searchData() throws IOException {
        //1. 创建检索请求
        SearchRequest searchRequest = new SearchRequest();

        //1.1)指定索引
        searchRequest.indices("bank");
        //1.2)构造检索条件
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        sourceBuilder.query(QueryBuilders.matchQuery("address","Mill"));

        //1.2.1)按照年龄分布进行聚合
        TermsAggregationBuilder ageAgg=AggregationBuilders.terms("ageAgg").field("age").size(10);
        sourceBuilder.aggregation(ageAgg);

        //1.2.2)计算平均年龄
        AvgAggregationBuilder ageAvg = AggregationBuilders.avg("ageAvg").field("age");
        sourceBuilder.aggregation(ageAvg);
        //1.2.3)计算平均薪资
        AvgAggregationBuilder balanceAvg = AggregationBuilders.avg("balanceAvg").field("balance");
        sourceBuilder.aggregation(balanceAvg);

        System.out.println("检索条件:"+sourceBuilder);
        searchRequest.source(sourceBuilder);
        //2. 执行检索
        SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
        System.out.println("检索结果:"+searchResponse);

        //3. 将检索结果封装为Bean
        SearchHits hits = searchResponse.getHits();
        SearchHit[] searchHits = hits.getHits();
        for (SearchHit searchHit : searchHits) {
            String sourceAsString = searchHit.getSourceAsString();
            Account account = JSON.parseObject(sourceAsString, Account.class);
            System.out.println(account);

        }

        //4. 获取聚合信息
        Aggregations aggregations = searchResponse.getAggregations();

        Terms ageAgg1 = aggregations.get("ageAgg");

        for (Terms.Bucket bucket : ageAgg1.getBuckets()) {
            String keyAsString = bucket.getKeyAsString();
            System.out.println("年龄:"+keyAsString+" ==> "+bucket.getDocCount());
        }
        Avg ageAvg1 = aggregations.get("ageAvg");
        System.out.println("平均年龄:"+ageAvg1.getValue());

        Avg balanceAvg1 = aggregations.get("balanceAvg");
        System.out.println("平均薪资:"+balanceAvg1.getValue());


    }