Elasticsearch

学习目标

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BGW4RqWM-1635414988340)(es.assets/Snipaste_2020-07-06_13-03-45.png)]

Elasticsearch简介与安装

什么是Elasticsearch?

​ ElasticSearch是一个基于Lucene的搜索服务器。它提供了一个分布式多用户能力的全文搜索引擎,基于RESTful web接口。Elasticsearch是用Java开发的,并作为Apache许可条款下的开放源码发布,是当前流行的企业级搜索引擎。设计用于云计算中,能够达到实时搜索,稳定,可靠,快速,安装使用方便。

Lucene与Elasticsearch关系?

Lucene不是一个完整的全文检索引擎,而是一个全文检索引擎的架构,提供了完整的查询引擎和索引引擎,部分文本分析引擎。想要使用它,你必须使用Java来作为开发语言并将其直接集成到你的应用中,更糟糕的是,Lucene非常复杂,你需要深入了解检索的相关知识来理解它是如何工作的。

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

当然Elasticsearch并不仅仅是Lucene这么简单,它不但包括了全文搜索功能,还可以进行以下工作:

  • 分布式实时文件存储,并将每一个字段都编入索引,使其可以被搜索。
  • 实时分析的分布式搜索引擎。
  • 可以扩展到上百台服务器,处理PB级别的结构化或非结构化数据。

这么多的功能被集成到一台服务器上,你可以轻松地通过客户端或者任何你喜欢的程序语言与ES的RESTful API进行交流。

Elasticsearch的上手是非常简单的。它附带了很多非常合理的默认值,这让初学者很好地避免一上手就要面对复杂的理论,

它安装好了就可以使用了,用很小的学习成本就可以变得很有生产力。

随着越学越深入,还可以利用Elasticsearch更多高级的功能,整个引擎可以很灵活地进行配置。可以根据自身需求来定制属于自己的Elasticsearch。

Elasticsearch与Solr对比

优缺点

Elasticsearch

优点

  • Elasticsearch是分布式的。不需要其他组件,分发是实时的,被叫做”Push replication”。
  • Elasticsearch 完全支持 Apache Lucene 的接近实时的搜索。
  • 处理多租户(multitenancy)不需要特殊配置,而Solr则需要更多的高级设置。
  • Elasticsearch 采用 Gateway 的概念,使得完备份更加简单。
  • 各节点组成对等的网络结构,某些节点出现故障时会自动分配其他节点代替其进行工作。

缺点

  • 还不够自动(不适合当前新的Index Warmup API,即冷启动/预热数据的方式。当系统长期处于低水位的情况下,流量突然增加时,直接把系统拉升到高水位可能瞬间把系统压垮。通过"冷启动",让通过的流量缓慢增加,在一定时间内逐渐增加到阈值上限,给冷系统一个预热的时间,避免冷系统被压垮。)
Solr

简介

Solr是Apache Lucene项目的开源企业搜索平台。其主要功能包括全文检索、命中标示、分面搜索、动态聚类、数据库集成,以及富文本(如Word、PDF)的处理。Solr是高度可扩展的,并提供了分布式搜索和索引复制。Solr是最流行的企业级搜索引擎,Solr4 还增加了NoSQL支持。

Solr是用Java编写、运行在Servlet容器(如 Apache Tomcat 或Jetty)的一个独立的全文搜索服务器。 Solr采用了 Lucene Java 搜索库为核心的全文索引和搜索,并具有类似REST的HTTP/XML和JSON的API。Solr强大的外部配置功能使得无需进行Java编码,便可对 其进行调整以适应多种类型的应用程序。Solr有一个插件架构,以支持更多的高级定制。

2010年 Apache Lucene 和 Apache Solr 项目合并,两个项目是由同一个Apache软件基金会开发团队制作实现的。提到技术或产品时,Lucene/Solr或Solr/Lucene是一样的。

现实生活中我们都知道大多数网站或应用都必须具有某种搜索功能,问题是搜索功能往往是巨大的资源消耗并且它们由于沉重的数据库加载而拖垮你的应用的性能。

这就是为什么转移负载到一个外部的搜索服务器是一个不错的主意,Apache Solr是一个独立的**企业级搜索应用服务器,**它对外提供类似于Web-service的API接口。用户可以通过http请求,向搜索引擎服务器提交一定格式的XML文件,生成索引;也可以通过Http Get操作提出查找请求,并得到XML格式的返回结果。它通过使用类似REST的HTTP API,确保你能从几乎任何编程语言来使用Solr。

优点

  • Solr有一个更大、更成熟的用户、开发和贡献者社区。
  • 支持添加多种格式的索引,如:HTML、PDF、微软 Office 系列软件格式以及 JSON、XML、CSV 等纯文本格式。
  • Solr比较成熟、稳定。
  • 不考虑建索引的同时进行搜索,速度更快。

缺点

  • 建立索引时,搜索效率下降,实时索引搜索效率不高。

性能

当单纯的对已有数据进行搜索时,Solr更快。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eFnpYa9l-1635414988343)(es.assets/image1-1594011945741.png)]

当实时建立索引时, Solr会产生io阻塞,查询性能较差, Elasticsearch具有明显的优势。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DN7QBQkV-1635414988344)(es.assets/image2-1594011945742.png)]

随着数据量的增加,Solr的搜索效率会变得更低,而Elasticsearch却没有明显的变化。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZGEEXnNY-1635414988346)(es.assets/image3-1594011945742.png)]

综上所述,Solr的架构不适合实时搜索的应用。

实际生产环境测试

下图为将搜索引擎从Solr转到Elasticsearch以后的平均查询速度有了50倍的提升。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-m5am0BlI-1635414988346)(es.assets/image4-1594011945742.jpeg)]

热度

可以百度指数—大数据分享平台进行比对:

https://index.baidu.com/v2/main/index.html#/trend/elasticsearch?words=elasticsearch,solr

结果如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eBY4oV6z-1635414988348)(es.assets/image5-1594011945742.png)]

Elasticsearch与关系型数据库对比

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-R7RI5cdE-1635414988348)(es.assets/image6-1594011945742.png)]

  1. 一个ES集群可以包含多个索引(数据库),每个索引又包含了很多类型(ES7中已作废),类型中包含了很多文档(行),每个文档又包含了很多字段(列)。
  2. 传统数据库为特定列增加一个索引,例如B-Tree索引来加速检索。Elasticsearch和Lucene使用一种叫做倒排索引(inverted index)的数据结构来达到相同目的。
  3. 倒排索引源于实际应用中需要根据属性的值来查找记录。这种索引表中的每一项都包括一个属性值和具有该属性值的各记录的地址。由于不是由记录来确定属性值,而是由属性值来确定记录的位置,因而称为倒排索引(inverted index)。

ElasticSearch相关概念(术语)

Elasticsearch核心概念

索引 index

一个索引就是一个拥有几分相似特征的文档的集合。比如说,你可以有一个客户数据的索引,另一个产品目录的索引,还有一个订单数据的索引。一个索引由一个名字来标识(必须全部是小写字母的),并且当我们要对对应于这个索引中的文档进行索引、搜索、更新和删除的时候,都要使用到这个名字。在一个集群中,可以定义任意多的索引。

类型 type

在一个索引中,你可以定义一种或多种类型。一个类型是你的索引的一个逻辑上的分类/分区,其语义完全由你来定。通常,会为具有一组共同字段的文档定义一个类型。比如说,我们假设你运营一个博客平台并且将你所有的数据存储到一个索引中。在这个索引中,你可以为用户数据定义一个类型,为博客数据定义另一个类型,当然,也可以为评论数据定义另一个类型。

字段Field

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

映射 mapping

mapping是处理数据的方式和规则方面做一些限制,如某个字段的数据类型、默认值、分析器、是否被索引等等,这些都是映射里面可以设置的,其它就是处理es里面数据的一些使用规则设置也叫做映射,按着最优规则处理数据对性能提高很大,因此才需要建立映射,并且需要思考如何建立映射才能对性能更好。

文档 document

一个文档是一个可被索引的基础信息单元。比如,你可以拥有某一个客户的文档,某一个产品的一个文档,当然,也可以拥有某个订单的一个文档。文档以JSON(Javascript Object Notation)格式来表示,而JSON是一个到处存在的互联网数据交互格式。

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

接近实时 NRT

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

集群 cluster

一个集群就是由一个或多个节点组织在一起,它们共同持有整个的数据,并一起提供索引和搜索功能。一个集群由一个唯一的名字标识,这个名字默认就是“elasticsearch”。这个名字是重要的,因为一个节点只能通过指定某个集群的名字,来加入这个集群

节点 node

一个节点是集群中的一个服务器,作为集群的一部分,它存储数据,参与集群的索引和搜索功能。和集群类似,一个节点也是由一个名字来标识的,默认情况下,这个名字是一个随机的漫威漫画角色的名字,这个名字会在启动的时候赋予节点。这个名字对于管理工作来说挺重要的,因为在这个管理过程中,你会去确定网络中的哪些服务器对应于Elasticsearch集群中的哪些节点。

一个节点可以通过配置集群名称的方式来加入一个指定的集群。默认情况下,每个节点都会被安排加入到一个叫做“elasticsearch”的集群中,这意味着,如果你在你的网络中启动了若干个节点,并假定它们能够相互发现彼此,它们将会自动地形成并加入到一个叫做“elasticsearch”的集群中。

在一个集群里,只要你想,可以拥有任意多个节点。而且,如果当前你的网络中没有运行任何Elasticsearch节点,这时启动一个节点,会默认创建并加入一个叫做“elasticsearch”的集群。

分片和复制 shards&replicas

一个索引可以存储超出单个结点硬件限制的大量数据。比如,一个具有10亿文档的索引占据1TB的磁盘空间,而任一节点都没有这样大的磁盘空间;或者单个节点处理搜索请求,响应太慢。为了解决这个问题,Elasticsearch提供了将索引划分成多份的能力,这些份就叫做分片。当你创建一个索引的时候,你可以指定你想要的分片的数量。每个分片本身也是一个功能完善并且独立的“索引”,这个“索引”可以被放置到集群中的任何节点上。分片很重要,主要有两方面的原因:
1)允许你水平分割/扩展你的内容容量。
2)允许你在分片(潜在地,位于多个节点上)之上进行分布式的、并行的操作,进而提高性能/吞吐量。

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

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

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

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

Elasticsearch部署与启动

下载

https://www.elastic.co/downloads/

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-INNzoXev-1635414988349)(es.assets/image7.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HOth76l4-1635414988349)(es.assets/image8.png)]

安装

单机版

将文件上传至服务器

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9vqeuxJi-1635414988350)(es.assets/image9.png)]

解压文件至 es 目录

tar zxvf elasticsearch-7.6.2-linux-x86_64.tar.gz -C /usr/local/es/

启动,报错

cd /usr/local/es/elasticsearch-7.6.2/
bin/elasticsearch

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-A9iBSqsy-1635414988351)(es.assets/1609289596279.png)]

错误

java.lang.RuntimeException: can not run elasticsearch as root

因为当前是 root 用户,es 默认不允许 root 用户操作。

解决:

创建 es 用户组和 es 用户,并将其添加到用户组 es 中

groupadd es
useradd es -g es

更改 es 文件夹及内部文件的所属用户及组为 es:es

chown -Rf es:es /usr/local/es/elasticsearch-7.6.2/

切换到 es 用户再次启动

su es

bin/elasticsearch

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-t7JluRpm-1635414988351)(es.assets/1634291378526.png)]

在本机通过curl命令

[es@localhost root]$ curl http://127.0.0.1:9200
{
  "name" : "node00",
  "cluster_name" : "elasticsearch",
  "cluster_uuid" : "qJJZlf4ASp2EjpcpxDZEqA",
  "version" : {
    "number" : "7.6.2",
    "build_flavor" : "default",
    "build_type" : "tar",
    "build_hash" : "ef48eb35cf30adf4db14086e8aabd07ef6fb113f",
    "build_date" : "2020-03-26T06:34:37.794943Z",
    "build_snapshot" : false,
    "lucene_version" : "8.4.0",
    "minimum_wire_compatibility_version" : "6.8.0",
    "minimum_index_compatibility_version" : "6.0.0-beta1"
  },
  "tagline" : "You Know, for Search"
}

如果需要远程访问,还需要以下步骤

修改config/elasticsearch.yml文件

vi config/elasticsearch.yml

实际生产环境请添加允许访问的IP,学习时使用0.0.0.0放行所有IP

network.host: 0.0.0.0

切换至root用户添加防火墙规则,重启防火墙,学习时直接关闭防火墙

su root
systemctl stop firewalld
1.启动防火墙 systemctl start firewalld
2.禁用防火墙 systemctl stop firewalld
3.设置开机启动 systemctl enable firewalld
4.停止并禁用开机启动 systemctl disable firewalld
su  es

切换es用户重启elasticsearch,重启之前先查询es进程然后杀死进程

kill -9 进程号
bin/elasticsearch -d

