(四)Elasticsearch

269 阅读29分钟

概述

Elaticsearch,简称为es, es是一个开源的高扩展的分布式全文检索引擎。

它可以近乎实时的存储、检索数据;本身扩展性很好,可以扩展到上百台服务器,处理PB级别的数据。

ES可以使用Java开发,并使用Lucene作为其核心来实现所有索引和搜索的功能,但是它的目的是通过简单的RESTful API来隐藏Lucene的复杂性,从而让全文搜索变得简单。

Lucene与ElasticSearch

处理分词,构建倒排索引等等,都是这个叫lucene做的。那么,能不能说这个Lucene就是搜索引擎呢?还不能。

Lucene只是一个提供全文搜索功能类库的核心工具包,而真正使用它还需要一个完善的服务框架搭建起来进行应用。

目前市面上流行的搜索引擎软件,主流的就两款:ElasticsearchSolr,这两款都是基于Lucene搭建的,可以独立部署启动的搜索引擎服务软件。由于内核相同,所以两者除了服务器安装、部署、管理、集群以外,对于数据的操作 修改、添加、保存、查询等等都十分类似。

ElasticSearch对比Solr

  1. Solr 利用 Zookeeper 进行分布式管理,而 Elasticsearch 自身带有分布式协调管理功能;
  2. Solr 支持更多格式的数据,而 Elasticsearch 仅支持json文件格式;
  3. Solr 官方提供的功能更多,而 Elasticsearch 本身更注重于核心功能,高级功能多由第三方插件提供;
  4. Solr 在传统的搜索应用中表现好于 ElasticSearch,但在处理实时搜索应用时效率明显低于 ElasticSearch
  5. ES底层使用Lucene,Solr底层使用Lucene,都是搜索引擎工具,ES的实时操作更强大。

es使用场景

  1. 为用户提供按关键字查询的全文搜索功能
  2. 著名的ELK框架(ElasticSearch,Logstash,Kibana),实现企业海量日志的处理分析的解决方案,是大数据领域的重要一份子。
  3. 分析用户行为,精准营销比如说:用户鼠标在哪个商品上,图片上停留的时间,对商品的点击量,查看评论的条数。

Logstash 是 Elastic Stack 的核心产品之一,可用来对数据进行聚合和处理,并将数据发送到 ElasticSearch。Logstash 是一个开源的服务器端数据处理管道,允许您在将数据索引到 Elasticsearch 之前同时从多个来源采集数据,并对数据进行充实和转换。

Kibana 是一款适用于 Elasticsearch 的数据可视化和管理工具,可以提供实时的直方图、线形图、饼状图和地图。Kibana 同时还包括诸如 Canvas 和 Elastic Maps 等高级应用程序;Canvas 允许用户基于自身数据创建定制的动态信息图表,而 Elastic Maps 则可用来对地理空间数据进行可视化。

ES概述

Elasticsearch是面向文档型数据库,一条数据在这里就是一个文档,用JSON作为文档序列化的格式。

ES索引(index)

一个索引就是一个拥有几分相似特征的文档的集合。能搜索的数据必须索引,这样的好处是可以提高查询速度,比如:新华字典前面的目录就是索引的意思,目录可以提高查询速度。

Elasticsearch索引的精髓:一切设计都是为了提高搜索的性能。

类型(type)

在一个索引中,你可以定义一种或者多种类型。

一个类型是你的索引的一个逻辑上的分类/分区,它的语义完全由你来定。通常,会为具有一组共同字段的文档定义一个类型。

字段(Field)

相当于是数据表的字段,对文档数据根据不同属性进行的分类表示。

映射(mapping)

mapping是处理数据的方式和规则方面做一些限制。如:某字段的数据类型、默认值、分析器、是否被索引等。

这些都是映射里面可以设置的,其它就是处理ES里面数据的一些使用规则设置也叫做映射,按着最优规则处理数据对性能提高很大,因此才需要建立映射,并且需要思考如何建立映射才能对性能更好。

文档(document)

一个文档是一个可被索引的基础信息单元。

在一个index/type里面,你可以存储任意多的文档。注意,尽管一个文档,物理上存在于一个索引之中,文档必须被索引/赋予一个索引的type。

接近实时

ElasticSearch是一个接近实时(Near Real Time,简称NRT)的搜索平台。这意味着,从索引一个文档直到这个文档能够被搜索到有一个轻微的延迟(通常是1秒以内)

安装和启动

安装ES

ElasticSearch的官方地址:www.elastic.co/cn/

下载6.8.1:www.elastic.co/cn/download…

以Windows版为例,

点击ElasticSearch下的bin目录下的elasticsearch.bat启动。

注意:9300是TCP通讯端口,集群间和TCP Client都执行该端口,9200是http协议的RESTful接口 。

访问127.0.0.1:9200,看到版本号就可以了。

注意事项一:

ElasticSearch是使用java开发的,且本版本的ES需要JDK版本要是1.8以上,所以安装ElasticSearch之前保证JDK1.8+安装完毕,并正确的配置好JDK环境变量,否则启动ElasticSearch失败。

注意事项二

出现闪退,通过路径访问发现“空间不足”

修改jvm.options文件的22行23行,把2改成1,让Elasticsearch启动的时候占用1个G的内存。

-Xmx512m:设置JVM最大可用内存为512M。

-Xms512m:设置JVM初始内存为512m。此值可以设置与-Xmx相同,以避免每次垃圾回收完成后JVM重新分配内存。

客户端(Kibana)安装

kibana

官网:www.elastic.co/cn/products…

Kibana是ElasticSearch的数据可视化和实时分析的工具,利用ElasticSearch的聚合功能,生成各种图表,如柱形图,线状图,饼图等。

下载地址

www.elastic.co/cn/download…

安装配置

进入安装目录下的config目录的kibana.yml文件

Kibana默认端口:5601

Kibana连接elasticsearch服务器的地址:elasticsearch.url: ["http://localhost:9200"]

修改kibana配置支持中文:i18n.locale: "zh-CN"

运行访问

执行kibana-6.8.1-windows-x86_64\bin\kibana.bat,执行完成后会提示运行的端口,默认是http://127.0.0.1:5601,访问即可进入。

安装Postman

Postman官网:www.getpostman.com

IK分词器

IKAnalyzer是一个开源的,基于Java语言开发的轻量级的中文分词工具包

安装

下载,GitHub仓库地址:github.com/medcl/elast…

解压安装IK插件,直接解压到plugins\ik\目录下, 注意目录结构,解压后的zip不要放在plugins目录下,删掉就行。

重新启动ElasticSearch之后,看到如下日志代表安装成功

ES-1.png

在Kibana中测试

IK分词器有两种分词模式**:ik_max_word和ik_smart模式。**

  • ik_max_word:会将文本做最细粒度的拆分
  • ik_smart:会做最粗粒度的拆分,智能拆分
  GET _analyze  {   "analyzer": "ik_max_word",   "text": ["我是中国人"]  }  

通过postman发送请求。

请求方式:GET  请求url:http://127.0.0.1:9200/_analyze  请求体:  {   "analyzer": "ik_max_word",   "text": ["我是中国人"]  } 

