Elasticsearch 今生前世

需求的诞生

刘备一大早就来到了公司,一看张飞和关羽已经在公司了,就问道:“两位贤弟,今天来的还蛮早啊。”
张飞一听就炸毛了,“大哥,你让我和二哥去做什么搜索功能,我们已经一晚没睡了,昨天就没回去好嘛。”
关羽也来气,“大哥,是啊,我们刚刚才上线电商网站,你这边又要加什么需求,现在用数据库检索不是好好的么,能不能让我们歇口气。”
“两位兄弟辛苦了,我也不想啊,最近咱们一单生意都没有啊。昨天我和一位朋友聊,他说我们的网站很不好用,找不到他想要的鞋,结果只好去别的地方买了。
不过他给我推荐了一位黑客高手,叫诸葛亮的家伙,说是啥都得懂,我们今天找他取经去。”


三顾茅庐

三人一行来找诸葛亮,不过前面两次都碰了壁。
据诸葛亮书童说,诸葛亮不在家,到了第三次,还是不在家。张飞仔细一听,明明是有人在家啊,而且玩游戏喊的声音还这么大,张飞怒了,搭梯子把诸葛亮家的保险给拔了。诸葛亮正郁闷呢,咋停电了呢?算了,今天没得玩了,于是让书童请他们进来。
“在下诸葛名亮,字孔明,不知三位…”,三人一说,是这么这么回事。诸葛亮一听,“哦,原来是这么这么回事啊,你们的网站我刚看了,你们家的草鞋品种确实不 Nan 少 Kan。
如今客户上网站找东西,都是先用网站的搜索来搜一下,但是你们网站的搜索功能实在是太 La 弱 Ji,明摆在那里的商品我都搜不出来,实在是大问题啊。”
“这样啊,我看你们仨都是好人,给你们推荐一个好东西,叫做 Elasticsearch,这个肯定可以帮助你们。”
“翼德,把先生放下来吧。”
“是,大哥。二哥,你把刀也放下吧。”
关羽一听,好像在哪里听说过 Elasticsearch,“大哥,这个东西好像有点耳熟啊,哦,诸葛亮先生这一说,我倒是记起来了,隔壁公司的吕布最近神神秘秘的,好像就是在用这个,难怪他们最近公司业务好的很”。


Elasticsearch 的故事

诸葛亮清了清嗓子,又从抽屉里摸出一把扇子,“还是让我来给你们讲讲吧”。
“Elasticsearch 以前叫 Elastic Search。顾名思义,就是“弹性的搜索”。很明显,它一开始是围绕着搜索功能,打造了一个分布式搜索引擎,底层是基于开源的搜索引擎库 Lucene,是由 Java 语言编写的,项目大概是 2010 年 2 月份在 Github 正式落户的。
咳咳,有必要首先给你介绍一下 Lucene
Lucene 是一个非常古老的搜索引擎工具包,也是用 Java 编写,主要用来构建倒排索引(一种数据结构)和对这些索引进行检索,从而实现全文检索功能。
Lucene 很强大,使用起来也非常灵活,缺点是它仅仅是一个基础类库,也没有考虑到高并发和分布式的场景。
如果你想在自己的程序里面使用 Lucene,还是需要做很多工作,并且涉及很多搜索原理和索引数据结构的知识,这就给我们带来了不少挑战。所以,Lucene 的上手时间一般都比较长。”
关羽插了一句,“Lucene 我知道,确实贼难用,使用起来一堆问题啊,我之前试过来着。” 关羽说完,脸又红了。
诸葛亮接着说。“时间一晃来到 2004 年,有一个以色列小伙子,名字叫谢伊·班农( Shay Banon),他成亲不久来到伦敦,因为当时他的夫人正好在伦敦学厨师。
初来乍到,也没有找到工作,于是班农就打算写一个叫作 iCook 的小程序来管理和搜索菜谱,一来练练手,方便找工作;二来这个小工具还可以给其夫人用。
班农在编写 iCook 的过程中,使用了 Lucene,感受到了直接使用 Lucene 开发程序的各种暴击和痛苦,于是他在 Lucene 之上,封装了一个叫作 Compass 的程序框架,与 Hibernate 和 JPA 等 ORM 框架进行集成,通过操作对象的方式来自动地调用 Lucene 以构建索引。
这样做的好处是,可以很方便地实现对‘领域对象’进行索引的创建,并实现‘字段级别’的检索,以及实现‘全文搜索’功能。可以说,Compass 大大简化了给 Java 程序添加搜索功能的开发。Compass 开源出来,变得很流行。
在 Compass 编写到 2.x 版本的时候,社区里面出现了更多需求,比如需要有处理更多数据的能力以及分布式的设计。班农发现只有重写 Compass ,才能更好地实现这些分布式搜索的需求,于是 Compass 3.0 就没有了,取而代之的是一个全新的项目,也就是 Elasticsearch。”

让人怦然心动的 Elasticsearch

看到刘备三人听的入迷,诸葛亮轻挥羽扇,继续说了下去。
“得益于 Compass 项目的积累,Elasticsearch 问世之初就考虑到了功能的易用性。
Elasticsearch 作为一个独立的搜索服务器,提供了非常方便的搜索功能。用户完全不用关心底层 Lucene 的细节,只需要通过标准的 Http+RESTful 风格的 API,就可以进行索引数据的增删改查。数据的输入输出采用 JSON 格式,以文档和面向对象的方式,这样就能非常方便地理解和表达领域数据。”
张飞一拍桌子,“Elasticsearch 简直就是一个 Compass 的 RESTful 实现啊!”
“没错。同时,Elasticsearch 基于分片和副本的方式实现了一个分布式的 Lucene Directory,再结合Map-reduce 的理念,实现了一个简单的搜索请求分发合并的策略,能轻松化解海量索引和分布式高可用的问题。
可以说,仅仅依靠这两点,Elasticsearch就已经秒杀了当时市面上所有的搜索引擎服务或是程序库,我当时看到 Elasticsearch 也眼前一亮。
如今,Elasticsearch 基本上已经是搜索引擎市场排名第一的产品了,从 DB-Engines 网站的排名可以看到,Elasitcsearch 基本上是一骑绝红尘,拉开第二名远远一大截。”
image.png

ELK 横空出世

