ElasticSearch
全文检索基础
什么是全文检索
将⾮结构化数据中的⼀部分信息提取出来,重新组织,使其变得有⼀定结构,然后对此有⼀定结构的数据进⾏搜索,从⽽达到搜索相对较快的⽬的。这部分从⾮结构化数据中提取出的然后重新组织的信息,我们称之索引。
例如:字典。字典的拼⾳表和部⾸检字表就相当于字典的索引,对每⼀个字的解释是⾮结构化的,如果字典没有⾳节表和部⾸检字表,在茫茫辞海中找⼀个字只能顺序扫描。然⽽字的某些信息可以提取出来进⾏结构化处理,⽐如读⾳,就⽐较结构化,分声母和韵母,分别只有⼏种可以⼀⼀列举,于是将读⾳拿出来按⼀定的顺序排列,每⼀项读⾳都指向此字的详细解释的页数。我们搜索时按结构化的拼⾳搜到读⾳,然后按其指向的页数,便可找到我们的⾮结构化数据——也即对字的解释。
这种先建⽴索引,再对索引进⾏搜索的过程就叫全⽂检索(Full-text Search)。
虽然创建索引的过程也是⾮常耗时的,但是索引⼀旦创建就可以多次使⽤,全⽂检索主要处理的是查
询,所以耗时间创建索引是值得的。
全文检索流程
相关概念
索引库
索引库就是存储索引的保存在磁盘上的⼀系列的⽂件。⾥⾯存储了建⽴好的索引信息以及⽂档对象。
⼀个索引库相当于数据库中的⼀张表。
document对象
获取原始内容的⽬的是为了索引,在索引前需要将原始内容创建成⽂档(Document),⽂档中包括⼀
个⼀个的域(Field),域中存储内容。每个⽂档都有⼀个唯⼀的编号,就是⽂档id。
document对象相当于表中的⼀条记录。
field对象
如果我们把document看做是数据库中⼀条记录的话,field相当于是记录中的字段。field是索引库中存
储数据的最⼩单位。field的数据类型⼤致可以分为数值类型和⽂本类型,⼀般需要查询的字段都是⽂本类型的,field的还有如下属性:
是否分词:是否对域的内容进⾏分词处理。前提是我们要对域的内容进⾏查询。
是否索引:将Field分析后的词或整个Field值进⾏索引,只有索引⽅可搜索到。
⽐如:商品名称、商品简介分析后进⾏索引,订单号、⾝份证号不⽤分词但也要索引,这些将来都
要作为查询条件。
是否存储:将Field值存储在⽂档中,存储在⽂档中的Field才可以从Document中获取
⽐如:商品名称、订单号,凡是将来要从Document中获取的Field都要存储。
term对象
从⽂档对象中拆分出来的每个单词叫做⼀个Term,不同的域中拆分出来的相同的单词是不同的
term。term中包含两部分⼀部分是⽂档的域名,另⼀部分是单词的内容。term是创建索引的关键词
对象。
ElasticSearch简介
什么是ElasticSearch
Elaticsearch,简称为es, es是⼀个开源的⾼扩展的分布式全⽂检索引擎,它可以近乎实时的存储、检索数据;本⾝扩展性很好,可以扩展到上百台服务器,处理PB级别的数据。es也使⽤Java开发并使⽤Lucene作为其核⼼来实现所有索引和搜索的功能,但是它的⽬的是通过简单的RESTful API来隐藏
Lucene的复杂性,从而让全文搜索变得简单。
ElasticSearch的使用案例
2013年初,GitHub抛弃了Solr,采取ElasticSearch 来做PB级的搜索。 “GitHub使⽤ElasticSearch搜
索20TB的数据,包括13亿⽂件和1300亿⾏代码”。
维基百科:启动以elasticsearch为基础的核⼼搜索架构
SoundCloud:“SoundCloud使⽤ElasticSearch为1.8亿用户提供即时而精准的音乐搜索服务”
百度:百度目前广泛使用ElasticSearch作为⽂本数据分析,采集百度所有服务器上的各类指标数据及
⽤户⾃定义数据,通过对各种数据进行多维分析展示,辅助定位分析实例异常或业务层⾯异常。⽬前
覆盖百度内部20多个业务线(包括casio、云分析、网盟、预测、⽂库、直达号、钱包、风控等),
单集群最⼤100台机器,200个ES节点,每天导⼊30TB+数据
新浪使用ES 分析处理32亿条实时日志
阿⾥使用ES 构建挖财自己的日志采集和分析体系
ElasticSearch对比Solr
Solr 利⽤ Zookeeper 进⾏分布式管理,⽽ Elasticsearch ⾃⾝带有分布式协调管理功能;
Solr ⽀持更多格式的数据,⽽ Elasticsearch 仅⽀持json⽂件格式;
Solr 官⽅提供的功能更多,⽽ Elasticsearch 本⾝更注重于核⼼功能,⾼级功能多有第三⽅插件提
供;
Solr 在传统的搜索应⽤中表现好于 Elasticsearch,但在处理实时搜索应⽤时效率明显低于
Elasticsearch
ElasticSearch相关概念
概述
Elasticsearch是面向⽂档(document oriented)的,这意味着它可以存储整个对象或⽂档(document)。然⽽它不仅仅是存储,还会索引(index)每个⽂档的内容使之可以被搜索。在Elasticsearch中,你可以对⽂档(而⾮成⾏成列的数据)进⾏索引、搜索、排序、过滤。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 安装
docker镜像下载
docker pull elasticsearch:5.6.8
安装es容器
docker run -di --name=kkb_es -p 9200:9200 -p 9300:9300 elasticsearch:5.6.8
9200端口(Web管理平台端口) 9300(服务默认端口)
浏览器输⼊地址访问:http://192.168.211.132:9200/
开启远程连接
上⾯完成安装后,es并不能正常使⽤,elasticsearch从5版本以后默认不开启远程连接,程序直接连接会报如下错误:
failed to load elasticsearch nodes :org.elasticsearch.client.transport.NoNodeAvailableException: None of theconfigured nodes are available: [{#transport#-1}{5ttLpMhkRjKLkvoY7ltUWg}
{192.168.211.132}{192.168.211.132:9300}]
我们需要修改es配置开启远程连接,代码如下:
登陆容器
docker exec -it kkb_es /bin/bash
查看⽬录结构 输⼊: dir
root@07f22eb41bb5:/usr/share/elasticsearch# dir
NOTICE.txt README.textile bin config data lib logs modules plugins
进⼊config⽬录
cd config
查看⽂件`
root@07f22eb41bb5:/usr/share/elasticsearch/config# ls
elasticsearch.yml log4j2.properties scripts
修改elasticsearch.yml⽂件
root@07f22eb41bb5:/usr/share/elasticsearch/config# vi elasticsearch.yml
bash: vi: command not found
vi命令⽆法识别,因为docker容器⾥⾯没有该命令,我们可以安装该编辑器。
设置apt下载源
docker cp ~/sources.list kkb_es:/etc/apt/sources.list
安装vim编辑器
apt-get update
apt-get install vim
安装好了后,修改elasticsearch.yml配置,
vi elasticsearch.yml
去掉transport.host前面的注释,同时添加下⾯⼀⾏代码:
cluster.name: my-elasticsearch
重启docker
docker restart kkb_es
系统参数配置
重启后发现重启启动失败了,这时什么原因呢?这与我们刚才修改的配置有关,因为elasticsearch在启动的时候会进⾏⼀些检查,⽐如最多打开的⽂件的个数以及虚拟内存区域数量等等,如果你放开了此配置,意味着需要打开更多的⽂件以及虚拟内存,所以我们还需要系统调优。
修改vi /etc/security/limits.conf ,追加内容 (nofile是单个进程允许打开的最⼤⽂件个数 soft nofile 是
软限制 hard nofile是硬限制 )
* soft nofile 65536
* hard nofile 65536
修改vi /etc/sysctl.conf,追加内容 (限制⼀个进程可以拥有的VMA(虚拟内存区域)的数量 )
vm.max_map_count=655360
执⾏下⾯命令 修改内核参数马上⽣效
sysctl -p
重新启动虚拟机,再次启动容器,发现已经可以启动并远程访问
reboot
跨域配置
修改elasticsearch/config下的配置⽂件:elasticsearch.yml,增加以下三句命令,并重启:
http.cors.enabled: true
http.cors.allow-origin: "*"
network.host: 192.168.220.100
其中:
http.cors.enabled: true:此步为允许elasticsearch跨域访问,默认是false。
http.cors.allow-origin: “”:表⽰跨域访问允许的域名地址(表⽰任意)。
重启
docker restart kkb_es
⼩提⽰:如果想让容器开启重启,可以执⾏下⾯命令
docker update --restart=always 容器名称或者容器id
ElasticSearch的客户端操作
实际开发中,主要有三种⽅式可以作为elasticsearch服务的客户端:
第⼀种,elasticsearch-head插件
第⼆种,使⽤elasticsearch提供的Restful接口直接访问
第三种,使⽤elasticsearch提供的API进⾏访问
elasticsearch-head
ElasticSearch不同于Solr⾃带图形化界⾯,我们可以通过安装ElasticSearch的head插件,完成图形化界⾯的效果,完成索引数据的查看。安装插件的⽅式有两种,在线安装和本地安装。本⽂档采⽤本地安装⽅式进⾏head插件的安装。elasticsearch-5-*以上版本安装head需要安装node和grunt
下载head插件:https://github.com/mobz/elasticsearch-head
将elasticsearch-head-master压缩包解压到任意⽬录,但是要和elasticsearch的安装⽬录区别开
下载nodejs:https://nodejs.org/en/download/
将grunt安装为全局命令 ,Grunt是基于Node.js的项⽬构建⼯具
在cmd控制台中输⼊如下执⾏命令:
cnpm install -g grunt-cli
进⼊elasticsearch-head-master⽬录启动head,在命令提⽰符下输⼊命令:
>npm install
>grunt server
打开浏览器,输⼊ http://localhost:9100,看到如下页⾯:
如果不能成功连接到es服务,需要修改ElasticSearch的config⽬录下的配置⽂件:
config/elasticsearch.yml,增加以下两句命令:
http.cors.enabled: true
http.cors.allow-origin: "*"
然后重新启动ElasticSearch服务。
使⽤Postman⼯具进⾏Restful接口访问
ElasticSearch的接口语法
curl -X<VERB> '<PROTOCOL>://<HOST>:<PORT>/<PATH>?<QUERY_STRING>' -d '<BODY>'
其中:
参数 | 解释 |
---|---|
VERB | 适当的 HTTP ⽅法 或 谓词 : GET、 POST、 PUT、 HEAD 或者 DELETE。 |
PROTOCOL | http 或者 https(如果你在 Elasticsearch 前⾯有⼀个 https 代理) |
HOST | Elasticsearch 集群中任意节点的主机名,或者⽤ localhost 代表本地机器上的节点。 |
PORT | 运⾏ Elasticsearch HTTP 服务的端⼜号,默认是 9200 。 |
PATH | API 的终端路径(例如 _count 将返回集群中⽂档数量)。Path 可能包含多个组件,例如:_cluster/stats 和 _nodes/stats/jvm 。 |
QUERY_STRING | 任意可选的查询字符串参数 (例如 ?pretty 将格式化地输出 JSON 返回值,使其更容易阅读) |
BODY | ⼀个 JSON 格式的请求体 (如果请求需要的话) |
创建索引index和映射mapping
请求url:
PUT 192.168.220.100:9200/blog1
请求体:
{
"mappings": {
"article": {
"properties": {
"id": {
"type": "long",
"store": true,
"index":"not_analyzed"
},
"title": {
"type": "text",
"store": true,
"index":"analyzed",
"analyzer":"standard"
},
"content": {
"type": "text",
"store": true,
"index":"analyzed",
"analyzer":"standar
}
}
}
}
}
post man 截图:
elasticsearch-head查看:
创建索引后设置Mapping
我们可以在创建索引时设置mapping信息,当然也可以先创建索引然后再设置mapping。
在上⼀个步骤中不设置maping信息,直接使⽤put⽅法创建⼀个索引,然后设置mapping信息。
请求的url:
POST http://192.168.220.100:9200/blog2/hello/_mapping
请求体:
"hello": {
"properties": {
"id":{
"type":"long",
"store":true
},
"title":{
"type":"text",
"store":true,
"index":true,
"analyzer":"standard"
},
"content":{
"type":"text",
"store":true,
"index":true,
"analyzer":"standard"
}
}
}
}
删除索引index
DELETE 192.168.220.100:9200/blog1
创建文档document
POST 192.168.220.100:9200/blog1/article/1
{
"id":1,
"title":"ElasticSearch是⼀个基于Lucene的搜索服务器",
"content":"它提供了⼀个分布式多⽤户能⼒的全⽂搜索引擎,基于RESTful web接⼝。
Elasticsearch是⽤Java开发的,并作为Apache许可条款下的开放源码发布,是当前流⾏的企业级搜
索引擎。设计⽤于云计算中,能够达到实时搜索,稳定,可靠,快速,安装使⽤⽅便。"
}
修改⽂档document
POST 192.168.220.100:9200/blog1/article/1
{
"id":1,
"title":"【修改】ElasticSearch是⼀个基于Lucene的搜索服务器",
"content":"【修改】它提供了⼀个分布式多⽤户能⼒的全⽂搜索引擎,基于RESTful web接⼝。
Elasticsearch是⽤Java开发的,并作为Apache许可条款下的开放源码发布,是当前流⾏的企业级搜
索引擎。设计⽤于云计算中,能够达到实时搜索,稳定,可靠,快速,安装使⽤⽅便。"
}
删除⽂档document
DELETE 192.168.220.100:9200/blog1/article/1
查询⽂档-根据id查询
GET 192.168.220.100:9200/blog1/article/1
查询⽂档-querystring查询
POST 192.168.220.100:9200/blog1/article/_search
{
"query": {
"query_string": {
"default_field": "title",
"query": "搜索服务器"
}
}
}
注意:
将搜索内容"搜索服务器"修改为"钢索",同样也能搜索到⽂档,该原因会在下⾯讲解中得到答案
{
"query": {
"query_string": {
"default_field": "title",
"query": "钢索"
}
}
}
钢索 -> “钢”, “索”,搜索是分成两个词,注意Standard标准分词器,会把汉字每个字⼀个词
存到索引库中的tilte,也是按照Standard进⾏的分词,所以搜索钢索能搜到这个document
查询⽂档-term查询
POST 192.168.220.100:9200/blog1/article/_search
{
"query": {
"term": {
"title": "搜索"
}
}
}
query_string:搜索之前对搜索的关键词分词
term:对搜索的关键词不分词
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检索的命中率。
IK分词器安装
安装ik分词器
IK分词器下载地址https://github.com/medcl/elasticsearch-analysis-ik/releases
将ik分词器上传到服务器上,然后解压,并改名字为ik
unzip elasticsearch-analysis-ik-5.6.8.zip
mv elasticsearch ik
将ik⽬录拷贝到docker容器的plugins⽬录下
docker cp ./ik kkb_es:/usr/share/elasticsearch/plugins
ik_max_word:会将⽂本做最细粒度的拆分
⽐如会将“中华⼈民共和国⼈民⼤会堂”拆分为“中华⼈民共和国、中华⼈民、中华、华⼈、⼈民共
和国、⼈民、共和国、⼤会堂、⼤会、会堂等词语。
ik_smart:会做最粗粒度的拆分
⽐如会将“中华⼈民共和国⼈民⼤会堂”拆分为中华⼈民共和国、⼈民⼤会堂。
Kibana使⽤-掌握DSL语句
我们上⾯使⽤的是elasticsearch-head插件实现数据查找的,但是elasticsearch-head的功能⽐较单⼀,我们这⾥需要⼀个更专业的⼯具实现对⽇志的实时分析,也就是我们接下来要讲的kibana。
Kibana 是⼀款开源的数据分析和可视化平台,它是 Elastic Stack 成员之⼀,设计⽤于和 Elasticsearch协作。您可以使⽤ Kibana 对 Elasticsearch 索引中的数据进⾏搜索、查看、交互操作。您可以很⽅便的利⽤图表、表格及地图对数据进⾏多元化的分析和呈现。
Kibana 可以使⼤数据通俗易懂。它很简单,基于浏览器的界⾯便于您快速创建和分享动态数据仪表板
来追踪 Elasticsearch 的实时数据变化。
搭建 Kibana ⾮常简单。您可以分分钟完成 Kibana 的安装并开始探索 Elasticsearch 的索引数据 — 没有代码、不需要额外的基础设施。
Kibana下载安装
镜像下载
docker pull docker.io/kibana:5.6.8
安装kibana容器
执⾏如下命令,开始安装kibana容器
docker run -it -d -e ELASTICSEARCH_URL=http://192.168.220.100:9200 --name
kibana -p 5601:5601 kibana:5.6.8
ELASTICSEARCH_URL=http://192.168.220.100:9200:是指链接的ES地址
restart=always:每次服务都会重启,也就是开启启动
5601:5601:端⼜号
快捷键
ctrl+i ⾃动缩进
ctrl+enter 提交请求
down 打开⾃动补全菜单
enter或tab 选中项⾃动补全
esc 关闭补全菜单
访问测试:
访问http://192.168.220.100:5601
Kibana使⽤
配置索引
要使⽤Kibana,您必须⾄少配置⼀个索引。索引⽤于标识Elasticsearch索引以运⾏搜索和分析。它们还⽤于配置字段。
我们修改索引名称的匹配⽅式即可,下⾯2个选项不⽤勾选。点击create,会展⽰出当前配置的索引的域信息,如下图:
域的每个标题选项分别代表如下意思:
数据搜索
Discover为数据搜索部分,可以对⽇志信息进⾏搜索操作。
可以使⽤Discover实现数据搜索过滤和搜索条件显⽰以及关键词搜索,如下图:
DSL语句使⽤
Query DSL结构化查询介绍
Query DSL是⼀个Java开源框架⽤于构建类型安全的SQL查询语句。采⽤API代替传统的拼接字符串来
构造查询语句。⽬前Querydsl⽀持的平台包括JPA,JDO,SQL,Java Collections,RDF,Lucene,
Hibernate Search。elasticsearch提供了⼀整套基于JSON的DSL语⾔来定义查询。
索引操作
查询所有索引
GET /_cat/indices?v
删除某个索引
DELETE /skuinfo
新增索引
PUT /user
创建映射
PUT /user/userinfo/_mapping
{
"properties": {
"name":{
"type": "text",
"analyzer": "ik_smart",
"search_analyzer": "ik_smart"
},
"city":{
"type": "text",
"analyzer": "ik_smart",
"search_analyzer": "ik_smart"
},
"age":{
"type": "long"
},
"description":{
"type": "text",
"analyzer": "ik_smart",
"search_analyzer": "ik_smart"
}
}
}
新增⽂档数据
PUT /user/userinfo/1
{
"name":"李四",
"age":22,
"city":"深圳",
"description":"李四来⾃湖北武汉!"
}
我们再增加3条记录:
#新增⽂档数据 id=2
PUT /user/userinfo/2
{
"name":"王五",
"age":35,
"city":"深圳",
"description":"王五家住在深圳!"
}
#新增⽂档数据 id=3
PUT /user/userinfo/3
{
"name":"张三",
"age":19,
"city":"深圳",
"description":"在深圳打⼯,来⾃湖北武汉"
}
#新增⽂档数据 id=4
PUT /user/userinfo/4
{
"name":"张三丰",
"age":66,
"city":"武汉",
"description":"在武汉读书,家在武汉!"
}
#新增⽂档数据 id=5
PUT /user/userinfo/5
{
"name":"赵⼦⻰",
"age":77,
"city":"⼴州",
"description":"赵⼦⻰来⾃深圳宝安,但是在⼴州⼯作!",
"address":"⼴东省茂名市"
}
#新增⽂档数据 id=6
PUT /user/userinfo/6
{
"name":"赵毅",
"age":55,
"city":"⼴州",
"description":"赵毅来⾃⼴州⽩云区,从事电⼦商务8年!"
}
#新增⽂档数据 id=7
PUT /user/userinfo/7
{
"name":"赵哈哈",
"age":57,
"city":"武汉",
"description":"武汉赵哈哈,在深圳打⼯已有半年了,⽉薪7500!"
}
修改数据
替换操作
更新数据可以使⽤之前的增加操作,这种操作会将整个数据替换掉,代码如下:
#更新数据,id=4
PUT /user/userinfo/4
{
"name":"张三丰",
"description":"在武汉读书,家在武汉!在深圳⼯作!"
}
使⽤GET命令查看:
#根据ID查询
GET /user/userinfo/4
更新操作
我们先使⽤下⾯命令恢复数据:
#恢复⽂档数据 id=4
PUT /user/userinfo/4
{
"name":"张三丰",
"age":66,
"city":"武汉",
"description":"在武汉读书,家在武汉!"
}
使⽤POST更新某个列的数据
#使⽤POST更新某个域的数据
POST /user/userinfo/4/_update
{
"doc":{
"name":"张三丰",
"description":"在武汉读书,家在武汉!在深圳⼯作!"
}
}
使⽤GET命令查看
#根据ID查询
GET /user/userinfo/4
删除Document
#删除数据
DELETE user/userinfo/7
数据查询
查询所有数据
#查询所有
GET /user/_search
根据ID查询
#根据ID查询
GET /user/userinfo/2
Sort排序
#搜索排序
GET /user/_search
{
"query":{
"match_all": {}
},
"sort":{
"age":{
"order":"desc"
}
}
}
分页
#分⻚实现
GET /user/_search
{
"query":{
"match_all": {}
},
"sort":{
"age":{
"order":"desc"
}
},
"from": 0,
"size": 2
}
解释:
from:从下N的记录开始查询
size:每页显⽰条数
查询模式
term查询
term主要⽤于分词精确匹配,如字符串、数值、⽇期等(不适合情况:1.列中除英⽂字符外有其它值 2.字符串值中有冒号或中⽂ 3.系统⾃带属性如_version)
#查询-term
GET _search
{
"query":{
"term":{
"city":"武汉"
}
}
}
terms 查询
terms 跟 term 有点类似,但 terms 允许指定多个匹配条件。 如果某个字段指定了多个值,那么⽂档需
要⼀起去做匹配 。
#查询-terms 允许多个Term
GET _search
{
"query":{
"terms":{
"city":
[
"武汉",
"⼴州"
]
}
}
}
match查询
GET _search
{
"query": {
"match": {
"city": "⼴州武汉"
}
}
}
query_string查询
GET _search
{
"query": {
"query_string": {
"default_field": "city",
"query": "⼴州武汉"
}
}
}
range 查询
ange过滤允许我们按照指定范围查找⼀批数据。例如我们查询年龄范围
#-range 范围过滤
#gt表示> gte表示=>
#lt表示< lte表示<=
GET _search
{
"query":{
"range": {
"age": {
"gte": 30,
"lte": 57
}
}
}
}
exists
exists 过滤可以⽤于查找拥有某个域的数据
#搜索 exists:是指包含某个域的数据检索
GET _search
{
"query": {
"exists":{
"field":"address"
}
}
}
bool 查询
bool 可以⽤来合并多个条件查询结果的布尔逻辑,它包含⼀下操作符:
must : 多个查询条件的完全匹配,相当于 and。
must_not : 多个查询条件的相反匹配,相当于 not。
should : ⾄少有⼀个查询条件匹配, 相当于 or。
这些参数可以分别继承⼀个过滤条件或者⼀个过滤条件的数组:
#过滤搜索 bool
#must : 多个查询条件的完全匹配,相当于 and。
#must_not : 多个查询条件的相反匹配,相当于 not。
#should : ⾄少有⼀个查询条件匹配, 相当于 or。
GET _search
{
"query": {
"bool": {
"must": [
{
"term": {
"city": {
"value": "深圳"
}
}
},
{
"range":{
"age":{
"gte":20,
"lte":99
}
}
}
]
}
}
}
match_all 查询
可以查询到所有⽂档,是没有查询条件下的默认语句
#查询所有 match_all
GET _search
{
"query": {
"match_all": {}
}
}
match 查询
match查询是⼀个标准查询,不管你需要全⽂本查询还是精确查询基本上都要⽤到它。
如果你使⽤ match 查询⼀个全⽂本字段,它会在真正查询之前⽤分析器先分析match⼀下查询字符:
#字符串匹配
GET _search
{
"query": {
"match": {
"description": "武汉"
}
}
}
prefix 查询
以什么字符开头的,可以更简单地⽤ prefix ,例如查询所有以张开始的⽤户描述
#前缀匹配 prefix
GET _search
{
"query": {
"prefix": {
"name": {
"value": "赵"
}
}
}
}
multi_match 查询
multi_match查询允许你做match查询的基础上同时搜索多个字段,在多个字段中同时查⼀个
#多个域匹配搜索
GET _search
{
"query": {
"multi_match": {
"query": "深圳",
"fields": [
"city",
"description"
]
}
}
}
完整DSL语句代码
#查看所有索引
GET /_cat/indices?v
#删除某个索引
DELETE /skuinfo
#新增索引
PUT /user
#创建映射
PUT /user/userinfo/_mapping
{
"properties": {
"name":{
"type": "text",
"analyzer": "ik_smart",
"search_analyzer": "ik_smart",
"store": false
},
"city":{
"type": "text",
"analyzer": "ik_smart",
"search_analyzer": "ik_smart",
"store": false
},
"age":{
"type": "long",
"store": false
},
"description":{
"type": "text",
"analyzer": "ik_smart",
"search_analyzer": "ik_smart",
"store": false
}
}
}
#新增⽂档数据 id=1
PUT /user/userinfo/1
{
"name":"李四",
"age":22,
"city":"深圳",
"description":"李四来⾃湖北武汉!"
}
#新增⽂档数据 id=2
PUT /user/userinfo/2
{
"name":"王五",
"age":35,
"city":"深圳",
"description":"王五家住在深圳!"
}
#新增⽂档数据 id=3
PUT /user/userinfo/3
{
"name":"张三",
"age":19,
"city":"深圳",
"description":"在深圳打⼯,来⾃湖北武汉"
}
#新增⽂档数据 id=4
PUT /user/userinfo/4
{
"name":"张三丰",
"age":66,
"city":"武汉",
"description":"在武汉读书,家在武汉!"
}
#新增⽂档数据 id=5
PUT /user/userinfo/5
{
"name":"赵⼦⻰",
"age":77,
"city":"⼴州",
"description":"赵⼦⻰来⾃深圳宝安,但是在⼴州⼯作!",
"address":"⼴东省茂名市"
}
#新增⽂档数据 id=6
PUT /user/userinfo/6
{
"name":"赵毅",
"age":55,
"city":"⼴州",
"description":"赵毅来⾃⼴州⽩云区,从事电⼦商务8年!"
}
#新增⽂档数据 id=7
PUT /user/userinfo/7
{
"name":"赵哈哈",
"age":57,
"city":"武汉",
"description":"武汉赵哈哈,在深圳打⼯已有半年了,⽉薪7500!"
}
#更新数据,id=4
PUT /user/userinfo/4
{
"name":"张三丰",
"description":"在武汉读书,家在武汉!在深圳⼯作!"
}
#根据ID查询
GET /user/userinfo/4
#恢复⽂档数据 id=4
PUT /user/userinfo/4
{
"name":"张三丰",
"age":66,
"city":"武汉",
"description":"在武汉读书,家在武汉!"
}
#使⽤POST更新某个域的数据
POST /user/userinfo/4/_update
{
"doc":{
"name":"张三丰",
"description":"在武汉读书,家在武汉!在深圳⼯作!"
}
}
#根据ID查询
GET /user/userinfo/4
#删除数据
DELETE user/userinfo/4
#查询所有
GET /user/_search
#根据ID查询
GET /user/userinfo/2
#搜索排序
GET /user/_search
{
"query":{
"match_all": {}
},
"sort":{
"age":{
"order":"desc"
}
}
}
#分⻚实现
GET /user/_search
{
"query":{
"match_all": {}
},
"sort":{
"age":{
"order":"desc"
}
},
"from": 0,
"size": 2
}
#查询-term
GET _search
{
"query":{
"term":{
"city":"武汉"
}
}
}
#查询-terms 允许多个Term
GET _search
{
"query":{
"terms":{
"city":
[
"武汉",
"⼴州"
]
}
}
}
#match查询
GET _search
{
"query": {
"match": {
"description": "武汉"
}
}
}
#过滤-range 范围过滤
#gt表示> gte表示=>
#lt表示< lte表示<=
GET _search
{
"query":{
"range": {
"age": {
"gte": 30,
"lte": 57
}
}
}
}
#过滤搜索 exists:是指包含某个域的数据检索
GET _search
{
"query": {
"exists":{
"field":"address"
}
}
}
#过滤搜索 bool
#must : 多个查询条件的完全匹配,相当于 and。
#must_not : 多个查询条件的相反匹配,相当于 not。
#should : ⾄少有⼀个查询条件匹配, 相当于 or。
GET _search
{
"query": {
"bool": {
"must": [
{
"term": {
"city": {
"value": "深圳"
}
}
},
{
"range":{
"age":{
"gte":20,
"lte":99
}
}
}
]
}
}
}
#查询所有 match_all
GET _search
{
"query": {
"match_all": {}
}
}
#字符串匹配
GET _search
{
"query": {
"match": {
"description": "武汉"
}
}
}
#前缀匹配 prefix
GET _search
{
"query": {
"prefix": {
"name": {
"value": "赵"
}
}
}
}
#多个域匹配搜索
GET _search
{
"query": {
"multi_match": {
"query": "深圳",
"fields": [
"city",
"description"
]
}
}
}
#过滤-range 范围过滤
#gt表示> gte表示=>
#lt表示< lte表示<=
GET _search
{
"query": {
"bool": {
"must": [
{"match": {
"city": "深圳武汉"
}}
],
"filter": {
"range": {
"age": {
"gte": 20,
"lte": 60
}
}
}
}
}
}
因为过滤可以使⽤缓存,同时不计算分数,通常的规则是,使⽤查询(query)语句来进⾏ 全⽂ 搜索或者其它任何需要影响 相关性得分 的搜索。除此以外的情况都使⽤过滤(filters)
ElasticSearch编程操作
创建⼯程,导⼊坐标
<dependencies>
<dependency>
<groupId>org.elasticsearch</groupId>
<artifactId>elasticsearch</artifactId>
<version>5.6.8</version>
</dependency>
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>transport</artifactId>
<version>5.6.8</version>
</dependency>
<dependency>
<groupId>org.apache.logging.log4j</groupId>
<artifactId>log4j-to-slf4j</artifactId>
<version>2.9.1</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.24</version>
</dependency>
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-simple</artifactId>
<version>1.7.21</version>
</dependency>
<dependency>
<groupId>log4j</groupId>
<artifactId>log4j</artifactId>
<version>1.2.12</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
</dependency>
</dependencies>
创建索引index
@Test
//创建索引
public void test1() throws Exception{
// 创建Client连接对象
Settings settings = Settings.builder().put("cluster.name", "myelasticsearch").build();
TransportClient client = new PreBuiltTransportClient(settings)
.addTransportAddress(new
InetSocketTransportAddress(InetAddress.getByName("192.168.220.100"), 9300));
//创建名称为blog2的索引
client.admin().indices().prepareCreate("blog2").get();
//释放资源
client.close();
}
创建映射mapping
@Test
//创建映射
public void test3() throws Exception{
// 创建Client连接对象
Settings settings = Settings.builder().put("cluster.name", "myelasticsearch").build();
TransportClient client = new PreBuiltTransportClient(settings)
.addTransportAddress(new
InetSocketTransportAddress(InetAddress.getByName("127.0.0.1"), 9300));
// 添加映射
/**
* 格式:
* "mappings" : {
"article" : {
"dynamic" : "false",
"properties" : {
"id" : { "type" : "string" },
"content" : { "type" : "string" },
"author" : { "type" : "string" }
}
}
}
*/
XContentBuilder builder = XContentFactory.jsonBuilder()
.startObject()
.startObject("article")
.startObject("properties")
.startObject("id")
.field("type", "integer").field("store", "yes")
.endObject()
.startObject("title")
.field("type", "string").field("store", "yes").field("analyzer",
"ik_smart")
.endObject()
.startObject("content")
.field("type", "string").field("store", "yes").field("analyzer",
"ik_smart")
.endObject()
.endObject()
.endObject()
.endObject();
// 创建映射
PutMappingRequest mapping = Requests.putMappingRequest("blog2")
.type("article").source(builder);
client.admin().indices().putMapping(mapping).get();
//释放资源
client.close();
}
建⽴⽂档document
建⽴⽂档(通过XContentBuilder)
@Test
public void testAddDocument() throws Exception {
//创建⼀个client对象
//创建⼀个⽂档对象
XContentBuilder builder = XContentFactory.jsonBuilder()
.startObject()
.field("id",2l)
.field("title","北⽅⼊秋速度明显加快 多地降温幅度最多可达10度
22222")
.field("content", "阿联酋⼀架客机在纽约机场被隔离 10名乘客病
倒")
.endObject();
//把⽂档对象添加到索引库
client.prepareIndex()
//设置索引名称
.setIndex("index_hello")
//设置type
.setType("article")
//设置⽂档的id,如果不设置的话⾃动的⽣成⼀个id
.setId("2")
//设置⽂档信息
.setSource(builder)
//执⾏操作
.get();
//关闭客户端
client.close();
}
建⽴⽂档(使⽤Jackson转换实体)
创建Article实体
public class Article {
private Integer id;
private String title;
private String content;
getter/setter...
}
添加jackson坐标
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
<version>2.8.1</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.8.1</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-annotations</artifactId>
<version>2.8.1</version>
</dependency>
代码实现
@Test
public void testAddDocument2() throws Exception {
//创建⼀个Article对象
Article article = new Article();
//设置对象的属性
article.setId(3l);
article.setTitle("MH370坠毁在柬埔寨密林?中国⼀公司调⼗颗卫星去拍摄");
article.setContent("警惕荒唐的死亡游戏!俄15岁少年输掉游戏后⽤电锯⾃杀");
//把article对象转换成json格式的字符串。
ObjectMapper objectMapper = new ObjectMapper();
String jsonDocument = objectMapper.writeValueAsString(article);
System.out.println(jsonDocument);
//使⽤client对象把⽂档写⼊索引库
client.prepareIndex("index_hello","article", "3")
.setSource(jsonDocument, XContentType.JSON)
.get();
//关闭客户端
client.close();
}
@Test
public void testAddDocument3() throws Exception {
for (int i = 4; i < 100; i++) {
//创建⼀个Article对象
Article article = new Article();
//设置对象的属性
article.setId(i);
article.setTitle("⼥护⼠路遇昏迷男⼦跪地抢救:救⼈是职责更是本能" + i);
article.setContent("江⻄变质营养餐事件已致24⼈就医 多名官员被调查" + i);
//把article对象转换成json格式的字符串。
ObjectMapper objectMapper = new ObjectMapper();
String jsonDocument = objectMapper.writeValueAsString(article);
System.out.println(jsonDocument);
//使⽤client对象把⽂档写⼊索引库
client.prepareIndex("index_hello","article", i + "")
.setSource(jsonDocument, XContentType.JSON)
.get();
}
//关闭客户端
client.close();
}
查询⽂档操作
termQuery
@Test
public void testQueryByTerm() throws Exception {
//创建⼀个QueryBuilder对象
//参数1:要搜索的字段
//参数2:要搜索的关键词
QueryBuilder queryBuilder = QueryBuilders.termQuery("title", "美丽的⼥
护⼠");
//执⾏查询
SearchResponse searchResponse = client.prepareSearch("index_hello")
.setTypes("article")
.setQuery(queryBuilder)
.get();
//取查询结果
SearchHits searchHits = searchResponse.getHits();
//取查询结果的总记录数
System.out.println("查询结果总记录数:" + searchHits.getTotalHits());
//查询结果列表
Iterator<SearchHit> iterator = searchHits.iterator();
while(iterator.hasNext()) {
SearchHit searchHit = iterator.next();
//打印⽂档对象,以json格式输出
System.out.println(searchHit.getSourceAsString());
//取⽂档的属性
System.out.println("-----------⽂档的属性");
Map<String, Object> document = searchHit.getSource();
System.out.println(document.get("id"));
System.out.println(document.get("title"));
System.out.println(document.get("content"));
}
//关闭client
client.close();
}
QueryString
@Test
public void testQueryByQueryString() throws Exception {
//创建⼀个QueryBuilder对象
//参数1:要搜索的字段
//参数2:要搜索的关键词
QueryBuilder queryBuilder = QueryBuilders.queryStringQuery("美丽的⼥护
⼠").defaultField("title");
//执⾏查询
SearchResponse searchResponse = client.prepareSearch("index_hello")
.setTypes("article")
.setQuery(queryBuilder)
.get();
//取查询结果
SearchHits searchHits = searchResponse.getHits();
//取查询结果的总记录数
System.out.println("查询结果总记录数:" + searchHits.getTotalHits());
//查询结果列表
Iterator<SearchHit> iterator = searchHits.iterator();
while(iterator.hasNext()) {
SearchHit searchHit = iterator.next();
//打印⽂档对象,以json格式输出
System.out.println(searchHit.getSourceAsString());
//取⽂档的属性
System.out.println("-----------⽂档的属性");
Map<String, Object> document = searchHit.getSource();
System.out.println(document.get("id"));
System.out.println(document.get("title"));
System.out.println(document.get("content"));
}
//关闭client
client.close();
}
MatchQuery
@Test
public void testQueryByMatchQuery() throws Exception {
//创建⼀个QueryBuilder对象
//参数1:要搜索的字段
//参数2:要搜索的关键词
QueryBuilder queryBuilder = QueryBuilders.matchQuery("title", "美丽的⼥护
⼠");
//执⾏查询
SearchResponse searchResponse = client.prepareSearch("index_hello")
.setTypes("article")
.setQuery(queryBuilder)
.get();
//取查询结果
SearchHits searchHits = searchResponse.getHits();
//取查询结果的总记录数
System.out.println("查询结果总记录数:" + searchHits.getTotalHits());
//查询结果列表
Iterator<SearchHit> iterator = searchHits.iterator();
while(iterator.hasNext()) {
SearchHit searchHit = iterator.next();
//打印⽂档对象,以json格式输出
System.out.println(searchHit.getSourceAsString());
//取⽂档的属性
System.out.println("-----------⽂档的属性");
Map<String, Object> document = searchHit.getSource();
System.out.println(document.get("id"));
System.out.println(document.get("title"));
System.out.println(document.get("content"));
}
//关闭client
client.close();
}
重构查询⽅法
private void search(QueryBuilder queryBuilder) throws Exception {
//执⾏查询
SearchResponse searchResponse = client.prepareSearch("index_hello")
.setTypes("article")
.setQuery(queryBuilder)
//设置分⻚信息
.setFrom(0)
//每⻚显示的⾏数
.setSize(5)
.get();
//取查询结果
SearchHits searchHits = searchResponse.getHits();
//取查询结果的总记录数
System.out.println("查询结果总记录数:" + searchHits.getTotalHits());
//查询结果列表
Iterator<SearchHit> iterator = searchHits.iterator();
while(iterator.hasNext()) {
SearchHit searchHit = iterator.next();
//打印⽂档对象,以json格式输出
System.out.println(searchHit.getSourceAsString());
//取⽂档的属性
System.out.println("-----------⽂档的属性");
Map<String, Object> document = searchHit.getSource();
System.out.println(document.get("id"));
System.out.println(document.get("title"));
System.out.println(document.get("content"));
}
//关闭client
client.close();
}
使⽤⽂档ID查询⽂档
@Test
public void testSearchById() throws Exception {
//创建⼀个client对象
//创建⼀个查询对象
QueryBuilder queryBuilder = QueryBuilders.idsQuery().addIds("1",
"2");
search(queryBuilder);
}
查询⽂档分页操作
private void search(QueryBuilder queryBuilder) throws Exception {
//执⾏查询
SearchResponse searchResponse = client.prepareSearch("index_hello")
.setTypes("article")
.setQuery(queryBuilder)
//设置分⻚信息
.setFrom(0)
//每⻚显示的⾏数
.setSize(5)
.get();
//取查询结果
SearchHits searchHits = searchResponse.getHits();
//取查询结果的总记录数
System.out.println("查询结果总记录数:" + searchHits.getTotalHits());
//查询结果列表
Iterator<SearchHit> iterator = searchHits.iterator();
while(iterator.hasNext()) {
SearchHit searchHit = iterator.next();
//打印⽂档对象,以json格式输出
System.out.println(searchHit.getSourceAsString());
//取⽂档的属性
System.out.println("-----------⽂档的属性");
Map<String, Object> document = searchHit.getSource();
System.out.println(document.get("id"));
System.out.println(document.get("title"));
System.out.println(document.get("content"));
}
//关闭client
client.close();
}
@Test
public void testQueryMatchAllQuery() throws Exception {
//创建⼀个QueryBuilder对象
QueryBuilder queryBuilder = QueryBuilders.matchAllQuery();
//执⾏查询
search(queryBuilder);
}
查询结果⾼亮操作
在进⾏关键字搜索时,搜索出的内容中的关键字会显⽰不同的颜⾊,称之为⾼亮
百度搜索关键字"华为"
京东商城搜索"笔记本
⾼亮显⽰的html分析
通过开发者⼯具查看⾼亮数据的html代码实现
ElasticSearch可以对查询出的内容中关键字部分进⾏标签和样式的设置,但是你需要告诉ElasticSearch使⽤什么标签对⾼亮关键字进⾏包裹
⾼亮显⽰代码实现
@Test
public void highlightSearchTest() throws Exception {
QueryBuilder queryBuilder = QueryBuilders.multiMatchQuery("北京市⺠",
"title", "content");
HighlightBuilder highlightBuilder = new HighlightBuilder();
highlightBuilder.field("title").field("content");
highlightBuilder.preTags("<em>");
highlightBuilder.postTags("</em>");
//执⾏查询
SearchResponse searchResponse = client.prepareSearch("index_hello")
.setTypes("article")
.setQuery(queryBuilder)
//设置⾼亮信息
.highlighter(highlightBuilder)
.get();
//取查询结果
SearchHits searchHits = searchResponse.getHits();
//取查询结果的总记录数
System.out.println("查询结果总记录数:" + searchHits.getTotalHits());
//查询结果列表
Iterator<SearchHit> iterator = searchHits.iterator();
while(iterator.hasNext()) {
SearchHit searchHit = iterator.next();
//取⽂档的属性
System.out.println("-----------⽂档的属性");
Map<String, Object> document = searchHit.getSource();
System.out.println(document.get("id"));
System.out.println(document.get("title"));
System.out.println(document.get("content"));
System.out.println("************⾼亮结果");
Map<String, HighlightField> highlightFields =
searchHit.getHighlightFields();
// //取title⾼亮显示的结果
if (highlightFields.size() <=0) continue;
for (Map.Entry<String, HighlightField> entry :
highlightFields.entrySet()) {
System.out.println(entry.getKey() + ":\t" +
Arrays.toString(entry.getValue().getFragments()));
}
}
//关闭client
client.close();
}
Spring Data ElasticSearch
Spring Data
Spring Data是⼀个⽤于简化数据库访问,并⽀持云服务的开源框架。其主要⽬标是使得对数据的访问变得⽅便快捷,并⽀持map-reduce框架和云计算数据服务。 Spring Data可以极⼤的简化JPA的写法,可以在⼏乎不⽤写实现的情况下,实现对数据的访问和操作。除了CRUD外,还包括如分页、排序等⼀些常⽤的功能。
Spring Data的官⽹:http://projects.spring.io/spring-data/
Spring Data常⽤的功能模块如下:
Spring Data ElasticSearch
Spring Data ElasticSearch 基于 spring data API 简化 elasticSearch操作,将原始操作elasticSearch的客户端API 进⾏封装 。Spring Data为Elasticsearch项⽬提供集成搜索引擎。Spring Data ElasticsearchPOJO的关键功能区域为中⼼的模型与Elastichsearch交互⽂档和轻松地编写⼀个存储库数据访问层。
官⽅⽹站:http://projects.spring.io/spring-data-elasticsearch/
⼊门案例
导⼊Spring Data ElasticSearch坐标
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.16.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
启动器配置⽂件
package com.example.demo;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
spring:
data:
elasticsearch:
cluster-name: my-elasticsearch
cluster-nodes: 192.168.220.100:9300
编写实体Article
@Document(indexName = "lxs_blog", type = "article")
public class Article {
@Id
@Field(type = FieldType.Long, store = true)
private long id;
@Field(type = FieldType.Text, store = true, analyzer = "ik_smart")
private String title;
@Field(type = FieldType.Text, store = true, analyzer = "ik_smart")
private String content;
。。。
}
编写Dao
⽅法命名规则查询的基本语法findBy + 属性 + 关键词 + 连接符
package com.example.demo.repositories;
import com.example.demo.entity.Article;
import org.springframework.data.domain.Pageable;
import
org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
import java.util.List;
public interface ArticleRepository extends ElasticsearchRepository<Article,
Long> {
}
package com.example.demo;
import com.example.demo.entity.Article;
import com.example.demo.repositories.ArticleRepository;
import org.elasticsearch.index.query.QueryBuilders;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.query.NativeSearchQuery;
import
org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.List;
import java.util.Optional;
@RunWith(SpringRunner.class)
@SpringBootTest
public class DemoApplicationTests {
@Autowired
private ArticleRepository articleRepository;
@Autowired
private ElasticsearchTemplate template;
@Test
public void createIndex() throws Exception {
//创建索引,并配置映射关系
template.createIndex(Article.class);
//配置映射关系
//template.putMapping(Article.class);
}
@Test
public void addDocument() throws Exception {
for (int i = 10; i <= 20; i++) {
//创建⼀个Article对象
Article article = new Article();
article.setId(i);
article.setTitle("⼥护⼠路遇昏迷男⼦跪地抢救:救⼈是职责更是本能" + i);
article.setContent("这是⼀个美丽的⼥护⼠妹妹" + i);
//把⽂档写⼊索引库
articleRepository.save(article);
}
}
@Test
public void deleteDocumentById() throws Exception {
// articleRepository.deleteById(1l);
//全部删除
articleRepository.deleteAll();
}
@Test
public void findAll() throws Exception {
Iterable<Article> articles = articleRepository.findAll();
articles.forEach(a-> System.out.println(a));
}
@Test
public void testFindById() throws Exception {
Optional<Article> optional = articleRepository.findById(10l);
Article article = optional.get();
System.out.println(article);
}
@Test
public void testFindByTitle() throws Exception {
List<Article> list = articleRepository.findByTitle("⼥护⼠");
list.stream().forEach(a-> System.out.println(a));
}
@Test
public void testFindByTitleOrContent() throws Exception {
Pageable pageable = PageRequest.of(1, 5);
articleRepository.findByTitleOrContent("title", "⼥护⼠", pageable)
.forEach(a-> System.out.println(a));
}
@Test
public void testNativeSearchQuery() throws Exception {
//创建⼀个查询对象
NativeSearchQuery query = new NativeSearchQueryBuilder()
.withQuery(QueryBuilders.queryStringQuery("⼥护
⼠").defaultField("title"))
.withPageable(PageRequest.of(0, 15))
.build();
//执⾏查询
List<Article> articleList = template.queryForList(query,
Article.class);
articleList.forEach(a-> System.out.println(a));
}
}
聚合查询
实体类
@Document(indexName = "car_index", type = "car")
public class Car {
@Id
@Field(type = FieldType.Long, store = true)
private Long id;
@Field(type = FieldType.Text, store = true, analyzer = "ik_smart")
private String name;
@Field(type = FieldType.Text, store = true, analyzer = "ik_smart",
fielddata = true)
private String color;
@Field(type = FieldType.Text, store = true, analyzer = "ik_smart",
fielddata = true)
private String brand;
@Field(type = FieldType.Double, store = true)
private Double price;
。。。
}
Dao
package com.lxs.esdemo2.dao;
import com.lxs.esdemo2.domain.Article;
import com.lxs.esdemo2.domain.Car;
import
org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
public interface CarDao extends ElasticsearchRepository<Car, Long> {
}
初始化索引库
@RunWith(SpringRunner.class)
@SpringBootTest
public class CarDaoTest {
@Autowired
private CarDao carDao;
@Test
public void initIndex() {
carDao.save(new Car(1l, "⽐亚迪A1", "红⾊", "⽐亚迪", 50000d));
carDao.save(new Car(2l, "⽐亚迪A2", "⽩⾊", "⽐亚迪", 70000d));
carDao.save(new Car(3l, "⽐亚迪A3", "⽩⾊", "⽐亚迪", 80000d));
carDao.save(new Car(4l, "⽐亚迪A4", "红⾊", "⽐亚迪", 60000d));
carDao.save(new Car(5l, "⽐亚迪A5", "红⾊", "⽐亚迪", 90000d));
carDao.save(new Car(6l, "宝⻢A1", "红⾊", "宝⻢", 10000d));
carDao.save(new Car(7l, "宝⻢A2", "⿊⾊", "宝⻢", 20000d));
carDao.save(new Car(8l, "宝⻢A3", "⿊⾊", "宝⻢", 30000d));
carDao.save(new Car(9l, "宝⻢A4", "红⾊", "宝⻢", 40000d));
carDao.save(new Car(10l, "宝⻢A5", "红⾊", "宝⻢", 50000d));
carDao.save(new Car(11l, "奔驰A1", "红⾊", "奔驰", 10000d));
carDao.save(new Car(12l, "奔驰A2", "⿊⾊", "奔驰", 20000d));
carDao.save(new Car(13l, "奔驰A3", "⿊⾊", "奔驰", 30000d));
carDao.save(new Car(14l, "奔驰A4", "红⾊", "奔驰", 40000d));
carDao.save(new Car(15l, "奔驰A5", "红⾊", "奔驰", 50000d));
}
}
划分桶
GET /car_index/car/_search
{
"query": {
"bool": {
"should": [
{
"match_all": {}
}
]
}
},
"aggs": {
"group_by_bland": {
"terms": {
"field": "color"
}
}
}
}
桶内度量
GET /car_index/car/_search
{
"query": {
"bool": {
"should": [
{
"match_all": {}
}
]
}
},
"aggs": {
"group_by_bland": {
"terms": {
"field": "color"
},
"aggs": {
"avg_price": {
"avg": {
"field": "price"
}
}
}
}
}
}
Spring Data ElasticSearch代码实现
划分桶
@Test
public void testQuerySelfAggs(){
//查询条件的构建器
NativeSearchQueryBuilder queryBuilder = new
NativeSearchQueryBuilder().withQuery(QueryBuilders.matchAllQuery());
//排除所有的字段查询,
queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]
{},null));
//添加聚合条件
queryBuilder.addAggregation(AggregationBuilders.terms("group_by_color").field
("color"));
//执⾏查询,把查询结果直接转为聚合page
AggregatedPage<Car> aggPage = (AggregatedPage<Car>)
carDao.search(queryBuilder.build());
//从所有的聚合中获取对应名称的聚合
StringTerms agg = (StringTerms) aggPage.getAggregation("group_by_color");
//从聚合的结果中获取所有的桶信息
List<StringTerms.Bucket> buckets = agg.getBuckets();
for (StringTerms.Bucket bucket : buckets) {
String brand = bucket.getKeyAsString();
long docCount = bucket.getDocCount();
System.out.println("color = " + brand+" 总数:"+docCount);
}
}
桶内度量
@Test
public void testQuerySelfSubAggs(){
//查询条件的构建器
NativeSearchQueryBuilder queryBuilder = new
NativeSearchQueryBuilder().withQuery(QueryBuilders.matchAllQuery());
//排除所有的字段查询,
queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]
{},null));
//添加聚合条件
queryBuilder.addAggregation(AggregationBuilders.terms("group_by_color").field
("color")
.subAggregation(AggregationBuilders.avg("avg_price").field("price")));
//执⾏查询,把查询结果直接转为聚合page
AggregatedPage<Car> aggPage = (AggregatedPage<Car>)
carDao.search(queryBuilder.build());
//从所有的聚合中获取对应名称的聚合
StringTerms agg = (StringTerms) aggPage.getAggregation("group_by_color");
//从聚合的结果中获取所有的桶信息
List<StringTerms.Bucket> buckets = agg.getBuckets();
for (StringTerms.Bucket bucket : buckets) {
String brand = bucket.getKeyAsString();
long docCount = bucket.getDocCount();
//取得内部聚合
InternalAvg avg = (InternalAvg)
bucket.getAggregations().asMap().get("avg_price");
System.out.println("brand = " + brand+" 总数:"+docCount+" 平均价
格:"+avg.getValue());
}
}