报错

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hF8n5CE8-1635414988352)(es.assets/1634292176727.png)]

解决:

错误1:Elasticsearch进程的最大文件描述符[4096]太低,请至少增加到[65535]

https://www.elastic.co/guide/en/elasticsearch/reference/7.4/vm-max-map-count.html

错误2 :用户[es]可以创建的最大线程数[3795]太低,请至少增加到[4096]

https://www.elastic.co/guide/en/elasticsearch/reference/7.4/max-number-of-threads.html

解决

vim /etc/security/limits.conf

在文件末尾添加如下信息

es soft nofile 65535
es hard nofile 65535
es soft nproc 4096
es hard nproc 4096

错误3:最大虚拟内存区域vm.max_map_count[65530]太低,请至少增加到[262144]

https://www.elastic.co/guide/en/elasticsearch/reference/7.4/vm-max-map-count.html#vm-max-map-count

解决

vi /etc/sysctl.conf

在文件末尾添加如下信息

vm.max_map_count = 262144

重新加载虚拟内存配置

sysctl -p

错误4:当前配置不适合生产环境使用;必须至少配置[discovery.seed_hosts,discovery.seed_providers,cluster.initial_master_nodes]之一

discovery.seed_hosts:集群发现配置,提供集群中符合主机要求的节点的列表. 每个值的格式为host:porthost,其中port默认为设置transport.profiles.default.port

discovery.seed_providers:以文件的方式提供主机列表,可以动态修改,而不用重启节点(容器化环境适用)

cluster.initial_master_nodes:指定可以成为 master 的所有节点的 name 或者 ip,这些配置将会在第一次选举中进行计算

过时配置 新配置
discovery.zen.ping.unicast.hosts discovery.seed_hosts
discovery.zen.hosts_provider discovery.seed_providers
cluster.initial_master_nodes(7新添加)

解决:

vi config/elasticsearch.yml

在文件末尾添加如下信息

discovery.seed_hosts: ["192.168.126.4"]
cluster.initial_master_nodes: ["192.168.126.4"]

ES已经可以正常访问了,但是我们在每次启动时都会看到一个警告信息

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Avfghzte-1635414988352)(es.assets/1634292525605-16346307890281.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-05mxhY5f-1635414988353)(es.assets/1634292683878.png)]

这是提醒你 CMS 垃圾收集器在 JDK 9 就开始被标注为 @Deprecated,JDK 11支持的垃圾回收器为G1ZGC,而ZGC在JDK 11 还处于实验阶段。

修改es下的config/jvm.options配置文件

vim config/jvm.options

将:-XX:+UseConcMarkSweepGC改为:-XX:+UseG1GC

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-op9HEGdt-1635414988353)(es.assets/1609291352399.png)]

查看启动进程

 ps -ef|grep elastic
 kill -9   101183

ElasticSearch后端启动命令

bin/elasticsearch -d

以后启动将不再有警告信息。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tkUEBgd3-1635414988354)(es.assets/1634292525605-16346307890282.png)]

伪集群版

Elasticsearch天生就是为分布式而生的搜索引擎,我们搭建一下集群环境

创建三目录:node-1,node-2,node-3

