ElasticSearch基础

本文介绍了Elasticsearch的基础知识,包括基本名词解释、安装步骤、初步检索操作、进阶检索技巧如SearchAPI和Query DSL,以及如何使用Java操作Elasticsearch。详细讲解了索引、映射、分词器的配置,以及Java客户端的整合和测试。

全文搜索属于最常见的需求,开源的Elasticsearch 是目前全文搜索引擎的首选。

它可以快速地储存、搜索和分析海量数据。

一、基本名词

  1. 索引

相当于mysql的数据库

  1. 类型(6.0之后已移除)

相当于mysql的表

  1. 文档

相当于mysql表里的数据,也就是msyql中的行

  1. 属性(字段)

相当于mysql的列名

  1. 映射

相当于mysql中个字段中的数据类型


----es里面所有数据都是json格式保存的

 二、安装

1、下载镜像文件

docker pull elasticsearch:7.4.2 存储和检索数据

docker pull kibana:7.4.2 可视化检索数据

2、创建实例

1). 创建所需文件夹

mkdir -p /mydata/elasticsearch/config (用于存放es配置文件)

mkdir -p /mydata/elasticsearch/data(用于存放es数据)

echo "http.host: 0.0.0.0" >> /mydata/elasticsearch/config/elasticsearch.yml

  1. . 创建容器实例

docker run --name elasticsearch -p 9200:9200 -p 9300:9300 --privileged=true \

-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.4.2

