浅学elasticsearch
前言
根据传统的格式,将数据分为三个大类:
- 结构化数据:一般表现为二维的表结构,常保存到关系型数据库如MySQL,并通过SQL语句来查询,可以通过创建一些索引来提高效率。但是扩展结构不方便
- 非结构化数据:无法用二维表来表现的数据,就是非结构化数据,比如服务器日志,图片,视频等,查询成本高,往往需要专业的人员和大量的统计模型来进行处理,一般会保存到NoSQL中,如redis,这样的数据库一般是以key-value结构来存储数据,通过key来查询数据,相对来说比较快
- 半结构化数据:就是数据和结构混在一起没有什么明显的区分,比如xml和html这种文档,一般也是保存到redis这样的数据库中,缺点就是不方便查询
生活中很多场景下,我们搜索的对象并不都是关系型结构化数据,不能都像数据库那样模糊查询,ES就是为了解决这些问题而产生的软件。
什么是ELK工作栈?
ELK Stack 是 Elasticsearch(存储搜索)、Logstash(采集和传输数据)、Kibana(展示数据) 三个开源软件的组合,Elasticsearch是其中的核心。在实时数据检索和分析场合,三者通常是配合共用,而且又都先后归于 Elastic.co 公司名下,故有此简称。
ELK Stack 成为机器数据分析,或者说实时日志处理领域,开源界的第一选择。和传统的日志处理方案相比,ELK Stack 具有如下几个优点:
处理方式灵活。Elasticsearch 是实时全文索引,不需要像 storm 那样预先编程才能使用;
配置简易上手。Elasticsearch 全部采用 JSON 接口,Logstash 是 Ruby DSL 设计,都是目前业界最通用的配置语法设计;
检索性能高效。虽然每次查询都是实时计算,但是优秀的设计和实现基本可以达到全天数据查询的秒级响应;
集群线性扩展。不管是 Elasticsearch 集群还是 Logstash 集群都是可以线性扩展的;
前端操作炫丽。Kibana 界面上,只需要点击鼠标,就可以完成搜索、聚合功能,生成炫丽的仪表板。
当然,ELK Stack 也并不是实时数据分析界的灵丹妙药。在不恰当的场景,反而会事倍功半。
一、Elasticsearch简介
Elasticsearch官网:https://www.elastic.co/cn/elasticsearch/
1、什么是Elasticsearch(弹性搜索)?
Elasticsearch 是位于 Elastic Stack 核心的分布式搜索和分析引擎。Logstash 和 Beats 有助于收集、聚合和丰富您的数据并将其存储在 Elasticsearch 中。Kibana 使您能够以交互方式探索、可视化和共享对数据的洞察,并管理和监控堆栈。Elasticsearch 是索引、搜索和分析的神奇之处。
Elasticsearch 为所有类型的数据提供近乎实时的搜索和分析。无论您拥有结构化或非结构化文本、数值数据还是地理空间数据,Elasticsearch 都可以以支持快速搜索的方式高效地存储和索引它。您可以超越简单的数据检索和聚合信息来发现数据中的趋势和模式。随着您的数据和查询量的增长,Elasticsearch 的分布式特性使您的部署能够随之无缝增长。
虽然并非所有问题都是搜索问题,但 Elasticsearch 提供了在各种用例中处理数据的速度和灵活性:
- 向应用或网站添加搜索框
- 存储和分析日志、指标和安全事件数据
- 使用机器学习实时自动建模数据的行为
- 使用 Elasticsearch 作为存储引擎自动化业务工作流
- 使用 Elasticsearch 作为地理信息系统 (GIS) 管理、集成和分析空间信息
- 使用 Elasticsearch 作为生物信息学研究工具存储和处理遗传数据
我们不断对人们使用搜索的新颖方式感到惊讶。但是,无论您的用例是否与其中之一类似,或者您正在使用 Elasticsearch 来解决新问题,您在 Elasticsearch 中处理数据、文档和索引的方式都是相同的。
数据输入:文档和索引 [https://www.elastic.co/guide/en/elasticsearch/reference/current/documents-indices.html]
信息输出:搜索和分析 [https://www.elastic.co/guide/en/elasticsearch/reference/current/search-analyze.html]
可扩展性和弹性:集群,节点和分片 [https://www.elastic.co/guide/en/elasticsearch/reference/current/scalability.html]
2、Elasticsearch应用案例
- GitHub: 2013 年初,抛弃了 Solr,采取 Elasticsearch 来做 PB 级的搜索。“GitHub 使用Elasticsearch搜索20TB的数据,包括13亿文件和1300亿行代码”。
- 维基百科:启动以 Elasticsearch 为基础的核心搜索架构
- SoundCloud:“SoundCloud 使用 Elasticsearch 为 1.8 亿用户提供即时而精准的音乐搜索服务”。
- 百度:目前广泛使用 Elasticsearch 作为文本数据分析,采集百度所有服务器上的各类指标数据及用户自定义数据,通过对各种数据进行多维分析展示,辅助定位分析实例异常或业务层面异常。目前覆盖百度内部 20 多个业务线(包括云分析、网盟、预测、文库、直达号、钱包、风控等),单集群最大 100 台机器,200 个 ES 节点,每天导入 30TB+数据。
- 新浪:使用 Elasticsearch 分析处理 32 亿条实时日志。
- 阿里:使用 Elasticsearch 构建日志采集和分析体系。
- Stack Overflow:解决 Bug 问题的网站,全英文,编程人员交流的网站。
二、Elasticsearch入门
1、Elasticsearch安装
elasticsearch版本和对应的JDK版本:[https://www.elastic.co/cn/support/matrix#matrix_jvm]
官网下载,可选版本 [https://www.elastic.co/cn/downloads/past-releases#elasticsearch]
注意:
- Elasticsearch是Java开发的,7.8版本的ES需要JDK1.8以上的版本,默认安装包带有jdk环境,如果系统配置JAVA_HOME,那么使用系统默认的JDK,如果没有则使用自带的JDK,一般建议使用系统配置的JDK
- 双击启动窗口闪退,通过路径访问追踪错误,如果是“空间不足”,修改config/jvm.options配置文件里的jvm内存
- 这里统一使用最新版7.13.x
Kibana是Elasticsearch可视化客户端:下载地址 [https://www.elastic.co/cn/downloads/kibana]
(Kibana和Elasticsearch配套,自行选中Windows或Linux)
1. Windows
- 去官网下载自己需要的版本
- 解压,得到以下目录结构
- 进入bin目录,双击elasticsearch.bat
- 启动后出现黑窗口,表示开启了ES服务
注:9300端口为ES集群间组件的通信端口,9200端口为浏览器访问的http协议RESTful端口
- 打开浏览器,输入http://localhost:9200,测试
2. Linux
- 官网下载自己需要的版本
elasticsearch-7.13.1-linux-x86_64.tar.gz
- 上传到Linux,一般放在/usr/local下
- 解压
tar -zxvf elasticsearch-7.13.1-linux-x86_64.tar.gz
- 如果解压的包名太长,可以重命名
mv elasticsearch-7.13.1-linux-x86_64.tar.gz elasticsearch
- 进入目录
cd elasticsearch
- 新建目录
mkdir data
mkdir logs
- 修改配置
cd config
vi elasticsearch.yml
#添加内容
path.data: /usr/local/elasticsearch/data # 数据目录位置
path.logs: /usr/local/elasticsearch/logs # 日志目录位置
#修改绑定的ip
network.host: 0.0.0.0 # 绑定到0.0.0.0,允许任何ip来访问
- esc退出插入,:wq保存并退出
- elasticsearch不允许root运行,新建一个用户运行
新建用户:useradd 用户名
配置密码:passwd 用户名
切换用户:su 用户名
赋予权限(赋予elasticsearch文件夹权限即可):chown -R myelasticsearch /usr/local/elasticsearch
- 进入bin,运行elasticsearch
出现警告:
我的Linux的JDK是1.8,这个警告提示未来版本都会需要JDK11
出现错误:
错误1
内存不够,直接杀死了,更改/config/jvm.options的jvm内存大小
Elasticsearch7.13.x配置文件改了,和以前不一样,在jvm.options里注释了这些话,大概就是根据系统自动配置可用jvm内存,如果要特别设置大小,就在jvm.options.d文件夹下新建个文件,里面贴上下面注释的两句话即可,4g表示内存大小,根据自己所需进行更改。还是不明白可进下面给出的链接查看怎么操作
错误2
根据错误提示
the default discovery settings are unsuitable for production use; at least one of [discovery.seed_hosts, discovery.seed_providers, cluster.initial_master_nodes] must be configured
即默认发现设置不适合生产使用;必须至少配置[discovery.seed\u hosts、discovery.seed\u providers、cluster.initial\u master\u nodes]中的一个
可进入官网文档查看对应版本怎么配置:[https://www.elastic.co/guide/en/elasticsearch/reference/current/important-settings.html]
这里去掉其一的注释,保存一个节点,这个节点名对应了node.name
再次运行,成功
注意在Linux里开放9200和9300端口
使用ip和端口,访问成功
Kibana安装
- 需要先安装nodejs
(补充,nodejs官网下载地址:[https://nodejs.org/zh-cn/download/])
tar -vxf node-v12.18.2-linux-x64.tar.xz
mv node-v12.18.2-linux-x64 nodejs
ln -s /usr/local/nodejs/bin/node /usr/local/bin
ln -s /usr/local/nodejs/bin/npm /usr/local/bin
node -v
- 官网下载(上面写了)
- 上传到Linux
- 解压
tar -vxf kibana-7.13.1-linux-x86_64.tar.gz
- 重命名
mv kibana-7.13.1-linux-x86_64 kibana
- 修改配置,config/kibana.yml,添加
server.host: "0.0.0.0"
elasticsearch.hosts: ["http://机器IP:9200"]
- 和elasticsearch一样,不能使用root用户启动,进入/kibana/bin,启动kibana
./kibana
注意:开放5601端口,给启动kibana的用户赋予权限
chown -R myelasticsearch /usr/local/kibana
补充:查看kibana进程
ps -ef|grep kibana
运行一直卡死,还没解决
2、Elasticsearch基本操作
1. RESTful是什么?
REST 指的是一组架构约束条件和原则。满足这些约束条件和原则的应用程序或设计就是 RESTful。Web 应用程序最重要的 REST 原则是,客户端和服务器之间的交互在请求之间是无状态的。从客户端到服务器的每个请求都必须包含理解请求所必需的信息。如果服务器在请求之间的任何时间点重启,客户端不会得到通知。此外,无状态请求可以由任何可用服务器回答,这十分适合云计算之类的环境。客户端可以缓存数据以改进性能。
在服务器端,应用程序状态和功能可以分为各种资源。资源是一个有趣的概念实体,它向客户端公开。资源的例子有:应用程序对象、数据库记录、算法等等。每个资源都使用 URI (Universal Resource Identifier) 得到一个唯一的地址。所有资源都共享统一的接口,以便在客户端和服务器之间传输状态。使用的是标准的 HTTP 方法,比如 GET、PUT、POST 和DELETE。
在 REST 样式的 Web 服务中,每个资源都有一个地址。资源本身都是方法调用的目标,方法列表对所有资源都是一样的。这些方法都是标准方法,包括 HTTP GET、POST、PUT、DELETE,还可能包括 HEAD 和 OPTIONS。简单的理解就是,如果想要访问互联网上的资源,就必须向资源所在的服务器发出请求,请求体中必须包含资源的网络路径,以及对资源进行的操作(增删改查)。
2. Elasticsearch的数据格式
Elasticsearch 是面向文档型数据库,一条数据在这里就是一个文档。
ES 里的 Index 可以看做一个库,而 Types 相当于表,Documents 则相当于表的行
Elasticsearch 6.X 中,一个index下已经只能包含一个type
Elasticsearch 7.X 中, Type的概念已经被删除了
Elasticsearch 存储文档数据和关系型数据库 MySQL 存储数据的概念对比:
MySQL | Database(数据库) | Table(数据表) | Row(行) | Column(列) |
Elasticsearch | Index(索引) | Type(类型) | Documents(文档) | Fields(字段) |
3. HTTP操作
Elasticsearch 提供了一个简单、一致的 REST API 来管理集群以及索引和搜索数据。
使用 REST API 向 Elasticsearch 发送数据和其他请求。
可以使用任何发送 HTTP 请求的客户端(例如curl )与 Elasticsearch 进行交互。还可以使用 Kibana 的控制台向 Elasticsearch 发送请求。
将数据作为称为文档的 JSON 对象添加到 Elasticsearch,Elasticsearch 将这些文档存储在可搜索索引中。
注:
- 以下操作均使用Postman进行,前提是成功开启了ES服务
- 127.1.1.0就是本地ip,服务器则用服务器ip,本地ip可直接用localhost
发送REST API请求操作可用Postman:下载地址 [https://www.postman.com/downloads/]
3.1 索引操作
正排索引和倒排索引
-----------------------------------------------
ES使用了倒排索引
比如保存一篇文章,有文章编号和文章内容
id content
-----------------
1 name zhangsan
2 name lisi
正排索引:
将文章编号设定为主键,同时生成主键索引,通过主键索引快速关联到存储的信息
倒排索引:
为什么要倒排索引?
如果要查询文章的内容中包含了哪些热门词汇,就需要做模糊查询,相对来说效率就差了很多,而且媒体数据要去遍历,性能就差了很多。而且查询时的大小写、时态等都会影响查询的准确率。
举例:
将关键字和文章的id进行关联
比如想查询name,那就拿name作为一个关键字,对应着有name的文章内容的id,即1、2
比如想查询zhang,那就拿zhang作为一个关键字,对应着有zhang的文章内容的id,即1
keyword id
------------------
name 1、2
zhang 1
总结:
通过关键字来查询主键id,再关联文章内容。相对来说,更强调关键字和文档编号的关联。表的概念已经不怎么存在了
3.1.1 创建索引
向ES服务器发送PUT请求:http://127.0.0.1:9200/shopping
返回一个json格式的数据
{
"acknowledged"【响应结果】: true, # true 操作成功
"shards_acknowledged"【分片结果】: true, # 分片操作成功
"index"【索引名称】: "shopping"
}
# 注意:创建索引库的分片数默认 1 片,在 7.0.0 之前的 Elasticsearch 版本中,默认 5 片
如果重复添加索引,会返回错误信息
3.1.2 查看索引
查看某一个索引:
向ES服务器发送GET请求:http://127.0.0.1:9200/shopping
查看所有的索引:
向ES服务器发送GET请求:http://127.0.0.1:9200/_cat/indices?v
注:
请求路径中的_cat 表示查看的意思,indices 表示索引,所以整体含义就是查看当前 ES 服务器中的所有索引,就好像 MySQL 中的 show tables
表头 | 含义 |
---|---|
health | 当前服务器健康: green(集群完整)、yellow(单点正常、集群不完整)、red(单点不正常) |
status | 索引打开、关闭状态 |
index | 索引名 |
uuid | 索引统一编号 |
pri | 主分片数量 |
rep | 副本数量 |
docs.count | 可用文档数量 |
docs.deleted | 文档删除状态(逻辑删除) |
store.size | 主分片和副分片整体占空间大小 |
pri.store.size | 主分片占空间大小 |
3.1.3 删除索引
向ES服务器发送DELETE请求:http://127.0.0.1:9200/_cat/shopping
再次查看,索引不存在
3.2 文档操作
这里的文档可以类比为关系型数据库中的表数据,添加的数据格式为 JSON 格式
3.2.1 创建文档
向ES服务器发送POST请求:http://localhost:9200/shopping/_doc
请求体内容为:
{
"title":"小米手机",
"category":"小米",
"images":"https://cdn.cnbj0.fds.api.mi-img.com/b2c-shopapi-pms/pms_1621955784.84614979.jpg",
"price":1699.00
}
{
"_index"【索引】: "shopping",
"_type"【类型-文档】: "_doc",
"_id"【唯一标识】: "Xhsa2ncBlvF_7lxyCE9G", #可以类比为 MySQL 中的主键,随机生成
"_version"【版本】: 1,
"result"【结果】: "created", #这里的 create 表示创建成功
"_shards"【分片】: {
"total"【分片 - 总数】: 2,
"successful"【分片 - 成功】: 1,
"failed"【分片 - 失败】: 0
},
"_seq_no": 0,
"_primary_term": 1
}
注:
由于没有指定数据唯一性标识(ID),默认情况下,ES 服务器会随机生成一个。所以每post一次,_id都不一样。
注:此处发送请求的方式必须为 POST,不能是 PUT,否则会发生错误。因为每次请求的结果都不一致,每次都会随机分配一个新的id
自定义唯一性标识,需要在创建时指定:http://localhost:9200/shopping/_doc/1
注:因为此时每次发送的请求结果都一致,所以此处也可以使用PUT请求
如果想更加明确这是创建操作,把_doc改成_create也可以。类型依然是_doc
3.2.2 查看文档
查看文档时,需要指明文档的唯一性标识,即查询索引下某唯一id对应的数据
向 ES 服务器发 GET 请求:http://localhost:9200/shopping/_doc/1
查看索引下所有数据
向 ES 服务器发 GET 请求:http://localhost:9200/shopping/_search
带条件查询,q表示查询
向 ES 服务器发 GET 请求:http://localhost:9200/shopping/_search?q=category:小米
注:
在请求路径中添加额外的参数是比较麻烦的,而且这里的中文在请求路径中容易出现乱码
一般通过请求体来传递参数
match 匹配类型查询,会把查询条件进行分词
在多字段中匹配查询
multi_match 与 match 类似,不同的是它可以在多个字段中查询
请求体内容:
{
"query":{
"multi_match":{
"query":"小米",
"fields":["title","category"]
}
}
}
match_all:表示全量查询
分页查询
from:起始位置
size:每页的数据条数
请求体内容:
{
"query":{
"match_all":{
}
},
"from":0,
"size":2
}
指定字段查询,_source:[]
比如只想分页查询title字段的数据
请求体内容:
{
"query":{
"match_all":{
}
},
"from":0,
"size":2,
"_souce":["title"]
}
排序查询
如分页查询title字段的数据,并按价格降序
1、单字段排序
请求体内容:
{
"query":{
"match_all":{
}
},
"from":0,
"size":2,
"_source":["title"],
"sort":{
"price":{
"order":"desc"
}
}
}
2、多字段排序
请求体内容:
{
"query":{
"match_all":{
}
},
"from":0,
"size":2,
"_source":["title"],
"sort":[
{
"price":{
"order":"desc"
}
},
{
"_score":{
"order": "desc"
}
}
]
}
组合查询
--------------------------------
1、必须同时满足所有条件匹配
请求体内容:
{
"query":{
"bool":{
"must":[
{
"match":{
"category":"小米"
}
},
{
"match":{
"price":1699.00
}
}
]
}
}
}
2、满足任一条件匹配
请求体内容:
{
"query":{
"bool":{
"should":[
{
"match":{
"category":"小米"
}
},
{
"match":{
"category":"华为"
}
}
]
}
}
}
范围查询:
1、请求体内容:
{
"query":{
"bool":{
"should":[
{
"match":{
"category":"小米"
}
},
{
"match":{
"category":"华为"
}
}
],
"filter":{
"range":{
"price":{
"gt":5000
}
}
}
}
}
}
2、请求体内容
{
"query":{
"range":{
"price":{
"gte":1000,
"lte":3000
}
}
}
}
操作符 | 说明 |
---|---|
gt | 大于 |
gte | 大于等于 |
lt | 小于 |
lte | 小于等于 |
高亮查询
在使用 match 查询的同时,加上一个 highlight 属性:
- pre_tags:前置标签
- post_tags:后置标签
- fields:需要高亮的字段
- title:这里声明 title 字段需要高亮,后面可以为这个字段设置特有配置,也可以空
请求体内容:
{
"query": {
"match": {
"title": "小米"
}
},
"highlight": {
"pre_tags": "<font color='red'>",
"post_tags": "</font>",
"fields": {
"category": {}
}
}
}
聚合查询
对某个字段取最大值 max
对某个字段取最小值 min
对某个字段求和 sum
对某个字段取平均值 avg
对某个字段的值进行去重之后再取总数 cardinality
对某个字段一次性返回 count,max,min,avg 和 sum 五个指标 stats
分组统计 terms
(好像只能数值型的分组,不然报错illegal_argument_exception)
请求体:
{
"aggs":{ //聚合操作
"price_group":{ //随意起名
"terms":{ //组名
"field":"price" //分组字段
}
}
},
"size":0 //不用返回原始数据
}
模糊查询
返回包含与搜索字词相似的字词的文档
编辑距离是将一个术语转换为另一个术语所需的一个字符更改的次数。这些更改可以包括:
- 更改字符(box → fox)
- 删除字符(black → lack)
- 插入字符(sic → sick)
- 转置两个相邻字符(act → cat)
为了找到相似的术语,fuzzy 查询会在指定的编辑距离内创建一组搜索词的所有可能的变体
或扩展。然后查询返回每个扩展的完全匹配。
通过 fuzziness 修改编辑距离。一般使用默认值 AUTO,根据术语的长度生成编辑距离
请求体内容:
{
"query": {
"fuzzy": {
"title": {
"value": "小米",
"fuzziness":2
}
}
}
}
过滤字段查询
- includes:来指定想要显示的字段
- excludes:来指定不想要显示的字段
请求体内容:
{
"_source": {
"includes": ["title","category"]
},
"query": {
"terms": {
"title": ["小"]
}
}
}
精确的关键词匹配查询,不对查询条件进行分词
请求体内容:
{
"query": {
"term": {
"title": {
"value": "小"
}
}
}
}
{
"query": {
"term": {
"title": {
"value": ["小","米"]
}
}
}
}
3.2.3 修改文档
全局修改,就是将原有的数据内容覆盖。可以用PUT,POST
向 ES 服务器发 POST 请求:http://localhost:9200/shopping/_doc/1
请求体内容:
{
"title":"红米手机",
"category":"小米",
"images":"https://cdn.cnbj0.fds.api.mi-img.com/b2c-shopapi-pms/pms_1621955784.84614979.jpg",
"price":1699.00
}
局部修改,即修改字段,只能使用POST
向ES服务器发送请求:http://localhost:9200/shopping/_update/1
注:写_doc可能会被认为是新增,所以用_update
这里是实现向id为1的数据里再新添一个type字段
请求体内容:
{
"doc":{
"type":"红米"
}
}
3.2.4 删除文档
删除一个文档不会立即从磁盘上移除,它只是被标记成已删除(逻辑删除)
向 ES 服务器发 DELETE 请求:http://localhost:9200/shopping/_doc/1
条件删除文档
向 ES 服务器发 POST 请求:http://localhost:9200/shopping/_delete_by_query
删除price为4000的数据
请求体内容:
{
"query":{
"match":{
"price":4000.00
}
}
}
3.3 映射操作
类似于数据库(database)中的表结构(table)。
创建数据库表需要设置字段名称,类型,长度,约束等;索引库也一样,需要知道这个类型
下有哪些字段,每个字段有哪些约束信息,这就叫做映射(mapping)
映射数据说明:
- 字段名:任意填写,下面指定许多属性,例如:title、subtitle、images、price
- type:类型,Elasticsearch 中支持的数据类型非常丰富,说几个关键的:
String 类型,又分两种:
text:可分词
keyword:不可分词,数据会作为完整字段进行匹配
Numerical:数值类型,分两类
基本数据类型:long、integer、short、byte、double、float、half_float
浮点数的高精度类型:scaled_float
Date:日期类型
Array:数组类型
Object:对象
- index:是否索引,默认为 true,也就是说你不进行任何配置,所有字段都会被索引。
true:字段会被索引,则可以用来进行搜索
false:字段不会被索引,不能用来搜索
- store:是否将数据进行独立存储,默认为 false
原始的文本会存储在_source 里面,默认情况下其他提取出来的字段都不是独立存储的,是从_source 里面提取出来的。当然你也可 以独立的存储某个字段,只要设置"store": true 即可,获取独立存储的字段要比从_source 中解析快得多,但是也会占用更多的空 间,所以要根据实际业务需求来设置。
- analyzer:分词器,这里的 ik_max_word 即使用 ik 分词器
3.3.1 创建映射
向 ES 服务器发 PUT 请求 :http://localhost:9200/student/_mapping
请求体内容为:
{
"properties": {
"name":{
"type": "text",
"index": true
},
"sex":{
"type": "text",
"index": false
},
"age":{
"type": "long",
"index": false
}
}
}
3.3.2 查看映射
向 ES 服务器发 GET 请求 :http://localhost:9200/student/_mapping
3.3.3 索引映射关联
向 ES 服务器发 PUT 请求 :http://localhost:9200/student1
{
"settings":{},
"mappings":{
"properties":{
"title":{
"type":"text",
"index":"true"
},
"category":{
"type":"text",
"index":"false"
},
"image":{
"type":"text",
"index":"false"
},
"price":{
"type":"text",
"index":false
}
}
}
}
4.springboot整合Elasticsearch
Elasticsearch 软件是由 Java 语言开发的,所以也可以通过 Java API 的方式对 Elasticsearch服务进行访问
新建springboot项目,2.5.0
添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
创建Elasticsearch客户端
代码中创建 Elasticsearch 客户端对象
因为早期版本的客户端对象已经不再推荐使用,且在未来版本中会被删除,所以这里采用高级 REST 客户端对象
import org.apache.http.HttpHost;
import org.elasticsearch.client.RestClient;
import org.elasticsearch.client.RestHighLevelClient;
import org.springframework.stereotype.Component;
/**
* @author jadexu
* es客户端
*/
@Component
public class ElasticsearchClient {
public RestHighLevelClient getESClient(){
//创建客户端对象
RestHighLevelClient client = new RestHighLevelClient(
RestClient.builder(new HttpHost("127.0.0.1",9200,"http"))
);
return client;
}
}
测试
@SpringBootTest
class EsApplicationTests {
@Autowired
private ElasticsearchClient client;
@Test
void clientTest() throws IOException {
client.getESClient();
}
}
能跑起来就证明连接成功
4.1 索引操作
import org.elasticsearch.action.admin.indices.delete.DeleteIndexRequest;
import org.elasticsearch.action.support.master.AcknowledgedResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.indices.CreateIndexRequest;
import org.elasticsearch.client.indices.CreateIndexResponse;
import org.elasticsearch.client.indices.GetIndexRequest;
import org.elasticsearch.client.indices.GetIndexResponse;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.io.IOException;
@Service
public class IndexService {
@Autowired
private ElasticsearchClient client;
/**
* 创建索引
* @return
* @throws IOException
*/
public boolean create() throws IOException {
//新建创建索引请求
CreateIndexRequest request = new CreateIndexRequest("user");
//发送请求并创建索引,获得响应
CreateIndexResponse response = client.getESClient().indices().create(request, RequestOptions.DEFAULT);
//返回响应的状态
return response.isAcknowledged();
}
/**
* 获得索引
* @return
* @throws IOException
*/
public GetIndexResponse select() throws IOException {
//新建获取索引请求
GetIndexRequest request = new GetIndexRequest("user");
//发送获得索引请求,获得响应
GetIndexResponse response = client.getESClient().indices().get(request, RequestOptions.DEFAULT);
return response;
}
/**
* 删除索引
* @return
* @throws IOException
*/
public boolean delete() throws IOException {
//新建删除索引请求
DeleteIndexRequest request = new DeleteIndexRequest("user");
//发送请求删除索引,获得响应
AcknowledgedResponse response = client.getESClient().indices().delete(request, RequestOptions.DEFAULT);
return response.isAcknowledged();
}
}
测试
@Autowired
private IndexService indexService;
@Test
void createIndexTest() throws IOException {
System.out.println(indexService.create());
}
@Test
void selectIndexTest() throws IOException {
System.out.println(indexService.select().getAliases());
System.out.println(indexService.select().getMappings());
System.out.println(indexService.select().getSettings());
}
@Test
void deleteIndexTest() throws IOException {
System.out.println(indexService.delete());
}
4.2 文档操作
import com.fasterxml.jackson.databind.ObjectMapper;
import com.hhx.es.entity.User;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.delete.DeleteResponse;
import org.elasticsearch.action.get.GetRequest;
import org.elasticsearch.action.get.GetResponse;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.index.IndexResponse;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.action.update.UpdateResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.common.xcontent.XContentType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.io.IOException;
/**
* @author jadexu
* 文档操作服务
*/
@Service
public class DocService {
@Autowired
private ElasticsearchClient client;
/**
* 创建数据,添加到某索引文档里
* @return
* @throws IOException
*/
public IndexResponse create() throws IOException {
IndexRequest request = new IndexRequest();
request.index("user").id("1001");
User user = new User(1,"zhangsan",30,"male");
ObjectMapper mapper = new ObjectMapper();
String userJson = mapper.writeValueAsString(user);
request.source(userJson, XContentType.JSON);
IndexResponse response = client.getESClient().index(request, RequestOptions.DEFAULT);
return response;
}
/**
* 修改数据
* @return
* @throws IOException
*/
public UpdateResponse update() throws IOException {
UpdateRequest request = new UpdateRequest();
request.index("user").id("1001");
request.doc(XContentType.JSON,"sex","female");
UpdateResponse response = client.getESClient().update(request, RequestOptions.DEFAULT);
return response;
}
/**
* 查询数据
* @return
* @throws IOException
*/
public GetResponse select() throws IOException {
GetRequest request = new GetRequest();
request.index("user").id("1001");
GetResponse response = client.getESClient().get(request,RequestOptions.DEFAULT);
return response;
}
/**
* 删除数据
* @return
* @throws IOException
*/
public DeleteResponse delete() throws IOException {
DeleteRequest request = new DeleteRequest();
request.index("user").id("1001");
DeleteResponse response = client.getESClient().delete(request,RequestOptions.DEFAULT);
return response;
}
}
测试
@Autowired
private DocService docService;
@Test
void createDocTest() throws IOException {
System.out.println(docService.create().getResult());
}
@Test
void updateDocTest() throws IOException {
System.out.println(docService.update().getResult());
}
@Test
void selectDocTest() throws IOException {
System.out.println(docService.select().toString());
}
@Test
void deleteDocTest() throws IOException {
System.out.println(docService.delete().toString());
}
4.3 文档批量操作
import com.fasterxml.jackson.databind.ObjectMapper;
import com.hhx.es.entity.User;
import org.elasticsearch.action.bulk.BulkRequest;
import org.elasticsearch.action.bulk.BulkResponse;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.common.xcontent.XContentType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.io.IOException;
/**
* @author jadexu
* 文档批量操作
*/
@Service
public class DocBatchService {
@Autowired
private ElasticsearchClient client;
private static User[] users = {
new User(1,"zhangsan",30,"male"),
new User(2,"lisi",40,"female"),
new User(3,"wangwu",20,"male")
};
/**
* 批量添加数据
* @return
* @throws IOException
*/
public BulkResponse insert() throws IOException {
//创建批量新增请求对象
BulkRequest request = new BulkRequest();
//json解析对象
ObjectMapper mapper = new ObjectMapper();
for (User u :users) {
String userJson = mapper.writeValueAsString(u);
request.add(new IndexRequest().index("user").id(u.getId().toString()).source(userJson,XContentType.JSON));
}
//发送请求,获得响应
BulkResponse response = client.getESClient().bulk(request, RequestOptions.DEFAULT);
return response;
}
/**
* 批量删除
* @return
* @throws IOException
*/
public BulkResponse delete() throws IOException {
BulkRequest request = new BulkRequest();
for (User u:users) {
request.add(new DeleteRequest().index("user").id(u.getId().toString()));
}
BulkResponse response = client.getESClient().bulk(request,RequestOptions.DEFAULT);
return response;
}
}
测试
@Autowired
private DocBatchService docBatchService;
@Test
void inertBatchDocTest() throws IOException {
System.out.println(docBatchService.insert().getItems());
}
@Test
void deleteBatchDocTest() throws IOException {
System.out.println(docBatchService.delete().getTook());
}
4.4 搜索操作
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.common.unit.Fuzziness;
import org.elasticsearch.index.query.*;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.io.IOException;
/**
* @author jadexu
* 搜索服务
*/
@Service
public class SearchService {
@Autowired
private ElasticsearchClient client;
/**
* 查询索引所有数据
* @return
* @throws IOException
*/
public SearchResponse selectIndices() throws IOException {
//创建搜索请求对象
SearchRequest request = new SearchRequest();
request.indices("user");
//构建查询的请求体
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
//查询所有数据
sourceBuilder.query(QueryBuilders.matchAllQuery());
request.source(sourceBuilder);
SearchResponse response = client.getESClient().search(request, RequestOptions.DEFAULT);
return response;
}
/**
* 根据关键字查询
* @return
* @throws IOException
*/
public SearchResponse selectByTerm() throws IOException {
SearchRequest request = new SearchRequest();
request.indices("user");
SearchSourceBuilder builder = new SearchSourceBuilder();
builder.query(QueryBuilders.termQuery("sex","male"));
request.source(builder);
SearchResponse response = client.getESClient().search(request,RequestOptions.DEFAULT);
return response;
}
/**
* 分页查询
* @return
* @throws IOException
*/
public SearchResponse selectByPage() throws IOException {
SearchRequest request = new SearchRequest();
request.indices("user");
SearchSourceBuilder builder = new SearchSourceBuilder();
builder.query(QueryBuilders.matchAllQuery());
//分页查询
//当前页起始索引
builder.from(0);
//每页显示多少条
builder.size(2);
request.source(builder);
SearchResponse response = client.getESClient().search(request,RequestOptions.DEFAULT);
return response;
}
/**
* 排序查询
* @return
* @throws IOException
*/
public SearchResponse selectBySort() throws IOException {
SearchRequest request = new SearchRequest();
request.indices("user");
SearchSourceBuilder builder = new SearchSourceBuilder();
builder.query(QueryBuilders.matchAllQuery());
//排序
builder.sort("age", SortOrder.DESC);
request.source(builder);
SearchResponse response = client.getESClient().search(request,RequestOptions.DEFAULT);
return response;
}
/**
* 过滤字段查询
* @return
* @throws IOException
*/
public SearchResponse selectByFilter() throws IOException {
SearchRequest request = new SearchRequest();
request.indices("user");
SearchSourceBuilder builder = new SearchSourceBuilder();
builder.query(QueryBuilders.matchAllQuery());
//查询字段过滤
String[] excludes = {};
String[] includes = {"name","age"};
builder.fetchSource(includes,excludes);
request.source(builder);
SearchResponse response = client.getESClient().search(request,RequestOptions.DEFAULT);
return response;
}
/**
* Bool查询
* @return
* @throws IOException
*/
public SearchResponse selectByBool() throws IOException {
SearchRequest request = new SearchRequest();
request.indices("user");
SearchSourceBuilder builder = new SearchSourceBuilder();
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
//必须包含
boolQueryBuilder.must(QueryBuilders.matchQuery("sex","male"));
//一定不包含
boolQueryBuilder.mustNot(QueryBuilders.matchQuery("name","zhangsan"));
//可能包含
boolQueryBuilder.should(QueryBuilders.matchQuery("age","40"));
builder.query(boolQueryBuilder);
request.source(builder);
SearchResponse response = client.getESClient().search(request,RequestOptions.DEFAULT);
return response;
}
/**
* 范围查询
* @return
* @throws IOException
*/
public SearchResponse selectByRange() throws IOException {
SearchRequest request = new SearchRequest();
request.indices("user");
SearchSourceBuilder builder = new SearchSourceBuilder();
//范围查询
RangeQueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery("age");
//大于等于
rangeQueryBuilder.gte("20");
//小于
rangeQueryBuilder.lt("40");
builder.query(rangeQueryBuilder);
request.source(builder);
SearchResponse response = client.getESClient().search(request,RequestOptions.DEFAULT);
return response;
}
/**
* 模糊查询
* @return
* @throws IOException
*/
public SearchResponse selectByFuzzy() throws IOException {
SearchRequest request = new SearchRequest();
request.indices("user");
SearchSourceBuilder builder = new SearchSourceBuilder();
//模糊查询
FuzzyQueryBuilder fuzzyQueryBuilder = QueryBuilders.fuzzyQuery("sex","male").fuzziness(Fuzziness.TWO);
builder.query(fuzzyQueryBuilder);
request.source(builder);
SearchResponse response = client.getESClient().search(request,RequestOptions.DEFAULT);
return response;
}
/**
* 高亮查询
* @return
* @throws IOException
*/
public SearchResponse selectByHighlight() throws IOException {
SearchRequest request = new SearchRequest();
request.indices("user");
SearchSourceBuilder builder = new SearchSourceBuilder();
//关键字查询
TermQueryBuilder termQueryBuilder = QueryBuilders.termQuery("sex","male");
builder.query(termQueryBuilder);
//设置高亮
HighlightBuilder highlightBuilder = new HighlightBuilder();
//设置标签前缀
highlightBuilder.preTags("<font color='red'>");
//设置标签后缀
highlightBuilder.postTags("</font>");
//设置高亮字段
highlightBuilder.field("sex");
//设置高亮构建对象
builder.highlighter(highlightBuilder);
request.source(builder);
SearchResponse response = client.getESClient().search(request,RequestOptions.DEFAULT);
return response;
}
/**
* 聚合查询——max
* @return
* @throws IOException
*/
public SearchResponse selectByAggregationForMax() throws IOException {
SearchRequest request = new SearchRequest().indices("user");
SearchSourceBuilder builder = new SearchSourceBuilder();
//聚合查询
builder.aggregation(AggregationBuilders.max("maxAge").field("age"));
request.source(builder);
SearchResponse response = client.getESClient().search(request,RequestOptions.DEFAULT);
return response;
}
/**
* 聚合查询——terms
* @return
* @throws IOException
*/
public SearchResponse selectByAggregationForTerms() throws IOException {
SearchRequest request = new SearchRequest().indices("user");
SearchSourceBuilder builder = new SearchSourceBuilder();
//聚合查询——分组统计
builder.aggregation(AggregationBuilders.terms("age_groupBy").field("age"));
request.source(builder);
SearchResponse response = client.getESClient().search(request,RequestOptions.DEFAULT);
return response;
}
}
测试
@Autowired
private SearchService searchService;
@Test
void selectAllTest() throws IOException {
for (SearchHit h:searchService.selectIndices().getHits()) {
System.out.println(h.getSourceAsString());
}
}
@Test
void selectByTermTest() throws IOException {
for (SearchHit h:searchService.selectByTerm().getHits()) {
System.out.println(h.getSourceAsString());
}
}
@Test
void selectByPageTest() throws IOException {
for (SearchHit h:searchService.selectByPage().getHits()) {
System.out.println(h.getSourceAsString());
}
}
@Test
void selectBySortTest() throws IOException {
for (SearchHit h:searchService.selectBySort().getHits()) {
System.out.println(h.getSourceAsString());
}
}
@Test
void selectByFilterTest() throws IOException {
for (SearchHit h:searchService.selectByFilter().getHits()) {
System.out.println(h.getSourceAsString());
}
}
@Test
void selectByBoolTest() throws IOException {
for (SearchHit h:searchService.selectByBool().getHits()) {
System.out.println(h.getSourceAsString());
}
}
@Test
void selectByRangeTest() throws IOException {
for (SearchHit h:searchService.selectByRange().getHits()) {
System.out.println(h.getSourceAsString());
}
}
@Test
void selectByFuzzyTest() throws IOException {
for (SearchHit h:searchService.selectByFuzzy().getHits()) {
System.out.println(h.getSourceAsString());
}
}
@Test
void selectByHighlightTest() throws IOException {
for (SearchHit h:searchService.selectByHighlight().getHits()) {
System.out.println(h.getSourceAsString());
//获得高亮的字段
System.out.println(h.getHighlightFields());
}
}
@Test
void selectByAggregationByMaxTest() throws IOException {
System.out.println(searchService.selectByAggregationForMax().getAggregations());
}
@Test
void selectByAggregationByTermsTest() throws IOException {
//获得分组
Aggregations aggregations = searchService.selectByAggregationForTerms().getAggregations();
//遍历
for (Aggregation a:aggregations) {
Terms terms = (Terms) a;
//遍历每个聚合的bucket
for (Terms.Bucket b:terms.getBuckets()) {
//获得分组的key
System.out.println(b.getKeyAsNumber().longValue());
}
}
}
三、补充
百度了很多几乎没有找到新版本的解说,而官网的说法又太理论不知道如何实操
在b站找到了个比较新版本的教学视频,是基于7.8.x的
以下的b站视频地址和对应文档资料:
视频地址:https://www.bilibili.com/video/BV1hh411D7sb
文档资料:https://pan.baidu.com/s/15k6inHdb6RBy_W6eGnUHOA
提取码:cwq3