cp -r /usr/local/es/elasticsearch-7.6.2/*  /usr/local/es/node-1/

注意:不要使用刚才的单机版,重新解压一份新的ES搭建,因为刚才的单机版ES已经运行过会生成一些默认配置如果在其之上继续搭建可能会导致ES无法组成集群环境。

修改node-1,node-2,node-3下的config/jvm.options配置文件

vim config/jvm.options

将:

    -Xms1g 修改为 ===> -Xms1000m
    -Xmx1g 修改为 ===> -Xmx1000m    

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dagXH2z6-1635414988354)(es.assets/1634389690158.png)]

修改config/elasticsearch.yml

切换用户:
su root
cd /usr/local/es/node-1/

vim   config/elasticsearch.yml

配置信息

#节点1的配置信息:
#集群名称,保证唯一
cluster.name: my-elasticsearch
##节点名称,必须不一样
node.name: node-1
###必须为本机的ip地址
network.host: 192.168.126.4
###服务端口号,在同一机器下必须不一样
http.port: 9201
###集群间通信端口号,在同一机器下必须不一样
transport.tcp.port: 9301
###设置集群自动发现机器ip集合
discovery.seed_hosts: ["192.168.126.4:9301","192.168.126.4:9302","192.168.126.4:9303"]
cluster.initial_master_nodes: ["node-1", "node-2", "node-3"]
discovery.zen.ping_timeout: 60s

#节点2的配置信息:
#集群名称,保证唯一
cluster.name: my-elasticsearch
##节点名称,必须不一样
node.name: node-2
###必须为本机的ip地址
network.host: 192.168.126.4
###服务端口号,在同一机器下必须不一样
http.port: 9202
###集群间通信端口号,在同一机器下必须不一样
transport.tcp.port: 9302
###设置集群自动发现机器ip集合
discovery.seed_hosts: ["192.168.126.4:9301","192.168.126.4:9302","192.168.126.4:9303"]
cluster.initial_master_nodes: ["node-1", "node-2", "node-3"]
discovery.zen.ping_timeout: 60s

#节点3的配置信息:
#集群名称,保证唯一
cluster.name: my-elasticsearch
##节点名称,必须不一样
node.name: node-3
###必须为本机的ip地址
network.host: 192.168.126.4
###服务端口号,在同一机器下必须不一样
http.port: 9203
###集群间通信端口号,在同一机器下必须不一样
transport.tcp.port: 9303
###设置集群自动发现机器ip集合
discovery.seed_hosts: ["192.168.126.4:9301","192.168.126.4:9302","192.168.126.4:9303"]
cluster.initial_master_nodes: ["node-1", "node-2", "node-3"]
discovery.zen.ping_timeout: 60s

注意:如果设置了node.name需要将cluster.initial_master_nodes改为对应节点名称。

更改 es 文件夹及内部文件的所属用户及组为 es:es

chown -Rf es:es /usr/locall/es/node-1
chown -Rf es:es /usr/local/es/node-2
chown -Rf es:es /usr/local/es/node-3

赋予权限

 chown es /usr/local/es/node-1 -R
 chown es /usr/local/es/node-2 -R
 chown es /usr/local/es/node-3 -R
 切换用户:
 su  es

切换到 es 用户再次启动

启动es服务器

/usr/local/es/node-1/bin/elasticsearch -d
/usr/local/es/node-2/bin/elasticsearch -d
/usr/local/es/node-3/bin/elasticsearch -d

访问:http://192.168.126.4:9201/_cluster/health

“status”: “red”表示集群环境不ok,“status”: “green”表示集群环境ok

  • green:最健康得状态,说明所有的分片包括备份都可用
  • yellow:基本的分片可用,但是备份不可用(或者是没有备份)
  • red:部分的分片可用,表明分片有一部分损坏。此时执行查询部分数据仍然可以查到,遇到这种情况,还是赶快解决比较好

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6ktic95I-1635414988355)(es.assets/1634299510163.png)]

提示:如果虚拟机1 G 1 CPU,这时候就算你启动node-3也会报错

OpenJDK 64-Bit Server VM warning: INFO: os::commit_memory(0x00000000c5330000, 986513408, 0) failed; error='Not enough space' (errno=12)
	at org.elasticsearch.tools.launchers.JvmErgonomics.flagsFinal(JvmErgonomics.java:111)
	at org.elasticsearch.tools.launchers.JvmErgonomics.finalJvmOptions(JvmErgonomics.java:79)
	at org.elasticsearch.tools.launchers.JvmErgonomics.choose(JvmErgonomics.java:57)
	at org.elasticsearch.tools.launchers.JvmOptionsParser.main(JvmOptionsParser.java:89)

错误信息的意思就是默认分配的 jvm 空间大小不足,无法启动。

修改每个节点下的config/jvm.options配置文件

vi config/jvm.options  

将默认大小1g改为1000m,注意着点别改成1000g了。

-Xms1000m
-Xmx1000m

Elasticsearch插件安装

可视化工具

在学习和使用Elasticsearch的过程中,必不可少需要通过一些工具查看es的运行状态以及数据。如果都是通过rest请求,未免太过麻烦,而且也不够人性化。

​ 以下插件三选一即可,不需要都安装。

head

在学习和使用Elasticsearch的过程中,必不可少需要通过一些工具查看es的运行状态以及数据。如果都是通过rest请求,未免太过麻烦,而且也不够人性化。head可以完美帮我们快速学习和使用es。

head 插件在ES 5版本以前开箱即用非常简单,ES 5版本以后需要运行在node环境下,所以我们要先准备一下环境。

安装
  • 安装Git
yum -y install git
  • 安装Node

根据自己的需求下载对应的版本

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BJea2xuZ-1635414988355)(es.assets/image15.png)]

将文件上传至服务器并解压

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ge4bLgPv-1635414988356)(es.assets/image16-1594011945743.png)]

tar xvf node-v14.15.1-linux-x64.tar.xz -C /usr/local/

配置环境变量

配置环境变量
/usr/local/node-v14.15.1-linux-x64
export PATH=$PATH:$NODE_HOME/bin

查看版本

node -v

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-i7h5wVD3-1635414988356)(es.assets/image17-1594011945743.png)]

安装cnpm

npm install -g cnpm --registry=https://registry.npm.taobao.org
  • 安装head

​ 官网:https://github.com/mobz/elasticsearch-head

​ https://github.com/mobz/elasticsearch-head/releases

​ 下载:

mkdir -p /usr/local/elasticsearch/plugins
cd /usr/local/elasticsearch/plugins
git clone git://github.com/mobz/elasticsearch-head.git

或者直接从网页下载并上传至服务器

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mYsxkyrM-1635414988357)(es.assets/image18-1594011945743.png)]

  • 修改配置

进入Elasticsearch安装目录下的config目录,修改elasticsearch.yml文件.在文件的末尾加入以下代码,然后去掉network.host: 192.168.0.1的注释并改为network.host: 0.0.0.0

配置说明:

https://www.ibm.com/support/knowledgecenter/zh/SSFPJS_8.5.6/com.ibm.wbpm.main.doc/topics/rfps_esearch_configoptions.html

# 如果启用了 HTTP 端口,那么此属性会指定是否允许跨源 REST 请求。
http.cors.enabled: true
# 如果 http.cors.enabled 的值为 true,那么该属性会指定允许 REST 请求来自何处。
http.cors.allow-origin: "*"
  • 配置插件

进入 elasticsearch-head 目录,修改Gruntfile.js文件

cd /usr/local/elasticsearch/plugins/elasticsearch-head/
vi Gruntfile.js

添加hostname: '*'

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-wRqq4dCP-1635414988357)(es.assets/image19-1594011945744.png)]

非必须:修改elasticsearch-head/_site/app.js,也可以不修改此文件,在启动head插件以后通过浏览器输入参数的方式连接也可以。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9jNJqHCy-1635414988358)(es.assets/image20-1594011945744.png)]

启动head插件以后通过浏览器输入参数的方式连接。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vJ13wOwY-1635414988358)(es.assets/image21-1594011945744.png)]

在elasticsearch-head目录下执行npm install安装(或者cnpm install),完成后在elasticsearch-head目录下执行npm run start运行head插件。

npm install
npm run start
或者
npm run start & # 后台启动

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0JTZH0z8-1635414988359)(es.assets/image22-1594011945744.png)]

提示:如果启动失败提示grunt相关错误信息,重新安装grunt再重新启动

npm install -g grunt-cli # 安装grunt命令行工具grunt-cli
npm install grunt --save-dev # 安装grunt及其插件
  • 启动访问

启动Elasticsearch,访问http://192.168.10.100:9100

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6eKTFMXy-1635414988359)(es.assets/image23-1594011945744.png)]

这里就要提一下分片的概念了。还记得之前节点3一直没有启动,现在启动节点3

su es
/usr/local/elasticsearch/es3/elasticsearch-7.6.2/bin/elasticsearch

重新访问,如下:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Q1rgkvE6-1635414988360)(es.assets/image24-1594011945744.png)]

说明:一个索引库默认5个分片(一组完整的数据),分片可以自定义设置修改,每个分片又有备份分片,所以按默认值来计算,一个索引库就会有10个分片(两组完整数据),这10个分片会被分配到所有节点中。

  • head插件操作Elasticsearch

    创建索引库

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-B0jNZfzh-1635414988360)(es.assets/image25-1594011945744.png)]

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-hwZE4ggr-1635414988361)(es.assets/image26-1594011945744.png)]

    创建document

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-QstXi9BU-1635414988361)(es.assets/image27-1594011945745.png)]

    索引名字是:es_head,本记录的id是:1;

    ​ 返回的信息可以看到创建是成功的,并且版本号是1;ES会对记录修改进行版本跟踪,第一次创建记录为1,同一条记录每修改一次就追加1。 至此一条记录就提交到ES中建立了索引,注意HTTP的方法是PUT,PUT需要指定_id插入数据,否则报错。POST无需指定_id插入数据,会使用随机值。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dSyzUp0N-1635414988362)(es.assets/image28-1594011945745.png)]

​ 更新document

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Iz4z5JXn-1635414988362)(es.assets/image29-1594011945745.png)]

​ 结果中的version字段已经成了2,因为我们这是是修改,索引版本递增;更新接口与创建接口完全一样,ES会查询记录是否存在,如果不存在就是创建,存在就是更新操作。

​ PUT操作必须指定_id,否则报错,_id存在则为更新否则新增。POST操作无需指定_id,会使用随机值,_id存在则为更新否则新增。

​ 查询document

​ 查询所有

​ [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0tfVvYt7-1635414988363)(es.assets/image30-1594011945745.jpeg)]

​ 根据_id查询

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Yv5UuDCY-1635414988364)(es.assets/image31-1594011945745.png)]

found值为true,表明查询到该文档,_source字段是文档的内容。

​ 删除document

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0Y01cbzY-1635414988364)(es.assets/image32-1594011945745.png)]

​ 删除索引库

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RPJXyLNl-1635414988364)(es.assets/image33-1594011945745.png)]

cerebro

cerebro是一个开源(MIT许可)的Elasticsearch可视化管理工具,使用Scala,AngularJS,Bootstrap构建。cerebro需要Java 1.8或更新的运行环境。

下载地址:https://github.com/lmenezes/cerebro/releases

不同平台的资源文件

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Il4dCKO0-1635414988365)(es.assets/image34-1594011945745.png)]

将文件上传至服务器,解压至指定路径

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-m7q4HOcz-1635414988366)(es.assets/image35-1594011945745.png)]

tar -zxvf cerebro-0.9.2.tgz -C /usr/local/elasticsearch-7.6.2/plugins/

启动(默认端口9000可访问IP 0.0.0.0),访问

cd /usr/local/elasticsearch-7.6.2/plugins/cerebro-0.9.2/
bin/cerebro
或者指定端口和可访问IP
bin/cerebro -Dhttp.port=1234 -Dhttp.address=127.0.0.1

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nhw8TrXu-1635414988366)(es.assets/image36-1594011945746.png)]

输入需要连接的ES服务器地址

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vyA4jebS-1635414988367)(es.assets/image37-1594011945746.png)]

非必须:如果经常使用的话,可以先在conf/application.conf中配置好Elasticsearch服务器地址。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DGUJOA9T-1635414988367)(es.assets/image38-1594011945746.png)]

点击配置好的es cluster连接ES

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2qT6dAYm-1635414988368)(es.assets/image39-1594011945746.png)]

界面如下

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-17KhiSKW-1635414988368)(es.assets/image40-1594011945746.png)]

elasticHD

elasticHD 是一款 Elasticsearch的可视化管理工具。不依赖ES的插件安装,更便捷;导航栏直接填写对应的ES IP和端口就可以操作Es了。支持Es监控、实时搜索,Index template快捷替换修改,索引列表信息查看, SQL converts to DSL等。

下载地址:https://github.com/360EntSecGroup-Skylar/ElasticHD/releases/

不同平台的资源文件

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iBwGXFIj-1635414988369)(es.assets/image41-1594011945746.png)]

将文件上传至服务器,安装unzip

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8uPsxYtY-1635414988369)(es.assets/image42-1594011945746.png)]

yum -y install unzip

将文件解压至指定路径

unzip elasticHD_linux_amd64.zip -d /usr/local/elasticsearch/plugins/

启动(默认端口9800可访问IP 0.0.0.0),访问

cd /usr/local/elasticsearch/plugins/
./ElasticHD

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GSjR7zLw-1635414988370)(es.assets/image43-1594011945746.png)]

界面如下

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-LH2kBVvF-1635414988371)(es.assets/image44-1594011945746.png)]

kibana多种收索方式:https://blog.csdn.net/weixin_30906671/article/details/99402837?utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7Edefault-1.no_search_link&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7Edefault-1.no_search_link

Kibana

首先明确一点,Kibana是一个软件,不是插件。

Kibana 是一款开源的数据分析和可视化平台,它是 Elastic Stack 成员之一,设计用于和 Elasticsearch 协作。您可以使用 Kibana 对 Elasticsearch 索引中的数据进行搜索、查看、交互操作。可以很方便的利用图表、表格及地图对数据进行多元化的分析和呈现。

Kibana 可以使大数据通俗易懂。它很简单,基于浏览器的界面便于您快速创建和分享动态数据仪表板来追踪 Elasticsearch 的实时数据变化。

搭建 Kibana 非常简单。可以分分钟完成 Kibana 的安装并开始探索 Elasticsearch 的索引数据——没有代码、不需要额外的基础设施。

安装

​ 官网:https://www.elastic.co/products/kibana

​ 下载地址:https://www.elastic.co/cn/downloads/kibana

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GPZ6L835-1635414988371)(es.assets/image45-1594011945746.jpeg)]

将文件上传至服务器

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-quUehp2j-1635414988371)(es.assets/image46-1594011945746.png)]

创建文件夹并解压

mkdir -p /usr/local/es/kibana
tar -zxvf kibana-7.6.2-linux-x86_64.tar.gz -C /usr/local/es/kibana/

修改kibana.yml配置文件

cd /usr/local/es/kibana/kibana-7.6.2-linux-x86_64
vim config/kibana.yml

修改以下三处内容

i18n.locale: "zh-CH"
# 服务端口,默认5601
server.port: 5601
# 允许访问IP
server.host: "0.0.0.0"
# 设置 elasticsearch 节点及端口
elasticsearch.hosts:  ["http://192.168.126.4:9201","http://192.168.126.4:9202","http://192.168.126.4:9203"] 

启动kibana(需要先启动es)

bin/kibana --allow-root
关闭kibana,
 ps -ef|grep node

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CViRIAro-1635414988372)(es.assets/image47-1594011945746.png)]

访问:http://192.168.126.4:5601/

​ 下图意思是:通过提供基本功能的使用统计信息来帮助我们改善Elastic Stack。 我们不会在Elastic之外共享此数据。选择yes

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qKqUyegw-1635414988372)(…/…/…/…/…/…/…/%25E6%2596%25B0%25E5%25BB%25BA%25E6%2596%2587%25E4%25BB%25B6%25E5%25A4%25B9/%25E8%25AF%25BE%25E4%25BB%25B6%25E6%2595%25B4%25E7%2590%2586/2021/es/linux/%25E8%25AF%25BE%25E4%25BB%25B6/imgs/image48-1594011945747.png)]

下图意思是:您可以尝试我们的示例数据和仪表板,也可以使用自己的数据。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RCQdU0EI-1635414988373)(…/…/…/…/…/…/…/%25E6%2596%25B0%25E5%25BB%25BA%25E6%2596%2587%25E4%25BB%25B6%25E5%25A4%25B9/%25E8%25AF%25BE%25E4%25BB%25B6%25E6%2595%25B4%25E7%2590%2586/2021/es/linux/%25E8%25AF%25BE%25E4%25BB%25B6/imgs/image49-1594011945747.png)]

最终界面

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-WmBFZHIF-1635414988373)(es.assets/image50-1594011945747.png)]

关联索引库

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vRZd5sIk-1635414988374)(es.assets/image52-1594011945747.png)]

通过Dev Tools查询

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yKSq7pJw-1635414988374)(es.assets/1634303068114.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lDxPFXPS-1635414988375)(es.assets/1634303123905.png)]

创建索引库

PUT /user

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3bF6jN0M-1635414988375)(es.assets/1634303462051-1634389470799.png)]

查询索引库

GET /user	

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RpvLVvce-1635414988376)(es.assets/1634303526322-1634389470799.png)]

添加文档

####添加文档 /索引名称/类型/id
PUT /index/user/1
{
  "name":"yushengjun",
  "sex":0,
  "age":22
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8DN3kjsQ-1635414988376)(es.assets/1634303827842-1634389470799.png)]

###查询文档

GET /index/user/1

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Eikv4Bon-1635414988376)(es.assets/1634303872714-1634389470799.png)]

###删除索引库

DELETE /index

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-eK0RUBsN-1635414988377)(es.assets/1634303924521-1634389470800.png)]

IK 分词器和ElasticSearch集成使用

在进行字符串查询时,我们发现去搜索"搜索服务器"和"你好"都可以搜索到数据;

而在进行词条查询时,我们搜索"搜索"却没有搜索到数据;

究其原因是ElasticSearch的标准分词器导致的,当我们创建索引时,字段使用的是标准分词器:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7lb8lR2L-1635414988377)(es.assets/1634304937064.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vzh8zQx9-1635414988377)(es.assets/1634304754277.png)]

这样的话就需要对中文支持良好的分析器的支持,支持中文分词的分词器有很多,word分词器、庖丁解牛、盘古分词、Ansj分词等,但我们常用的还是下面要介绍的IK分词器

IK分词器简介

IKAnalyzer是一个开源的,基于java语言开发的轻量级的中文分词工具包。从2006年12月推出1.0版开始,IKAnalyzer已经推出 了3个大版本。最初,它是以开源项目Lucene为应用主体的,结合词典分词和文法分析算法的中文分词组件。新版本的IKAnalyzer3.0则发展为 面向Java的公用分词组件,独立于Lucene项目,同时提供了对Lucene的默认优化实现。

IK分词器3.0的特性如下:

1)采用了特有的“正向迭代最细粒度切分算法“,具有60万字/秒的高速处理能力。
2)采用了多子处理器分析模式,支持:英文字母(IP地址、Email、URL)、数字(日期,常用中文数量词,罗马数字,科学计数法),中文词汇(姓名、地名处理)等分词处理。
3)对中英联合支持不是很好,在这方面的处理比较麻烦.需再做一次查询,同时是支持个人词条的优化的词典存储,更小的内存占用。
4)支持用户词典扩展定义。
5)针对Lucene全文检索优化的查询分析器IKQueryParser;采用歧义分析算法优化查询关键字的搜索排列组合,能极大的提高Lucene检索的命中率。

下载

下载地址:https://github.com/medcl/elasticsearch-analysis-ik/releases

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-w2dVxMNW-1635414988378)(es.assets/image60-1594011945748.png)]

将文件上传至服务器

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-y0PFnvpa-1635414988378)(es.assets/image61-1594011945748.png)]

创建ik目录,然后将ik分词器解压至ik目录(三个节点都需要操作)

# 创建ik目录
mkdir -p /usr/local/es/node-3/plugins/ik
# 解压至ik目录
unzip elasticsearch-analysis-ik-7.6.2_2.zip -d /usr/local/es/node-3/plugins/ik/
chown -Rf es:es /usr/local/elasticsearch/

重启ES,测试

测试

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1UDLdWfx-1635414988379)(es.assets/1634305728751.png)]

Analyzer分词配置解释:

  • ik_smart:粗粒度分词,比如中华人民共和国国歌,会拆分为中华人民共和国,国歌;
  • ik_max_word:细粒度分词,比如中华人民共和国国歌,会拆分为中华人民共和国,中华人民,中华,华人,人民共和国,人民,人,民,共和国,共和,和,国国,国歌,会穷尽各种可能的组合。

关于字段类型type配置解释:

Text 数据类型被用来索引长文本,比如说电子邮件的主体部分或者一款产品的介绍。这些文本会被分析,在建立索引前会将这些文本进行分词,转化为词的组合,建立索引。允许 ES来检索这些词语。Text数据类型不能用来排序和聚合。

Keyword 数据类型用来建立电子邮箱地址、姓名、邮政编码和标签等数据,不需要进行分词。可以被用来检索过滤、排序和聚合。keyword 类型字段只能用本身来进行检索。

​ 当然还有其他类型,比如Double等我们用到再详细讲解吧。

创建索引

创建索引的请求格式:

  • 请求方式:PUT

  • 请求路径:/索引库名

  • 请求参数:json格式:

    {
        "settings": {
            "number_of_shards": 3,
            "number_of_replicas": 2
          }
    }
    
    • settings:索引库的设置
      • number_of_shards:分片数量
      • number_of_replicas:副本数量

kibana的控制台,可以对http请求进行简化,示例:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PC4W3uM1-1635414988379)(es.assets/1634398887806.png)]

相当于是省去了elasticsearch的服务器地址

而且还有语法提示,非常舒服。

查看索引设置

语法

Get请求可以帮我们查看索引信息,格式:

GET /索引库名

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qtMnScoV-1635414988380)(es.assets/1634398939940.png)]

或者,我们可以使用*来查询所有索引库配置:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EJVC07Zd-1635414988380)(es.assets/1634398961570.png)]

删除索引

删除索引使用DELETE请求

语法

DELETE /索引库名

示例

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Mfed3MdD-1635414988380)(es.assets/1634398994060.png)]

再次查看user:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2kJmNxu1-1635414988381)(es.assets/1634399021675.png)]

当然,我们也可以用HEAD请求,查看索引是否存在:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UQTfmho2-1635414988381)(es.assets/1634399049017.png)]

映射配置

索引有了,接下来肯定是添加数据。但是,在添加数据之前必须定义映射。

什么是映射?

映射是定义文档的过程,文档包含哪些字段,这些字段是否保存,是否索引,是否分词等

只有配置清楚,Elasticsearch才会帮我们进行索引库的创建(不一定)

创建映射字段

语法

请求方式依然是PUT

PUT /索引库名/_mapping
{
  "properties": {
    "字段名": {
      "type": "类型",
      "index": true,
      "store": true,
      "analyzer": "分词器"
    }
  }
}
  • 类型名称:就是前面讲的type的概念,类似于数据库中的不同表
    字段名:任意填写 ,可以指定许多属性,例如:
  • type:类型,可以是text、long、short、date、integer、object等
  • index:是否索引,默认为true
  • store:是否存储,默认为false
  • analyzer:分词器,这里的ik_max_word即使用ik分词器

示例

发起请求:

1

PUT  goods

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-gjiDyCdq-1635414988382)(es.assets/1634401885713.png)]

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

响应结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nRFengZ9-1635414988382)(es.assets/1634402614474.png)]

2.5.2.查看映射关系

语法:

GET /索引库名/_mapping

示例:

GET /goods/_mapping

响应:

{
  "goods" : {
    "mappings" : {
      "properties" : {
        "images" : {
          "type" : "keyword",
          "index" : false
        },
        "price" : {
          "type" : "float"
        },
        "title" : {
          "type" : "text",
          "analyzer" : "ik_max_word"
        }
      }
    }
  }
}

2.5.3.字段属性详解

2.5.3.1.type

Elasticsearch中支持的数据类型非常丰富:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-llNYEJse-1635414988382)(es.assets/1531712631982.png)]

我们说几个关键的:

  • String类型,又分两种:

    • text:可分词,不可参与聚合
    • keyword:不可分词,数据会作为完整字段进行匹配,可以参与聚合
  • Numerical:数值类型,分两类

    • 基本数据类型:long、interger、short、byte、double、float、half_float
    • 浮点数的高精度类型:scaled_float
      • 需要指定一个精度因子,比如10或100。elasticsearch会把真实值乘以这个因子后存储,取出时再还原。
  • Date:日期类型

    elasticsearch可以对日期格式化为字符串存储,但是建议我们存储为毫秒值,存储为long,节省空间。

2.5.3.2.index

index影响字段的索引情况。

  • true:字段会被索引,则可以用来进行搜索。默认值就是true
  • false:字段不会被索引,不能用来搜索

index的默认值就是true,也就是说你不进行任何配置,所有字段都会被索引。

但是有些字段是我们不希望被索引的,比如商品的图片信息,就需要手动设置index为false。

2.5.3.3.store

是否将数据进行额外存储。

在学习lucene和solr时,我们知道如果一个字段的store设置为false,那么在文档列表中就不会有这个字段的值,用户的搜索结果中不会显示出来。

但是在Elasticsearch中,即便store设置为false,也可以搜索到结果。

原因是Elasticsearch在创建文档索引时,会将文档中的原始数据备份,保存到一个叫做_source的属性中。而且我们可以通过过滤_source来选择哪些要显示,哪些不显示。

而如果设置store为true,就会在_source以外额外存储一份数据,多余,因此一般我们都会将store设置为false,事实上,store的默认值就是false。

2.5.3.4.boost

激励因子,这个与lucene中一样

其它的不再一一讲解,用的不多,大家参考官方文档:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZYAQrRik-1635414988383)(es.assets/1531713176079.png)]

2.6.新增数据

2.6.1.随机生成id

通过POST请求,可以向一个已经存在的索引库中添加数据。

语法:

POST /索引库名/——doc/1
{
    "key":"value"
}

示例:

POST /goods/_doc
{
    "title":"小米手机",
    "images":"http://12479122.jpg",
    "price":2699.00
}

响应:

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

通过查看数据:

get goods/_search
{
    "query":{
        "match_all":{}
    }
}
{
  "took" : 2,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "goods",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 1.0,
        "_source" : {
          "title" : "小米手机",
          "images" : "http://12479122.jpg",
          "price" : 2699.0
        }
      }
    ]
  }
}

  • _source:源文档信息,所有的数据都在里面。
  • _id:这条文档的唯一标示,与文档自己的id字段没有关联

2.6.2.自定义id

如果我们想要自己新增的时候指定id,可以这么做:

POST /类型/id值
{
    ...
}

示例:

POST /goods/_doc/2
{
    "title":"大米手机",
    "images":"http://12479122.jpg",
    "price":2899.00
}

得到的数据:

{
  "took" : 750,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 2,
      "relation" : "eq"
    },
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "goods",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 1.0,
        "_source" : {
          "title" : "小米手机",
          "images" : "http://12479122.jpg",
          "price" : 2699.0
        }
      },
      {
        "_index" : "goods",
        "_type" : "_doc",
        "_id" : "2",
        "_score" : 1.0,
        "_source" : {
          "title" : "大米手机",
          "images" : "http://12479122.jpg",
          "price" : 2899.0
        }
      }
    ]
  }
}

2.6.3.智能判断

在学习Solr时我们发现,我们在新增数据时,只能使用提前配置好映射属性的字段,否则就会报错。

不过在Elasticsearch中并没有这样的规定。

事实上Elasticsearch非常智能,你不需要给索引库设置任何mapping映射,它也可以根据你输入的数据来判断类型,动态添加数据映射。

测试一下:

POST /goods/_doc/3
{
    "title":"超米手机",
    "images":"http://12479122.jpg",
    "price":2899.00,
    "stock": 200,
    "saleable":true
}

我们额外添加了stock库存,和saleable是否上架两个字段。

来看结果:

{
  "took" : 750,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 2,
      "relation" : "eq"
    },
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "goods",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 1.0,
        "_source" : {
          "title" : "小米手机",
          "images" : "http://12479122.jpg",
          "price" : 2699.0
        }
      },
      {
        "_index" : "goods",
        "_type" : "_doc",
        "_id" : "2",
        "_score" : 1.0,
        "_source" : {
          "title" : "大米手机",
          "images" : "http://12479122.jpg",
          "price" : 2899.0
        }
      }
    ]
  }
}

在看下索引库的映射关系:

{
  "goods" : {
    "mappings" : {
      "properties" : {
        "images" : {
          "type" : "keyword",
          "index" : false
        },
        "price" : {
          "type" : "float"
        },
        "title" : {
          "type" : "text",
          "analyzer" : "ik_max_word"
        }
      }
    }
  }
}

stock和saleable都被成功映射了。

2.7.修改数据

把刚才新增的请求方式改为PUT,就是修改了。不过修改必须指定id,

  • id对应文档存在,则修改
  • id对应文档不存在,则新增

比如,我们把id为3的数据进行修改:

PUT /goods/_doc/1
{
    "title":"超大米手机",
    "images":"http://image.leyou.com/12479122.jpg",
    "price":3899.00,
    "stock": 100,
    "saleable":true
}

结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZrMK9iss-1635414988383)(es.assets/1634403536324.png)]

{
  "took" : 533,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 4,
      "relation" : "eq"
    },
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "goods",
        "_type" : "_doc",
        "_id" : "2",
        "_score" : 1.0,
        "_source" : {
          "title" : "大米手机",
          "images" : "http://12479122.jpg",
          "price" : 2899.0
        }
      },
      {
        "_index" : "goods",
        "_type" : "_doc",
        "_id" : "3",
        "_score" : 1.0,
        "_source" : {
          "title" : "超米手机",
          "images" : "http://12479122.jpg",
          "price" : 2899.0,
          "stock" : 200,
          "saleable" : true
        }
      },
      {
        "_index" : "goods",
        "_type" : "_doc",
        "_id" : "i06_i3wBGSEp1lHhuw_r",
        "_score" : 1.0,
        "_source" : {
          "title" : "小米手机",
          "images" : "http://12479122.jpg",
          "price" : 2699.0
        }
      },
      {
        "_index" : "goods",
        "_type" : "_doc",
        "_id" : "1",
        "_score" : 1.0,
        "_source" : {
          "title" : "超大米手机",
          "images" : "http://12479122.jpg",
          "price" : 3899.0,
          "stock" : 100,
          "saleable" : true
        }
      }
    ]
  }
}

2.8.删除数据

删除使用DELETE请求,同样,需要根据id进行删除:

语法

DELETE /索引库名/类型名/id值

示例:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jO8Jvbv6-1635414988384)(es.assets/1634403583344.png)]

3.查询

我们从4块来讲查询:

  • 基本查询
  • _source过滤
  • 结果过滤
  • 高级查询
  • 排序

3.1.基本查询

基本语法

GET /索引库名/_search
{
    "query":{
        "查询类型":{
            "查询条件":"查询条件值"
        }
    }
}

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

  • 查询类型:
    • 例如:match_allmatchtermrange 等等
  • 查询条件:查询条件会根据类型的不同,写法也有差异,后面详细讲解

3.1.1 查询所有(match_all)

示例:

GET /goods/_search
{
    "query":{
        "match_all": {}
    }
}
  • query:代表查询对象
  • match_all:代表查询所有

结果:

{
  "took" : 0,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 3,
      "relation" : "eq"
    },
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "goods",
        "_type" : "_doc",
        "_id" : "2",
        "_score" : 1.0,
        "_source" : {
          "title" : "大米手机",
          "images" : "http://12479122.jpg",
          "price" : 2899.0
        }
      },
      {
        "_index" : "goods",
        "_type" : "_doc",
        "_id" : "3",
        "_score" : 1.0,
        "_source" : {
          "title" : "超米手机",
          "images" : "http://12479122.jpg",
          "price" : 2899.0,
          "stock" : 200,
          "saleable" : true
        }
      },
      {
        "_index" : "goods",
        "_type" : "_doc",
        "_id" : "i06_i3wBGSEp1lHhuw_r",
        "_score" : 1.0,
        "_source" : {
          "title" : "小米手机",
          "images" : "http://12479122.jpg",
          "price" : 2699.0
        }
      }
    ]
  }
}

  • took:查询花费时间,单位是毫秒
  • time_out:是否超时
  • _shards:分片信息
  • hits:搜索结果总览对象
    • total:搜索到的总条数
    • max_score:所有结果中文档得分的最高分
    • hits:搜索结果的文档对象数组,每个元素是一条搜索到的文档信息
      • _index:索引库
      • _type:文档类型
      • _id:文档id
      • _score:文档得分
      • _source:文档的源数据

3.1.2 匹配查询(match)

我们先加入一条数据,便于测试:

PUT /goods/_doc/4
{
    "title":"小米电视4A",
    "images":"http://12479122.jpg",
    "price":3899.00
}

现在,索引库中有3部手机,1台电视:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oEgk9cks-1635414988384)(es.assets/1634403765050.png)]

  • or关系

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

GET /goods_search
{
    "query":{
        "match":{
            "title":"小米电视"
        }
    }
}

结果:

{
  "took" : 4,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 2,
      "relation" : "eq"
    },
    "max_score" : 1.8327961,
    "hits" : [
      {
        "_index" : "goods",
        "_type" : "_doc",
        "_id" : "4",
        "_score" : 1.8327961,
        "_source" : {
          "title" : "小米电视4A",
          "images" : "http://12479122.jpg",
          "price" : 3899.0
        }
      },
      {
        "_index" : "goods",
        "_type" : "_doc",
        "_id" : "i06_i3wBGSEp1lHhuw_r",
        "_score" : 0.6329006,
        "_source" : {
          "title" : "小米手机",
          "images" : "http://12479122.jpg",
          "price" : 2699.0
        }
      }
    ]
  }
}

在上面的案例中,不仅会查询到电视,而且与小米相关的都会查询到,多个词之间是or的关系。

  • and关系

某些情况下,我们需要更精确查找,我们希望这个关系变成and,可以这样做:

GET /goods/_search
{
    "query":{
        "match": {
          "title": {
            "query": "小米电视",
            "operator": "and"
          }
        }
    }
}

结果:

{
  "took" : 3,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : 1.8327961,
    "hits" : [
      {
        "_index" : "goods",
        "_type" : "_doc",
        "_id" : "4",
        "_score" : 1.8327961,
        "_source" : {
          "title" : "小米电视4A",
          "images" : "http://12479122.jpg",
          "price" : 3899.0
        }
      }
    ]
  }
}

本例中,只有同时包含小米电视的词条才会被搜索到。

  • or和and之间?

orand 间二选一有点过于非黑即白。 如果用户给定的条件分词后有 5 个查询词项,想查找只包含其中 4 个词的文档,该如何处理?将 operator 操作符参数设置成 and 只会将此文档排除。

有时候这正是我们期望的,但在全文搜索的大多数应用场景下,我们既想包含那些可能相关的文档,同时又排除那些不太相关的。换句话说,我们想要处于中间某种结果。

match 查询支持 minimum_should_match 最小匹配参数, 这让我们可以指定必须匹配的词项数用来表示一个文档是否相关。我们可以将其设置为某个具体数字,更常用的做法是将其设置为一个百分数,因为我们无法控制用户搜索时输入的单词数量:

GET /goods/_search
{
    "query":{
        "match":{
            "title":{
            	"query":"小米曲面电视",
            	"minimum_should_match": "75%"
            }
        }
    }
}

本例中,搜索语句可以分为3个词,如果使用and关系,需要同时满足3个词才会被搜索到。这里我们采用最小品牌数:75%,那么也就是说只要匹配到总词条数量的75%即可,这里3*75% 约等于2。所以只要包含2个词条就算满足条件了。

结果:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YMBBr5Af-1635414988385)(es.assets/1634403868724.png)]

3.1.3 多字段查询(multi_match)

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

GET /goods/_search
{
    "query":{
        "multi_match": {
            "query":    "小米",
            "fields":   [ "title", "subTitle" ]
        }
	}
}

本例中,我们会在title字段和subtitle字段中查询小米这个词

3.1.4 词条匹配(term)

term 查询被用于精确值 匹配,这些精确值可能是数字、时间、布尔或者那些未分词的字符串

GET /goods/_search
{
    "query":{
        "term":{
            "price":2699.00
        }
    }
}

结果:

{
  "took" : 0,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "goods",
        "_type" : "_doc",
        "_id" : "i06_i3wBGSEp1lHhuw_r",
        "_score" : 1.0,
        "_source" : {
          "title" : "小米手机",
          "images" : "http://12479122.jpg",
          "price" : 2699.0
        }
      }
    ]
  }
}

3.1.5 多词条精确匹配(terms)

terms 查询和 term 查询一样,但它允许你指定多值进行匹配。如果这个字段包含了指定值中的任何一个值,那么这个文档满足条件:

GET /goods/_search
{
    "query":{
        "terms":{
            "price":[2699.00,2899.00,3899.00]
        }
    }
}

结果:

{
  "took" : 2,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 4,
      "relation" : "eq"
    },
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "goods",
        "_type" : "_doc",
        "_id" : "2",
        "_score" : 1.0,
        "_source" : {
          "title" : "大米手机",
          "images" : "http://12479122.jpg",
          "price" : 2899.0
        }
      },
      {
        "_index" : "goods",
        "_type" : "_doc",
        "_id" : "3",
        "_score" : 1.0,
        "_source" : {
          "title" : "超米手机",
          "images" : "http://12479122.jpg",
          "price" : 2899.0,
          "stock" : 200,
          "saleable" : true
        }
      },
      {
        "_index" : "goods",
        "_type" : "_doc",
        "_id" : "i06_i3wBGSEp1lHhuw_r",
        "_score" : 1.0,
        "_source" : {
          "title" : "小米手机",
          "images" : "http://12479122.jpg",
          "price" : 2699.0
        }
      },
      {
        "_index" : "goods",
        "_type" : "_doc",
        "_id" : "4",
        "_score" : 1.0,
        "_source" : {
          "title" : "小米电视4A",
          "images" : "http://12479122.jpg",
          "price" : 3899.0
        }
      }
    ]
  }
}

3.2.结果过滤

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

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

3.2.1.直接指定字段

示例:

GET /goods/_search
{
  "_source": ["title","price"],
  "query": {
    "term": {
      "price": 2699
    }
  }
}

返回的结果:

{
  "took" : 13,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : 1.0,
    "hits" : [
      {
        "_index" : "goods",
        "_type" : "_doc",
        "_id" : "i06_i3wBGSEp1lHhuw_r",
        "_score" : 1.0,
        "_source" : {
          "price" : 2699.0,
          "title" : "小米手机"
        }
      }
    ]
  }
}

3.2.2.指定includes和excludes

我们也可以通过:

  • includes:来指定想要显示的字段
  • excludes:来指定不想要显示的字段

二者都是可选的。

示例:

GET /goods/_search
{
  "_source": {
    "includes":["title","price"]
  },
  "query": {
    "term": {
      "price": 2699
    }
  }
}

与下面的结果将是一样的:

GET /goods/_search
{
  "_source": {
     "excludes": ["images"]
  },
  "query": {
    "term": {
      "price": 2699
    }
  }
}

3.3 高级查询

3.3.1 布尔组合(bool)

bool把各种其它查询通过must(与)、must_not(非)、should(或)的方式进行组合

GET /goods/_search
{
    "query":{
        "bool":{
        	"must":     { "match": { "title": "大米" }},
        	"must_not": { "match": { "title":  "电视" }},
        	"should":   { "match": { "title": "手机" }}
        }
    }
}

结果:

{
  "took" : 1,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 1,
      "relation" : "eq"
    },
    "max_score" : 1.5078692,
    "hits" : [
      {
        "_index" : "goods",
        "_type" : "_doc",
        "_id" : "2",
        "_score" : 1.5078692,
        "_source" : {
          "title" : "大米手机",
          "images" : "http://12479122.jpg",
          "price" : 2899.0
        }
      }
    ]
  }
}

3.3.2 范围查询(range)

range 查询找出那些落在指定区间内的数字或者时间

GET /goods/_search
{
    "query":{
        "range": {
            "price": {
                "gte":  1000.0,
                "lt":   2800.00
            }
    	}
    }
}

range查询允许以下字符:

操作符 说明
gt 大于
gte 大于等于
lt 小于
lte 小于等于

3.3.3 模糊查询(fuzzy)

我们新增一个商品:

POST /goods/_doc/4
{
    "title":"apple手机",
    "images":"http://12479122.jpg",
    "price":6899.00
}

fuzzy 查询是 term 查询的模糊等价。它允许用户搜索词条与实际词条的拼写出现偏差,但是偏差的编辑距离不得超过2:

GET /goods/_search
{
  "query": {
    "fuzzy": {
      "title": "appla"
    }
  }
}

上面的查询,也能查询到apple手机

我们可以通过fuzziness来指定允许的编辑距离:

GET /goods/_search
{
  "query": {
    "fuzzy": {
        "title": {
            "value":"appla",
            "fuzziness":1
        }
    }
  }
}

3.4 过滤(filter)

条件查询中进行过滤

所有的查询都会影响到文档的评分及排名。如果我们需要在查询结果中进行过滤,并且不希望过滤条件影响评分,那么就不要把过滤条件作为查询条件来用。而是使用filter方式:

GET /goods/_search
{
    "query":{
        "bool":{
        	"must":{ "match": { "title": "小米手机" }},
        	"filter":{
                "range":{"price":{"gt":2000.00,"lt":3800.00}}
        	}
        }
    }
}

注意:filter中还可以再次进行bool组合条件过滤。

无查询条件,直接过滤

如果一次查询只有过滤,没有查询条件,不希望进行评分,我们可以使用constant_score取代只有 filter 语句的 bool 查询。在性能上是完全相同的,但对于提高查询简洁性和清晰度有很大帮助。

GET /goods/_search
{
    "query":{
        "constant_score":   {
            "filter": {
            	 "range":{"price":{"gt":2000.00,"lt":3000.00}}
            }
        }
}
}

3.5 排序

3.4.1 单字段排序

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

GET /goods/_search
{
  "query": {
    "match": {
      "title": "小米手机"
    }
  },
  "sort": [
    {
      "price": {
        "order": "desc"
      }
    }
  ]
}

3.4.2 多字段排序

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

GET /goods/_search
{
    "query":{
        "bool":{
        	"must":{ "match": { "title": "小米手机" }},
        	"filter":{
                "range":{"price":{"gt":200000,"lt":300000}}
        	}
        }
    },
    "sort": [
      { "price": { "order": "desc" }},
      { "_score": { "order": "desc" }}
    ]
}

4. 聚合aggregations

聚合可以让我们极其方便的实现对数据的统计、分析。例如:

  • 什么品牌的手机最受欢迎?
  • 这些手机的平均价格、最高价格、最低价格?
  • 这些手机每月的销售情况如何?

实现这些统计功能的比数据库的sql要方便的多,而且查询速度非常快,可以实现实时搜索效果。

4.1 基本概念

Elasticsearch中的聚合,包含多种类型,最常用的两种,一个叫,一个叫度量

桶(bucket)

桶的作用,是按照某种方式对数据进行分组,每一组数据在ES中称为一个,例如我们根据国籍对人划分,可以得到中国桶英国桶日本桶……或者我们按照年龄段对人进行划分:010,1020,2030,3040等。

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:求总数
  • ……

为了测试聚合,我们先批量导入一些数据

创建索引:

PUT /transactions
{
  "settings": {
    "number_of_shards": 1,
    "number_of_replicas": 0
  }
}




PUT transactions/_mapping/
{
      "properties": {
        "color": {
          "type": "keyword"
        },
        "make": {
          "type": "keyword"
        }
      }
    
  }

注意:在ES中,需要进行聚合、排序、过滤的字段其处理方式比较特殊,因此不能被分词。这里我们将color和make这两个文字类型的字段设置为keyword类型,这个类型不会被分词,将来就可以参与聚合

导入数据

POST /transactions/_bulk
{ "index": {}}
{ "price" : 10000, "color" : "red", "make" : "honda", "sold" : "2014-10-28" }
{ "index": {}}
{ "price" : 20000, "color" : "red", "make" : "honda", "sold" : "2014-11-05" }
{ "index": {}}
{ "price" : 30000, "color" : "green", "make" : "ford", "sold" : "2014-05-18" }
{ "index": {}}
{ "price" : 15000, "color" : "blue", "make" : "toyota", "sold" : "2014-07-02" }
{ "index": {}}
{ "price" : 12000, "color" : "green", "make" : "toyota", "sold" : "2014-08-19" }
{ "index": {}}
{ "price" : 20000, "color" : "red", "make" : "honda", "sold" : "2014-11-05" }
{ "index": {}}
{ "price" : 80000, "color" : "red", "make" : "bmw", "sold" : "2014-01-01" }
{ "index": {}}
{ "price" : 25000, "color" : "blue", "make" : "ford", "sold" : "2014-02-12" }

4.2 聚合为桶

首先,我们按照 汽车的颜色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:这个桶中的文档数量

通过聚合的结果我们发现,目前红色的小车比较畅销!

4.3 桶内度量

前面的例子告诉我们每个桶里面的文档数量,这很有用。 但通常,我们的应用需要提供更复杂的文档度量。 例如,每种颜色汽车的平均价格是多少?

因此,我们需要告诉Elasticsearch使用哪个字段使用何种度量方式进行运算,这些信息要嵌套在内,度量的运算会基于内的文档进行

现在,我们为刚刚的聚合结果添加 求价格平均值的度量:

GET /transactions/_search
{
    "size" : 0,
    "aggs" : { 
        "popular_colors" : { 
            "terms" : { 
              "field" : "color"
            },
            "aggs":{
                "avg_price": { 
                   "avg": {
                      "field": "price" 
                   }
                }
            }
        }
    }
}
  • aggs:我们在上一个aggs(popular_colors)中添加新的aggs。可见度量也是一个聚合
  • avg_price:聚合的名称
  • avg:度量的类型,这里是求平均值
  • field:度量运算的字段

结果:

...
  "aggregations": {
    "popular_colors": {
      "doc_count_error_upper_bound": 0,
      "sum_other_doc_count": 0,
      "buckets": [
        {
          "key": "red",
          "doc_count": 4,
          "avg_price": {
            "value": 32500
          }
        },
        {
          "key": "blue",
          "doc_count": 2,
          "avg_price": {
            "value": 20000
          }
        },
        {
          "key": "green",
          "doc_count": 2,
          "avg_price": {
            "value": 21000
          }
        }
      ]
    }
  }
...

可以看到每个桶中都有自己的avg_price字段,这是度量聚合的结果

4.4 桶内嵌套桶

刚刚的案例中,我们在桶内嵌套度量运算。事实上桶不仅可以嵌套运算, 还可以再嵌套其它桶。也就是说在每个分组中,再分更多组。

比如:我们想统计每种颜色的汽车中,分别属于哪个制造商,按照make字段再进行分桶

GET /transactions/_search
{
    "size" : 0,
    "aggs" : { 
        "popular_colors" : { 
            "terms" : { 
              "field" : "color"
            },
            "aggs":{
                "avg_price": { 
                   "avg": {
                      "field": "price" 
                   }
                },
                "maker":{
                    "terms":{
                        "field":"make"
                    }
                }
            }
        }
    }
}
  • 原来的color桶和avg计算我们不变
  • maker:在嵌套的aggs下新添一个桶,叫做maker
  • terms:桶的划分类型依然是词条
  • filed:这里根据make字段进行划分

部分结果:

{
  "took" : 3,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 8,
      "relation" : "eq"
    },
    "max_score" : null,
    "hits" : [ ]
  },
  "aggregations" : {
    "popular_colors" : {
      "doc_count_error_upper_bound" : 0,
      "sum_other_doc_count" : 0,
      "buckets" : [
        {
          "key" : "red",
          "doc_count" : 4,
          "maker" : {
            "doc_count_error_upper_bound" : 0,
            "sum_other_doc_count" : 0,
            "buckets" : [
              {
                "key" : "honda",
                "doc_count" : 3
              },
              {
                "key" : "bmw",
                "doc_count" : 1
              }
            ]
          },
          "avg_price" : {
            "value" : 32500.0
          }
        },
        {
          "key" : "blue",
          "doc_count" : 2,
          "maker" : {
            "doc_count_error_upper_bound" : 0,
            "sum_other_doc_count" : 0,
            "buckets" : [
              {
                "key" : "ford",
                "doc_count" : 1
              },
              {
                "key" : "toyota",
                "doc_count" : 1
              }
            ]
          },
          "avg_price" : {
            "value" : 20000.0
          }
        },
        {
          "key" : "green",
          "doc_count" : 2,
          "maker" : {
            "doc_count_error_upper_bound" : 0,
            "sum_other_doc_count" : 0,
            "buckets" : [
              {
                "key" : "ford",
                "doc_count" : 1
              },
              {
                "key" : "toyota",
                "doc_count" : 1
              }
            ]
          },
          "avg_price" : {
            "value" : 21000.0
          }
        }
      ]
    }
  }
}

  • 我们可以看到,新的聚合maker被嵌套在原来每一个color的桶中。
  • 每个颜色下面都根据 make字段进行了分组
  • 我们能读取到的信息:
    • 红色车共有4辆
    • 红色车的平均售价是 $32,500 美元。
    • 其中3辆是 Honda 本田制造,1辆是 BMW 宝马制造。

4.5.划分桶的其它方式

前面讲了,划分桶的方式有很多,例如:

  • Date Histogram Aggregation:根据日期阶梯分组,例如给定阶梯为周,会自动每周分为一组
  • Histogram Aggregation:根据数值阶梯分组,与日期类似
  • Terms Aggregation:根据词条内容分组,词条内容完全匹配的为一组
  • Range Aggregation:数值和日期的范围分组,指定开始和结束,然后按段分组

刚刚的案例中,我们采用的是Terms Aggregation,即根据词条划分桶。

接下来,我们再学习几个比较实用的:

4.5.1.阶梯分桶Histogram

原理:

histogram是把数值类型的字段,按照一定的阶梯大小进行分组。你需要指定一个阶梯值(interval)来划分阶梯大小。

举例:

比如你有价格字段,如果你设定interval的值为200,那么阶梯就会是这样的:

0,200,400,600,…

上面列出的是每个阶梯的key,也是区间的启点。

如果一件商品的价格是450,会落入哪个阶梯区间呢?计算公式如下:

bucket_key = Math.floor((value - offset) / interval) * interval + offset

value:就是当前数据的值,本例中是450

offset:起始偏移量,默认为0

interval:阶梯间隔,比如200

因此你得到的key = Math.floor((450 - 0) / 200) * 200 + 0 = 400

操作一下:

比如,我们对汽车的价格进行分组,指定间隔interval为5000:

GET /transactions/_search
{
  "size":0,
  "aggs":{
    "price":{
      "histogram": {
        "field": "price",
        "interval": 5000
      }
    }
  }
}

结果:

{
  "took" : 8,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 8,
      "relation" : "eq"
    },
    "max_score" : null,
    "hits" : [ ]
  },
  "aggregations" : {
    "price" : {
      "buckets" : [
        {
          "key" : 10000.0,
          "doc_count" : 2
        },
        {
          "key" : 15000.0,
          "doc_count" : 1
        },
        {
          "key" : 20000.0,
          "doc_count" : 2
        },
        {
          "key" : 25000.0,
          "doc_count" : 1
        },
        {
          "key" : 30000.0,
          "doc_count" : 1
        },
        {
          "key" : 35000.0,
          "doc_count" : 0
        },
        {
          "key" : 40000.0,
          "doc_count" : 0
        },
        {
          "key" : 45000.0,
          "doc_count" : 0
        },
        {
          "key" : 50000.0,
          "doc_count" : 0
        },
        {
          "key" : 55000.0,
          "doc_count" : 0
        },
        {
          "key" : 60000.0,
          "doc_count" : 0
        },
        {
          "key" : 65000.0,
          "doc_count" : 0
        },
        {
          "key" : 70000.0,
          "doc_count" : 0
        },
        {
          "key" : 75000.0,
          "doc_count" : 0
        },
        {
          "key" : 80000.0,
          "doc_count" : 1
        }
      ]
    }
  }
}

你会发现,中间有大量的文档数量为0 的桶,看起来很丑。

我们可以增加一个参数min_doc_count为1,来约束最少文档数量为1,这样文档数量为0的桶会被过滤

示例:

GET /transactions/_search
{
  "size":0,
  "aggs":{
    "price":{
      "histogram": {
        "field": "price",
        "interval": 5000,
        "min_doc_count": 1
      }
    }
  }
}

结果:

{
  "took" : 1,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 8,
      "relation" : "eq"
    },
    "max_score" : null,
    "hits" : [ ]
  },
  "aggregations" : {
    "price" : {
      "buckets" : [
        {
          "key" : 10000.0,
          "doc_count" : 2
        },
        {
          "key" : 15000.0,
          "doc_count" : 1
        },
        {
          "key" : 20000.0,
          "doc_count" : 2
        },
        {
          "key" : 25000.0,
          "doc_count" : 1
        },
        {
          "key" : 30000.0,
          "doc_count" : 1
        },
        {
          "key" : 80000.0,
          "doc_count" : 1
        }
      ]
    }
  }
}

完美,!

如果你用kibana将结果变为柱形图,会更好看:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Y46gZlFQ-1635414988386)(es.assets/1531752558505.png)]

4.5.2.范围分桶range

范围分桶与阶梯分桶类似,也是把数字按照阶段进行分组,只不过range方式需要你自己指定每一组的起始和结束大小。

Elasticsearch导入MySQL数据

Logstash

​ ES官网:https://www.elastic.co/products/logstash

​ 下载地址:https://www.elastic.co/cn/downloads/logstash

​ Logstash 是开源的服务器端数据处理管道,能够同时从多个来源采集数据,转换数据,然后将数据发送到您最喜欢的 “存储库” 中。(我们的存储库当然是 Elasticsearch。)

安装

将文件上传至服务器

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Wx44pby0-1635414988386)(es.assets/image84-1594011945749.png)]

创建目录,解压至该目录

mkdir -p /usr/local/es/logstash
tar -zxvf logstash-7.6.2.tar.gz -C /usr/local/es/logstash/

修改logstash下的config/jvm.options配置文件

vim config/jvm.options

将:

    -Xms1g 修改为 ===> -Xms512m
    -Xmx1g 修改为 ===> -Xmx512m    

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Ar9AjNx4-1635414988387)(es.assets/1634299208528-16346307890303.png)]

运行

cd /usr/local/es/logstash/logstash-7.6.2
bin/logstash -e 'input { stdin {} } output { stdout {} }'

使用-e参数在命令行中指定配置是一种方式,不过如果需要配置更多设置则需要很长的内容。这种情况,我们首先创建一个配置文件,并且指定logstash使用这个配置文件。标准配置为文件含有input{} ,filter{}output{}三部分。

看到如下界面后,输入hello world并看到如下效果则表示安装启动成功

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TZSKJQQ2-1635414988387)(es.assets/1634307036388.png)]

同步MySQL数据

需要建立两个文件,一个jdbc.conf,一个jdbc.sql,名称自定义;

导入MySQL的Java驱动包:mysql-connector-java-8.0.18.jar,并确保数据库服务可用。

注意:请将mysql的驱动包上传至logstash-7.6.2/logstash-core/lib/jars/ 目录下

/usr/local/es/logstash/logstash-7.6.2/logstash-core/lib/jars/

jdbc.conf

cd /usr/local/es/logstash/logstash-7.6.2
vim jdbc.conf

编写如下内容

input {
    stdin {
    }
    jdbc {
      jdbc_connection_string => "jdbc:mysql://106.55.162.206:3306/shop?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai"
      jdbc_driver_class => "com.mysql.cj.jdbc.Driver"
      jdbc_user => "root"
      jdbc_password => "root"
      jdbc_paging_enabled => "true"
      jdbc_page_size => "50000"
      jdbc_default_timezone => "Asia/Shanghai"
      statement_filepath => "/usr/local/es/logstash/logstash-7.6.2/jdbc.sql"
      schedule => "* * * * *"
      lowercase_column_names => false
    }
}
output {
    elasticsearch {
        hosts =>  ["192.168.126.4:9201","192.168.126.4:9202","192.168.126.4:9203"]
        index => "shop"
        document_id => "%{goodsId}"
    }
    stdout {
        codec => json_lines
    }
}

jdbc.sql

别名驼峰会被默认设置为全小写,需要配置lowercase_column_names => false

SELECT
	goods_id goodsId,
	goods_name goodsName,
	market_price marketPrice,
	original_img originalImg
FROM
	t_goods

检测配置文件是否编写正确

bin/logstash -f /usr/local/es/logstash/logstash-7.6.2/jdbc.conf -t

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BSsCM9nC-1635414988388)(es.assets/image90-1594011945749.png)]

创建索引库

PUT /shop
{

  "settings": {

      "number_of_shards": 5,

      "number_of_replicas": 1

}

}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yjhxKHee-1635414988389)(es.assets/1634307893831.png)]

设置mapping

Analyzer分词配置解释:

  • ik_smart:粗粒度分词,比如中华人民共和国国歌,会拆分为中华人民共和国,国歌;
  • ik_max_word:细粒度分词,比如中华人民共和国国歌,会拆分为中华人民共和国,中华人民,中华,华人,人民共和国,人民,人,民,共和国,共和,和,国国,国歌,会穷尽各种可能的组合。
POST /shop/_mapping
{

      "properties": {

            "goodsName": {

                "type": "text",

                "analyzer": "ik_max_word",

                "search_analyzer": "ik_max_word"

            }

       }

}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yicdPs0D-1635414988389)(es.assets/1634307912338.png)]

执行导入

cd /usr/local/es/logstash/logstash-7.6.2/
bin/logstash -f /usr/local/es/logstash/logstash-7.6.2/jdbc.conf   --path.data=/usr/local/es/data/

该工具不会断开连接,会持续和ES保持连接,并按照指定的时间循环执行导入。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2CsKY0HT-1635414988390)(es.assets/image91-1594011945750.png)]

查询

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Wa7Rrh30-1635414988390)(es.assets/1634389621286.png)]

GET /shop/_search?pretty
{
    "query": { "term" : { "goodsId": "62"}}
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xa9G9F9Z-1635414988391)(es.assets/1634390373998.png)]

GET /shop/_search?pretty
{
    "query": { "term" : { "goodsName": "智能"}}
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fKQAdxlw-1635414988392)(es.assets/1634390481472.png)]

Elasticsearch的JavaAPI

JavaAPI

创建项目

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-S95KjyhD-1635414988392)(es.assets/1634391660809.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JBERENgR-1635414988392)(es.assets/1634391633182.png)]

添加依赖

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0F64y9DR-1635414988393)(es.assets/image97-1594011945750.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JyeDHimf-1635414988394)(es.assets/image98-1594011945750.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tIWNYW4c-1635414988394)(es.assets/image99-1594011945750.png)]

pom.xml


<dependency>
    <groupId>junitgroupId>
    <artifactId>junitartifactId>
    <version>4.12version>
    <scope>testscope>
dependency>

<dependency>
    <groupId>org.elasticsearchgroupId>
    <artifactId>elasticsearchartifactId>
    <version>7.6.2version>
dependency>

<dependency>
    <groupId>org.elasticsearch.clientgroupId>
    <artifactId>elasticsearch-rest-clientartifactId>
    <version>7.6.2version>
dependency>

<dependency>
    <groupId>org.elasticsearch.clientgroupId>
    <artifactId>elasticsearch-rest-high-level-clientartifactId>
    <version>7.6.2version>
dependency>

Java连接Elasticsearch

Elasticsearch有两种连接方式:transportresttransport通过TCP方式访问ES(只支持java),rest方式通过http API 访问ES(没有语言限制)。

ES官方建议使用rest方式, transport 7.0版本中不建议使用,在8.X的版本中废弃。

你可以用Java客户端做很多事情:

执行标准的index,get,delete,update,search等操作。

在正在运行的集群上执行管理任务。

但是,通过官方文档可以得知,现在存在至少三种Java客户端。

  • Transport Client
  • Java High Level REST Client
  • Java Low Level Rest Client

造成这种混乱的原因是:

长久以来,ES并没有官方的Java客户端,并且Java自身是可以简单支持ES的API的,于是就先做成了TransportClient。但是TransportClient的缺点是显而易见的,它没有使用RESTful风格的接口,而是二进制的方式传输数据。

之后ES官方推出了Java Low Level REST Client,它支持RESTful,用起来也不错。但是缺点也很明显,因为TransportClient的使用者把代码迁移到Low Level REST Client的工作量比较大。官方文档专门为迁移代码出了一堆文档来提供参考。

现在ES官方推出Java High Level REST Client,它是基于Java Low Level REST Client的封装,并且API接收参数和返回值和TransportClient是一样的,使得代码迁移变得容易并且支持了RESTful的风格,兼容了这两种客户端的优点。当然缺点是存在的,就是版本的问题。ES的小版本更新非常频繁,在最理想的情况下,客户端的版本要和ES的版本一致(至少主版本号一致),次版本号不一致的话,基本操作也许可以,但是新API就不支持了。

package cn.es;

import org.apache.http.HttpHost;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.junit.After;
import org.junit.Before;

import java.io.IOException;

public class ElasticsearchTest {

    // ES服务器IP
    private final static String HOST = "192.168.10.100";

    // ES服务器连接方式
    private final static String SCHEME = "http";

    // 初始化 ES 服务器集群
    // 参数分别为:IP,端口,连接方式(默认为http)
    private final static HttpHost[] httpHosts = {
            new HttpHost(HOST, 9200, SCHEME),
            new HttpHost(HOST, 9201, SCHEME),
            new HttpHost(HOST, 9202, SCHEME)
    };

    // 客户端
    private RestHighLevelClient client = null;

    /**
     * 获取客户端
     */
    @Before
    public void getConnect() {
        client = new RestHighLevelClient(RestClient.builder(httpHosts));
    }

    /**
     * 关闭连接
     */
    @After
    public void closeConnect() {
        try {
            if (null != client) client.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

}

增删查改

  • 查询时,如果索引库不存在会报错
ElasticsearchStatusException[Elasticsearch exception [type=index_not_found_exception, reason=no such index [索引名]]
  • 查询时,如果不设置size,默认只返回10条数据,默认from:0, size:10

ElasticsearchTest.java

/**
 * 添加数据
 */
@Test
public void testCreate() throws IOException {
    // 准备数据
    Map<String, Object> jsonMap = new HashMap<>();
    jsonMap.put("username", "zhangsan");
    jsonMap.put("age", 18);
    jsonMap.put("address", "sh");
    // 指定索引库和id及数据
    IndexRequest indexRequest = new IndexRequest("ik").id("5").source(jsonMap);
    // 执行请求
    IndexResponse indexResponse = client.index(indexRequest, RequestOptions.DEFAULT);
    System.out.println(indexResponse.toString());
}

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XLlKNb8s-1635414988395)(es.assets/1634392257365.png)]