诸葛亮口水狂飙,显得很兴奋,“如果只是 Elasticsearch 单独使用,那我们的故事也就结束了,事实上好戏这才刚刚开始。俗话说,一个好汉三个帮,开源社区亦是如此。”
“这一个好汉三个帮,说的不就是咱仨嘛。” 刘备接过话茬。
“别打岔,”诸葛亮继续说,“这里我要说的是 ‘ELK’ 的出现,不过首先我要给你们讲讲 Logstash。”
Logstash 是一个开源的日志收集工具,用 JRuby 写的,主要特点是基于灵活的 Pipeline 管道架构来处理数据。
什么意思呢?可以理解为将数据放进一个管道内进行处理,并且就跟真正的自来水管一样,管道由一截一截管子组成,每一个小管代表着一个数据处理的流程,每一个流程只做一件事情,然后可以根据数据的处理需要,选择多个不同类型的管子灵活组装。
Logstash 社区非常活跃,支持多种输入数据源和多种输出数据源。一开始, Elasticsearch 只是作为其中一个输出的存储,主要用于日志数据的存储。
不过,随着大家把日志发送到 Elasticsearch 之后,大家发现这家伙用起来很方便嘛,不仅能够存储大量的数据,水平伸缩还很方便。更关键的是,你能够很方便地把数据找出来,也就是进行全文搜索。
全文搜索在日志分析里面是非常基础的一个功能,通过一个关键字就能定位具体的详细日志,相比存放到关系型数据库和普通的文件存储,Elasticsearch 优势非常明显。于是 Logstash 搭配 Elasticsearch 变得很受欢迎。

Kibana 的故事

不过 Logstash 自带的 UI 查询日志的界面有点简陋,于是有一个叫作 Rashid Khan 的运维工程师表示完全忍不了了,用 PHP 写了一个叫作 Kibana 的程序,一个更好看和更好用的前端界面。PHP 写完一版,他又用 Ruby 写一版,后面又用 AngularJS 写了一版,不仅有日志的搜索和查看,还加上了一些统计展示功能。
Kibana 的名字其实是俩个水果的名字的组合(Kiwi+Banana)。
张飞听到这里:“工作不饱和啊这家伙”。孔明瞪了他一眼,继续说道。
这个时候,Elasticsearch 已经有 Facet 概念,也就是分面统计( 注:1.0 之后推出了 Aggregation 来代替 Facet),可以对数据里面的某个字段进行单个维度的统计,支持多种统计类型。
比如, TermFacet 可以计算字段里面某些值出现了多少次;Histogram Facet 还可以按时间区间进行汇总统计等。
这些统计功能在前端 UI 就可以被利用起来,展示一些饼图、时间曲线等等,在运维的分析里面自然也都是需要的。慢慢的 Kibana 越做越复杂,支持的功能越来越多,Kibana 3 变得流行起来。

于是乎,ELK 横空出世(Elasticsearch、Logstash 和 Kibana 这三个产品的首字母缩写),风靡了整个运维界。
故事讲到这里,相信你们对于 Elasticsearch 就有了一个大概的认识,可以用它做搜索,也可以用它做日志。”
张飞点点头,“还是相当的强悍嘛。”

Elastic Stack 平台的魅力

“不过,这还没完。”诸葛亮吞了吞口水,继续说。
“Elastic 后面又引入了 Beats 家族。这是一系列非常轻量级的数据收集端,我给你介绍几个比较典型的,比如:

  • Packetbeat 可以实时监听网卡流量,并实时解析网络协议数据,可用来做 NPM 网络数据分析;
  • Metricbeat 可以用来收集服务器,以及服务器上部署的应用服务的各项监控指标数据,这样就可以替代 Zabbix 等传统的监控软件,来做服务器的性能指标分析;
  • Auditbeat可以实时收集服务器的行为事件,用于安全方面的入侵检测和安全日志审计分析;
  • Winlogbeat用于 Windows 平台的事件日志收集;
  • Filebeat 用于日志文件的收集等。

Elasticsearch、Logstash、Kibana、Beats ,这几个放在一起,就叫作 Elastic Stack。
如今,Elastic 的版图越来越大,前年,Elastic 收购 Opbeat,开源了业界第一个完整的 APM 解决方案,通过探针可以实现无侵入的代码级别的应用性能监控;
去年7月又收购了代码搜索 http://Insight.IO,后续可以实现代码级别的语义检索。今年又收购了一个做终端安全的厂商 Endgame。这样 Elastic Stack 这一个平台就可以同时做到:

  • 日志分析
  • 性能指标分析
  • 安全日志分析
  • APM 应用性能分析
  • NPM 网络性能分析
  • 网站站内搜索
  • 企业级搜索
  • 代码搜索
  • 实时 BI 业务分析
  • SIEM 解决方案
  • 终端设备安全
  • ……

试想一下:
在一个风和日丽的下午,你手机上收到一条告警短信,于是点击链接,打开 Kibana 的监控仪表盘,发现某台服务器的 CPU 达到 100% 了。
于是,你顺手点击过滤这台服务器的所有相关信息,可以看到相关的日志显示,是这台服务器上面部署的某一个业务服务的 QPS 有显著下降,然后过滤到这个业务的日志,发现有很多异常的日志信息,前端 Nginx 代理日志还显示有很多请求被拒绝,看样子是后端的微服务处理能力达到瓶颈。

这个时候,继续点击 APM 的分析面板,切换到事务和会话分析界面,看到有很多数据库链接处于开启状态。
你点击查看调用代码,立马就找到了性能瓶颈的原因,原来是某个类的某个方法调用 MySQL 却没有及时释放链接造成了泄露,于是修改这行代码,提交上线,问题解决。然后,你可以若无其事地继续浏览相亲网站啦。
尽管这是一个假想的例子,但是可以看到,基于 Elastic Stack ,你可以覆盖一整套完整的,从全局性能监控到具体代码级别的排障和解决问题的过程,并且使用起来要比很多现有的方案更加高效和便捷。

好了,现在你们是否对 Elasticsearch 已经有了一个初步的了解呢?是不是也有跃跃欲试的打算?”
刘备点点头:“今天来先生这里真的是收获不少,之前多有冒犯,还请多多包涵啊。”
关羽也说:“大哥,明天我就和三弟开始研究 Elasticsearch,争取早日改造好咱们的网站。”
“刚说的相亲网站要不也发我一下”,张飞连忙问道。刘备没好气白了一眼张飞。
“天色已晚,告辞了!”
刘备三人作别孔明,各自高兴的回家了。
“慢走不送,有空来喝茶啊。”
孔明抹了一把额头,总算送走这仨了,恐怕从此江湖上估计要不平静喽。


Elasticsearch 概述

https://blog.csdn.net/py_123456/article/details/81707633

概述

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

ElasticSearch 与 Solr 区别

Solr 定义:

Solr是Apache 下的一个开源项目,使用Java基于Lucene开发的全文检索服务是一个独立的企业级搜索应用服务器,它对外提供类似于Web-service的API接口。用户可以通过http请求,向搜索引擎服务器提交一定格式的XML文件,生成索引;也可以通过Http Get操作提出查找请求,并得到XML格式的返回结果。

ElasticSearch vs Solr 优缺点

图片.png

ElasticSearch 与 Solr 检索速度区别

1.当单纯的对已有数据进行搜索时,Solr更快。
图片.png

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

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

图片.png

4.大型互联网公司,实际生产环境测试,将搜索引擎从Solr转到Elasticsearch以后的平均查询速度有了50倍的提升。

图片.png

ElasticSearch 与 Solr 热度

图片.png

ElasticSearch 与 Solr 总结

 二者安装都很简单

  • Solr 利用 Zookeeper 进行分布式管理,而 Elasticsearch 自身带有分布式协调管理功能。
  • Solr 支持更多格式的数据,比如JSON、XML、CSV,而 Elasticsearch 仅支持json文件格式。
  • Solr 官方提供的功能更多,而 Elasticsearch 本身更注重于核心功能,高级功能多有第三方插件提供。
  • Solr 在传统的搜索应用中表现好于 Elasticsearch,但在处理实时搜索应用时效率明显低于 Elasticsearch。
  • Solr 是传统搜索应用的有力解决方案,但 Elasticsearch 更适用于新兴的实时搜索应用。

    ElasticSearch 架构

    图片.png

    ElasticSearch 工作原理

图片.png

ElasticSearch 在Hadoop生态圈的位置

图片.png

图片.png

ElasticSearch 应用场景

1.站内搜索:主要和 Solr 竞争,属于后起之秀

2.NoSQL Json文档数据库:主要抢占 Mongo 的市场,它在读写性能上优于 Mongo ,同时也支持地理位置查询,还方便地理位置和文本混合查询。

3.监控:统计、日志类时间序的数据存储和分析、可视化,这方面是引领者

4.国外:Wikipedia(维基百科)使用ES提供全文搜索并高亮关键字、StackOverflow(IT问答网站)结合全文搜索与地理位置查询、Github使用Elasticsearch检索1300亿行的代码

5.国内:百度(在云分析、网盟、预测、文库、钱包、风控等业务上都应用了ES,单集群每天导入30TB+数据,总共每天60TB+)、新浪 、阿里巴巴、腾讯等公司均有对ES的使用

6.使用比较广泛的平台ELK(ElasticSearch, Logstash, Kibana)


Elasticsearch 前期准备工作

Elasticsearch 安装

安装 Elasticsearch 前提条件,必须本地环境安装 JDK1.8或以上环境。

下载地址

https://www.elastic.co/cn/downloads/past-releases/elasticsearch-7-6-2

图片.png文件目录详解

图片.png

  1. bin 启动
  2. config 配置
  3. elasticsearch.yml 配置文件
  4. jvm.options jvm 参数配置
  5. log4j2.properties 日志文件配置
  6. lib jar目录
  7. logs 日志文件目录
  8. modules 模块目录
  9. plugins 插件目录(以后安装的插件都要放在这个目录下)

启动 ES

点击E:\ruanjian\javatools\es\elasticsearch-7.6.2\bin\elasticsearch.bat 启动 Elasticsearch
图片.png

图片.png访问地址:http://localhost:9200/
图片.png


kibana 安装

下载地址

https://artifacts.elastic.co/downloads/kibana/kibana-7.6.2-windows-x86_64.zip

下载完成,解压后在bin目录下,双击【kibana.bat】,启动Kibana。
启动需要一定时间
图片.png

默认启动后,通过浏览器访问 【http://localhost:5601】即可访问kibana。
图片.png
通过 Dev Tools 工具,可以使用 DSL 语句查询ES
图片.png


IK 分词器

下载地址

https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.6.2/elasticsearch-analysis-ik-7.6.2.zip

把下载ik分词器解压到E:\javaTools\es\elasticsearch-7.6.2\plugins\ik 如下图

图片.png


ElasticSearch-Head插件