若启动容器之后会闪退,查看日志(Caused by: java.nio.file.AccessDeniedException: /usr/share/elasticsearch/data/nodes",)需设置权限运行下列语句

chmod -R 777 /mydata/elasticsearch/  

(该命令表示文件所有者有读写执行权限(4+2+1)、文件所属组有读写执行权限(4+2+1)、其他人有读写执行权限(4+2+1))

之后再启动就不闪退了

(端口9200用于发送请求的端口,端口9300用于es集群节点之间的通讯端口)

(第一个-e表示单节点启动,第二个-e设置占用内存的初始值占用64M与最大值占用256M,避免一启动就直接占满内存导致整个虚拟机卡死)

(第一个-v是将容器内的elasticsearch.yml文件与虚拟机中的elasticsearch.yml进行关联,这样以后修改虚拟机中的文件时将同步至容器内的该文件,后两个-v挂载文件,也是同理,文件夹里有变化会相应同步之容器内的该文件夹中)

三、安装kibana可视化界面

docker run --name kibana -e ELASTICSEARCH_HOSTS=http://192.168.111.200:9200 -p 5601:5601 \

-d kibana:7.4.2

使用它直接访问网页http://192.168.111.200:5601

  • 至此,即可开始直接在网址或者网址打开kibana中使用es了。

四、初步检索

1、_cat

GET /_cat/nodes:查看所有节点(如:http://192.168.111.200:9200/_cat/nodes

GET /_cat/health:查看es 健康状况

GET /_cat/master:查看主节点

GET /_cat/indices:查看所有索引(相当于mysql中的show databases;)

2、索引一个文档(保存)

发送PUT请求:http://192.168.111.200:9200/customer/external/1

请求体:{"name": "John Doe"}

customer:这里是索引,相当于mysql数据库

external:这里是类型,相当于mysql的数据表

1:是标识

最后返回结果如下:

{
    "_index": "customer", //(索引)
    "_type": "external", //(类型)
    "_id": "1", //(保存数据的id)
    "_version": 1, //(版本)
    "result": "created", //(最终的结果:新建)
    "_shards": { //(分片)
        "total": 2,
        "successful": 1,
        "failed": 0
    },
    "_seq_no": 0,
    "_primary_term": 1
}

当再发送同样的一次时,其中的version和result会改变

{
    "_index": "customer",
    "_type": "external",
    "_id": "1",
    "_version": 2,
    "result": "updated",
    "_shards": {
        "total": 2,
        "successful": 1,
        "failed": 0
    },
    "_seq_no": 1,
    "_primary_term": 1
}

PUT 和POST 都可以,区别是put必须带id,而post可以不带

POST 新增。如果不指定id,会自动生成id。指定id 就会修改这个数据,并新增版本号

PUT 可以新增可以修改。PUT 必须指定id;由于PUT 需要指定id,我们一般都用来做修改

操作,不指定id 会报错。

3、查询文档

发送get请求:http://192.168.111.200:9200/customer/external/1

{
    "_index": "customer", //在哪个索引    
    "_type": "external", //在哪个类型
    "_id": "1", 
    "_version": 2, //版本号
    "_seq_no": 1, //并发控制字段,每次更新就会+1,用来做乐观锁
    "_primary_term": 1, //同上,主分片重新分配,如重启,就会变化
    "found": true, //(true表示找到了这个数据)
    "_source": { //(查找到的数据内容)
        "name": "John Doe"
    }
}

更新携带?if_seq_no=0&if_primary_term=1可以做一个乐观锁操作

发送put请求http://192.168.111.200:9200/customer/external/1?if_seq_no=1&if_primary_term=1

若seq_no不对,则不能修改

{
    "_index": "customer",
    "_type": "external",
    "_id": "1",
    "_version": 3,
    "result": "updated",
    "_shards": {
        "total": 2,
        "successful": 1,
        "failed": 0
    },
    "_seq_no": 2,
    "_primary_term": 1
}

再get请求查询:http://192.168.111.200:9200/customer/external/1

seq_no会更新为2,数据也会更新

{
    "_index": "customer",
    "_type": "external",
    "_id": "1",
    "_version": 3,
    "_seq_no": 2,
    "_primary_term": 1,
    "found": true,
    "_source": {
        "name": 1
    }
}

4、更新文档

除上面保存操作中的更新操作,还有如下专门末尾带_update的更新操作

发送post请求:http://192.168.111.200:9200/customer/external/1/_update

请求体中需要写如下doc的固定格式

{
"doc":{
"name": "John Doew"
}
}

返回数据如下

{
    "_index": "customer",
    "_type": "external",
    "_id": "1",
    "_version": 4,
    "result": "updated",
    "_shards": {
        "total": 2,
        "successful": 1,
        "failed": 0
    },
    "_seq_no": 3,
    "_primary_term": 1
}

若继续发送该请求,会对比原来数据,不会像保存操作那样继续更新版本号,只是结果为noop,表示没有改变的意思

{
    "_index": "customer",
    "_type": "external",
    "_id": "1",
    "_version": 4,
    "result": "noop",
    "_shards": {
        "total": 0,
        "successful": 0,
        "failed": 0
    },
    "_seq_no": 3,
    "_primary_term": 1
}

另外,两种方式都可在更新数据的同时,增加属性,如下:

{
"doc": { "name": "Jane Doe", "age": 20 }
}

即可加入age属性

{
    "_index": "customer",
    "_type": "external",
    "_id": "1",
    "_version": 5,
    "result": "updated",
    "_shards": {
        "total": 2,
        "successful": 1,
        "failed": 0
    },
    "_seq_no": 4,
    "_primary_term": 1
}

查询之后:http://192.168.111.200:9200/customer/external/1得到

{
    "_index": "customer",
    "_type": "external",
    "_id": "1",
    "_version": 5,
    "_seq_no": 4,
    "_primary_term": 1,
    "found": true,
    "_source": {
        "name": "Jane Doe",
        "age": 20
    }
}

5、删除数据/索引

发送delete请求删除数据:http://192.168.111.200:9200/customer/external/1

{
    "_index": "customer",
    "_type": "external",
    "_id": "1",
    "_version": 6,
    "result": "deleted",
    "_shards": {
        "total": 2,
        "successful": 1,
        "failed": 0
    },
    "_seq_no": 5,
    "_primary_term": 1
}

发送delete请求删除索引:http://192.168.111.200:9200/customer

{
    "acknowledged": true
}

注:不能直接删除类型,只能删除索引或删除类型中的全部数据

6、批量导入bulk

在kibana上发送如下左侧请求,请求体的每两行为一个操作。

语法格式:

{ action: { metadata }}

{ request body }

{ action: { metadata }}

{ request body }

took:导入时间(ms)

errors:错误信息,false为没有错误

items:导入的数据

每条导入的记录是独立的,上一条的失败不会影响到下一条

更复杂的导入实例,举例如下:

POST /_bulk
{ "delete": { "_index": "website", "_type": "blog", "_id": "123" }}
{ "create": { "_index": "website", "_type": "blog", "_id": "123" }}
{ "title": "My first blog post" }
{ "index": { "_index": "website", "_type": "blog" }}
{ "title": "My second blog post" }
{ "update": { "_index": "website", "_type": "blog", "_id": "123"} }
{ "doc" : {"title" : "My updated blog post"} }

五、进阶检索

1、SearchAPI

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

1)、一个是通过使用REST request URI 发送搜索参数(uri+检索参数)

发送请求:GET bank/_search?q=*&sort=account_number:asc

_search是固定格式 ?后面为搜索条件

q=*表示查找所有的数据 sort表示排序,上面为按照account_number属性升序

返回的结果不会一下全部返回,而是分页进行返回:

2)、 另一个是通过使用REST request body 来发送它们(uri+请求体)

GET bank/_search
{
  "query": {  //查询条件
    "match_all": {}  //代表匹配所有的意思
  },
  "sort": [  //排序(可增加多个排序,如下,先按照account_number升序再按照balance升序
    {
      "account_number":  "desc" //完整语法应是{"order": "desc"}
    },
    {
      "balance": "desc"
    }
  ]
}

2、Query DSL

1)、基本语法格式

a.一个查询语句的典型结构

{

QUERY_NAME: {

ARGUMENT: VALUE,

ARGUMENT: VALUE,...

}}

GET bank/_search
{
"query": {
"match_all": {}
}

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

{

QUERY_NAME: { //对应下面的sort

FIELD_NAME: { //对应下面的account_number

ARGUMENT: VALUE, //对应下面的order: desc

ARGUMENT: VALUE,...

}}}

GET bank/_search
{
"query": {
"match_all": {}
},
"from": 0, //从第一条记录
"size": 5, //每次拿取5条记录
"sort": [
{"account_number": {"order": "desc"}}
]
}

2)、_source返回部分字段