/**
 * 查询数据
 */
@Test
public void testRetrieve() throws IOException {
    // 指定索引库和id
    GetRequest getRequest = new GetRequest("ik", "5");
    // 执行请求
    GetResponse getResponse = client.get(getRequest, RequestOptions.DEFAULT);
    System.out.println(getResponse.getSource());
}

/**
 * 修改数据
 */
@Test
public void testUpdate() throws IOException {
    // 准备数据
    Map<String, Object> jsonMap = new HashMap<>();
    jsonMap.put("username", "lisi");
    jsonMap.put("age", 20);
    jsonMap.put("address", "bj");
    // 指定索引库和id及数据
    UpdateRequest updateRequest = new UpdateRequest("ik", "5").doc(jsonMap);
    // 执行请求
    UpdateResponse updateResponse = client.update(updateRequest, RequestOptions.DEFAULT);
    System.out.println(updateResponse.toString());
}

/**
 * 删除数据
 */
@Test
public void testDelete() throws IOException {
    // 指定索引库和id
    DeleteRequest deleteRequest = new DeleteRequest("ik", "5");
    DeleteResponse deleteResponse = client.delete(deleteRequest, RequestOptions.DEFAULT);
    System.out.println(deleteResponse.toString());
}

/**
 * 批量增删改操作
 */