ElasticSearch的客户端操作

客户端工具:发送http请求(RESTful风格)操作:9200端口

  1. 使用Postman发送请求直接操作
  2. 使用ElasticSearch-head-master图形化界面插件操作
  3. 使用Elastic官方数据可视化的平台Kibana进行操作**【推荐】**

Java代码操作:9300端口

  1. Elasticsearch提供的Java API 客户端进行操作
  2. Spring Data ElasticSearch 持久层框架进行操作

官网支持的客户端访问方式:www.elastic.co/guide/en/el…

索引库操作

索引库操作,完成对索引的增、删、查操作

这里使用的是Kibana。

创建索引库(index)

在kibana中,不用写地址和端口,/shopping是简化写法,真实请求地址是:

http://127.0.0.1.9200/shopping
#请求方式:PUT
PUT /shopping

响应结果:

#! Deprecation: the default number of shards will change from [5] to [1] in 7.0.0; if you wish to continue using the default of [5] shards, you must manage this on the create index request or with an index template
{
  "acknowledged" : true,
  "shards_acknowledged" : true,
  "index" : "shopping"
}

"acknowledged" : true,代表操作成功,"shards_acknowledged" : true,代表分片操作成功,"index" : "shopping"表示创建的索引库名称,注意:创建索引库的分片数默认5片,在7.0.0之后的ES版本中,默认一片。

查看所有索引

GET /_cat/indices?v

查看表头含义(查看帮助信息:GET /_cat/indices?help)

查看某个索引

GET /shopping

返回的内容解释

{
  "shopping【索引库名】" : {
    "aliases【别名】" : { },
    "mappings【映射】" : { },
    "settings"【索引库设置】 : {
      "index【索引】" : {
        "creation_date【创建时间】" : "1586587411462",
        "number_of_shards【索引库分片数】" : "5",
        "number_of_replicas【索引库副本数】" : "1",
        "uuid【唯一标识】" : "VCl1hHsJQDe2p2dn46o0NA",
        "version【版本】" : {
          "created" : "6080199"
        },
        "provided_name【索引库名称】" : "shopping"
      }
    }
  }
}

删除索引

DELETE /shopping

类型以及映射操作

类型:type

映射:mapping

创建类型映射

索引库相当于数据库中的database

接下来需要创建索引库中的类型(type)了,类似于数据库中的表。创建数据库表需要设置字段名称,类型,长度,约束等;索引库也一样,在创建索引库的类型时,需要直到这个类型下有哪些字段,每个字段有哪些约束信息,这就叫映射。

给shopping这个索引库添加了一个名为product的类型,并且在类型中设置了4个字段

  • title:商品标题
  • subtitle:商品子标题
  • images:商品图片
  • price:商品价格

发送请求:

PUT /shopping/product/_mapping
{
	"properties":{
	"title":{
	"type":"text",
	"analyzer":"ik_max_word",
	},
	"subtitle":{
	"type":"text",
	"analyzer":"ik_max_word",
	},
	"images":{
	"type":"keyword",
	"index":false
	},
	"price":{
	"type":"float",
	"index":true
	}
	}
}

类型名称:就是前面的type的概念,类似于数据库中的表

字段名:任意填写,下面指定许多类型

type:类型,ElasticSearch中支持的数据类型非常丰富,如:

  • String类型:分为两种,text可分词,keyword不可分词。
  • Numerical:数值类型,分为基本数据类型(long,interger,short,byte,double,float,half_float)和浮点数的高精度模型(scaled_float)
  • Date:日期类型
  • Array:数组类型
  • Object:对象

index:是否索引,默认是true,可以进行搜索,否则不可搜索。

store:是否独立存储,默认false,在_source中存储。获取独立存储的字段要比从_source中解析快得多,但是也会占用更多的空间,所以要根据实际业务需求来设置。

查看类型映射

GET /shopping/product/_mapping

创建索引库同时进行映射配置(常用)

PUT /shopping2
{
  "settings": {},
  "mappings": {
    "product":{
      "properties": {
        "title":{
          "type": "text",
          "analyzer": "ik_max_word"
          
        },
        "subtitle":{
          "type": "text",
          "analyzer": "ik_max_word"
        },
        "images":{
          "type": "keyword",
          "index": false
        },
        "price":{
          "type": "float",
          "index": true
        }
      }
    }
  }
}

文档操作

新建文档

相当于数据库中向表插入一条数据。

POST /shopping/product
{
    "title":"小米手机",
    "images":"http://www.gulixueyuan.com/xm.jpg",
    "price":3999.00
}

插入成功后返回created是创建成功了,在响应结果中有一个'_id'字段,就是这个文档数据的唯一标识,以后增删改查都依赖这个id作为唯一标识。我们新增时没有指定id,那么ES会帮我们随机生成id。

查看文档

GET /shopping/product/【_id值】

自定义id新建文档

POST /shopping/product/1
{
    "title":"小米手机",
   	   "images":"http://www.gulixueyuan.com/xm.jpg",
    "price":3999.00

}

修改文档(覆盖方式)

请求url不变,请求体变化,会将原有的数据内容覆盖。

POST /shopping/product/1
{
    "title":"华为手机",
    "images":"http://www.gulixueyuan.com/hw.jpg",
    "price":4999.00
}

根据id修改某一个字段

需要加上_update。

POST /shopping/product/1/_update
{ 
  "doc": {
    "price":3000.00
  } 
}

删除一条文档

删除一个文档不会立即从磁盘上移除,它只是被标记成已删除(逻辑删除)。

ES会在段合并时(磁盘碎片整理)进行删除内容的清理。

发送请求

DELETE /shopping/product/1

根据条件删除文档

加上_delete_by_query

POST /shopping/_delete_by_query
{
  "query":{
    "match":{
      "title":"手机"
    }
  }
}

查询DSL

基本查询

ES基于JSON提供完整的查询DSL来定义查询。

DSL:领域特定语言。

叶子查询子句:寻找一个特定的值子啊某一特定领域,如:match,term或range查询。这些查询可以自己使用。

复合查询子句:包装其他叶查询或者复合查询,并用于以逻辑方式组合多个查询(例如bool或dis_max查询),或者更改其行为(例如constant_score查询)。

查询子句的行为会有所不同,具体取决于它们是在上下文中还是过滤器上下文中使用。

准备数据

POST /shopping/product/1
{
    "title":"小米手机",
    "images":"http://www.gulixueyuan.com/xm.jpg",
    "price":3999.00
}
 
POST /shopping/product/2
{
    "title":"华为手机",
    "images":"http://www.gulixueyuan.com/hw.jpg",
    "price":4999.00
}
 
POST /shopping/product/3
{
    "title":"小米电视",
    "images":"http://www.gulixueyuan.com/xmds.jpg",
    "price":5999.00
}

基本查询

1.查询所有(match_all)
# 请求方法:GET
# 请求地址:http://127.0.0.1:9200/索引库名/_search
GET /shopping/_search
{
  "query": {
    "match_all": {}
  }
}