GET bank/_search
{
"query": {
"match_all": {}
},
"from": 0,
"size": 2,
"_source": ["age","balance"] 
}

结果为

{
  "took" : 0,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1000,
      "relation" : "eq"
    },
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "bank",
        "_type" : "account",
        "_id" : "1",
        "_score" : 1.0,
        "_source" : {
          "balance" : 39225,
          "age" : 32
        }},
      {
        "_index" : "bank",
        "_type" : "account",
        "_id" : "6",
        "_score" : 1.0,
        "_source" : {
          "balance" : 5686,
          "age" : 36
        }}]}}

3)、match【匹配查询】

基本类型(非字符串),精确查询

GET bank/_search
{
"query": {
"match": {
"account_number": "20" //因为数据中是数字,这里可以直接写数字不加引号
}
}
}

结果为

{
  "took" : 4,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "bank",
        "_type" : "account",
        "_id" : "20",
        "_score" : 1.0,
        "_source" : {
          "account_number" : 20,
          "balance" : 16418,
          "firstname" : "Elinor",
          "lastname" : "Ratliff",
          "age" : 36,
          "gender" : "M",
          "address" : "282 Kings Place",
          "employer" : "Scentric",
          "email" : "elinorratliff@scentric.com",
          "city" : "Ribera",
          "state" : "WA"
        }}]}}

字符串,全文检索(模糊查询)

GET bank/_search
{
"query": {
"match": {
"address": "mill Lane "
}},
"_source":["address"]
}

最终查询出address 中包含mill 单词的所有记录

match 当搜索字符串类型的时候,会进行全文检索,并且每条记录有相关性得分(max_score),结果也会按照得分从高到低进行排序。

若有多个检索词时,会进行分词匹配。如(mill lane将会匹配包含mill或者lane以及两个都包含的语句)

{
  "took" : 0,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 4,
      "relation" : "eq"
    },
    "max_score" : 5.4032025,
    "hits" : [
      {
        "_index" : "bank",
        "_type" : "account",
        "_id" : "970",
        "_score" : 5.4032025,
        "_source" : {
          "address" : "990 Mill Road"
        }},
      {
        "_index" : "bank",
        "_type" : "account",
        "_id" : "136",
        "_score" : 5.4032025,
        "_source" : {
          "address" : "198 Mill Lane"
        }},
      {
        "_index" : "bank",
        "_type" : "account",
        "_id" : "345",
        "_score" : 5.4032025,
        "_source" : {
          "address" : "715 Mill Avenue"
        }},
      {
        "_index" : "bank",
        "_type" : "account",
        "_id" : "472",
        "_score" : 5.4032025,
        "_source" : {
          "address" : "288 Mill Street"
        }}]}}

4)、match_phrase【短语匹配】

将需要匹配的值当成一个整体单词(不分词)进行检索

GET bank/_search
{"query": {
"match_phrase": {
"address": "mill road"}},
"_source":["address"]}

结果为

{
  "took" : 1,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : 8.926605,
    "hits" : [
      {
        "_index" : "bank",
        "_type" : "account",
        "_id" : "970",
        "_score" : 8.926605,
        "_source" : {
          "address" : "990 Mill Road"}}]}}

5)、multi_match【多字段匹配】

查询属性state 或者address 中包含mill的数据,若是查询字段是多个词,将会和match一样会进行分词查询。

GET bank/_search
{"query": {
"multi_match": {
"query": "mill",
"fields": ["state","address"]
}}}

6)、bool【复合查询】

  • bool :用来做复合查询
  • must:表示必须是指定的情况

复合语句可以合并任何其它查询语句,包括复合语句,了解这一点是很重要的。这就意味着,复合语句之间可以互相嵌套,可以表达非常复杂的逻辑。

如下举例,查询结果需必须满足must里面的两个条件,才能够查询成功。

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

结果为

{
  "took" : 0,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : 6.1104345,
    "hits" : [
      {
        "_index" : "bank",
        "_type" : "account",
        "_id" : "472",
        "_score" : 6.1104345,
        "_source" : {
          "address" : "288 Mill Street",
          "gender" : "F"
        }}]}}
  • must_not 必须不是指定的情况
GET bank/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "address": "mill"
          }
        },
        {
          "match": {
            "gender": "F"
          }
        }
      ],
      "must_not": [
        {
          "match": {
            "age":38
}}]}},
  "_source": ["address","gender","age"]
}

结果为:

{
  "took" : 1,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : 6.1104345,
    "hits" : [
      {
        "_index" : "bank",
        "_type" : "account",
        "_id" : "472",
        "_score" : 6.1104345,
        "_source" : {
          "address" : "288 Mill Street",
          "gender" : "F",
          "age" : 32
        }}]}}
  • should

应该达到should 列举的条件,如果达到会增加相关文档的评分,并不会改变查询的结果。

如果query 中只有should 且只有一种匹配规则,那么should 的条件就会被作为默认匹配条件而去改变查询结果。

GET bank/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "address": "mill"
          }
        },
        {
          "match": {
            "gender": "M"
          }
        }
      ],
      "must_not": [
        {
          "match": {
            "age": 18
          }
        }
      ],
      "should": [
        {
          "match": {
            "lastname": "Wallace"
          }}]}},
  "_source": [
    "address",
    "gender",
    "age",
    "lastname"
  ]}