安装node环境

  1. 从地址:https://nodejs.org/en/download/ 下载相应系统的msi,双击安装。
    Elasticsearch 学习 - 图24
  2. 安装完成用cmd进入安装目录执行 node -v可查看版本号
    Elasticsearch 学习 - 图25
  3. 执行 npm install -g grunt-cli 安装grunt ,安装完成后执行grunt -version查看是否安装成功,会显示安装的版本号
    Elasticsearch 学习 - 图26
  4. 开始安装head
    ① 进入ES安装目录下的config目录,修改elasticsearch.yml文件.在文件的末尾加入以下代码,支持跨域调用ES服务
    1. http.cors.enabled: true
    2. http.cors.allow-origin: "*"
    3. node.master: true node.data: true
    然后去掉network.host: 192.168.0.1的注释并改为network.host: 0.0.0.0,去掉cluster.name;node.name;http.port的注释(也就是去掉#)
    ② 双击elasticsearch.bat重启es

下载 elasticsearch-head


https://github.com/mobz/elasticsearch-head 中下载head插件,选择下载zip
Elasticsearch 学习 - 图27
④解压到指定文件夹下,G:\elasticsearch-7.6.2\elasticsearch-head-master\ 进入该文件夹,修改G:\elasticsearch-7.6.2\elasticsearch-head-master\Gruntfile.js 在对应的位置加上hostname:’*’
Elasticsearch 学习 - 图28
⑤在G:\elasticsearch-7.6.2\elasticsearch-head-master\ 下执行 npm install 安装完成后执行 npm run start 运行head插件,如果不成功重新安装grunt。成功如下

图片.png
访问地址 : http://localhost:9100/

图片.png


Elasticsearch 中 CRUD

创建索引

https://blog.csdn.net/weixin_43453386/article/details/108673982
https://blog.csdn.net/weixin_33806300/article/details/89566183
NBA的新的赛季又开始了,我相信大部分人有精彩比赛的时候还是会去关注的,我们创建一个 NBA 球队的索引,开始我们的学习之路,索引名称需是小写。

备注:在 7.X 版本中,直接去除了 type 的概念,就是说 index 不再会有 type
原因分析
1、为何要去除 type 的概念?
答: 因为 Elasticsearch 设计初期,是直接查考了关系型数据库的设计模式,存在了 type(数据表)的概念。
但是,其搜索引擎基于 Lucene 的,这种 “基因”决定了 type 是多余的。 Lucene 的全文检索功能之所以快,是因为 倒序索引 的存在。
而这种 倒序索引 的生成是基于 index 的,而并非 type。多个type 反而会减慢搜索的速度。
为了保持 Elasticsearch “一切为了搜索” 的宗旨,适当的做些改变(去除 type)也是无可厚非的,也是值得的。
所以,Why not?!

  1. PUT /nba
  2. {
  3. "settings":{
  4. "number_of_shards": 1,
  5. "number_of_replicas": 0
  6. },
  7. "mappings":{
  8. "properties":{
  9. "name_cn":{
  10. "type":"text"
  11. },
  12. "name_en":{
  13. "type":"text"
  14. },
  15. "gymnasium":{
  16. "type":"text"
  17. },
  18. "topStar":{
  19. "type":"text"
  20. },
  21. "championship":{
  22. "type":"integer"
  23. },
  24. "date":{
  25. "type":"date"
  26. }
  27. }
  28. }
  29. }

如果格式书写正确,我们会得到如下返回信息,表示创建成功

  1. {
  2. "acknowledged" : true,
  3. "shards_acknowledged" : true,
  4. "index" : "nba"
  5. }

设置分片和副本信息

  1. "settings":{
  2. "number_of_shards": 1,
  3. "number_of_replicas": 0
  4. }

字段说明:

字段名称 字段说明
nba 索引
number_of_shards 分片数
number_of_replicas 副本数
name_cn 球队中文名
name_en 球队英文名
gymnasium 球馆名称
championship 总冠军次数
topStar 当家球星
date 加入NBA年份

添加

索引创建完成之后,我们往索引中加入球队数据,1,2,3 是我们指定的 ID,如果不写 ES 会默认ID。
其实我们可以不创建上面的索引 mapping 直接推送数据,但是这样 ES 会根据数据信息自动为我们设定字段类型,这会造成索引信息不准确的风险。

  1. PUT /nba/_doc/1
  2. {
  3. "name_en":"San Antonio Spurs SAS",
  4. "name_cn":"圣安东尼安马刺",
  5. "gymnasium":"AT&T中心球馆",
  6. "championship": 5,
  7. "topStar":"蒂姆·邓肯",
  8. "date":"1995-04-12"
  9. }
  10. PUT /nba/_doc/2
  11. {
  12. "name_en":"Los Angeles Lakers",
  13. "name_cn":"洛杉矶湖人",
  14. "gymnasium":"斯台普斯中心球馆",
  15. "championship": 16,
  16. "topStar":"科比·布莱恩特",
  17. "date":"1947-05-12"
  18. }
  19. PUT /nba/_doc/3
  20. {
  21. "name_en":"Golden State Warriors",
  22. "name_cn":"金州勇士队",
  23. "gymnasium":"甲骨文球馆",
  24. "championship": 6,
  25. "topStar":"斯蒂芬·库里",
  26. "date":"1949-06-13"
  27. }
  28. PUT /nba/_doc/4
  29. {
  30. "name_en":"Miami Heat",
  31. "name_cn":"迈阿密热火队",
  32. "gymnasium":"美国航空球场",
  33. "championship": 3,
  34. "topStar":"勒布朗·詹姆斯",
  35. "date":"1988-06-13"
  36. }
  37. PUT /nba/_doc/5
  38. {
  39. "name_en":"Cleveland Cavaliers",
  40. "name_cn":"克利夫兰骑士队",
  41. "gymnasium":"速贷球馆",
  42. "championship": 1,
  43. "topStar":"勒布朗·詹姆斯",
  44. "date":"1970-06-13"
  45. }

修改

put 修改

put 修改必须字段全部映射上,不然会映射为空字段(丢失字段)
说明:修改id = 5 这条数据,把 “name_cn”:”克利夫兰骑士队” 为 “洛杉矶湖人队”

  1. PUT /nba/_doc/5
  2. {
  3. "name_en":"Cleveland Cavaliers",
  4. "name_cn":"洛杉矶湖人队",
  5. "gymnasium":"速贷球馆",
  6. "championship": 5,
  7. "topStar":"勒布朗·詹姆斯",
  8. "date":"1970-06-13"
  9. }

返回结果
修改成功:version 版本会自动+1

  1. {
  2. "_index" : "nba",
  3. "_type" : "_doc",
  4. "_id" : "5",
  5. "_version" : 2,
  6. "result" : "updated",
  7. "_shards" : {
  8. "total" : 1,
  9. "successful" : 1,
  10. "failed" : 0
  11. },
  12. "_seq_no" : 5,
  13. "_primary_term" : 2
  14. }

修改单个字段

  1. POST /nba/_doc/5/_update
  2. {
  3. "doc":{
  4. "name_cn":"洛杉矶湖人队2"
  5. }
  6. }

返回结果

  1. {
  2. "_index" : "nba",
  3. "_type" : "_doc",
  4. "_id" : "5",
  5. "_version" : 7,
  6. "_seq_no" : 10,
  7. "_primary_term" : 2,
  8. "found" : true,
  9. "_source" : {
  10. "name_en" : "Cleveland Cavaliers",
  11. "name_cn" : "洛杉矶湖人队2",
  12. "gymnasium" : "速贷球馆",
  13. "championship" : 5,
  14. "topStar" : "勒布朗·詹姆斯",
  15. "date" : "1970-06-13"
  16. }
  17. }

查询

上面新增完数据之后,这时候我们再执行开始的 MATCH_ALL ,就会发现我们自己的索引信息也在查询的结果里面了,只是查询的结果是全部信息,其中包括索引、分片和副本的信息,内容比较多。我们可单独查询自己需要的索引信息。
Elasticsearch 提供丰富且灵活的查询语言叫做 DSL 查询 (Query Domain Specific language) ,它允许你构建更加复杂、强大的搜索。

匹配查询 match,match_all

  1. GET /nba/_doc/_search
  2. {
  3. "query": {
  4. "match_all": {}
  5. }
  6. }

查询返回结果

  1. {
  2. ----------------first part--------------------
  3. "took" : 5,
  4. "timed_out" : false,
  5. "_shards" : {
  6. "total" : 1,
  7. "successful" : 1,
  8. "skipped" : 0,
  9. "failed" : 0
  10. },
  11. -----------------second part---------------------
  12. "hits" : {
  13. "total" : {
  14. "value" : 5,
  15. "relation" : "eq"
  16. },
  17. "max_score" : 1.0,
  18. "hits" : [
  19. {
  20. "_index" : "nba",
  21. "_type" : "_doc",
  22. "_id" : "1",
  23. "_score" : 1.0,
  24. "_source" : {
  25. "name_en" : "San Antonio Spurs SAS",
  26. "name_cn" : "圣安东尼安马刺",
  27. "gymnasium" : "AT&T中心球馆",
  28. "championship" : 5,
  29. "topStar" : "蒂姆·邓肯",
  30. "date" : "1995-04-12"
  31. }
  32. },
  33. {
  34. "_index" : "nba",
  35. "_type" : "_doc",
  36. "_id" : "2",
  37. "_score" : 1.0,
  38. "_source" : {
  39. "name_en" : "Los Angeles Lakers",
  40. "name_cn" : "洛杉矶湖人",
  41. "gymnasium" : "斯台普斯中心球馆",
  42. "championship" : 16,
  43. "topStar" : "科比·布莱恩特",
  44. "date" : "1947-05-12"
  45. }
  46. },
  47. {
  48. "_index" : "nba",
  49. "_type" : "_doc",
  50. "_id" : "3",
  51. "_score" : 1.0,
  52. "_source" : {
  53. "name_en" : "Golden State Warriors",
  54. "name_cn" : "金州勇士队",
  55. "gymnasium" : "甲骨文球馆",
  56. "championship" : 6,
  57. "topStar" : "斯蒂芬·库里",
  58. "date" : "1949-06-13"
  59. }
  60. },
  61. {
  62. "_index" : "nba",
  63. "_type" : "_doc",
  64. "_id" : "4",
  65. "_score" : 1.0,
  66. "_source" : {
  67. "name_en" : "Miami Heat",
  68. "name_cn" : "迈阿密热火队",
  69. "gymnasium" : "美国航空球场",
  70. "championship" : 3,
  71. "topStar" : "勒布朗·詹姆斯",
  72. "date" : "1988-06-13"
  73. }
  74. },
  75. {
  76. "_index" : "nba",
  77. "_type" : "_doc",
  78. "_id" : "5",
  79. "_score" : 1.0,
  80. "_source" : {
  81. "name_en" : "Cleveland Cavaliers",
  82. "name_cn" : "克利夫兰骑士队",
  83. "gymnasium" : "速贷球馆",
  84. "championship" : 1,
  85. "topStar" : "勒布朗·詹姆斯",
  86. "date" : "1970-06-13"
  87. }
  88. }
  89. ]
  90. }
  91. }

描述:响应的数据结果分为两部分
第一部分为:分片副本信息,第二部分 hits 包装的为查询的数据集。

match 查询

查询英文名称为:”Golden State Warriors” 的球队信息

  1. GET /nba/_doc/_search
  2. {
  3. "query": {
  4. "match": {
  5. "name_en": "Golden State Warriors"
  6. }
  7. }
  8. }

查看所有的索引

  1. GET _cat/indices

返回结果

  1. yellow open testquery XVxmphCKRm6NZOZqskTSAw 1 1 1 0 3.1kb 3.1kb
  2. yellow open multimatchtest 5DV6kiD0TmOOhCja-slXHQ 1 1 0 0 283b 283b
  3. green open .kibana_task_manager_1 ZcvulkaOTSi_t39rKLyU1A 1 0 2 1 26.8kb 26.8kb
  4. green open .apm-agent-configuration MQK1xmi8T2yLF14df0v00Q 1 0 0 0 283b 283b
  5. green open pms waSiNQFjSX-XC-F9_QAPEA 1 0 64 0 26.3kb 26.3kb
  6. yellow open testquery2 y3w_9mXoS8aPAHQjs2Vrxg 1 1 1 0 3.1kb 3.1kb
  7. green open nba wRQCEB8DRv6rn5Q5f286Nw 1 0 5 0 6.3kb 6.3kb
  8. green open .kibana_1 IhQxb6mhT2-0crKIe_7gTA 1 0 22 2 45.5kb 45.5kb
  9. green open zlp uHZF61y4Sl2gPxNndRoubA 1 0 5 0 10.3kb 10.3kb

指定字段查询

根据球队中文名称查询

GET /nba/_search
{

  "query": {
    "match": {
      "name_cn": "洛杉矶湖人"
    }
  }
}

返回结果

{
  "took" : 1,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 2,
      "relation" : "eq"
    },
    "max_score" : 4.697637,
    "hits" : [
      {
        "_index" : "nba",
        "_type" : "_doc",
        "_id" : "2",
        "_score" : 4.697637,
        "_source" : {
          "name_en" : "Los Angeles Lakers",
          "name_cn" : "洛杉矶湖人",
          "gymnasium" : "斯台普斯中心球馆",
          "championship" : 16,
          "topStar" : "科比·布莱恩特",
          "date" : "1947-05-12"
        }
      },
      {
        "_index" : "nba",
        "_type" : "_doc",
        "_id" : "5",
        "_score" : 4.097939,
        "_source" : {
          "name_en" : "Cleveland Cavaliers",
          "name_cn" : "洛杉矶湖人队2",
          "gymnasium" : "速贷球馆",
          "championship" : 5,
          "topStar" : "勒布朗·詹姆斯",
          "date" : "1970-06-13"
        }
      }
    ]
  }
}