"query":这里的query代表一个查询对象,里面可以有不同的查询属性

"查询类型":例如:match_all代表查所有,除此之外还有match,term,range等等。

"查询条件":查询条件会根据类型的不同,写法也有差异。

2.匹配查询(match)

match类型查询,会把查询条件进行分词,然后进行查询,多个词条之间是or的关系

# 请求方法:GET
GET /shopping/_search
{
  "query": {
    "match": {
      "title": "小米手机"
    }
  }
}

那么查询结果中不仅会有小米手机,华为手机和小米电视也会有。

想要同时包含小米和手机的词条?

需要指定operator为and

GET /shopping/_search
{
  "query": {
    "match": {
      "title": {
        "query": "小米手机",
        "operator": "and"
      }
    }
  }
}
3.多字段匹配(multi_match)

和match类似,不同的是它可以在多个字段中查询。

# 请求方法:GET
#fields属性:设置查询的多个字段名称
GET /shopping/_search
{
  "query": {
    "multi_match": {
        "query": "小米",
        "fields": ["title","subtitle"]
    }
  }
}
4.关键词精确查询(term)

term查询,精确的关键词匹配查询,不对查询条件进行分词。

# 请求方法:GET
GET /shopping/_search
{
  "query": {
    "term": {
      "title": {
        "value": "小米"
      }
    }
  }
}
5.多关键词精确查询(terms)

terms查询和term查询一样,但它允许指定多值进行匹配。

如果这个字段包含了指定值中的任何一个值,那么这个文档满足条件,类似于mysql的in

# 请求方法:GET
GET /shopping/_search
{
  "query": {
    "terms": {
      "price": [3999,5999]
    }
  }
}

结果过滤

这个过滤的意思是留下需要的数据(不是去掉一部分数据的意思)。

默认情况下,ES在搜索的结果中,会把文档保存在_source的所有字段都返回。

如果我们只想要获取其中的部分字段,我们可以添加_source的过滤

# 请求方法:GET
GET /shopping/_search
{
  "_source": ["title","price"],  
  "query": {
    "terms": {
      "price": [3999]
    }
  }
}
过滤指定字段:includes和excludes

我们也可以通过:

  • includs:指定想要的字段
  • excludes:来指定不想要显示的字段
# 请求方法:GET
GET /shopping/_search
{
  "_source": {
    "includes": ["title","price"]
  },  
  "query": {
    "terms": {
      "price": [3999]
    }
  }
}
 
GET /shopping/_search
{
  "_source": {
    "excludes": ["images"]
  },  
  "query": {
    "terms": {
      "price": [3999]
    }
  }
}

高级查询

1.布尔组合(bool)

bool把各种其它查询通过must(必须 )、must_not(必须不)、should(应该)的方式进行组合

# 请求方法:GET
GET /shopping/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "title": "小米"
          }
        }
      ],
      "must_not": [
        {
          "match": {
            "title": "电视"
          }
        }
      ],
      "should": [
        {
          "match": {
            "title": "手机"
          }
        }
      ]
    }
  }
}

2.范围查询(range)

range查询找出哪些落在指定区间内的数字或者时间。range允许以下字符:

操作符说明
gt == (greater than)大于>
gte == (greater than equal)大于等于>=
lt == (less than)小于<
lte == (less than equal)小于等于<=
# 请求方法:GET
GET /shopping/_search
{
  "query": {
    "range": {
      "price": {
        "gte": 2500,
        "lte": 4000
      }
    }
  }
}
3.模糊查询(fuzzy)

返回包含和搜索字词相似的字词的文档。

编辑距离是将一个术语转换为另一个术语所需的一个字符更改的次数。这些更改可以包括:

  • 更改字符
  • 删除字符
  • 插入字符
  • 转置两个相邻字符

为了找到相似的术语,fuzzy查询会在指定的编辑距离内创建一组搜索词的所有可能变体或者扩展,然后返回的每个扩展的完全匹配。

通过fuzziness修改编辑距离。一般使用默认值AUTO,根据属于的长度生成编辑距离。

表达式含义
0..2必须完全匹配
3..5允许一次编辑
>5允许进行两次编辑
GET /shopping/_search
{
  "query": {
    "fuzzy": {
      "title": {
        "value": "ccple",
        "fuzziness": 2
      }
    }
  }
}

查询排序

order

1.单字段排序

sort可以让我们按照不同的字段进行排序,并且通过order指定排序的方式。

desc降序;asc升序。

# 请求方法:GET
GET /shopping/_search
{
  "query": {
    "match_all": {}
  },
  "sort": [
    {
      "price": {
        "order": "desc"
      }
    }
  ]
}
2.多字段排序

假定我们想要结合使用price和_score(得分)进行查询,并且匹配的结果先按照价格排序,然后按照相关性得分排序:

# 请求方法:GET
GET /shopping/_search
{
  "query": {
    "match_all": {}
  },
  "sort": [
    {
      "price": {
        "order": "desc"
      }
    },
    {
      "_score":{
        "order": "desc"
      }
    }
  ]
}

高亮查询

在进行关键字搜索时,搜索出的内容中的关键字会显示不同的颜色,称之为高亮。

高亮查询请求:

ES可以对查询内容中的关键字部分,进行标签和央视(高亮)的设置。

在使用match查询的同时,加上一个highlight属性:

  • pre_tags:前置标签
  • post_tags:后置标签
  • fields:需要高亮的字段
  • title:这里声明title字段需要高亮,后面可以为这个字段设置特有配置,也可以为空。
# 请求方法:GET
GET /shopping/_search
{
  "query": {
    "match": {
      "title": "华为"
    }
  },
  "highlight": {
    "pre_tags": "<font color='red'>",
    "post_tags": "</font>",
    "fields": {
      "title": {}
    }
  }
}

分页查询

from:当前页的起始索引,默认从0开始。from=(pageNum - 1)*size

size:每页显示多少条。

# 请求方法:GET
GET /shopping/_search
{
  "query": {
    "match_all": {}
  },
  "sort": [
    {
      "price": {
        "order": "desc"
      }
    },
    {
      "_score":{
        "order": "desc"
      }
    }
  ],
  "from": 0,
  "size": 2
}
 

Rest客户端

Spirng Boot整合ElasticSearch,通过Java代码操作ElasticSearch。

www.elastic.co/guide/en/el…

在引入依赖的时候,需要严格注意版本关系,否则在使用过程中会出很多问题。

Spring Boot集成ElasticSearch的时候,我们需要引入高阶客户端【elasticsearch-rest-high-level-client】【elasticsearch-rest-client】和【elasticsearch】来操作Elasticsearch。

TransportClient 为代表的ES原生客户端,不能执行原生DSL语句,必须使用它的Java API方法。

TransportClient 实际使用上很麻烦,建议使用highLevelRestClient

最近elasticsearch官网,宣布计划在7.0以后的版本中废除TransportClient。以Java high Level Rest Client为主。

1.依赖

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.10.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>

    <groupId>com.dyy</groupId>
    <artifactId>es_0419</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>es_0419</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

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

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
</project>