结果为

{
  "took" : 1,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 3,
      "relation" : "eq"
    },
    "max_score" : 12.585751,
    "hits" : [
      {
        "_index" : "bank",
        "_type" : "account",
        "_id" : "970",
        "_score" : 12.585751,
        "_source" : {
          "address" : "990 Mill Road",
          "gender" : "M",
          "age" : 28,
          "lastname" : "Wallace"
        }
      },
      {
        "_index" : "bank",
        "_type" : "account",
        "_id" : "136",
        "_score" : 6.0824604,
        "_source" : {
          "address" : "198 Mill Lane",
          "gender" : "M",
          "age" : 38,
          "lastname" : "Holland"
        }
      },
      {
        "_index" : "bank",
        "_type" : "account",
        "_id" : "345",
        "_score" : 6.0824604,
        "_source" : {
          "address" : "715 Mill Avenue",
          "gender" : "M",
          "age" : 38,
          "lastname" : "Hines"
        }}]}}

7)、filter【结果过滤】

用来过滤数据,只留下满足条件的数据,且不会计算相关性得分

GET bank/_search
{
"query": {
"bool": {
"filter": {
"range": {
"balance": {
"gte": 10000,
"lte": 20000
}}}}}}

结果如下,可看到_score均为0分

{ //部分结果
        "_index" : "bank",
        "_type" : "account",
        "_id" : "20",
        "_score" : 0.0,
        "_source" : {
          "account_number" : 20,
          "balance" : 16418,
          "firstname" : "Elinor",
          "lastname" : "Ratliff",
          "age" : 36,
          "gender" : "M",
          "address" : "282 Kings Place",
          "employer" : "Scentric",
          "email" : "elinorratliff@scentric.com",
          "city" : "Ribera",
          "state" : "WA"
        }

8)、term

和match 一样。匹配某个属性的值。全文检索字段用match,其他非text 字段匹配用term。它是精确匹配,不会分词查询。

GET bank/_search
{
"query": {
"bool": {
"must": [
{"term": {"age":  "28"}},
{"match": {
"address": "990 Mill Road"}}]}}}

9)、aggs(aggregations执行聚合)

聚合提供了从数据中分组和提取数据的能力。最简单的聚合方法大致等于MySql中的 GROUPBY 和SQL 聚合函数(count, in等)。

在Elasticsearch 中,您有执行搜索返回hits(命中结果),并且同时返回聚合结果,把一个响应中的所有hits(命中结果)分隔开的能力。

这是非常强大且有效的,您可以执行查询和多个聚合,并且在一次使用中得到各自的(任何一个的)返回结果,使用一次简洁和简化的API 来避免网络往返。

  • aggs:执行聚合。聚合语法如下

"aggs": {

"aggs_name 这次聚合的名字,方便展示在结果集中": {

"AGG_TYPE 聚合的类型(avg,term,terms)": {要聚合的属性:属性值,size: aggregations要显示的聚合结果数量}

},

...可写多个

}

Q:搜索address 中包含mill 的所有人的年龄分布以及平均年龄,但不显示这些人的详情。

GET bank/_search
{
  "query": {
    "match": {
      "address": "mill"
    }
  },
  "aggs": { //聚合固定格式,里面可以写多个聚合
    "ageAgg": { //自定义的聚合操作名字
      "terms": { //terms聚合:表示下面的属性有几种可能
        "field": "age", //要聚合的字段
        "size": 10   //返回结果中取前10个现实
      }
    },
    "ageAvg":{
      "avg": { //avg聚合:求下面属性平均值
        "field": "age"
      }
    }
  },
    "size": 0 //加上这个,则下面结果中的hits里查询出来的4条数据不显示,但上面的统计信息会显示
}

结果

{
  "took" : 6,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 4,
      "relation" : "eq"
    },
    "max_score" : 5.4032025,
    "hits" : [
      {
        "_index" : "bank",
        "_type" : "account",
        "_id" : "970",
        "_score" : 5.4032025,
        "_source" : {
          "account_number" : 970,
          "balance" : 19648,
          "firstname" : "Forbes",
          "lastname" : "Wallace",
          "age" : 28,
          "gender" : "M",
          "address" : "990 Mill Road",
          "employer" : "Pheast",
          "email" : "forbeswallace@pheast.com",
          "city" : "Lopezo",
          "state" : "AK"
        }
      },
      {
        "_index" : "bank",
        "_type" : "account",
        "_id" : "136",
        "_score" : 5.4032025,
        "_source" : {
          "account_number" : 136,
          "balance" : 45801,
          "firstname" : "Winnie",
          "lastname" : "Holland",
          "age" : 38,
          "gender" : "M",
          "address" : "198 Mill Lane",
          "employer" : "Neteria",
          "email" : "winnieholland@neteria.com",
          "city" : "Urie",
          "state" : "IL"
        }
      },
      {
        "_index" : "bank",
        "_type" : "account",
        "_id" : "345",
        "_score" : 5.4032025,
        "_source" : {
          "account_number" : 345,
          "balance" : 9812,
          "firstname" : "Parker",
          "lastname" : "Hines",
          "age" : 38,
          "gender" : "M",
          "address" : "715 Mill Avenue",
          "employer" : "Baluba",
          "email" : "parkerhines@baluba.com",
          "city" : "Blackgum",
          "state" : "KY"
        }
      },
      {
        "_index" : "bank",
        "_type" : "account",
        "_id" : "472",
        "_score" : 5.4032025,
        "_source" : {
          "account_number" : 472,
          "balance" : 25571,
          "firstname" : "Lee",
          "lastname" : "Long",
          "age" : 32,
          "gender" : "F",
          "address" : "288 Mill Street",
          "employer" : "Comverges",
          "email" : "leelong@comverges.com",
          "city" : "Movico",
          "state" : "MT"
        }
      }
    ]
  },
  "aggregations" : {
    "ageAgg" : {
      "doc_count_error_upper_bound" : 0,
      "sum_other_doc_count" : 0,
      "buckets" : [
        {
          "key" : 38,
          "doc_count" : 2
        },
        {
          "key" : 28,
          "doc_count" : 1
        },
        {
          "key" : 32,
          "doc_count" : 1
        }
      ]
    },
    "ageAvg" : {
      "value" : 34.0
    }
  }
}
  • 使用子聚合,实现更复杂的功能