布尔值查询

must (and),所有的条件都要符合,相当Mysql Where age = 19 and username = xxx


GET nba/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {"name_cn": "洛杉矶湖人"}

        },
        {
          "match": {
            "topStar":"勒布朗·詹姆斯"
          }
        }
      ]
    }
  }
}
{
  "took" : 8,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 2,
      "relation" : "eq"
    },
    "max_score" : 8.156839,
    "hits" : [
      {
        "_index" : "nba",
        "_type" : "_doc",
        "_id" : "5",
        "_score" : 8.156839,
        "_source" : {
          "name_en" : "Cleveland Cavaliers",
          "name_cn" : "洛杉矶湖人队2",
          "gymnasium" : "速贷球馆",
          "championship" : 5,
          "topStar" : "勒布朗·詹姆斯",
          "date" : "1970-06-13"
        }
      },
      {
        "_index" : "nba",
        "_type" : "_doc",
        "_id" : "2",
        "_score" : 5.213199,
        "_source" : {
          "name_en" : "Los Angeles Lakers",
          "name_cn" : "洛杉矶湖人",
          "gymnasium" : "斯台普斯中心球馆",
          "championship" : 16,
          "topStar" : "科比·布莱恩特",
          "date" : "1947-05-12"
        }
      }
    ]
  }
}

should (or),所有的条件符合一个即可, 相当Mysql Where age = 19 and username = xxx