2.yml/properties

# es服务地址
elasticsearch.host=127.0.0.1
# es服务端口
elasticsearch.port=9200
# 配置日志级别,开启debug日志
logging.level.com.atguigu.es=debug

3.启动类

@SpringBootApplication
public class ElasticSearchMainApplication {
    public static void main(String[] args) {
        SpringApplication.run(ElasticSearchMainApplication.class);
    }
}

4.配置类

将ES的客户端对象,注入Spring容器中

import lombok.Data;
import org.apache.http.HttpHost;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestClientBuilder;
import org.elasticsearch.client.RestHighLevelClient;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.stereotype.Component;
 
@Configuration
@ConfigurationProperties(prefix = "elasticsearch")
@Component
@Data //安装Lombok插件
public class ElasticSearchConfig {
    private String host ;
    private Integer port ;
 
    @Bean
    public RestHighLevelClient client(){
        RestClientBuilder builder = RestClient.builder(new HttpHost(host, port));
        RestHighLevelClient restHighLevelClient = new RestHighLevelClient(builder);
        return restHighLevelClient;
    }
 
}

5.操作

索引库操作

@SpringBootTest
class EsDemo1 {

    @Autowired
    private RestHighLevelClient client;

    /**
     * 目标:创建索引库
     * 1.创建请求对象 设置索引库name
     * 2.客户端发送请求,获取响应对象
     * 3.打印响应对象中的返回结果
     * 4.关闭客户端,释放连接资源
     */
    @Test
    public void create() throws IOException {
        //1.创建请求对象,创建索引的请求
        CreateIndexRequest request = new CreateIndexRequest("shopping");
        //2.客户端发送请求,获取响应对象
        CreateIndexResponse response = client.indices().create(request, RequestOptions.DEFAULT);
        //3.打印响应对象中的返回结果
        //返回index信息
        System.out.println("index:" + response.index());
        //acknowledged代表创建成功
        System.out.println("acknowledged:" + response.isAcknowledged());
        //4.关闭客户端,释放连接资源
        client.close();
    }


    //查看索引库
    @Test
    public void getIndex() throws IOException {
        //1.创建请求对象:查询索引库
        GetIndexRequest request = new GetIndexRequest("shopping");
        //2.客户端执行请求发送,返回响应对象
        GetIndexResponse response = client.indices().get(request, RequestOptions.DEFAULT);
        //3.打印结果信息
        System.out.println("aliases:"+response.getAliases());
        System.out.println("mappings:"+response.getMappings());
        System.out.println("settings:"+response.getSettings());
        //4.关闭客户端,释放连接资源
        client.close();
    }

    //删除索引库
    @Test
    public void deleteIndex() throws IOException {
        //1.创建请求对象
        DeleteIndexRequest request = new DeleteIndexRequest("shopping");
        //2.客户端发送请求,获取响应对象
        AcknowledgedResponse response = client.indices().delete(request, RequestOptions.DEFAULT);
        //3.打印结果信息
        System.out.println("ack:" + response.isAcknowledged());
        //4.关闭客户端,释放连接资源
        client.close();
    }
    
}


配置映射

RestHighLevelClient配置映射,与kibana略有区别。在客户端中配置映射,不支持设置类型type。不设置type,并不代表没有,而是默认的type为_doc

映射操作:

  1. 配置映射,一共两种方式:一是使用XContentBuilder,构建请求体;二是使用JSON字符串

  2. 查看映射配置

    @SpringBootTest
    class EsDemo2 {
    
        @Autowired
        private RestHighLevelClient client;
    
        /**
         * 目标:配置映射。第一种方式,使用XContentBuilder,构建请求体
         * 1.创建请求对象:配置映射
         * 设置索引库name
         * 设置配置映射请求体
         * 2.客户端发送请求,获取响应对象
         * 3.打印响应结果
         */
        @Test
        public void putMapping01() throws IOException {
            //1.创建请求对象:配置映射
            PutMappingRequest request = new PutMappingRequest("shopping");
            //构建请求体
            XContentBuilder jsonBuilder = XContentFactory.jsonBuilder();
            jsonBuilder.startObject()
                    .startObject("properties")
                    .startObject("title")
                    .field("type", "text").field("analyzer", "ik_max_word")
                    .endObject()
                    .startObject("subtitle")
                    .field("type", "text").field("analyzer", "ik_max_word")
                    .endObject()
                    .startObject("images")
                    .field("type", "keyword").field("index", false)
                    .endObject()
                    .startObject("price")
                    .field("type", "float")
                    .endObject()
                    .endObject()
                    .endObject();
            //设置请求体,source("请求体json构建器对象");
            request.source(jsonBuilder);
            //2.客户端发送请求,获取响应对象
            AcknowledgedResponse response = client.indices().putMapping(request, RequestOptions.DEFAULT);
            //3.打印响应结果
            System.out.println("acknowledged::" + response.isAcknowledged());
        }
    
        /**
         * 目标:配置映射。第二种方式,使用JSON字符串
         * 1.创建请求对象:配置映射
         * 设置索引库name
         * 设置配置映射请求体
         * 2.客户端发送请求,获取响应对象
         * 3.打印响应结果
         */
        @Test
        public void putMapping02() throws IOException {
            //1.创建请求对象:配置映射
            PutMappingRequest request = new PutMappingRequest("shopping1");
            //设置请求体,source("请求体json字符串","请求体的数据类型");
            request.source("{\"properties\":{\"title\":{\"type\":\"text\",\"analyzer\":\"ik_max_word\"},\"subtitle\":{\"type\":\"text\",\"analyzer\":\"ik_max_word\"},\"price\":{\"type\":\"float\"},\"images\":{\"type\":\"keyword\",\"index\":false}}}", XContentType.JSON);
            //2.客户端发送请求,获取响应对象
            AcknowledgedResponse response = client.indices().putMapping(request, RequestOptions.DEFAULT);
            //3.打印响应结果
            System.out.println("acknowledged::" + response.isAcknowledged());
        }
    
        /**
         * 查看映射
         * 1.创建请求对象:查看映射
         * 设置索引库name
         * 2.客户端发送请求,获取响应对象
         * 3.打印响应结果
         */
        @Test
        public void getMapping() throws IOException {
            //1.创建请求对象:查看映射
            GetMappingsRequest request = new GetMappingsRequest();
            //设置索引库name
            request.indices("shopping");
            //2.客户端发送请求,获取响应对象
            GetMappingsResponse response = client.indices().getMapping(request, RequestOptions.DEFAULT);
            //3.打印响应结果
            System.out.println("mappings::" + response.mappings());
            System.out.println("Source::" + response.mappings().get("shopping").getSourceAsMap());
        }
    
    }
    
    

文档操作

@SpringBootTest
class EsDemo3 {

    @Autowired
    private RestHighLevelClient client;