@Test
public void testCUD() throws IOException {
    // 初始化 BulkRequest
    BulkRequest request = new BulkRequest();
    // 指定索引库和id及数据
    // 批量添加
    request.add(new IndexRequest("ik").id("6")
            .source(XContentType.JSON, "username", "zhangsan", "age", 18));
    request.add(new IndexRequest("ik").id("7")
            .source(XContentType.JSON, "username", "lisi", "age", 20));
    // 批量修改
    request.add(new UpdateRequest("ik", "6")
            .doc(XContentType.JSON, "", ""));
    // 批量删除
    request.add(new DeleteRequest("ik", "6"));
    // 执行请求
    BulkResponse bulkResponse = client.bulk(request, RequestOptions.DEFAULT);
    System.out.println(bulkResponse);
}

/**
 * 批量查询-查询所有
 */
@Test
public void testRetrieveAll() throws IOException {
    // 指定索引库
    SearchRequest searchRequest = new SearchRequest("ik", "shop");
    // 构建查询对象
    SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
    // 添加查询条件
    searchSourceBuilder.query(QueryBuilders.matchAllQuery());
    // 执行请求
    searchRequest.source(searchSourceBuilder);
    SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
    // 总条数
    System.out.println(searchResponse.getHits().getTotalHits().value);
    // 结果数据(如果不设置返回条数,大于十条默认只返回十条)
    SearchHit[] hits = searchResponse.getHits().getHits();
    for (SearchHit hit : hits) {
        System.out.println("分数:" + hit.getScore());
        Map<String, Object> source = hit.getSourceAsMap();
        System.out.println("index -> " + hit.getIndex());
        System.out.println("id -> " + hit.getId());
        for (Map.Entry<String, Object> s : source.entrySet()) {
            System.out.println(s.getKey() + " -- " + s.getValue());
        }
        System.out.println("----------------------------");
    }
}