GET zlp/_search
{
  "query": {
    "bool": {
      "should": [
        {
          "match": {"username": "change"}

        },{
          "match": {"age": 44}
        }
      ]
    }
  }
}

过滤器查询


GET zlp/_search
{
  "query": {
    "bool": {
      "should": [
        {
          "match": {"username": "change"}

        }
      ],
      "filter": [
        {
          "range": {
            "age": {
              "gte": 10,
              "lte": 30
            }
          }
        }
      ]
    }
  }
}

图片.png

  • gt 大于
  • lt 小于
  • gte 大于等于
  • lte 小于等于

    trem 查询

term 与 match 区别


全文搜索, 通常用于对text类型字段的查询,会对进行查询的文本先进行分词操作,如下图
image.png
term
精确查询,通常用于对 keyword 类型 和有精确值的字段进行查询,不会对进行查询的文本进行分词操作,精确匹配,
如果字段类型设置了分词器,会查询分词器倒排索引分词 如下图
image.png

新建索引

PUT /testquery
{
  "mappings": {
      "properties": { 
        "bookName":{
          "type":"text",
          "analyzer": "ik_max_word"
        }
      }

  }
}

测试分词效果

GET /_analyze
{
  "analyzer": "ik_max_word",
  "text": ["Java编程思想"]
}
{
  "tokens" : [
    {
      "token" : "java",
      "start_offset" : 0,
      "end_offset" : 4,
      "type" : "ENGLISH",
      "position" : 0
    },
    {
      "token" : "编程",
      "start_offset" : 4,
      "end_offset" : 6,
      "type" : "CN_WORD",
      "position" : 1
    },
    {
      "token" : "思想",
      "start_offset" : 6,
      "end_offset" : 8,
      "type" : "CN_WORD",
      "position" : 2
    }
  ]
}

所以,Java编程思想 实际分词效果如下
图片.png

添加数据

POST /testquery/_doc/1
{
  "bookName":"Java编程思想"
}

先看看 Java 的分词效果

GET /_analyze
{
  "analyzer": "ik_max_word",
  "text": ["Java"]
}

返回结果

{
  "tokens": [
    {
      "token": "java",
      "start_offset": 0,
      "end_offset": 4,
      "type": "ENGLISH",
      "position": 0
    }
  ]
}

存在, 所以是可以查询到的

POST /testquery/_search
{
  "query": {
    "match": {
      "bookName": "Java"
    }
  }
}

{
  "took": 60,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": 1,
    "max_score": 0.2876821,
    "hits": [
      {
        "_index": "testquery",
        "_type": "books",
        "_id": "vYOz-msB_TzP5bFY5JlL",
        "_score": 0.2876821,
        "_source": {
          "bookName": "Java编程思想"
        }
      }
    ]
  }
}

测试term查询, 不进行分词操作

查看上面的分词结果, java是在分词结果中的,所以可以查询到

POST /testquery/_search
{
  "query": {
    "term": {
      "bookName": "java"
    }
  }
}
{
  "took": 7,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": 1,
    "max_score": 0.2876821,
    "hits": [
      {
        "_index": "testquery",
        "_type": "books",
        "_id": "vYOz-msB_TzP5bFY5JlL",
        "_score": 0.2876821,
        "_source": {
          "bookName": "Java编程思想"
        }
      }
    ]
  }
}

查询 Java
Java不在上面的分词结果中, 结果当然就查询不到

POST /testquery/_search
{
  "query": {
    "term": {
      "bookName": "Java"
    }
  }
}
{
  "took": 0,
  "timed_out": false,
  "_shards": {
    "total": 5,
    "successful": 5,
    "skipped": 0,
    "failed": 0
  },
  "hits": {
    "total": 0,
    "max_score": null,
    "hits": []
  }
}

分词器

GET /_analyze
{
  "analyzer": "ik_max_word",
  "text": ["Java编程思想"]
}


返回结果

{
  "tokens" : [
    {
      "token" : "java",
      "start_offset" : 0,
      "end_offset" : 4,
      "type" : "ENGLISH",
      "position" : 0
    },
    {
      "token" : "编程",
      "start_offset" : 4,
      "end_offset" : 6,
      "type" : "CN_WORD",
      "position" : 1
    },
    {
      "token" : "思想",
      "start_offset" : 6,
      "end_offset" : 8,
      "type" : "CN_WORD",
      "position" : 2
    }
  ]
}

分页

from:起始数量,size: 每页显示多少条,相当于数据库中 limit x,y

GET zlp/_search
{
  "query": {
    "match_all": {}
  },
  "from": 0,
  "size": 2
}

删除

删除索引

DELETE /zlp

删除文档记录

删除单条记录

DELETE /zlp/1

高亮

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

  • pre_tags:前置标签
  • post_tags:后置标签
  • fields:需要高亮的字段
GET zlp/_search
{
  "query": {
    "match": {
      "username": "change"
    }
  },
  "highlight": {
    "pre_tags": "<font color='red'>",
    "post_tags": "</font>",
    "fields": {
      "username": {}
    }
  }
}

返回结果

{
  "took" : 1,
  "timed_out" : false,
  "_shards" : {
    "total" : 1,
    "successful" : 1,
    "skipped" : 0,
    "failed" : 0
  },
  "hits" : {
    "total" : {
      "value" : 3,
      "relation" : "eq"
    },
    "max_score" : 0.7079357,
    "hits" : [
      {
        "_index" : "zlp",
        "_type" : "_doc",
        "_id" : "3",
        "_score" : 0.7079357,
        "_source" : {
          "username" : "change",
          "age" : 23,
          "birthday" : "1970-06-13"
        },
        "highlight" : {
          "username" : [
            "<font color='red'>change</font>"
          ]
        }
      },
      {
        "_index" : "zlp",
        "_type" : "_doc",
        "_id" : "5",
        "_score" : 0.4889865,
        "_source" : {
          "username" : "change study java",
          "age" : 34,
          "birthday" : "1970-06-13"
        },
        "highlight" : {
          "username" : [
            "<font color='red'>change</font> study java"
          ]
        }
      },
      {
        "_index" : "zlp",
        "_type" : "_doc",
        "_id" : "4",
        "_score" : 0.4889865,
        "_source" : {
          "username" : "change study java2",
          "age" : 34,
          "birthday" : "1970-06-13"
        },
        "highlight" : {
          "username" : [
            "<font color='red'>change</font> study java2"
          ]
        }
      }
    ]
  }
}

Elasticsearch 整合 SpringBoot

pom依赖

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

<properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    <java.version>1.8</java.version>
</properties>
<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter</artifactId>
    </dependency>
    <!-- elasticsearch启动器 (必须)-->
    <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>
        <scope>test</scope>
    </dependency>
     <dependency>
         <groupId>cn.hutool</groupId>
         <artifactId>hutool-all</artifactId>
         <version>4.5.7</version>
    </dependency>
</dependencies>

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

application.yml 文件配置

## 服务启动端口号
server:
  port: 9190
spring:
  data:
    ## Elasticsearch配置文件
    elasticsearch:
      ## 集群名称
      cluster-name: elasticsearch
      ## 访问地址
      cluster-nodes: 10.3.149.131:9300

索引操作

创建索引和映射

SpringBoot-data-elasticsearch 提供了面向对象的方式操作 elasticsearch

业务:创建一个商品对象,有这些属性:
id,title,categoryId,brandid,price……,图片地址在 SpringDataElasticSearch 中,只需要操作对象,就可以操作elasticsearch中的数据

ES实体类

首先我们准备好实体类:


/**
 * @author :ZouLiPing
 * @description:商品 entity
 * @date :2021-11-3 11:29:14
 */
@Data
@AllArgsConstructor
@NoArgsConstructor
@Document(indexName = "good", replicas = 0)
public class Googs {

    /**
     * 作用在成员变量,标记一个字段作为id主键
     */
    @Id
    private Long id;
    /**
     * 标题
     */
    @Field(type = FieldType.Text, analyzer = "ik_max_word")
    private String title;
    /**
     * 分类
     */
    @Field(type = FieldType.Keyword)
    private String category;
    /**
     * 品牌
     */
    @Field(type = FieldType.Keyword)
    private String brandName;
    /**
     * 价格
     */
    @Field(type = FieldType.Long)
    private Long price;

    /**
     * 图片地址
     */
    @Field(index = false, type = FieldType.Keyword)
    private String images;

}

映射—注解

Spring Data 通过注解来声明字段的映射属性,有下面的三个注解:

  • @Document 作用在类,标记实体类为文档对象,一般有两个属性indexName:对应索引库名称type:对应在索引库中的类型
  • shards:分片数量,默认5
  • replicas:副本数量,默认1
  • @Id 作用在成员变量,标记一个字段作为id主键
  • @Field 作用在成员变量,标记为文档的字段,并指定字段映射属性:
  • type:字段类型,是枚举:FieldType,可以是 text、long、short、date、integer、object等
  • text:存储数据时候,会自动分词,并生成索引
  • keyword:存储数据时候,不会分词建立索引
  • Numerical:数值类型,分两类
  • 基本数据类型:long、interger、short、byte、double、float、half_float
  • 浮点数的高精度类型:scaled_float
  • 需要指定一个精度因子,比如10或100。elasticsearch会把真实值乘以这个因子后存储,取出时再还原。
  • Date:日期类型
  • elasticsearch 可以对日期格式化为字符串存储,但是建议我们存储为毫秒值,存储为long,节省空间。
  • index:是否索引,布尔类型,默认是true
  • store:是否存储,布尔类型,默认是false
  • analyzer:分词器名称,这里的ik_max_word即使用ik分词器

创建索引
ElasticsearchTemplate 中提供了创建索引的API:
Elasticsearch 学习 - 图35
可以根据类的信息自动生成,也可以手动指定 indexName 和 Settings

映射相关的API
一样,可以根据类的字节码信息(注解配置)来生成映射,或者手动编写映射
我们这里采用类的字节码信息创建索引并映射,下面是测试类代码:


@RestController
@RequiredArgsConstructor
public class ElasticsearchController {

    private final ElasticsearchRestTemplate elasticsearchRestTemplate;

    /**
     * 创建 Goods 文档对象
     */
    @GetMapping("createIndex")
    public boolean createIndex() {
        boolean result = elasticsearchRestTemplate.createIndex(Googs.class);
        return result;
    }
}

运行 createIndex(),索引创建成功后打开 elasticsearch-head-master 插件(es-head插件的安装)查看索引信息,
索引信息:
Elasticsearch 学习 - 图36

删除索引

可以根据类名或索引名删除。

    /**
     * 删除索引
     */
    @GetMapping("testDeleteIndex")
    public boolean testDeleteIndex() {
        boolean result = elasticsearchRestTemplate.deleteIndex(Googs.class);
        return result;
    }

新增文档数据

Repository接口

Spring Data 的强大之处,就在于你不用写任何DAO处理,自动根据方法名或类的信息进行CRUD操作。只要你定义一个接口,然后继承Repository提供的一些子接口,就能具备各种基本的CRUD功能。

我们看到有一个 ElasticsearchCrudRepository 接口:
Elasticsearch 学习 - 图37
所以,我们只需要定义接口,然后继承它就OK了


/**
 * @author :ZouLiPing
 * @description:定义ItemRepository 接口
 * @date :2021-11-3 11:43:51
 */
public interface GoodsRepository extends ElasticsearchRepository<Googs,Long> {

}

接下来,我们测试新增数据:

新增一个对象

@Autowiredprivate 
GoodsRepository coodsRepository;

@GetMapping("insert")
public void insert() {    
    Googs item = new Googs(10L, "荣耀10", " 手机", "华为", 349900L, "http://image.baidu.com/13123.jpg");    
    coodsRepository.save(item);
}

Elasticsearch 学习 - 图38
OK,新增成功!

批量新增

代码如下:

/**
  * @desc 定义批量新增方法 
  * 
  */
@GetMapping("insertList")public void insertList() {
    List<Googs> list = new ArrayList<>();    
    list.add(new Googs(11L, "小米R1", " 手机", "锤子", 369900L, "http://image.baidu.com/13123.jpg"));    list.add(new Googs(12L, "vivo10", " 手机", "华为", 280000L, "http://image.baidu.com/13123.jpg"));    
    list.add(new Googs(13L, "华为META10", " 手机", "华为", 44993L, "http://image.baidu.com/13123.jpg"));    
    list.add(new Googs(14L, "华为META10", " 手机", "华为", 44993L, "http://image.baidu.com/13123.jpg"));    
    list.add(new Googs(15L, "华为META10", " 手机", "华为", 44993L, "http://image.baidu.com/13123.jpg"));    
    list.add(new Googs(16L, "华为META10", " 手机", "华为", 44993L, "http://image.baidu.com/13123.jpg"));    
    // 接收对象集合,实现批量新增    
    coodsRepository.saveAll(list);
}

再次去页面查询:
Elasticsearch 学习 - 图39
OK,批量新增成功!

修改