    //新增文档
    @Test
    public void saveDoc() throws IOException {
        //1.创建请求对象(创建索引库CreateIndexRequest),索引库名称、类型名称、主键id
        IndexRequest request = new IndexRequest().index("shopping").type("_doc").id("2");
        //方式1:写一个Product对象将对象转为json字符串
//        Product product = Product.builder().id(1L).title("小米手机").price(1999.0).build();
//        ObjectMapper objectMapper = new ObjectMapper();
//        String productJson = objectMapper.writeValueAsString(product);
//        request.source(productJson,XContentType.JSON);

        //方式2:直接在source中写入key-value参数
        request.source(XContentType.JSON, "id", "2","title", "小米手机",  "price", "3999");

        //2.客户端发送请求,获取响应对象
        IndexResponse response = client.index(request, RequestOptions.DEFAULT);
        ////3.打印结果信息
        System.out.println("_index:" + response.getIndex());
        System.out.println("_type:" + response.getType());
        System.out.println("_id:" + response.getId());
        System.out.println("_result:" + response.getResult());
    }


    //修改文档
    @Test
    public void update() throws IOException {
        //1.创建请求对象(创建索引库CreateIndexRequest),索引库名称、类型名称、主键id
        UpdateRequest request = new UpdateRequest().index("shopping").type("_doc").id("2");
        //设置请求体
        request.doc(XContentType.JSON, "id", "2", "title", "小米手机", "price", "2999");

        //2.客户端发送请求,获取响应对象
        UpdateResponse response = client.update(request, RequestOptions.DEFAULT);
        ////3.打印结果信息
        System.out.println("_index:" + response.getIndex());
        System.out.println("_type:" + response.getType());
        System.out.println("_id:" + response.getId());
        System.out.println("_result:" + response.getResult());
    }


    //查询文档
    @Test
    public void getDoc() throws IOException {
        //1.创建请求对象
        GetRequest request = new GetRequest().index("shopping").type("_doc").id("2");
        //2.客户端发送请求,获取响应对象
        GetResponse response = client.get(request, RequestOptions.DEFAULT);
        ////3.打印结果信息
        System.out.println("_index:" + response.getIndex());
        System.out.println("_type:" + response.getType());
        System.out.println("_id:" + response.getId());
        System.out.println("source:" + response.getSourceAsString());
    }


    //删除文档
    @Test
    public void deleteDoc() throws IOException {
        //创建请求对象
        DeleteRequest request = new DeleteRequest().index("shopping").type("_doc").id("2");
        //客户端发送请求,获取响应对象
        DeleteResponse response = client.delete(request, RequestOptions.DEFAULT);
        //打印信息
        System.out.println(response.toString());
    }


    //批量新增操作
    @Test
    public void bulkSave() throws IOException {
        //创建请求对象
        BulkRequest request = new BulkRequest();
        request.add(new IndexRequest().index("shopping").type("_doc").id("1").source(XContentType.JSON, "title", "小米手机"));
        request.add(new IndexRequest().index("shopping").type("_doc").id("2").source(XContentType.JSON, "title", "苹果手机"));
        request.add(new IndexRequest().index("shopping").type("_doc").id("3").source(XContentType.JSON, "title", "华为手机"));
        //客户端发送请求,获取响应对象
        BulkResponse responses = client.bulk(request, RequestOptions.DEFAULT);
        //打印结果信息
        System.out.println("took:" + responses.getTook());
        System.out.println("items:" + responses.getItems());
    }


    //批量删除操作
    @Test
    public void bulkDelete() throws IOException {
        //创建请求对象
        BulkRequest request = new BulkRequest();
        request.add(new DeleteRequest().index("shopping").type("_doc").id("1"));
        request.add(new DeleteRequest().index("shopping").type("_doc").id("2"));
        request.add(new DeleteRequest().index("shopping").type("_doc").id("3"));
        //客户端发送请求,获取响应对象
        BulkResponse responses = client.bulk(request, RequestOptions.DEFAULT);
        //打印结果信息
        System.out.println("took:" + responses.getTook());
        System.out.println("items:" + responses.getItems());
    }

}

请求体查询

@SpringBootTest
class EsDemo3 {

    @Autowired
    private RestHighLevelClient client;

    /**
     * 初始化查询数据
     */
    @Test
    public void initData() throws IOException {
        //批量新增操作
        BulkRequest request = new BulkRequest();
        request.add(new IndexRequest().type("_doc").index("shopping01").source(XContentType.JSON, "title", "小米手机", "images", "http://www.gulixueyuan.com/xm.jpg", "price", 1999.0));
        request.add(new IndexRequest().type("_doc").index("shopping01").source(XContentType.JSON, "title", "小米电视", "images", "http://www.gulixueyuan.com/xmds.jpg", "price", 2999.0));
        request.add(new IndexRequest().type("_doc").index("shopping01").source(XContentType.JSON, "title", "华为手机", "images", "http://www.gulixueyuan.com/hw.jpg", "price", 4999.0, "subtitle", "小米"));
        request.add(new IndexRequest().type("_doc").index("shopping01").source(XContentType.JSON, "title", "apple手机", "images", "http://www.gulixueyuan.com/appletl.jpg", "price", 5999.00));
        request.add(new IndexRequest().type("_doc").index("shopping01").source(XContentType.JSON, "title", "apple", "images", "http://www.gulixueyuan.com/apple.jpg", "price", 3999.00));
        BulkResponse response = client.bulk(request, RequestOptions.DEFAULT);
        System.out.println("took::" + response.getTook());
        System.out.println("Items::" + response.getItems());
    }

    //请求体查询-基本查询
    //1.创建请求对象
    //2.客户端发送请求,获取响应对象
    //3.打印结果信息
    @Test
    public void basicQuery() throws IOException {
        //1.创建请求对象
        SearchRequest request = new SearchRequest().indices("shopping01").types("_doc");
        //构建查询的请求体
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        //查询所有
        //sourceBuilder.query(QueryBuilders.matchAllQuery());//
        //match查询,带分词器的查询
        //sourceBuilder.query(QueryBuilders.matchQuery("title","小米手机").operator(Operator.AND));
        //term查询:不带分词器,查询条件作为关键词
        sourceBuilder.query(QueryBuilders.termQuery("price", "1999"));
        //TODO ...multi_match:多个字段的match查询
        //TODO ...terms查询:多个关键词去匹配
        request.source(sourceBuilder);
        //2.客户端发送请求,获取响应对象
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        //3.打印结果信息
        printResult(response);
    }

    /**
     * 打印结果信息
     */
    private void printResult(SearchResponse response) {
        SearchHits hits = response.getHits();
        System.out.println("took:" + response.getTook());
        System.out.println("timeout:" + response.isTimedOut());
        System.out.println("total:" + hits.getTotalHits());
        System.out.println("MaxScore:" + hits.getMaxScore());
        System.out.println("hits========>>");
        for (SearchHit hit : hits) {
            //输出每条查询的结果信息
            System.out.println(hit.getSourceAsString());
        }
        System.out.println("<<========");
    }