/**
 * 批量查询-匹配查询
 */
@Test
public void testRetrieveMatch() throws IOException {
    // 指定索引库
    SearchRequest searchRequest = new SearchRequest("ik", "shop");
    // 构建查询对象
    SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
    // 添加查询条件
    // 指定从 content 和 goodsName 字段中查询
    String key = "中国";
    searchSourceBuilder.query(QueryBuilders.multiMatchQuery(key, "content", "goodsName"));
    // 执行请求
    searchRequest.source(searchSourceBuilder);
    SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
    // 总条数
    System.out.println(searchResponse.getHits().getTotalHits().value);
    // 结果数据(如果不设置返回条数,大于十条默认只返回十条)
    SearchHit[] hits = searchResponse.getHits().getHits();
    for (SearchHit hit : hits) {
        System.out.println("分数:" + hit.getScore());
        Map<String, Object> source = hit.getSourceAsMap();
        System.out.println("index -> " + hit.getIndex());
        System.out.println("id -> " + hit.getId());
        for (Map.Entry<String, Object> s : source.entrySet()) {
            System.out.println(s.getKey() + " -- " + s.getValue());
        }
        System.out.println("----------------------------");
    }
}

/**
 * 批量查询-分页查询-按分数或id排序
 */
@Test
public void testRetrievePage() throws IOException {
    // 指定索引库
    SearchRequest searchRequest = new SearchRequest("shop");
    // 构建查询对象
    SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
    // 添加分页条件,从第 0 个开始,返回 5 个
    searchSourceBuilder.from(0).size(5);
    // 添加查询条件
    // 指定从 goodsName 字段中查询
    String key = "中国移动联通电信";
    searchSourceBuilder.query(QueryBuilders.multiMatchQuery(key, "goodsName"));
    // 按照 score 正序排列(默认倒序)
    //searchSourceBuilder.sort(SortBuilders.scoreSort().order(SortOrder.ASC));
    // 并且按照 id 倒序排列(分数字段会失效返回 NaN)
    //searchSourceBuilder.sort(SortBuilders.fieldSort("_id").order(SortOrder.DESC));
    // 执行请求
    searchRequest.source(searchSourceBuilder);
    SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
    // 总条数
    System.out.println(searchResponse.getHits().getTotalHits().value);
    // 结果数据(如果不设置返回条数,大于十条默认只返回十条)
    SearchHit[] hits = searchResponse.getHits().getHits();
    for (SearchHit hit : hits) {
        System.out.println("分数:" + hit.getScore());
        Map<String, Object> source = hit.getSourceAsMap();
        System.out.println("index -> " + hit.getIndex());
        System.out.println("id -> " + hit.getId());
        for (Map.Entry<String, Object> s : source.entrySet()) {
            System.out.println(s.getKey() + " -- " + s.getValue());
        }
        System.out.println("----------------------------");
    }
}