Q:按照年龄聚合,并且请求这些年龄段的这些人的平均薪资

GET bank/account/_search
{
  "query": {
    "match_all": {}
  },
  "aggs": { //聚合
    "age_avg": {
      "terms": {
        "field": "age",
        "size": 2
      },
      "aggs": { //嵌套子聚合
        "banlances_avg": {
          "avg": {
            "field": "balance"
          }
        }
      }
    }
  },
  "size": 0
}

结果如下

{
  "took" : 4,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1000,
      "relation" : "eq"
    },
    "max_score" : null,
    "hits" : [ ]
  },
  "aggregations" : {
    "age_avg" : {
      "doc_count_error_upper_bound" : 0,
      "sum_other_doc_count" : 879,
      "buckets" : [
        {
          "key" : 31,
          "doc_count" : 61,
          "banlances_avg" : {
            "value" : 28312.918032786885
          }
        },
        {
          "key" : 39,
          "doc_count" : 60,
          "banlances_avg" : {
            "value" : 25269.583333333332
          }}]}}}

再复杂一步:

Q:查出所有年龄分布,并且这些年龄段中M 的平均薪资和F 的平均薪资以及这个年龄段的总体平均薪资

GET bank/account/_search
{
  "query": {
    "match_all": {}
  },
  "aggs": {
    "age_agg": {
      "terms": {
        "field": "age",
        "size": 1
      },
      "aggs": {
        "gender_agg": {
          "terms": {
            "field": "gender.keyword",  //keyword是精确匹配这个属性
            "size": 3
          },
          "aggs": {
            "balance_avg": {
              "avg": {
                "field": "balance"
              }}}},
        "balance_avg": {
          "avg": {
            "field": "balance"
         }}}}},
  "size": 0
}

结果如下:

{
  "took" : 4,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1000,
      "relation" : "eq"
    },
    "max_score" : null,
    "hits" : [ ]
  },
  "aggregations" : {
    "age_agg" : {
      "doc_count_error_upper_bound" : 0,
      "sum_other_doc_count" : 939,
      "buckets" : [
        {
          "key" : 31,
          "doc_count" : 61,
          "balance_avg" : {
            "value" : 28312.918032786885
          },
          "gender_agg" : {
            "doc_count_error_upper_bound" : 0,
            "sum_other_doc_count" : 0,
            "buckets" : [
              {
                "key" : "M",
                "doc_count" : 35,
                "balance_avg" : {
                  "value" : 29565.628571428573
                }
              },
              {
                "key" : "F",
                "doc_count" : 26,
                "balance_avg" : {
                  "value" : 26626.576923076922
                }}]}}]}}}

更多聚合操作可见官网:https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations.html

3、Mapping映射

相当于mysql中将每个字段设置类型(String,Int,Boolean....)

1)、字段类型

核心类型

复合类型

字符串类型(string)

text(可以进行全文检索,保存时分词,检索时可进行分词检索),

keyword(不会进行全文搜索,只会进行精确匹配)

数字类型:

long、integer、short、byte、double、float、half_float、scaled_float

日期类型(data):

data

布尔类型(boolearn):

boolearn

二进制类型(binary):

binary

数组(Array):

支持不针对特定的类型

对象数据类型(Object):

用于单独的JSON对象

嵌套数据类型(Nested):

用于JSON对象的数组

特定类型

地理类型

IP类型

ip用于描述ipv4和ipv6地址

补全类型( Completion )

completion提供自动完成提示

令牌计数类型( Token count )

token_ count 用于统计字符串中的词条数量

附件类型( attachment )

参考mapper-attachements插件,支持将附件如Microsoft Office格式,Open Document格式,ePub, HTML 等等索引为attachment数据类型。

抽取类型( Percolator )

接受特定领域查询语言(query-dsI) 的查询

地理坐标:

geo_point 用于描述经纬度

地理图形:

geo_shape用于描述复杂形状,如多边形

多字段