elasticsearch中本没有修改,它的修改原理是该是先删除在新增
修改和新增是同一个接口,区分的依据就是id。

/**
 * 定义修改方法
 */
@GetMapping("update")
public void update(){
    Googs item = new Googs(1L, "苹果XSMax", " 手机",
            "小米", 349923L, "http://image.baidu.com/13123.jpg");
    coodsRepository.save(item);
}

查看结果:
Elasticsearch 学习 - 图40

查询

基本查询

ElasticsearchRepository提供了一些基本的查询方法:
我们来试试查询所有:

/**
 * 定义查询方法,含对价格的降序、升序查询
 */
@GetMapping("testQueryAll")
public void testQueryAll(){
    // 查找所有
    //Iterable<Item> list = this.itemRepository.findAll();
    // 对某字段排序查找所有 Sort.by("price").descending() 降序
    // Sort.by("price").ascending():升序
    Iterable<Googs> list = this.coodsRepository.findAll(Sort.by("price").ascending());
    for (Googs item:list){
        System.out.println(item);
    }
}

控制台输出结果:
Elasticsearch 学习 - 图41

自定义方法

Spring Data 的另一个强大功能,是根据方法名称自动实现功能。
比如:你的方法名叫做:findByTitle,那么它就知道你是根据title查询,然后自动帮你完成,无需写实现类。
当然,方法名称要符合一定的约定:

Keyword  Sample
And  findByNameAndPrice
Or  findByNameOrPrice    → 或 
Is  findByName        → 是 相当于and
Not  findByNameNot
Between  findByPriceBetween   →查询价格范围直接
LessThanEqual  findByPriceLessThan  →小于等于
GreaterThanEqual  findByPriceGreaterThan
Before  findByPriceBefore    → 小于
After  findByPriceAfter        → 大于
Like  findByNameLike        → 模糊
StartingWith  findByNameStartingWith
EndingWith  findByNameEndingWith
Contains/Containing  findByNameContaining
In  findByNameIn(Collection<String>names)
NotIn  findByNameNotIn(Collection<String>names)
Near  findByStoreNear
True  findByAvailableTrue
False  findByAvailableFalse
OrderBy  findByAvailableTrueOrderByNameDesc

例如,我们来按照价格区间查询,定义这样的一个方法:

/**
 * @author :ZouLiPing
 * @description:定义ItemRepository 接口
 * @date :2019/9/2 18:06
 */
public interface GoodsRepository extends ElasticsearchRepository<Googs,Long> {


    /**
     * @Description:根据价格区间查询
     * @Param price1
     * @Param price2
     */
    List<Googs> findByPriceBetween(Long price1, Long price2);
}
/**
 * @description:matchQuery底层采用的是词条匹配查询
 */
@GetMapping("testMatchQuery")
public Page<Googs> testMatchQuery(){
    // 构建查询条件
    NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
    // 添加基本分词查询
    queryBuilder.withQuery(QueryBuilders.matchQuery("title", "小米手机"));
    // 搜索,获取结果
    Page<Googs> items = this.coodsRepository.search(queryBuilder.build());
    // 总条数
    long total = items.getTotalElements();
    System.out.println("total = " + total);
    for (Googs goods : items) {
        System.out.println(goods.toString());
    }
    return items;
}

NativeSearchQueryBuilder:Spring提供的一个查询条件构建器,帮助构建json格式的请求体
QueryBuilders.matchQuery(“title”, “小米手机”):利用QueryBuilders来生成一个查询。QueryBuilders提供了大量的静态方法,用于生成各种不同类型的查询:
Elasticsearch 学习 - 图42

Page:默认是分页查询,因此返回的是一个分页的结果对象,包含属性:
totalElements:总条数
totalPages:总页数
Iterator:迭代器,本身实现了Iterator接口,因此可直接迭代得到当前页的数据
其它属性:
Elasticsearch 学习 - 图43
结果:
Elasticsearch 学习 - 图44

聚合

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

  • 什么品牌的手机最受欢迎?
  • 这些手机的平均价格、最高价格、最低价格?
  • 这些手机每月的销售情况如何?
  • 实现这些统计功能的比数据库的sql要方便的多,而且查询速度非常快,可以实现近实时搜索效果。

聚合基本概念

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

ok,SpringBoot整合Spring Data Elasticsearch到此完结
要是还有不太明白的地方请留言,评论必回
要是对我的文章感兴趣的话,关注一下吧,谢谢!

高亮查询

 /** 
     * 高亮查询
     * @date: 2021/11/3 15:34
     * @return: boolean 
     */
    @GetMapping("hightLightSearch")
    public Pager hightLightSearch(
            @RequestParam(required = false) String keyword,
            @RequestParam(required = false, defaultValue = "0") Integer pageNum,
            @RequestParam(required = false, defaultValue = "5") Integer pageSize
    ) {

        Pageable pageable = PageRequest.of(pageNum, pageSize);
        Page page;
        // 构建查询条件
        NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
        // 添加基本分词查询
        queryBuilder.withQuery(QueryBuilders.matchQuery("title", keyword));
        // 高亮查询
        HighlightBuilder highlightBuilder = new HighlightBuilder();
        highlightBuilder.preTags("<font color=\"red\">");
        highlightBuilder.postTags("</font>");
        highlightBuilder.highlighterType("unified");
        highlightBuilder.field("title");
        highlightBuilder.requireFieldMatch(false);//多次段高亮需要设置为false
        queryBuilder.withHighlightBuilder(highlightBuilder);

        NativeSearchQuery build = queryBuilder.build();
        // 搜索,获取结果
        SearchHits<Googs> searchHits = this.elasticsearchRestTemplate.search(build,Googs.class);
        if (searchHits.getTotalHits() <= 0){
            return  new Pager();
        }
        log.info("DSL:{}", build.getQuery().toString());
        List<Googs> searchProductList = searchHits.stream().map(SearchHit::getContent).collect(Collectors.toList());
        List<Map<String, List<String>>> highlightFields = searchHits.stream().map(SearchHit::getHighlightFields).collect(Collectors.toList());
        log.info("highlightFields={}", JSON.toJSONString(highlightFields));
        for (int i = 0; i < searchProductList.size(); i++) {
            Map<String, List<String>> fieldListMap = highlightFields.get(i);
            Googs googs = searchProductList.get(i);
            googs.setTitle(fieldListMap.get("title").get(0));
        }
        log.info("searchProductList={}",JSON.toJSONString(searchProductList));
        page = new PageImpl<>(searchProductList, pageable, searchHits.getTotalHits());
        return Pager.restPage(page);
    }