    /**
     * 目标:查询的字段过滤,分页,排序
     *
     * @throws IOException
     */
    @Test
    public void fetchSourceAndSortAndByPage() throws IOException {
        //1.创建请求对象
        SearchRequest request = new SearchRequest().indices("shopping01").types("_doc");
        //构建查询的请求体
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        //查询所有
        sourceBuilder.query(QueryBuilders.matchAllQuery());

        //分页信息
        //当前页其实索引(第一条数据的顺序号),from
        sourceBuilder.from(2);
        //每页显示多少条size
        sourceBuilder.size(2);

        //排序信息,参数一:排序的字段,参数二:顺序ASC升序,降序DESC
        sourceBuilder.sort("price", SortOrder.ASC);

        //查询字段过滤
        String[] excludes = {};
        String[] includes = {"title", "subtitle", "price"};
        sourceBuilder.fetchSource(includes, excludes);

        request.source(sourceBuilder);
        //2.客户端发送请求,获取响应对象
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        //3.打印结果信息
        printResult(response);
    }


    /**
     * 高级查询
     */
    @Test
    public void boolAndRangeAndFuzzyQuery() throws IOException {
        //1.创建请求对象
        SearchRequest request = new SearchRequest().indices("shopping01").types("_doc");
        //构建查询的请求体
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        //高级查询的三种方式:
        //-----------------------------------------------------------------------------
//        //bool查询:查询title中必须包含小米,一定不含有电视,应该含有手机的所有商品
//        BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
//        //must
//        boolQueryBuilder.must(QueryBuilders.matchQuery("title", "小米"));
//        //must not
//        boolQueryBuilder.mustNot(QueryBuilders.matchQuery("title", "电视"));
//        //should
//        boolQueryBuilder.should(QueryBuilders.matchQuery("title", "手机"));
//        sourceBuilder.query(boolQueryBuilder);
        //-----------------------------------------------------------------------------
        //范围查询:查询价格大于3千,小于5千的所有商品
//        RangeQueryBuilder rangeQuery = QueryBuilders.rangeQuery("price");
//        //#### gt 大于(greater than)
//        rangeQuery.gt("3000");
//        //#### lt 小于(less than)
//        rangeQuery.lt("5000");
//        //#### gte 大于等于(greater than equals)
//        //#### lte 小于等于(less than equals)
//        sourceBuilder.query(rangeQuery);
        //-----------------------------------------------------------------------------
        //模糊查询:查询包含apple关键词的所有商品,完成模糊查询cpple
        sourceBuilder.query(QueryBuilders.fuzzyQuery("title","cpple").fuzziness(Fuzziness.ONE));
        //-----------------------------------------------------------------------------

        request.source(sourceBuilder);
        //2.客户端发送请求,获取响应对象
        SearchResponse response = client.search(request, RequestOptions.DEFAULT);
        //3.打印结果信息
        printResult(response);
    }
}

高亮查询

/**
 * 高亮查询,对查询关键词进行高亮
 * 1.创建请求对象:高亮查询
 *    设置索引库name
 *    设置类型type
 * 2.创建查询请求体构建器
 *    设置请求体
 * 3.客户端发送请求,获取响应对象
 * 4.打印响应结果
 */
@Test
public void highLighterQuery() throws IOException {
    //1.创建请求对象
    SearchRequest request = new SearchRequest().types("_doc").indices("shopping01");
    //2.创建查询请求体构建器
    SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
    //构建查询方式:高亮查询
    TermsQueryBuilder termsQueryBuilder = QueryBuilders.termsQuery("title","apple");
    //设置查询方式
    sourceBuilder.query(termsQueryBuilder);
    //构建高亮字段
    HighlightBuilder highlightBuilder = new HighlightBuilder();
    highlightBuilder.preTags("<font color='red'>");//设置标签前缀
    highlightBuilder.postTags("</font>");//设置标签后缀
    highlightBuilder.field("title");//设置高亮字段
    //设置高亮构建对象
    sourceBuilder.highlighter(highlightBuilder);
    //设置请求体
    request.source(sourceBuilder);
    //3.客户端发送请求,获取响应对象
    SearchResponse response = client.search(request, RequestOptions.DEFAULT);

    //4.打印响应结果
    SearchHits hits = response.getHits();
    System.out.println("took::"+response.getTook());
    System.out.println("time_out::"+response.isTimedOut());
    System.out.println("total::"+hits.getTotalHits());
    System.out.println("max_score::"+hits.getMaxScore());
    System.out.println("hits::::>>");
    for (SearchHit hit : hits) {
        String sourceAsString = hit.getSourceAsString();
        System.out.println(sourceAsString);
        //打印高亮结果
        Map<String, HighlightField> highlightFields = hit.getHighlightFields();
        System.out.println(highlightFields);
    }
    System.out.println("<<::::");
}


SpringData Elasticsearch案例

简介

Spring Data是一个用于简化数据库、非关系型数据库、索引库访问,并支持云服务的开源框架。其主要目标是使得对数据的访问变得方便快捷,并支持map-reduce框架和云计算数据服务。 Spring Data可以极大的简化JPA(Elasticsearch…)的写法,可以在几乎不用写实现的情况下,实现对数据的访问和操作。除了CRUD外,还包括如分页、排序等一些常用的功能。

Spring Data的官网:projects.spring.io/spring-data…

依赖

<parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.2.10.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
<dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

yml配置

# es服务地址
elasticsearch.host=127.0.0.1
# es服务端口
elasticsearch.port=9200
# 配置日志级别,开启debug日志
logging.level.com.atguigu.es=debug

主程序


import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
 
@SpringBootApplication
public class SpringDataElasticSearchMainApplication {
    public static void main(String[] args) {
        SpringApplication.run(SpringDataElasticSearchMainApplication.class,args);
    }
}

实体类


import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
 
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class Product {
    private Long id;//商品唯一标识
    private String title;//商品名称
    private String category;//分类名称
    private Double price;//商品价格
    private String images;//图片地址
}

配置类

docs.spring.io/spring-data…

  • ElasticsearchRestTemplate是spring-data-elasticsearch项目中的一个类,和其他spring项目中的template类似。
  • 在新版的spring-data-elasticsearch中,ElasticsearchRestTemplate代替了原来的ElasticsearchTemplate。原因是ElasticsearchTemplate基于TransportClient,TransportClient即将在8.x以后的版本中移除。所以,我们推荐使用ElasticsearchRestTemplate。
  • **ElasticsearchRestTemplate基于RestHighLevelClient客户端的。**需要自定义配置类,继承AbstractElasticsearchConfiguration,并实现elasticsearchClient()抽象方法,创建RestHighLevelClient对象。

配置类继承AbstractElasticsearchConfiguration类


import lombok.Data;
import org.apache.http.HttpHost;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestClientBuilder;
import org.elasticsearch.client.RestHighLevelClient;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.support.DefaultConversionService;
import org.springframework.data.elasticsearch.client.ClientConfiguration;
import org.springframework.data.elasticsearch.client.RestClients;
import org.springframework.data.elasticsearch.config.AbstractElasticsearchConfiguration;
import org.springframework.data.elasticsearch.core.ElasticsearchEntityMapper;
import org.springframework.data.elasticsearch.core.EntityMapper;
 