通常用于为不同目的用不同的方法索引同-一个字段。例如,string 字段可以映射为一个text字段用于全文检索,同样可以映射为一个keyword字段用于排序和聚合。

另外,你可以使用standard analyzer, english analyzer, french analyzer来索引-个text字段这就是muti-fields的目的。大多数的数据类型通过fields参数来支持muti-fields.

2)、映射

Mapping 是用来定义一个文档(document),以及它所包含的属性(field)是如何存储和

索引的。比如,使用mapping 来定义:

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

 哪些属性包含数字,日期或者地理位置。

 文档中的所有属性是否都能被索引(_all 配置)。

 日期的格式。

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

2.1)、创建与查看映射

创建映射,并指定索引中相应字段类型

PUT /my_index //创建索引
{
  "mappings": { //创建映射规则
    "properties": { //要设置的属性类型的属性写在其内
      "age":    { "type": "integer" },  //属性名:{字段类型}
      "email":  { "type": "keyword"  }, 
      "name":   { "type": "text"  }     //
    }
  }
}

结果:

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

查看映射

GET /my_index/_mapping
2.2)、添加新的字段映射
PUT /my_index/_mapping
{
  "properties": {
    "employee-id": { //要添加的字段
      "type": "keyword",
      "index": false  //为false时表示该字段,不会被检索,作为一个冗余字段存在,默认不写为true
    }
  }
}

结果

{"acknowledged" : true}
2.3)、更新映射

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

2.4)、数据迁移
  1. 先获取到原来的映射

GET /bank/_mapping
  1. 再复制原来的映射数据,复制到要创建的新索引中,进行想要的修改

PUT /newbank
{
  "mappings": {
    "properties": {
      "account_number": {
        "type": "long"
      },
      "address": {
        "type": "text"
      },
      "age": {
        "type": "integer"
      },
      "balance": {
        "type": "long"
      },
      "city": {
        "type": "keyword"
      },
      "email": {
        "type": "keyword"
      },
      "employer": {
        "type": "keyword"
      },
      "firstname": {
        "type": "text"
      },
      "gender": {
        "type": "keyword"
      },
      "lastname": {
        "type": "text",
        "fields": {
          "keyword": {
            "type": "keyword",
            "ignore_above": 256
          }
        }
      },
      "state": {
        "type": "keyword"
      }
    }
  }
}

结果

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

3.迁移数据

a).若索引是新版不含类型的,按下面操作

POST _reindex //固定写法
{
"source": {
"index": "twitter" //旧索引名字
},
"dest": {
"index": "new_twitter" //新索引名字
}
}

b).若索引是旧版含类型,复制数据到现在不含类型的索引,按下面操作

POST _reindex
{
  "source": {
    "index": "bank", //旧索引名字
    "type": "account" //旧类型名字
  },
  "dest": {
    "index": "newbank" //新索引名字
  }
}

结果

#! Deprecation: [types removal] Specifying types in reindex requests is deprecated.
{
  "took" : 542,
  "timed_out" : false,
  "total" : 1000,
  "updated" : 0,
  "created" : 1000,
  "deleted" : 0,
  "batches" : 1,
  "version_conflicts" : 0,
  "noops" : 0,
  "retries" : {
    "bulk" : 0,
    "search" : 0
  },
  "throttled_millis" : 0,
  "requests_per_second" : -1.0,
  "throttled_until_millis" : 0,
  "failures" : [ ]
}

4、分词

一个tokenizer(分词器)接收一个字符流,将之分割为独立的tokens(词元,通常是独立的单词),然后输出tokens 流。

例如,whitespace tokenizer 遇到空白字符时分割文本。它会将文本"Quick brown fox!" 分割为[Quick, brown, fox!]。

该tokenizer(分词器)还负责记录各个term(词条)的顺序或position 位置(用于phrase 短语和word proximity 词近邻查询),以及term(词条)所代表的原始word(单词)的start起始)和end(结束)的character offsets(字符偏移量)(用于高亮显示搜索的内容)。Elasticsearch 提供了很多内置的分词器,可以用来构建custom analyzers(自定义分词器)。

1)、简单示例

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

简单示例结果

[ the, 2, quick, brown, foxes, jumped, over, the, lazy, dog's, bone ]

由于中文的也是直接分成一个一个的单字,所以需要安装新的分词器,方便更加智能的使用。

2)、安装ik分词器:

注意:不能用默认elasticsearch-plugin install xxx.zip 进行自动安装

  1. 进入网址https://github.com/medcl/elasticsearch-analysis-ik/releases 对应es 版本安装

(注意ik分词器版本一定要一致,不然es将会启动不了一直闪退)

2. 进入es 容器内部plugins 目录,创建一个ik文件夹 (docker exec -it 容器id /bin/bash)

(若创建容器时,已在虚拟机挂载了位置,则在其位置上下载也可,这里我是在/mydata/elasticsearch/plugins中)

wget https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.4.2/elasticsearch-analysis-ik-7.4.2.zip

3. unzip 下载的文件

4.删除里面的elasticsearch-analysis-ik-7.4.2.zip文件 (rm –f elasticsearch-analysis-ik-7.4.2.zip)