/**
 * 批量查询-分页查询-高亮查询
 */
@Test
public void testHighlight() throws IOException {
    // 指定索引库
    SearchRequest searchRequest = new SearchRequest("shop");
    // 构建查询对象
    SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
    // 添加分页条件,从第 0 个开始,返回 5 个
    searchSourceBuilder.from(0).size(5);
    // 构建高亮对象
    HighlightBuilder highlightBuilder = new HighlightBuilder();
    // 指定高亮字段和高亮样式
    highlightBuilder.field("goodsName")
            .preTags("")
            .postTags("");
    searchSourceBuilder.highlighter(highlightBuilder);
    // 添加查询条件
    // 指定从 goodsName 字段中查询
    String key = "中国移动联通电信";
    searchSourceBuilder.query(QueryBuilders.multiMatchQuery(key, "goodsName"));
    // 执行请求
    searchRequest.source(searchSourceBuilder);
    SearchResponse searchResponse = client.search(searchRequest, RequestOptions.DEFAULT);
    // 总条数
    System.out.println(searchResponse.getHits().getTotalHits().value);
    // 结果数据(如果不设置返回条数,大于十条默认只返回十条)
    SearchHit[] hits = searchResponse.getHits().getHits();
    for (SearchHit hit : hits) {
        // 构建项目中所需的数据结果集
        String highlightMessage = String.valueOf(hit.getHighlightFields().get("goodsName").fragments()[0]);
        Integer goodsId = Integer.valueOf((Integer) hit.getSourceAsMap().get("goodsId"));
        String goodsName = String.valueOf(hit.getSourceAsMap().get("goodsName"));
        BigDecimal marketPrice = new BigDecimal(String.valueOf(hit.getSourceAsMap().get("marketPrice")));
        String originalImg = String.valueOf(hit.getSourceAsMap().get("originalImg"));
        System.out.println("goodsId -> " + goodsId);
        System.out.println("goodsName -> " + goodsName);
        System.out.println("highlightMessage -> " + highlightMessage);
        System.out.println("marketPrice -> " + marketPrice);
        System.out.println("originalImg -> " + originalImg);
        System.out.println("----------------------------");
    }
}

SpringBoot整合Elasticsearch

创建项目

创建SpringBoot项目添加Elasticsearch依赖

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HWJYF4vS-1635414988396)(es.assets/image100-1594011945750.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CzOyQvs0-1635414988396)(es.assets/Snipaste_2020-05-25_21-44-15-1594011945750.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-dd4pSaJ7-1635414988397)(es.assets/Snipaste_2020-05-25_21-44-22-1594011945750.png)]

添加依赖

pom.xml


<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.0modelVersion>
	<parent>
		<groupId>org.springframework.bootgroupId>
		<artifactId>spring-boot-starter-parentartifactId>
		<version>2.3.0.RELEASEversion>
		<relativePath/> 
	parent>
	<groupId>cn.esgroupId>
	<artifactId>spring-data-elasticsearch-demoartifactId>
	<version>0.0.1-SNAPSHOTversion>
	<name>spring-data-elasticsearch-demoname>
	<description>Demo project for Spring Bootdescription>

	<properties>
		<java.version>1.8java.version>
	properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework.bootgroupId>
			<artifactId>spring-boot-starter-data-elasticsearchartifactId>
		dependency>
		<dependency>
			<groupId>org.springframework.bootgroupId>
			<artifactId>spring-boot-starter-webartifactId>
		dependency>

		<dependency>
			<groupId>org.springframework.bootgroupId>
			<artifactId>spring-boot-starter-testartifactId>
			<scope>testscope>
			<exclusions>
				<exclusion>
					<groupId>org.junit.vintagegroupId>
					<artifactId>junit-vintage-engineartifactId>
				exclusion>
			exclusions>
		dependency>
	dependencies>

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

project>

配置文件

application.yml

#配置es
spring:
  elasticsearch:
    rest:
      uris: 192.168.126.4:9201, 192.168.126.4:9202, 192.168.126.4:9203

实体类

Goods.java

package cn.es.springdataelasticsearchdemo.pojo;

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;
import java.math.BigDecimal;

@Document(indexName = "shop1",shards = 5,replicas = 1,createIndex = false)
public class Goods implements Serializable {
    /**
     * 商品id
     */
    @Id
    private Integer goodsId;

    /**
     * 商品名称
     */
    @Field(type = FieldType.Text,analyzer = "ik_max_word")
    private String goodsName;

    /**
     * 市场价
     */
    @Field(type = FieldType.Double)
    private BigDecimal marketPrice;

    /**
     * 商品上传原始图
     */
    @Field(type = FieldType.Keyword)
    private String originalImg;
    
    /**
     * t_goods
     */
    private static final long serialVersionUID = 1L;

    public Integer getGoodsId() {
        return goodsId;
    }

    public void setGoodsId(Integer goodsId) {
        this.goodsId = goodsId;
    }