@ConfigurationProperties(prefix = "elasticsearch")
@Configuration
@Data
public class ElasticsearchConfig extends AbstractElasticsearchConfiguration {
    private String host ;
    private Integer port ;
 
    //重写父类方法
    @Override
    public RestHighLevelClient elasticsearchClient() {
        RestClientBuilder builder = RestClient.builder(new HttpHost(host, port));
        RestHighLevelClient restHighLevelClient = new RestHighLevelClient(builder);
        return restHighLevelClient;
}
}

生成一个RestHighLevelclient的bean,而ElasticsearchRestTemplate基于RestHighLevelclient的。

Dao

集成接口ElasticsearchRepository


import com.atguigu.es.entities.Product;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
import org.springframework.stereotype.Repository;
 
@Repository
public interface ProductDao extends ElasticsearchRepository<Product,Long> {
 
}

索引库操作

1.实体类索引库以及映射配置


import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;
 
import java.io.Serializable;
 
/**
 * 商品实体类:
 * @Document()注解作用:定义一个索引库,一个类型
 *
 * indexName:定义索引库名称
 * type:定义类型名称
 * shards:指定分片数量
 * replicas:指定副本数量
 */
@Data
@NoArgsConstructor
@AllArgsConstructor
@ToString
@Document(indexName = "shopping", type = "product", shards = 5, replicas = 1)
public class Product implements Serializable {
    //必须有id,这里的id是全局唯一的标识,等同于es中的"—id"
    @Id
    private Long id;//商品唯一标识
 
    /**
     * type : 字段数据类型
     * analyzer : 分词器类型
     * index : 是否索引(默认:true)
     * Keyword : 短语,不进行分词
     */
    @Field(type = FieldType.Text, analyzer = "ik_max_word")
    private String title;//商品名称
 
    @Field(type = FieldType.Keyword)
    private String category;//分类名称
 
    @Field(type = FieldType.Double)
    private Double price;//商品价格
 
    @Field(type = FieldType.Keyword, index = false)
    private String images;//图片地址
}

2.索引库操作


import com.atguigu.es.entities.Product;
import org.elasticsearch.client.ElasticsearchClient;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.test.context.junit4.SpringRunner;
 
