1、ElasticSearch简介
1.1 什么是ElasticSearch
Elasticsearch,简称为es,是一款开源的高扩展的分布式全文搜索服务,它可以近乎实时的检索数据;本身扩展性很好,可以扩展到上百台服务器,处理PB级别的数据。es也是使用Java开发并使用Lucene作为其核心来实现搜索的功能,但是它是通过简单的RESTful API来隐藏Lucene的复杂性,从而让全文搜索变得简单。
1.2 ElasticSearch使用案例
-
GitHub:2013年初,GitHub抛弃了Solr,采取ElasticSearch来做PB级的搜索。“GitHub使用ElasticSearch搜索20TB的数据,包括13亿文件和1300亿行代码”。
-
维基百科:启动以elasticsearch为基础的核心搜索架构。
-
SoundCloud:“SoundCloud使用ElasticSearch为1.8亿用户提供即时而精准的音乐搜索服务”。
-
百度:百度目前广泛使用ElasticSearch作为文本数据分析,采集百度所有服务器上的各类指标数据及用户自定义数据,通过对各种数据进行多维分析展示,辅助定位分析实例异常或业务层面异常。目前覆盖百度内部20多个业务线(包括casio、云分析、网盟、预测、文库、直达号、钱包、风控等),单集群最大100台机器,200个ES节点,每天导入30TB+数据。
-
新浪使用ES 分析处理32亿条实时日志。
-
阿里使用ES 构建自己的日志采集和分析体系。
1.3 为什么不直接用Lucene?
-
当多个系统都需要用到搜索的时候,需要单独部署一套,共用性差。
-
lucene不能做集群,当数据量越来越大,无法动态扩展,扩展性差。
1.4 ElasticSearch对比Solr
-
Solr利用 Zookeeper 进行分布式管理,而 Elasticsearch 自身带有分布式协调管理功能。
-
Solr支持更多格式的数据,而Elasticsearch仅支持json文件格式。
-
Solr官方提供的高级功能更多,例如自带控制台可查询数据,而Elasticsearch更注重于核心功能:索引流程和检索流程。
-
Solr在建立索引库时会产生io阻塞,导致查询性能变差,并且随着数据量的增加,Solr的搜索效率会变得更低,而Elasticsearch却没有明显的变化。综上所述,Solr是传统搜索应用的有力解决方案,而Elasticsearch 更适用于实时搜索应用。
2、ElasticSearch:核心概念
2 .1概述
Elasticsearch面向文档(document oriented)的,这意味着它可以存储整个对象或文档(document)。然而它不仅仅是存储,还会索引(index)每个文档的内容使之可以被搜索。在Elasticsearch中,你可以对文档进行索引、搜索、排序、过滤。
关系型数据库与ES索引库类比:
2.2 核心概念
近实时 NRT(Near Realtime)
Elasticsearch是一个接近实时的搜索平台。这意味着,从索引一个文档直到这个文档能够被搜索到有一个轻微的延迟(通常是1秒以内)
每隔1s刷新buffer缓存区数据到segment段,对搜索(需要使用倒排索引)可见,因此是近实时
节点 node
节点是一个Elasticsearch实例,本质上就是一个java进程,节点也有一个名称(默认是随机分配的),当然也可以通过配置文件配置,或者在启动的时候,-E node.name=node1指定。此名称对于管理目的很重要,因为您希望确定网络中的哪些服务器对应于ElasticSearch集群中的哪些节点。
在Elasticsearch中,节点的类型主要分为如下几种:
-
master eligible节点:有资格参加选举成为Master的节点,默认为true,可以通过node.master: false设置。
-
data节点:保存数据的节点,负责保存分片数据,默认为true,在数据扩展上起到了至关重要的作用。
-
Coordinating 节点:负责接收客户端请求,将请求发送到合适的节点,最终把结果汇集到一起返回,默认为true。
开发环境中一个节点可以承担多个角色,生产环境中,建议设置单一的角色,可以提高性能等
集群 cluster
一个集群就是由一个或多个节点组织在一起,它们共同持有整个的数据,并一起提供索引和搜索功能。一个集群由一个唯一的名字标识,这个名字默认就是“elasticsearch”,这个名字是重要的,因为一个节点只能通过指定某个集群的名字,来加入这个集群。
索引库 index(相当于mysql的database实例)
索引是具有某种相似特性的文档集合。例如,您可以拥有客户数据的索引、产品目录的另一个索引以及订单数据的另一个索引。索引由一个名称(必须全部是小写)标识。在单个集群中,您可以定义任意多个索引。每个索引都有自己的mapping定义,用于定义包含文档的字段名和字段类型。可以将其暂时理解为 MySql中的 database。
索引的包含两个部分:
-
mapping:定义文档字段(field)的属性
-
setting:定义分片数和副本数(默认5个分片,每个分片一个副本)
类型 type
相当于mysql的表,但是在一个索引库中不能再创建多个类型,在以后的版本中也将删除类型的整个概念。
文档 document(相当于数据库表中的一行数据)
一个文档是一个可被索引的基础信息单元。比如,你可以拥有某一个客户的文档,某一个产品的一个文档,当然,也可以拥有某个订单的一个文档。文档以JSON(Javascript Object Notation)格式来表示。 在一个index/type里面,你可以存储任意多的文档。注意,尽管一个文档,物理上存在于一个索引之中,但文档必须被索引/赋予一个索引的type。
分片
索引库可能存储大量数据,这些数据可能会超出单个节点的硬件限制。例如:占用1TB磁盘空间的10亿个文档的单个索引可能不适合单个节点的磁盘,或者速度太慢,无法单独满足单个节点的搜索请求(把索引库的数据分成多个部分存储在不同的节点上)。
为了解决这个问题,ElasticSearch提供了将索引细分为多个片段(称为分片shard)的能力。创建索引时,只需定义所需的分片数量。每个分片本身就是一个完全功能性和独立的"索引",可以托管在集群中的任何节点上。
为什么要分片?
-
它允许您水平拆分/缩放索引数据。
-
它允许您跨分片(可能在多个节点上)分布和并行操作,从而提高查询性能/吞吐量。
如何分配分片以及如何将其文档聚合回搜索请求的机制完全由ElasticSearch管理,并且对作为用户的您是透明的。分片数在索引库创建时指定,后续不允许修改,除非重新创建索引。
副本
副本即对分片数据的备份,为什么要有副本?
-
当分片/节点发生故障时提供高可用性。因此,需要注意的是,副本分片永远不会分配到复制它的原始/主分片所在的节点上。
-
提高搜索的并发量,可以在所有副本上并行执行搜索。
在创建索引时为每个索引定义分片和副本的数量。创建索引后,您还可以随时动态更改副本的数量,但是不能修改分片的数量。建议在创建索引时就考虑好分片和副本的数量。默认情况下,ElasticSearch为每个索引分配5个分片,每个分片1个副本。
其中A、B是节点,a1、a2是分片,b1、b2为副本,把分片和副本交叉放到不同的节点,保证最大程度的利用服务器资源。
倒排索引
-
DocID:出现某单词的文档ID
-
TF(词频):单词在该文档中出现的次数
-
POS:单词在文档中的位置
3.ES的REST风格API:基本CRUD
Elasticsearch提供了Rest风格的API,即http请求接口,也提供了各种语言的客户端API
Rest风格API
文档地址:https://www.elastic.co/guide/en/elasticsearch/reference/current/index.html
1.创建索引
Elasticsearch采用Rest风格API,因此其API就是一次http请求,你可以用任何工具发起http请求
创建索引的请求格式:
-
请求方式:PUT
-
请求路径:/索引库名
-
请求参数:json格式:
{
"settings": {
"number_of_shards": 3,
"number_of_replicas": 2
}
}
settings:索引库的设置
-
number_of_shards:分片数量
-
number_of_replicas:副本数量
kibana的控制台,可以对http请求进行简化,示例:
创建goods索引:
相当于是省去了elasticsearch的服务器地址
而且还有语法提示,非常舒服。
2.删除索引
删除索引使用DELETE请求
DELETE 索引库名
3.新增数据
随机生成id
通过POST请求,可以向一个已经存在的索引库中添加数据。
POST /索引库名/类型名
{
"key":"value"
}
示例:
POST /goods/docs
{
"title":"小米手机",
"images":"http://image.leyou.com/12479122.jpg",
"price":2699.00
}
自定义id
POST /索引库名/类型/id值
{
...
}
示例:
POST /goods/docs/2
{
"title":"大米手机",
"images":"http://image.leyou.com/12479122.jpg",
"price":2899.00
}
4.修改数据
把刚才新增的请求方式改为PUT,就是修改了。不过修改必须指定id,
-
id对应文档存在,则修改
-
id对应文档不存在,则新增
比如,我们把id为3的数据进行修改:
PUT /goods/docs/2
{
"title":"超大米手机",
"images":"http://image.leyou.com/12479122.jpg",
"price":3899.00,
"stock": 100,
"saleable":true
}
5.删除数据
删除使用DELETE请求,同样,需要根据id进行删除:
DELETE /索引库名/类型名/id值
4、ES的REST风格API
1.查询基本语法
基本语法
GET /索引库名/_search
{
"query":{
"查询类型":{
"查询条件":"查询条件值"
}
}
}
这里的query代表一个查询对象,里面可以有不同的查询属性
查询类型:
例如:
match_all
,match
,term
,range
等等查询条件:查询条件会根据类型的不同,写法也有差异,后面详细讲解
2.查询所有(match_all)
-
query
:代表查询对象 -
match_all
:代表查询所有
示例:
GET /goods/_search
{
"query":{
"match_all": {}
}
}
3.匹配查询(match)
匹配查询(会先分词再匹配)
GET /goods/_search
{
"query": {
"match": {
"title": "小米手机"
}
}
}
在上面的案例中,不仅会查询到电视,而且与小米相关的都会查询到,多个词之间是or
的关系。
-
and关系
某些情况下,我们需要更精确查找,我们希望这个关系变成and
,可以这样做:
GET /heima/_search
{
"query":{
"match": {
"title": {
"query": "小米电视",
"operator": "and"
}
}
}
}
本例中,只有同时包含小米
和电视
的词条才会被搜索到。
4.多字段查询(multi_match)
multi_match
与match
类似,不同的是它可以在多个字段中查询
GET /goods/_search
{
"query": {
"multi_match": {
"query": "小米高端大气",
"fields": ["title","subTitle"]
}
}
}
5.词条匹配(term)
term
查询被用于精确值 匹配,这些精确值可能是数字、时间、布尔或者那些未分词的字符串
GET /goods/_search
{
"query": {
"term": {
"price": {
"value": 2500
}
}
}
}
6.多词条精确匹配(terms)
terms
查询和 term 查询一样,但它允许你指定多值进行匹配。如果这个字段包含了指定值中的任何一个值,那么这个文档满足条件:
GET /goods/_search
{
"query": {
"terms": {
"price": [
4899.0,
2699.0
]
}
}
}
7.结果过滤(_source)
默认情况下,elasticsearch在搜索的结果中,会把文档中保存在_source
的所有字段都返回。
如果我们只想获取其中的部分字段,我们可以添加_source
的过滤
直接指定字段
GET /goods/_search
{
"query": {
"term": {
"price": 4899.0
}
},
"_source": ["title","price"]
}
指定includes和excludes
我们也可以通过:
-
includes:来指定想要显示的字段
-
excludes:来指定不想要显示的字段
二者都是可选的。
includes示例:
GET /goods/_search
{
"query": {
"term": {
"price": 4899.0
}
},
"_source":{
"includes": ["title","subTitle"]
}
}
8.布尔组合(bool)
bool
把各种其它查询通过must
(与)、must_not
(非)、should
(或)的方式进行组合
GET /goods/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"title": "小米"
}
},
{
"match": {
"subTitle": "小米"
}
}
]
}
}
}
9.过滤条件(filter)
所有的查询都会影响到文档的评分及排名。如果我们需要在查询结果中进行过滤,并且不希望过滤条件影响评分,那么就不要把过滤条件作为查询条件来用。而是使用filter
方式:
注意:filter
通常和bool一起使用(在bool基础上进行过滤)。
GET /goods/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"title": "小米"
}
},
{
"match": {
"subTitle": "小米"
}
}
],
"filter": {
"term": {
"category.keyword": "手机"
}
}
}
}
}
注意:如果我们使用过滤条件,那么过滤后的结果不会影响原来结果的排序。反之,如果不用过滤,则会影响原来结果排序
5、ES的REST风格API
聚合查询就是分组统计
聚合可以让我们极其方便的实现对数据的统计、分析。例如:
-
什么品牌的手机最受欢迎?
-
这些手机的平均价格、最高价格、最低价格?
-
这些手机每月的销售情况如何?
实现这些统计功能的比数据库的sql要方便的多,而且查询速度非常快,可以实现实时搜索效果。
1)聚合基本概念
Elasticsearch中的聚合,包含多种类型,最常用的两种,一个叫桶
,一个叫度量
:
桶(bucket)
桶的作用,是按照某种方式对数据进行分组,每一组数据在ES中称为一个桶
,例如我们根据国籍对人划分,可以得到中国桶
、英国桶
,日本桶
……或者我们按照年龄段对人进行划分:0~10,10~20,20~30,30~40等。
Elasticsearch中提供的划分桶的方式有很多:
-
Date Histogram Aggregation:根据日期阶梯分组,例如给定阶梯为周,会自动每周分为一组
-
Histogram Aggregation:根据数值阶梯分组,与日期类似
-
Terms Aggregation:根据词条内容分组,词条内容完全匹配的为一组
-
Range Aggregation:数值和日期的范围分组,指定开始和结束,然后按段分组
-
……
bucket aggregations 只负责对数据进行分组,并不进行计算,因此往往bucket中往往会嵌套另一种聚合:metrics aggregations即度量
度量(metrics)
分组完成以后,我们一般会对组中的数据进行聚合运算,例如求平均值、最大、最小、求和等,这些在ES中称为度量
比较常用的一些度量聚合方式:
-
Avg Aggregation:求平均值
-
Max Aggregation:求最大值
-
Min Aggregation:求最小值
-
Percentiles Aggregation:求百分比
-
Stats Aggregation:同时返回avg、max、min、sum、count等
-
Sum Aggregation:求和
-
Top hits Aggregation:求前几
-
Value Count Aggregation:求总数
-
……
2)聚合为桶 (Aggragation for Buket)
聚合为桶其实就是分组统计!
首先,我们按照 汽车的颜色color
来划分桶
GET /cars/_search
{
"size" : 0,
"aggs" : {
"popular_colors" : {
"terms" : {
"field" : "color"
}
}
}
}
-
size: 查询条数,这里设置为0,因为我们不关心搜索到的数据,只关心聚合结果,提高效率
-
aggs:声明这是一个聚合查询,是aggregations的缩写
-
popular_colors:给这次聚合起一个名字,任意。
-
terms:划分桶的方式,这里是根据词条划分
-
field:划分桶的字段
-
-
-
结果:
{
"took": 1,
"timed_out": false,
"_shards": {
"total": 1,
"successful": 1,
"skipped": 0,
"failed": 0
},
"hits": {
"total": 8,
"max_score": 0,
"hits": []
},
"aggregations": {
"popular_colors": {
"doc_count_error_upper_bound": 0,
"sum_other_doc_count": 0,
"buckets": [
{
"key": "red",
"doc_count": 4
},
{
"key": "blue",
"doc_count": 2
},
{
"key": "green",
"doc_count": 2
}
]
}
}
}
-
hits:查询结果为空,因为我们设置了size为0
-
aggregations:聚合的结果
-
popular_colors:我们定义的聚合名称
-
buckets:查找到的桶,每个不同的color字段值都会形成一个桶
-
key:这个桶对应的color字段的值
-
doc_count:这个桶中的文档数量
-
# 聚合查询(分组统计)
# AGG_TYPE: 聚合类型
# 常见的聚合类型
# terms: 按数量聚合(统计),类似SQL的count(*)
# avg: 按均值聚合(统计),类似SQL的avg(age)
# max: 按最大值聚合(统计),类似SQL的max(age)
# min: 按最小聚合(统计),类似SQL的min(age)
# sum: 按求和聚合(统计),类似SQL的sum(score)#统计不同颜色的汽车分别有多少部?
GET /cars/_search
{
"aggs": {
"colorAgg": {
"terms": {
"field": "color"
}
}
}
}# 统计所有汽车的均价
GET /cars/_search
{
"aggs": {
"priceAgg": {
"avg": {
"field": "price"
}
}
}
}#查询honda的汽车的均价?
GET /cars/_search
{
"query": {
"term": {
"make": {
"value": "honda"
}
}
},
"aggs": {
"priceAgg": {
"avg": {
"field": "price"
}
}
}
}
# 统计不同颜色的汽车的均价分别是多少?GET /cars/_search
{
"aggs": {
"colorAgg": {
"terms": {
"field": "color"
},
"aggs": {
"priceAgg": {
"avg": {
"field": "price"
}
}
}
}
}
}
6、SpringDataElasticsearch
1.SpringDataElasticsearch简介
Spring Data Elasticsearch是Spring Data项目下的一个子模块。
查看 Spring Data的官网:http://projects.spring.io/spring-data/
特征:
-
支持Spring的基于
@Configuration
的java配置方式,或者XML配置方式 -
提供了用于操作ES的便捷工具类
ElasticsearchTemplate
。包括实现文档到POJO之间的自动智能映射。 -
利用Spring的数据转换服务实现的功能丰富的对象映射
-
基于注解的元数据映射方式,而且可扩展以支持更多不同的数据格式
-
根据持久层接口自动生成对应实现方法,无需人工编写基本操作代码(类似mybatis,根据接口自动得到实现)。当然,也支持人工定制查询
2.搭建SpringDataElasticsearch环境
创建项目及导入依赖
pom依赖:
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion><groupId>com.itheima</groupId>
<artifactId>spring-data-es</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.6.RELEASE</version>
<relativePath/>
</parent><dependencies>
<!--spring data es-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
</dependencies>
</project>
application.yml配置
spring:
data:
elasticsearch:
cluster-name: elasticsearch # 集群名称
cluster-nodes: 127.0.0.1:9300 # 节点地址
编写启动类
package com.it;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
/**
*
*/
@SpringBootApplication
public class ESApplication {
public static void main(String[] args) {
SpringApplication.run(ESApplication.class,args);
}
}
实体类及注解
映射
Spring Data通过注解来声明字段的映射属性,有下面的三个注解:
-
@Document
作用在类,标记实体类为文档对象,一般有四个属性-
indexName:对应索引库名称
-
type:对应在索引库中的类型
-
shards:分片数量,默认5
-
replicas:副本数量,默认1
-
-
@Id
作用在成员变量,标记一个字段作为id主键 -
@Field
作用在成员变量,标记为文档的字段,并指定字段映射属性:-
type:字段类型,取值是枚举:FieldType
-
index:是否索引,布尔类型,默认是true
-
store:是否存储,布尔类型,默认是false
-
analyzer:分词器名称:ik_max_word
-
package com.it.pojo;
import lombok.Data;
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;
/**
* @Document: 映射索引库中的文档
* indexName: 索引库名称
* type: 类型
* shards:分片
* replicas:副本
* @Id: 映射文档ID
* @Field
* type: 字段的类型
* Text: 文本类型,分词的
* Keyword: 文本类型,不分词的
* Integer、Long、Float、Double: 数值类型,必须不分词的
* Boolean:布尔类型,必须不分词的
* Date: 日期类型,必须不分词的
* Object: 自定义对象或集合(List,Set,Map等),所有对象里面的属性都是索引和分词的
* index:
* 该字段是否索引 默认为true
* analyzer
* 指定分词器的算法
* 如:ik分词器的算法
* ik_smart: 最小分词 我是程序员 -> 我 是 程序员
* ik_max_word: 最细分词 我是程序员 -> 我 是 程序员 程序 员
*/
@Data
@Document(indexName = "goods",type = "docs" ,shards = 1,replicas = 1)
public class Item {
@Id
Long id;
@Field(type = FieldType.Text,analyzer ="ik_max_word" )
String title; //标题
@Field(type = FieldType.Keyword)
String category;// 分类
@Field(type = FieldType.Keyword)
String brand; // 品牌
@Field(type = FieldType.Double)
Double price; // 价格
@Field(type = FieldType.Keyword,index = false)
String images; // 图片地址
}
7、SpringDataElasticsearch:基本CRUD
1.编写Repository接口
Spring Data 的强大之处,就在于你不用写任何DAO处理,自动根据方法名或类的信息进行CRUD操作。只要你定义一个接口,然后继承Repository提供的一些子接口,就能具备各种基本的CRUD功能。
我们只需要定义接口,然后继承它就OK了。
package com.it.repository;
import com.it.pojo.Item;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
/**
* Dao接口
* 泛型一:操作的实体类类型
* 泛型二:实体类的ID类型
*/
public interface ItemRepository extends ElasticsearchRepository<Item,Long>{
}
2.增删改查
package com.it;
import com.it.pojo.Item;
import com.it.repository.ItemRepository;
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.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
/**
*新增文档
*/
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = ESApplication.class)
public class EsDemo1 {
@Autowired
private ItemRepository itemRepository;
@Test
public void save(){
Item item = new Item(1L, "小米手机7", "手机",
"小米", 3499.00, "http://image.leyou.com/13123.jpg");
itemRepository.save(item);
}
/**
* 批量新增索引
*/
@Test
public void testBatchSave(){
List<Item> list = new ArrayList<>();
list.add(new Item(2L, "坚果手机R1", "手机","锤子", 3699.00, "http://image.leyou.com/13124.jpg"));
list.add(new Item(3L, "华为META10", "手机","华为", 4499.00, "http://image.leyou.com/13125.jpg"));
list.add(new Item(4L, "小米电视1", "电视","小米", 5499.00, "http://image.leyou.com/13123.jpg"));
list.add(new Item(5L, "小米手机8", "手机","小米", 2199.00, "http://image.leyou.com/13124.jpg"));
itemRepository.saveAll(list);
}
/**
* 修改索引
*/
@Test
public void testUpdate(){
Item item = new Item(1L, "小米手机8", "手机",
"小米", 4499.00, "http://image.leyou.com/13124.jpg");
itemRepository.save(item);
}
/**
* 查询所有
*/
@Test
public void testFindAll(){
Iterable<Item> list = itemRepository.findAll();
list.forEach(System.out::println);
}
/**
* 根据id查询
*/
@Test
public void testFindById(){
Item item = itemRepository.findById(1L).get();
System.out.println(item);
}
/**
* 删除索引
*/
@Test
public void testDelete(){
Item item = new Item();
item.setId(1L);
itemRepository.delete(item);
}
}
8、SpringDataElasticsearch:高级查询(重点)
如果要完成更加复杂的查询,同时包含条件,分页,高亮,聚合等操作,需要用到ElasticsearchTemplate对象
8.1基本条件查询
# 基本查询
GET /goods/_search
{
"query": {
"match": {
"title": "小米"
}
}
}
@Test
public void test1(){
//1.创建本地查询构造器对象: 用于封装所有查询条件
NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
//添加query条件
//注意:所有的Query条件都是使用QueryBuilders类构造的
queryBuilder.withQuery(QueryBuilders.matchQuery("title","小米"));
//2.执行查询(执行本地查询),获取结果
/**
* 参数一:本地查询对象
* 参数二:需要封装数据的实体类
*/
List<Item> items = esTemplate.queryForList(queryBuilder.build(), Item.class);
//3.处理结果
items.forEach(System.out::println);
}
8.2分页查询
# 分页
# from: 起始索引行号,从0开始
# size: 页面大小
GET /goods/_search
{
"query": {
"match": {
"title": "小米"
}
},
"from": 0 ,
"size": 2
}
/**
* 2)分页条件查询
*/
@Test
public void testPageQuery(){
NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
//1. 添加Query条件
queryBuilder.withQuery( QueryBuilders.matchQuery("title","小米") );
//实际页面传递的参数
int page = 1;//页面传递的当前页码
int size = 2;//页面大小
//2.添加分页参数
/**
* page: ES的当前页码(page从0开始计算)
* size: 页面大小
*/
queryBuilder.withPageable(PageRequest.of(page-1,size));
//Page: 用于封装分页查询结果
Page<Item> pageBean = esTemplate.queryForPage(queryBuilder.build(),Item.class);
System.out.println("总记录数:"+pageBean.getTotalElements());
System.out.println("总页数:"+pageBean.getTotalPages());
pageBean.getContent().forEach(System.out::println);
}
8.3布尔组合+过滤查询(*)
# 布尔+过滤
GET /goods/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"title": "小米"
}
}
],
"filter": {
"term": {
"category": "手机"
}
}
}
}
}
/**
* 3)布尔+过滤
*/
@Test
public void testBoolQueryAndFilter(){
NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
//1.1 添加Query条件
//1)创建布尔查询
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
//2)往布尔查询中添加must条件
boolQueryBuilder.must( QueryBuilders.matchQuery("title","小米") );
//3)往布尔查询中添加filter过滤
boolQueryBuilder.filter( QueryBuilders.termQuery("category","手机") );
queryBuilder.withQuery( boolQueryBuilder );
List<Item> items = esTemplate.queryForList(queryBuilder.build(), Item.class);
items.forEach(System.out::println);
}
8.4聚合查询
# 统计不同品牌的手机分别有多少台?
# 聚合查询
GET /goods/_search
{
"aggs": {
"brandAgg": {
"terms": {
"field": "brand"
}
}
}
}
/**
* 4)聚合查询
*/
@Test
public void testAggQuery(){
NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
//1.添加聚合条件
//注意:聚合的条件都是由AggrationBuilders构建而来的
queryBuilder.addAggregation( AggregationBuilders.terms("brandAgg").field("brand") );
//注意:执行聚合查询必须使用queryForPage方法,否则无法获取聚合结果
//AggregatedPage是Page的子类,Page只是分页查询结果,AggregatedPage既封装了分页结果,也封装了聚合查询
AggregatedPage<Item> aggregatedPage = esTemplate.queryForPage(queryBuilder.build(),Item.class);
//只取出聚合结果
Aggregations aggregations = aggregatedPage.getAggregations();
//根据聚合别名取出聚合结果
Terms terms = aggregations.get("brandAgg");
//取出所有Bucket
List<? extends Terms.Bucket> buckets = terms.getBuckets();
//遍历Bucket
for(Terms.Bucket bucket:buckets){
String brandName = bucket.getKeyAsString();
long count = bucket.getDocCount();
System.out.println(brandName+"\t"+count);
}
}
8.5高亮查询
DSL语句:
# 高亮显示
# 注意:如果要某字段高亮显示,该字段必须是参与搜索,且可以分词
GET /goods/_search
{
"query": {
"match": {
"title": "小米"
}
},
"highlight": {
"pre_tags": "<font color='red'>",
"post_tags": "</font>",
"fields": {
"title": {}
}
}
}
/**
* 5)高亮查询
*/
@Test
public void testHighlightQuery(){
NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
//1.添加Query条件(title要高亮,所以title参与搜索)
queryBuilder.withQuery( QueryBuilders.matchQuery("title","小米") );
//2.添加highlight高亮字段
HighlightBuilder.Field field = new HighlightBuilder.Field("title");
//设置前缀和后缀
field.preTags("<font color='red'>");
field.postTags("</font>");
queryBuilder.withHighlightFields(field);
//3.执行查询
//SearchResultMapper: 用于自行封装搜索结果
AggregatedPage<Item> pageBean = esTemplate.queryForPage(queryBuilder.build(), Item.class, new SearchResultMapper() {
@Override
public <T> AggregatedPage<T> mapResults(SearchResponse response, Class<T> clazz, Pageable pageable) {
List<T> list = new ArrayList<>();
SearchHits hits = response.getHits();
for(SearchHit hit:hits){
//取出原来的文档内容
String json = hit.getSourceAsString();
//把json字符串转换对象
Item item = JsonUtils.toBean(json, Item.class);
//自行取出高亮title的内容
HighlightField titleField = hit.getHighlightFields().get("title");
if(titleField!=null){
//赋值给Item对象的title
item.setTitle(titleField.getFragments()[0].toString());
}
list.add((T)item);
}
AggregatedPage<T> aggregatedPage = new AggregatedPageImpl<>(list);
return aggregatedPage;
}
});
pageBean.getContent().forEach(System.out::println);
}
9.ElasticSearch多节点集群搭建与使用
搭建
-
复制es节点三份,并且删除
data
目录 -
修改三个节点上的信息,内容如下:
-
三个节点端口号: http端口号(9201、9202、9203) tcp端口号(9301、9302、9303)
-
第一个节点配置(elasticsearch.yml)
-
# 集群的名字,保证唯一,所有都必须一致 (17行)
cluster.name: cluster-es
# 节点名称,必须不一样 (23行)
node.name: node-1
# 必须为本机的ip地址 (55行)
network.host: 127.0.0.1
# 服务端口,在同一机器下必须不一样 (59行)
http.port: 9201
# 集群间通讯端口号,在同一机器下必须不一样 (60行)
transport.tcp.port: 9301
# 设置集群自动发现机器ip:port集合,采用广播模式 (70行)
discovery.zen.ping.unicast.hosts: ["127.0.0.1:9301","127.0.0.1:9302","127.0.0.1:9303"]
# 防止脑裂。声明大于几个的投票主节点有效,请设置为(nodes / 2) + 1 (75行)
discovery.zen.minimum_master_nodes: 2# 允许跨域 (92、93行)
http.cors.enabled: true
http.cors.allow-origin: "*"
第二个节点配置(elasticsearch.yml)
# 集群的名字,保证唯一,所有都必须一致 (17行)
cluster.name: cluster-es
# 节点名称,必须不一样 (23行 改)
node.name: node-2
# 必须为本机的ip地址 (55行)
network.host: 127.0.0.1
# 服务端口,在同一机器下必须不一样 (59行 改)
http.port: 9202
# 集群间通讯端口号,在同一机器下必须不一样 (60行 改)
transport.tcp.port: 9302
# 设置集群自动发现机器ip:port集合,采用广播模式 (70行)
discovery.zen.ping.unicast.hosts: ["127.0.0.1:9301","127.0.0.1:9302","127.0.0.1:9303"]
# 防止脑裂。声明大于几个的投票主节点有效,请设置为(nodes / 2) + 1 (75行)
discovery.zen.minimum_master_nodes: 2# 允许跨域 (92、93行)
http.cors.enabled: true
http.cors.allow-origin: "*"
第三个节点配置(elasticsearch.yml)
# 集群的名字,保证唯一,所有都必须一致 (17行)
cluster.name: cluster-es
# 节点名称,必须不一样 (23行 改)
node.name: node-3
# 必须为本机的ip地址 (55行)
network.host: 127.0.0.1
# 服务端口,在同一机器下必须不一样 (59行 改)
http.port: 9203
# 集群间通讯端口号,在同一机器下必须不一样 (60行 改)
transport.tcp.port: 9303
# 设置集群自动发现机器ip:port集合,采用广播模式 (70行)
discovery.zen.ping.unicast.hosts: ["127.0.0.1:9301","127.0.0.1:9302","127.0.0.1:9303"]
# 防止脑裂。声明大于几个的投票主节点有效,请设置为(nodes / 2) + 1 (75行)
discovery.zen.minimum_master_nodes: 2# 允许跨域 (92、93行)
http.cors.enabled: true
http.cors.allow-origin: "*"
-
分别启动三个es节点
-
通过header-master查看,连接任意一个节点都可以出现下面的效果,说明集群成功。
使用
-
连接集群
// 1. 创建Settings配置信息对象(主要配置集群名称)
// 参数一: 集群key (固定不变)
// 参数二:集群环境名称,默认的ES的环境集群名称为 "elasticsearch"
Settings settings = Settings.builder()
.put("cluster.name", "cluster-es").build();
// 2. 创建ES传输客户端对象
TransportClient transportClient = new PreBuiltTransportClient(settings);
// 2.1 添加传输地址对象(集群环境多个)
// 参数一:主机
// 参数二:端口
transportClient.addTransportAddress(new
TransportAddress(InetAddress.getByName("127.0.0.1"), 9301));
transportClient.addTransportAddress(new
TransportAddress(InetAddress.getByName("127.0.0.1"), 9302));
transportClient.addTransportAddress(new
TransportAddress(InetAddress.getByName("127.0.0.1"), 9303));
设置分片与副本
// 3. 创建索引库(index)
// 获取创建索引库请求构建对象
CreateIndexRequestBuilder cirb = transportClient.admin().indices()
.prepareCreate("blog1");
// 3.1 创建Map集合封装分片与副本设置信息
Map<String, Integer> source = new HashMap<String, Integer>();
// 3.2 设置分片数量
source.put("number_of_shards", 3);
// 3.3 设置副本数量
source.put("number_of_replicas", 1);
// 3.4 设置map集合,执行请求
cirb.setSettings(source).get();
Tips:测试关闭一个es节点,整个集群还是可以正常访问。