    public String getGoodsName() {
        return goodsName;
    }

    public void setGoodsName(String goodsName) {
        this.goodsName = goodsName == null ? null : goodsName.trim();
    }

    public BigDecimal getMarketPrice() {
        return marketPrice;
    }

    public void setMarketPrice(BigDecimal marketPrice) {
        this.marketPrice = marketPrice;
    }

    public String getOriginalImg() {
        return originalImg;
    }

    public void setOriginalImg(String originalImg) {
        this.originalImg = originalImg == null ? null : originalImg.trim();
    }


    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        sb.append(getClass().getSimpleName());
        sb.append(" [");
        sb.append("Hash = ").append(hashCode());
        sb.append(", goodsId=").append(goodsId);
        sb.append(", goodsName=").append(goodsName);
        sb.append(", marketPrice=").append(marketPrice);
        sb.append(", originalImg=").append(originalImg);
        sb.append("]");
        return sb.toString();
    }


	public Goods() {
	}

	public Goods(Integer goodsId, String goodsName, BigDecimal marketPrice, String originalImg) {
		this.goodsId = goodsId;
		this.goodsName = goodsName;
		this.marketPrice = marketPrice;
		this.originalImg = originalImg;
	}
}
  • 为需要使用索引库的实体类加上注解 @Document 部分属性如下

    • indexName="索引库名"
    • shards = 分片数量(默认1)
    • replicas = 副本数量(默认1)
  • 为id属性 添加 @Id 注释

  • 各个字段加上注解并制定类型 @Field 部分属性如下

    • type= FieldType.枚举: 指定字段类型 Keyword不分词, Text分词 对应着elasticsearch的字段类型
  • 为需要分词的字段添加分词器 analyzer="分词器名" (ik分词器固定写法 ik_max_word )

  • 是否创建索引 createIndex=boolean(默认true)

Dao层

package cn.es.springdataelasticsearchdemo.dao;

import com.xxxx.springdataelasticsearchdemo.pojo.Goods;
import org.springframework.data.elasticsearch.annotations.Query;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;

import java.util.List;


public interface GoodsRepository extends ElasticsearchRepository<Goods,Integer> {

	/**
	 * 根据商品名查询
	 * @param goodsName
	 * @return
	 */
	List<Goods> findByGoodsName(String goodsName);

	/**
	 * 根据id查询商品
	 * ?0为占位符
	 * @param id
	 * @return
	 */
	@Query("{\"match\": {\"goodsId\":{ \"query\": \"?0\"}}}")
	Goods findByIdValue(Integer id);
}

测试类

SpringDataElasticsearchDemoApplicationTests.java

package cn.es.springdataelasticsearchdemo;

import cn.es.springdataelasticsearchdemo.dao.GoodsRepository;
import cn.es.springdataelasticsearchdemo.pojo.Goods;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.data.elasticsearch.core.IndexOperations;
import org.springframework.data.elasticsearch.core.SearchHit;
import org.springframework.data.elasticsearch.core.SearchHits;
import org.springframework.data.elasticsearch.core.document.Document;
import org.springframework.data.elasticsearch.core.mapping.IndexCoordinates;
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;

import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

@SpringBootTest
public class SpringDataElasticsearchDemoApplicationTests {

	@Autowired
	private GoodsRepository goodsRepository;
	@Autowired
	private ElasticsearchRestTemplate elasticsearchRestTemplate;


	/**
	 * 批量插入,查询所有
	 */
	@Test
	public void testSaveAll() {
		//批量插入
		List<Goods> list = new ArrayList<>();
		list.add(new Goods(152, "测试手机1", new BigDecimal("500"), "jpg"));
		list.add(new Goods(153, "测试手机2", new BigDecimal("800"), "png"));
		goodsRepository.saveAll(list);
		//查询所有
		Iterable<Goods> all = goodsRepository.findAll();
		all.forEach(System.out::println);
	}

	/**
	 * 根据商品名查询
	 */
	@Test
	public void testFindByName() {
		List<Goods> list = goodsRepository.findByGoodsName("%中国%");
		list.forEach(System.out::println);
	}

	/**
	 * 根据商品id查询
	 */
	@Test
	public void testFindById() {
		System.out.println(goodsRepository.findByIdValue(150));
	}


	/**
	 * 索引操作
	 */
	@Test
	public void testIndex() {
		//设置索引信息(实体类),返回indexOperations
		IndexOperations indexOperations = elasticsearchRestTemplate.indexOps(Goods.class);
		indexOperations.create();
		//创建索引映射
		Document mapping = indexOperations.createMapping();
		//将映射写入索引
		indexOperations.putMapping(mapping);

		//获取索引
		Map<String, Object> map = indexOperations.getMapping();
		map.forEach((k, v) -> System.out.println(k + "-->" + v));

		//索引是否存在
		boolean exists = indexOperations.exists();
		System.out.println(exists);

		//删除索引
		indexOperations.delete();
	}


	/**
	 * 增删改
	 */
	@Test
	public void testDocument() {
		/**
		 * 根据id和索引删除,返回删除的id
		 * 第一个参数:id,String类型
		 * 第二个参数:索引库对象
		 */
		// String count = elasticsearchRestTemplate.delete("150", IndexCoordinates.of("shop"));
		// System.out.println(count);
		/**
		 * 删除查询结果
		 * 第一个参数:查询对象
		 * 第二个参数:索引类字节码
		 * 第三个参数:索引库对象
		 */
		elasticsearchRestTemplate.delete(
				new NativeSearchQueryBuilder()
						.withQuery(QueryBuilders.matchQuery("goodsName", "测试"))
						.build(),
				Goods.class,
				IndexCoordinates.of("shop"));
		//新增/更新(id不存在就新增,存在就更新)
		List<Goods> list = new ArrayList<>();
		list.add(new Goods(150, "测试手机3", new BigDecimal("100"), "jpg"));
		list.add(new Goods(151, "测试手机4", new BigDecimal("200"), "png"));
		Iterable<Goods> save = elasticsearchRestTemplate.save(list);
		save.forEach(System.out::println);
	}


	/**
	 * 匹配查询
	 */
	@Test
	public void testSearchMatch() {
		NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
		NativeSearchQuery query = nativeSearchQueryBuilder
				/**
				 * 第一个参数:关键词
				 * 第二个参数:对应es的字段
				 */
				.withQuery(QueryBuilders.multiMatchQuery("中国移动联通电信", "goodsName"))
				.build();
		SearchHits<Goods> search = elasticsearchRestTemplate.search(query, Goods.class);
		search.forEach(searchHit -> System.out.println(searchHit.getContent()));
	}


	/**
	 * 分页,排序,高亮查询
	 */
	@Test
	public void testSearchPage() {
		NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
		NativeSearchQuery query = nativeSearchQueryBuilder
				/**
				 * 第一个参数:关键词
				 * 第二个参数:对应es的字段
				 */
				.withQuery(QueryBuilders.multiMatchQuery("中国移动联通电信", "goodsName"))
				/**
				 * 第一个参数:当前页,0开始
				 * 第二个参数:每个条数
				 * 第三个参数:排序对象
				 *      升序/降序
				 *      比较字段
				 */
				// .withPageable(PageRequest.of(0,5, Sort.Direction.DESC,"goodsId","marketPrice"))
                // 分页
				.withPageable(PageRequest.of(0, 5))
				// .withSort(SortBuilders.fieldSort("marketPrice").order(SortOrder.ASC))
				//高亮,默认样式(斜体)
				// .withHighlightFields(new HighlightBuilder.Field("goodsName"))
				//高亮,指定样式
				.withHighlightBuilder(new HighlightBuilder().field("goodsName").preTags("").postTags(""))
				.build();
		SearchHits<Goods> search = elasticsearchRestTemplate.search(query, Goods.class);
		for (SearchHit<Goods> searchHit : search) {
			//id
			System.out.println(searchHit.getId());
			//分数
			System.out.println(searchHit.getScore());
			//排序的值
			Integer sortValues = (Integer) searchHit.getSortValues().get(0);
			System.out.println(sortValues);
			//高亮信息
			String highlightMessage = searchHit.getHighlightField("goodsName").get(0);
			System.out.println(highlightMessage);
			//结果对象
			System.out.println(searchHit.getContent());
		}
	}
}
public void testSaveAll() {
	//批量插入
	List list = new ArrayList<>();
	list.add(new Goods(152, "测试手机1", new BigDecimal("500"), "jpg"));
	list.add(new Goods(153, "测试手机2", new BigDecimal("800"), "png"));
	goodsRepository.saveAll(list);
	//查询所有
	Iterable all = goodsRepository.findAll();
	all.forEach(System.out::println);
}

/**
 * 根据商品名查询
 */
@Test
public void testFindByName() {
	List list = goodsRepository.findByGoodsName("%中国%");
	list.forEach(System.out::println);
}

/**
 * 根据商品id查询
 */
@Test
public void testFindById() {
	System.out.println(goodsRepository.findByIdValue(150));
}


/**
 * 索引操作
 */
@Test
public void testIndex() {
	//设置索引信息(实体类),返回indexOperations
	IndexOperations indexOperations = elasticsearchRestTemplate.indexOps(Goods.class);
	indexOperations.create();
	//创建索引映射
	Document mapping = indexOperations.createMapping();
	//将映射写入索引
	indexOperations.putMapping(mapping);

	//获取索引
	Map map = indexOperations.getMapping();
	map.forEach((k, v) -> System.out.println(k + "-->" + v));

	//索引是否存在
	boolean exists = indexOperations.exists();
	System.out.println(exists);

	//删除索引
	indexOperations.delete();
}


/**
 * 增删改
 */
@Test
public void testDocument() {
	/**
	 * 根据id和索引删除,返回删除的id
	 * 第一个参数:id,String类型
	 * 第二个参数:索引库对象
	 */
	// String count = elasticsearchRestTemplate.delete("150", IndexCoordinates.of("shop"));
	// System.out.println(count);
	/**
	 * 删除查询结果
	 * 第一个参数:查询对象
	 * 第二个参数:索引类字节码
	 * 第三个参数:索引库对象
	 */
	elasticsearchRestTemplate.delete(
			new NativeSearchQueryBuilder()
					.withQuery(QueryBuilders.matchQuery("goodsName", "测试"))
					.build(),
			Goods.class,
			IndexCoordinates.of("shop"));
	//新增/更新(id不存在就新增,存在就更新)
	List list = new ArrayList<>();
	list.add(new Goods(150, "测试手机3", new BigDecimal("100"), "jpg"));
	list.add(new Goods(151, "测试手机4", new BigDecimal("200"), "png"));
	Iterable save = elasticsearchRestTemplate.save(list);
	save.forEach(System.out::println);
}


/**
 * 匹配查询
 */
@Test
public void testSearchMatch() {
	NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
	NativeSearchQuery query = nativeSearchQueryBuilder
			/**
			 * 第一个参数:关键词
			 * 第二个参数:对应es的字段
			 */
			.withQuery(QueryBuilders.multiMatchQuery("中国移动联通电信", "goodsName"))
			.build();
	SearchHits search = elasticsearchRestTemplate.search(query, Goods.class);
	search.forEach(searchHit -> System.out.println(searchHit.getContent()));
}


/**
 * 分页,排序,高亮查询
 */
@Test
public void testSearchPage() {
	NativeSearchQueryBuilder nativeSearchQueryBuilder = new NativeSearchQueryBuilder();
	NativeSearchQuery query = nativeSearchQueryBuilder
			/**
			 * 第一个参数:关键词
			 * 第二个参数:对应es的字段
			 */
			.withQuery(QueryBuilders.multiMatchQuery("中国移动联通电信", "goodsName"))
			/**
			 * 第一个参数:当前页,0开始
			 * 第二个参数:每个条数
			 * 第三个参数:排序对象
			 *      升序/降序
			 *      比较字段
			 */
			// .withPageable(PageRequest.of(0,5, Sort.Direction.DESC,"goodsId","marketPrice"))
            // 分页
			.withPageable(PageRequest.of(0, 5))
			// .withSort(SortBuilders.fieldSort("marketPrice").order(SortOrder.ASC))
			//高亮,默认样式(斜体)
			// .withHighlightFields(new HighlightBuilder.Field("goodsName"))
			//高亮,指定样式
			.withHighlightBuilder(new HighlightBuilder().field("goodsName").preTags("").postTags(""))
			.build();
	SearchHits search = elasticsearchRestTemplate.search(query, Goods.class);
	for (SearchHit searchHit : search) {
		//id
		System.out.println(searchHit.getId());
		//分数
		System.out.println(searchHit.getScore());
		//排序的值
		Integer sortValues = (Integer) searchHit.getSortValues().get(0);
		System.out.println(sortValues);
		//高亮信息
		String highlightMessage = searchHit.getHighlightField("goodsName").get(0);
		System.out.println(highlightMessage);
		//结果对象
		System.out.println(searchHit.getContent());
	}
}

}


你可能感兴趣的:(elasticsearch,大数据,搜索引擎)