@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringDataESIndexTest {
 
    //注入ElasticsearchRestTemplate
    @Autowired
    private ElasticsearchRestTemplate elasticsearchRestTemplate;
 
    //创建索引并增加映射配置
    @Test
    public void createIndex(){
        //创建索引
        boolean index = elasticsearchRestTemplate.createIndex(Product.class);
        System.out.println("index = " + index);
        index = elasticsearchRestTemplate.putMapping(Product.class);
        System.out.println("index = " + index);
    }
 
    //删除索引
    @Test
    public void deleteIndex(){
        boolean index = elasticsearchRestTemplate.deleteIndex(Product.class);
        System.out.println("index = " + index);
    }
 

文档操作

1.简单的增删改查


import com.atguigu.es.dao.ProductDao;
import com.atguigu.es.entities.Product;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.test.context.junit4.SpringRunner;
 
import java.util.ArrayList;
import java.util.List;
 
@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringDataESProductDaoTest {
    @Autowired
    private ProductDao productDao;
 
    /**
     * 新增
     */
    @Test
    public void save(){
        Product product = new Product();
        product.setId(1L);
        product.setTitle("小米手机");
        product.setCategory("手机");
        product.setPrice(1999.0);
        product.setImages("http://www.gulixueyuan/xm.jpg");
        productDao.save(product);
    }
 
    //修改
    @Test
    public void update(){
        Product product = new Product();
        product.setId(1L);
        product.setTitle("小米2手机");
        product.setCategory("手机");
        product.setPrice(9999.0);
        product.setImages("http://www.gulixueyuan/xm.jpg");
        productDao.save(product);
    }
 
    //根据id查询
    @Test
    public void findById(){
        Product product = productDao.findById(1L).get();
        System.out.println(product);
}

    //查询所有
    @Test
    public void findAll(){
        Iterable<Product> products = productDao.findAll();
        for (Product product : products) {
            System.out.println(product);
        }
    }
 
    //删除
    @Test
    public void delete(){
        Product product = new Product();
        product.setId(1L);
        productDao.delete(product);
    }
 
    //批量新增
    @Test
    public void saveAll(){
        List<Product> productList = new ArrayList<>();
        for (int i = 0; i < 100; i++) {
            Product product = new Product();
            product.setId(Long.valueOf(i));
            product.setTitle("["+i+"]小米手机");
            product.setCategory("手机");
            product.setPrice(1999.0+i);
            product.setImages("http://www.gulixueyuan/xm.jpg");
            productList.add(product);
        }
        productDao.saveAll(productList);
}

    //分页查询
    @Test
    public void findByPageable(){
        //设置排序(排序方式,正序还是倒序,排序的id)
        Sort sort = Sort.by(Sort.Direction.DESC,"id");
        int currentPage=1;//当前页,第一页从0开始,1表示第二页
        int pageSize = 20;//每页显示多少条
        //设置查询分页
        PageRequest pageRequest = PageRequest.of(currentPage, pageSize,sort);
        //分页查询
        Page<Product> productPage = productDao.findAll(pageRequest);
        System.out.println(productPage);
    }
}

2.Search查询


import com.atguigu.es.dao.ProductDao;
import com.atguigu.es.entities.Product;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.index.query.TermQueryBuilder;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.domain.PageRequest;
import org.springframework.test.context.junit4.SpringRunner;
 
@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringDataESSearchTest {
    @Autowired
    private ProductDao productDao;
 
    /**
     * term查询
     * search(termQueryBuilder) 调用搜索方法,参数查询构建器对象
     */
    @Test
    public void termQuery(){
        TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("title", "小米");
        Iterable<Product> products = productDao.search(termQueryBuilder);
        for (Product product : products) {
            System.out.println(product);
        }
    }
    
    /**
     * term查询加分页
     */
    @Test
    public void termQueryByPage(){
        int currentPage= 0 ;
        int pageSize = 5;
        //设置查询分页
        PageRequest pageRequest = PageRequest.of(currentPage, pageSize);
        TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("title", "小米");
        Iterable<Product> products = productDao.search(termQueryBuilder,pageRequest);
        for (Product product : products) {
            System.out.println(product);
        }
    }
}

3.自定义Dao查询方法

关键字命名规则解释示例
andfindByField1AndField2根据Field1和Field2获得数据findByTitleAndPrice
orfindByField1OrField2根据Field1或Field2获得数据findByTitleOrPrice
isfindByField根据Field获得数据findByTitle
notfindByFieldNot根据Field获得补集数据findByTitleNot
betweenfindByFieldBetween获得指定范围的数据findByPriceBetween
lessThanEqualfindByFieldLessThan获得小于等于指定值的数据findByPriceLessThan
持久化Dao接口
import com.atguigu.es.entities.Product;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
import java.util.List;
 
/**
 * 继承持久化层接口的Elasticsearch的模板接口
 */
public interface ProductDao extends ElasticsearchRepository<Product,Long> {
 
    /**
     * 根据title和价格查询,and的关系
     */
    List<Product> findByTitleAndPrice(String title, Double price);
 
    /**
     * 根据商品价格范围查询
     * 最低价格lowPrice
     * 最高价格highPrice
     */
    List<Product> findByPriceBetween(Double lowPrice,Double highPrice);
    
}

测试:自定义Dao方法


import com.atguigu.es.dao.ProductDao;
import com.atguigu.es.entities.Product;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
 
import java.util.List;
 
@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringDataESCustomDaoMethodTest {
    @Autowired
    private ProductDao productDao;
 
    /**
     * 根据标题及价格查询
     * 要求价格等于2050且标题的内容包含小米关键词
     */
    @Test
    public void findAllByTitleAndPrice(){
        String title = "小米";
        Double price = 2050.0;
        List<Product> products = productDao.findByTitleAndPrice(title, price);
        for (Product product : products) {
            System.out.println(product);
        }
    }
    /**
     * 根据价格范围查询,包含最低和最高的价格。
     * 要求商品价格再2000,到2005之间
     */
    @Test
    public void findPriceBetween(){
        double lowPrice = 2000.0;//最低价
        double highPrice = 2005.0;//最高价
        List<Product> products = productDao.findByPriceBetween(lowPrice, highPrice);
        for (Product product : products) {
            System.out.println(product);
        }
    }
}

ElasticSearch集群

分片和复制shards&replicas

​ 一个索引可以存储超过单个节点硬件限制的大量数据,比如,一个具有10亿文档的索引占据1TB的磁盘空间,而任一节点都没有这样大的磁盘空间,或者单个节点处理搜索请求,响应太慢。为了解决这个问题ES提供了将索引划分成多份的能力,这些份就叫做分片。当你创建一个索引的时候,你可以指定你想要的分片的数量。每个分片本身也是一个功能完善并且独立的“索引”,这个索引可以被防止到集群的任何节点上。

​ 分片很重要,主要有两方面的原因:

  1. 允许你水平分割/扩展你的内容容量
  2. 允许你在分片(潜在的,位于多个节点上)之上进行分布式的、并行的操作,进而提高性能/吞吐量。

至于一个分片怎样分布,它的文档怎样聚合回搜索请求,是完全由ES管理的,对用户来说这些都是完全透明不可见的。

​ 在一个网络/云的环境里,失败随时可能发生,在某个分片/节点不知怎么就处于离线状态,或者由于任何原因消失了,这种情况下,有一个故障转移机制是非常有用并且是强力推荐的。为此目的,ES允许用户创建分片的一份或者多份拷贝,这些拷贝叫做复制分片(副本)。

​ 复制之所以重要,有两个主要原因:在分片/节点失败的情况下,提高了高可用性。因为这个原因,**需要注意到复制分片从不与原/主要分片置于同一个检点上。**扩展搜索量或吞吐量,因为搜索可以在所有的复制上并行运行。总之,每个索引可以被分成多个分片。一个索引也可以没有复制或者多次复制,一旦复制了,每个索引就有了主分片(作为复制源的原来的分片)和复制分片(主分片的拷贝)之别。分片和复制的数量可以在索引创建的时候指定。在索引创建之后,你可以在任何时候动态的改变复制的数量,但是事后就不能改变分片的数量了

​ 默认情况下,ES中的每个索引被分片成5个主分片和一个复制,这就意味着,如果你的集群中至少有两个节点,那么你的索引将会有5个主分片和另外5个复制分片(1个完全拷贝),这样的话每个索引总共就有10个分片。

如何搭建集群

此处讨论的是在一台机器上做的集群。

1.准备三台es服务器

创建elasticsearch-cluster文件夹,在内部复制三个elasticsearch服务

2.修改每台服务器的配置

修改elasticsearch-cluster\node*\config\elasticsearch.yml配置文件

注意:

  • 同一集群名称(唯一)
  • ip:本机
  • 节点名(唯一)
  • 服务端口号(唯一)
  • 集群通信端口号(唯一)
  • 发现机器IP集合(全部节点,包括自己)
  1. node1节点:

    #节点1的配置信息:
    #集群名称,保证唯一
    cluster.name: my-elasticsearch
    #默认为true。设置为false禁用磁盘分配决定器。
    cluster.routing.allocation.disk.threshold_enabled: false
    #节点名称,必须不一样
    node.name: node-1
    #必须为本机的ip地址
    network.host: 127.0.0.1
    #服务端口号,在同一机器下必须不一样
    http.port: 9201
    #集群间通信端口号,在同一机器下必须不一样
    transport.tcp.port: 9301
    #设置集群自动发现机器ip集合
    discovery.zen.ping.unicast.hosts: ["127.0.0.1:9301","127.0.0.1:9302","127.0.0.1:9303"]
    
    
  2. node2节点

    #节点2的配置信息:
    #集群名称,保证唯一
    cluster.name: my-elasticsearch
    #默认为true。设置为false禁用磁盘分配决定器。
    cluster.routing.allocation.disk.threshold_enabled: false
    #节点名称,必须不一样
    node.name: node-2
    #必须为本机的ip地址
    network.host: 127.0.0.1
    #服务端口号,在同一机器下必须不一样
    http.port: 9202
    #集群间通信端口号,在同一机器下必须不一样
    transport.tcp.port: 9302
    #设置集群自动发现机器ip集合
    discovery.zen.ping.unicast.hosts: ["127.0.0.1:9301","127.0.0.1:9302","127.0.0.1:9303"]
    
    
  3. node3节点

    #节点3的配置信息:
    #集群名称,保证唯一
    cluster.name: my-elasticsearch
    #默认为true。设置为false禁用磁盘分配决定器。
    cluster.routing.allocation.disk.threshold_enabled: false
    #节点名称,必须不一样
    node.name: node-3
    #必须为本机的ip地址
    network.host: 127.0.0.1
    #服务端口号,在同一机器下必须不一样
    http.port: 9203
    #集群间通信端口号,在同一机器下必须不一样
    transport.tcp.port: 9303
    #设置集群自动发现机器ip集合
    discovery.zen.ping.unicast.hosts: ["127.0.0.1:9301","127.0.0.1:9302","127.0.0.1:9303"]
    

3.启动所有节点

先清理掉之前数据:删除elasticsearch-cluster\node\data目录下的nodes目录*

双击elasticsearch-cluster\node*\bin\elasticsearch.bat

集群测试软件

在chrome浏览器的扩展程序中安装ES插件ElasticSearch-head

输入三个IP中任意一个即可查询。

服务器运行状态

命令: GET _cluster/health

Green

所有的主分片和副本分片都已分配。你的集群、是 100% 可用的。

yellow

所有的主分片已经分片了,但至少还有一个副本是缺失的。不会有数据丢失,所以搜索结果依然是完整的。不过,你的高可用性在某种程度上被弱化。如果 更多的 分片消失,你就会丢数据了。把 yellow 想象成一个需要及时调查的警告。

red

至少一个主分片(以及它的全部副本)都在缺失中。这意味着你在缺少数据:搜索只能返回部分数据,而分配到这个分片上的写入请求会返回一个异常。