5.进入es容器内部查看是否安装

docker exec -it 5d916bb70f2a /bin/bash

cd bin

elasticsearch_plugin list

6.重启es容器

7.进入kibana测试

a)、ik_smart格式

POST _analyze
{
  "analyzer": "ik_smart",
  "text": "我是中国人"
}

结果简写

[我,是,中国人]

b)、ik_max_word格式

POST _analyze
{
  "analyzer": "ik_max_word",
  "text": "我是中国人"
}

结果简写

[我,是,中国人,中国,国人,]

3)、自定义词库

修改/usr/share/elasticsearch/plugins/ik/config/中的IKAnalyzer.cfg.xml

/usr/share/elasticsearch/plugins/ik/config

原来的xml:

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE properties SYSTEM "" target="_blank">http://java.sun.com/dtd/properties.dtd">

<properties>

<comment>IK Analyzer 扩展配置</comment>

<!--用户可以在这里配置自己的扩展字典-->

<entry key="ext_dict"></entry>

<!--用户可以在这里配置自己的扩展停止词字典-->

<entry key="ext_stopwords"></entry>

<!--用户可以在这里配置远程扩展字典-->

<!-- <entry key="remote_ext_dict">words_location</entry> -->

<!--用户可以在这里配置远程扩展停止词字典-->

<!-- <entry key="remote_ext_stopwords">words_location</entry> -->

</properties>

修改红色下划线标记处网址:

这里红色处的网址是用的nginx里访问的地址)

<?xml version="1.0" encoding="UTF-8"?>

<!DOCTYPE properties SYSTEM "" target="_blank">http://java.sun.com/dtd/properties.dtd">

<properties>

<comment>IK Analyzer 扩展配置</comment>

<!--用户可以在这里配置自己的扩展字典-->

<entry key="ext_dict"></entry>

<!--用户可以在这里配置自己的扩展停止词字典-->

<entry key="ext_stopwords"></entry>

<!--用户可以在这里配置远程扩展字典-->

<entry key="remote_ext_dict">192.168.111.200:80/es/fenci.txt</entry>

<!--用户可以在这里配置远程扩展停止词字典-->

<!-- <entry key="remote_ext_stopwords">words_location</entry> -->

</properties>

五、利用Java操作ES

1、简介

第一种:

通过操控ES的9300端口,它是一个TCP端口,ES的集群之间的通信也是使用的9300端口。

但一般不使用此种方法,版本不同,其所依赖也不同。

官方也不建议使用该端口,7.x 已经不建议使用,8 以后就要废弃。

第二种:

通过9200端口操控ES。

直接给9200发送请求就行了,通常有以下几种方式。

 JestClient:非官方,更新慢

 RestTemplate:模拟发HTTP 请求,ES 很多操作需要自己封装,麻烦

 HttpClient:同上

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

最终选择Elasticsearch-Rest-Client(elasticsearch-rest-high-level-client)

2、正式开始整合java使用步骤:

a)、进入网址https://www.elastic.co/guide/en/elasticsearch/client/java-rest/7.5/index.html

b)、然后选择对应的es版本,使用下面的高阶版的,高阶与低阶相当于MyBatis-Plus与MyBatis.

c)、在idea中创建新的模块,导入依赖

由于我这里使用的是7.4.2版本的es,所以导入依赖时版本修改成了7.4.2

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

当导入这个依赖时,会自动将es的依赖导入,但这版本由于是SpringBoot自动适配导入,如下图es的版本是7.15.2,可能会跟我们使用的不一致,所以需要设置重新导入我这里使用的7.4.2。

点击SpringBoot

再点击里面的SpringBoot依赖

可以看到es默认适配的7.15.2

将其复制一份放至外面properties里,修改其版本号为7.4.2。刷新之后,点击maven中即可看到依赖已经变成了7.4.2了。

d)、在java的配置文件application.yml里配置nacos地址与服务器名

spring:
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
  application:
    name: gulimall-search

e)、创建一个配置类

参考官方文档

https://www.elastic.co/guide/en/elasticsearch/client/java-rest/7.5/java-rest-high-getting-started-initialization.html

由于现在没用到集群,所以builer里面只new了一个。

@Configuration
public class GulimallElasticSearchConfig {

    @Bean
    public RestHighLevelClient esRestClient(){
        RestHighLevelClient client = new RestHighLevelClient(
                RestClient.builder(
                        new HttpHost("192.168.111.200", 9200, "http")));
        //http是传输用到的协议
        return client;
    }
}

注意在启动类上开启服务发现与排除数据源

@EnableDiscoveryClient
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
public class GulimallSearchApplication {
    public static void main(String[] args) {
        SpringApplication.run(GulimallSearchApplication.class, args);
    }}

f)、在测试类里,测试一下,如下结果即可以使用了。

d)加入请求操作类

在e步骤的配置类中再加入请求操作类,用于后续请求需要设置的一些操作

 @Configuration
public class GulimallElasticSearchConfig {  
 public static final RequestOptions COMMON_OPTIONS;
    static {
        RequestOptions.Builder builder = RequestOptions.DEFAULT.toBuilder();
//注释这些暂时不需要的请求操作,后续根据请求需求加上
//        builder.addHeader("Authorization", "Bearer " + TOKEN);
//        builder.setHttpAsyncResponseConsumerFactory(
//                new HttpAsyncResponseConsumerFactory
//                        .HeapBufferedResponseConsumerFactory(30 * 1024 * 1024 * 1024));
        COMMON_OPTIONS = builder.build();
    }
//....e处所加内容
}

3、测试功能

可参考官方文档,实现功能操作

https://www.elastic.co/guide/en/elasticsearch/client/java-rest/7.5/java-rest-high-supported-apis.html

1)、测试储存功能

下面将对象转化为json数据需要导入fastjson依赖

<dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.73</version>
</dependency>
 //测试存储数据到es(也可这样直接更新数据)
    @Test
    public void indexData() throws IOException {
        //1、new一个对象,在users索引里存储数据
        IndexRequest indexRequest = new IndexRequest("users");
        //2、设置id
        indexRequest.id("1");
        //3、传入数据
        //第一种方式:
//        indexRequest.source("username","zhangsan","age",18,"gender","男"); //要保存的内容
//       第二种方式(一般用这种):
        User user = new User();
        user.setUserName("zhangsan");
        user.setAge(18);
        user.setGender("男");
//        将对象转化为json数据
        String jsonString = JSON.toJSONString(user);
        indexRequest.source(jsonString, XContentType.JSON);//要保存的内容

//        执行保存操作,这里发送的是同步请求
        IndexResponse index = restHighLevelClient.index(indexRequest, GulimallElasticSearchConfig.COMMON_OPTIONS);

        //4、若有需要,可提取响应回来的数据
        System.out.println(index);
    }

//返回的index内容(这里我是加了set的数据后又运行了此,所以结果是更新)
//IndexResponse[index=users,type=_doc,id=1,version=2,result=updated,seqNo=1,primaryTerm=1,shards={"total":2,"successful":1,"failed":0}]

去kibanan搜索查看

GET users/_search

//结果:
{
  "took" : 658,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "users",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 1.0,
        "_source" : {
          "age" : 18,
          "gender" : "男",
          "userName" : "zhangsan"
        }}]}}

2)、测试复杂检索功能

利用java重写上面的语句

Q:搜索address 中包含mill 的所有人的年龄分布以及平均年龄,但不显示这些人的详情。

public void searchData() throws IOException {
        //1、创建检索请求
        SearchRequest searchRequest = new SearchRequest();
        //1.1、填写所要检索的索引
        searchRequest.indices("bank");
        //1.2、创建检索条件
        SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
        //1.2.1 构造检索条件
        //1.2.1.1搜索条件
        searchSourceBuilder.query(QueryBuilders.matchQuery("address","mill"));
        //1.2.1.2聚合条件
        //按照年龄的值分布进行聚合并只展示两条数据
        TermsAggregationBuilder termsAggregationBuilder = AggregationBuilders.terms("ageAgg").field("age").size(2);
        searchSourceBuilder.aggregation(termsAggregationBuilder);
        //计算平均薪资
        AvgAggregationBuilder balanceAvg = AggregationBuilders.avg("balanceAvg").field("balance");
        searchSourceBuilder.aggregation(balanceAvg);

        //打印检索的条件
        System.out.println("条件"+searchSourceBuilder.toString());
        //分页条件
//        searchSourceBuilder.from();
//        searchSourceBuilder.size();

        searchRequest.source(searchSourceBuilder);

        //2、执行检索(这里使用的是同步请求)
        SearchResponse searchResponse = restHighLevelClient.search(searchRequest, GulimallElasticSearchConfig.COMMON_OPTIONS);

        //3、分析结果
        System.out.println("结果"+searchResponse);
        //3.1、获取所有查到的数据
        SearchHits hits = searchResponse.getHits(); //这里是拿到的外边的最大的hits
        //获取到里边的hits,这是我们命中的记录
        SearchHit[] searchHits = hits.getHits();
//        "_index": "bank",
//                "_type": "account",
//                "_id": "970",
//                "_score": 5.4032025,
//                "_source":
        for (SearchHit hit : searchHits) {
            String sourceAsString = hit.getSourceAsString();
            //将其转化成对应实体类,实体类的参数与json数据是对应的属性名
            Account account = JSON.parseObject(sourceAsString, Account.class);
            System.out.println("acount: "+account);
        }

        //3.2、获取到检索到的聚合信息
        Aggregations aggregations = searchResponse.getAggregations();

        //由于这个是terms聚合,可以直接转成terms
        Terms aggregation = aggregations.get("ageAgg");
        for (Terms.Bucket bucket : aggregation.getBuckets()) {
            String keyAsString = bucket.getKeyAsString();
            System.out.println("年龄: "+keyAsString+"==》人数:"+ bucket.getDocCount());
        }

        Avg balanceAvg1 = aggregations.get("balanceAvg");
        System.out.println("平均薪资:"+ balanceAvg1.getValue() );
        //将得到的聚合信息,转化成list集合
//        List<Aggregation> aggregations1 = aggregations.asList();
//
//        for (Aggregation aggregation : aggregations1) {
//            System.out.println("当前聚合的名字:"+aggregation.getName());
//        }
//
    }
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值