目录
6.5.4 SpringBoot整合Elasticsearch
6.5.1 ElasticSearch概述
6.5.1.1 什么是ElasticSearch
Elasticsearch([ɪˈlæstɪk sɜːtʃ])是一个基于Apache Lucene(TM)的开源搜索分析引擎。底层基于Lucene(['lusen] )。无论在开源还是专有领域,Lucene可以被认为是迄今为止最先进、性能最好的、功能最全的搜索引擎库。
Elasticsearch 是一个分布式、高扩展、高实时的搜索与数据分析引擎。它能搜索、分析海量数据
Elasticsearch 是一个文档型的,以用于搜索各种文档
Elasticsearch也使用Java开发并使用Lucene作为其核心来实现所有索引和搜索的功能,但是它的目的是通过简单的RESTful API
来隐藏Lucene的复杂性,从而让全文搜索变得简单。
6.5.1.2 Lucene
是一个开放源代码的全文检索引擎工具包
Lucene的目的是为软件开发人员提供一个简单易用的工具包
简单来说Lucene只是一个库。使用它必须使用Java来作为开发语言并将其直接集成到应用中,而且Lucene底层是非常复杂的
6.5.1.3 Elastic Stack
Elastic Stack,又称ELK stack,ELK
E:Elasticsearch类似于数据库,可以增删改查
L:Logstash [lɔɡ][stæ:ʃ]采集、转换数据并将其存储在 Elasticsearch 中
K:Kibana [kɪbana] 可视化操作 类似于Navicat
6.5.1.4 Solr与ES
6.5.1.4.1 背景
Solr诞生于2004年,比ES早。前几年Solr还不错,但是最近几年随着大数据时代的到来,在海量数据面前Solr性能低。而ES适合海量数据,因此目前ES使用率较高。
6.5.1.4.2 区别
相同点
- 都是基于Lucene搜索服务器基础之上开发,一款优秀的、高性能的企业级搜索服务器。【是因为他们都是基于分词技术构建的倒排索引的方式进行查询】
- 开发语言都是Java
不同点
- 当实时建立索引的时候,solr会产生IO阻塞,而es则不会,es查询性能要高于solro
- 在不断动态添加数据的时候,solr的检索效率会变的低下,而es则没有什么变化。
- Solr利用zookeeper进行分布式管理,而es自身带有分布式系统管理功能。Solr一般都要部署到web服务器上,比如tomcat。启动tomcat的时候需要配置tomcat与solr的关联。【Solr 的本质是一个动态web项目】
- Solr支持更多的格式数据[xml,json,csv等],而es仅支持json文件格式。
- Solr是传统搜索应用的有力解决方案,但是es更适用于新兴的实时搜索应用。单纯的对已有数据进行检索的时候,solr效率高于es。
- 6.Solr官网提供的功能更多,而es本身更注重于核心功能,高级功能多由第三方插件。
6.5.1.5 正向索引与倒排索引
先来简单了解分词
顾名思义就是分成一个一个的词语
网页1:新年来到,祝大家新年快乐
新年 年 来到 新年来到 祝 大家 祝大家 新年 年 快乐 新年快乐
网页2: 希望大家新年好
希望 大家 希望大家 新年 年 好 新年好
6.5.1.5.1 正向索引
一般是通过key,去找value
假设我们现在使用正向索引搜索新年这个词
那么就需要扫描索引库中的所有网页(文档)(由于后期应用于B/S架构,文档更多是网页的形式),找出所有包含关键词“新年”的网页(文档)
那么会到从第一个网页(文档)中从头开始去查找是否包含有 新年
这个关键词,如果有就将网页(文档)加入到结果集中;之后遍历其余的网页(文档),流程同理。
网页 | 关键字 |
网页1 | 新年 年 来到 新年来到 祝 大家 祝大家 新年 年 快乐 新年快乐 |
网页2 | 希望 大家 希望大家 新年 年 好 新年好 |
如果有成千上百个网页(文档),每个网页(文档)非常多的分词,那么搜索的效率将会非常低
6.5.1.5.2 倒排索引
倒排索引是按照分词与网页(文档)进行映射,我们来看看如果按照倒排索引的效果
关键字 | 网页 |
新年 | 网页1,网页2 |
年 | 网页1,网页2 |
来到 | 网页1 |
新年来到 | 网页1 |
祝 | 网页1,网页2 |
希望 | 网页2 |
...... | ...... |
采用倒排索引的方式搜索 新年 这个词,那么会直接找到关键词库中查找到 新年
,然后查找到对应的网页(文档)。
正向索引是通过网页(文档)去查找关键词,反向索引则是通过关键词去查找网页(文档)。
倒排索引的优点还包括在处理复杂的多关键字查询时,可在倒排表中先完成查询的并、交等逻辑运算,得到结果后再对记录进行存取,这样把对网页(文档)的查询转换为地址集合的运算,从而提高查找速度
6.5.2 Elasticsearch安装
6.5.3 Elasticsearch操作
6.5.3.1 索引、文档、类型
6.5.3.2 CRUD
6.5.3.2.1 增加和更新
POST 添加数据,没有指定ID, 系统会生成ID
新建索引user,类型为文档型
POST user/_doc
{
"name": "tom",
"age": 20,
"address": "beijing"
}
POST user/_doc
{
"name": "marry",
"age": 21,
"address": "shanghai"
}
查看user里的数据
GET user/_search
添加数据,并指明id(主键)为1111
POST user/_doc/1111
{
"name": "shack",
"age": 23,
"address": "nanjing"
}
如果主键1111存在,如果新增数据时还指定主键为1111,就会更新数据
POST user/_doc/1111
{
"name": "dingk",
"age": 19,
"address": "ningxia"
}
POST既可以是添加也可以是更新
也可以通过_create
添加数据,如果指定id已存在就会报错
POST user/_create/1111
{
"name": "red",
"age": 21,
"address": "zhengzhou"
}
指定ID,创建文档,如果文档存在就覆盖
PUT user/_doc/1111
{
"name": "green",
"age": 24,
"address": "lanzhou"
}
修改原有数据的结构
POST user/_doc/1111
{
"name": "green2",
"age": 26,
"address": "zhejiang"
}
批量插入(可以指定ID,也可以不指定ID)
POST user/_bulk
{"index":{"_id": 23}}
{"name":"black", "age":"31", "address": "nanchang"}
{"index":{}}
{"name":"pink", "age":"17", "address": "hangzhou"}
6.5.3.2.2 查询
GET命令为查询
类似于主键查询,查询主键为1111
GET user/_doc/1111
批量查询
GET _mget
{
"docs": [
{"_index":"user", "_id":"1111"},
{"_index":"user", "_id":"dxU2j4UBK1wP_TQZyv_7"}
]
}
分页查询
GET user/_search
{
"from": 0,
"size": 3
}
6.5.3.2.3 删除
删除指定id的文档
删除id为dhUyj4UBK1wP_TQZqP9L的数据
DELETE user/_doc/dhUyj4UBK1wP_TQZqP9L
6.5.3.3 URI查询
泛查询,就是不指定字段,全字段查找,q表示所有字段。如下查找所有字段中包含有2012的电影
GET movies/_search?q=2012
查询title中包含有2012的所有的电影(df是default field)
GET movies/_search?q=2012&df=title
或者
GET movies/_search?q=title:2012
查询title中包含有2012,取索引从10开始,共8条数据
:表示过滤条件
GET movies/_search?q=title:2012&from=10&size=8
字符串判断查询
# 查询titile中包含有Beautiful, Mind :表示过滤条件
GET movies/_search?q=title:Beautiful Mind
#查询title中包含有Beautiful, 并且年份大于2012
GET movies/_search?q=title:Beautiful AND year:>=2012
#查询titile中包含有Beautiful或者Mind +:或者
GET movies/_search?q=title:(Beautiful Mind)
GET movies/_search?q=title:(+Mind +Beautiful)
#查询title中包含有“Beautiful Mind”的所有的电影
GET movies/_search?q=title:"Beautiful Mind"
#查询title中既包含有Mind又包含有Beautiful的所有的电影,对先后顺序没有要求 AND:并且
GET movies/_search?q=title:(Mind AND Beautiful)
GET movies/_search?q=title:(+Mind AND +Beautiful)
#查询title中包含Mind但是不包含Beautiful的所有的电影 -:不包含
GET movies/_search?q=title:(Mind NOT Beautiful)
GET movies/_search?q=title:(Mind -Beautiful)
年份判断查询
#查询2018年以后上映的电影 :表示过滤条件
GET movies/_search?q=year:>=2018
#查询2012年到2017年上映的电影
GET movies/_search?q=year:(>=2012 AND <2018)
#查询2016年到2017年所有的电影,注意:必须以 ] 结尾 {:不包含 ]:包含
GET movies/_search?q=year:{2015 TO 2017]
正则判断查询
#查询title中以Mi开头,中间包含一个字符,以d结尾的所有的电影 ?表示一个字符
GET movies/_search?q=title:Mi?d
#查询title中以Min开头,后面为任何内容的电影 *表示多个字符
GET movies/_search?q=title:Min*
6.5.3.4 Request Body查询
复杂的查询,那么就需要使用Request Body查询。
以year的倒序排序,查询电影年份在 [2017, 2018]的数据, query只能单条件查询
GET movies/_search
{
"sort": [
{
"year": {
"order": "desc"
}
}
],
# query中只能有一个条件
"query": {
"range": {
"year": {
"gte": 2017,
"lte": 2018
}
}
}
}
gte:大于等于
lte:小于等于
以year的倒序排序,查询titile中包含有Beautiful或者Mind的数据, query只能单条件查询
GET movies/_search
{
"sort": [
{
"year": {
# 排序方式
"order": "desc"
}
}
],
# query中只能有一个条件
"query": {
"match": {
"title": "Beautiful Mind"
}
}
}
按照年份的倒序,分页查询
GET movies/_search
{
"sort": [
{
"year": {
"order": "desc"
}
}
],
"from": 0,
"size": 20
}
短语匹配,查询title中包含有 “Beautiful Mind” 这个短语的的电影
GET movies/_search
{
"query": {
"match_phrase": {
"title": "Beautiful Mind"
}
}
}
只查询部分列
GET movies/_search
{
# 只显示title和year
"_source": ["title", "year"]
}
多个条件查询,多条件查询必须使用bool
GET movies/_search
{
"query": {
"bool": {
# 多条件查询
# must:必要条件 should:或者条件
"must": [
{
"range": {
"year": {
"gte": 2017,
"lte": 2018
}
}
},
# 其他条件
{
"match": {
"title": "Beautiful Mind"
}
}
]
}
}
}
多字段同时匹配某些字符串
GET movies/_search
{
"query": {
"multi_match": {
# 查询条件
"query": "beautiful mind Romance",
# 在哪里查询
"fields": ["title", "genre"],
"type": "best_fields"
}
}
}
其中type的值有三个:
- most_fields:在多字段中匹配的越多排名越靠前
- best_fields: 能完全匹配的文档,排名越靠前。
- cross_fields: 查询越分散,排名越靠前。
query_string
字符串查询
GET movies/_search
{
"query": {
"query_string": {
"default_field": "title",
"query": "Beautiful Mind",
# 查询有Beautiful并且Mind的title
"default_operator": "AND" #不加默认是OR
}
}
}
GET movies/_search
{
"query": {
"query_string": {
"fields": ["title", "genre"],
# 查询有Beautiful或者Mind的title,genre
"query": "Beautiful Mind"
}
}
}
term
实现精准匹配,查询title为Beautiful Mind
的电影
GET movies/_search
{
"query": {
"term": {
"title.keyword": {
"value": "Beautiful Mind, A"
}
}
}
}
多条件或者判断(should表示或者,must表示必须)
GET movies/_search
{
"query": {
"bool": {
"should": [
{
"match": {
"title": "Beautiful Mind"
}
},
{
"range": {
"year": {
"gte": 2017,
"lte": 2018
}
}
}
]
}
}
}
推荐搜索
GET movies/_search
{
# suggest推荐查询,从title-suggest里查询
"suggest": {
# 起了名字叫title-suggest
"title-suggest": {
# 找minx
"text": "minx",
"term": {
"field": "title",
# 查找不到再推荐
"suggest_mode": "missing"
}
}
}
}
GET movies/_search
{
"suggest": {
"title-suggest": {
"text": "mine",
"term": {
"field": "title",
# 高频率才推荐
"suggest_mode": "popular"
}
}
}
}
GET movies/_search
{
"suggest": {
"title-suggest": {
"text": "minx",
"term": {
"field": "title",
# 总是推荐
"suggest_mode": "always"
}
}
}
}
suggest_mode
的三种模式:missing
、popular
、always
missing: 意思是当词典中没有找到对应的索引信息,才去推荐。
popular: 意思是即使我们去搜索一个被索引了的单词,但是还是会去给我们推荐类似的但是出现频率很高的词。
always: 无论在任何情况下,都给出推荐。
6.5.3.5 自动补全功能
Elasticsearch的自动补全功能是基于 suggest
来实现的,但是需要提前定义好需要进行搜索字段的mapping信息(mapping一旦创建好后是不能修改的)
使用 GET movies 命令查看,定义mapping并执行,设置自动补全的属性的 type
必须是 completion
GET movies
执行如下命令,先删除movies,再重新定义mapping
DELETE movies
PUT movies
{"mappings" : {
"properties" : {
"@version" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"genre" : {
"type" : "completion",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"id" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"title" : {
"type" : "completion",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"year" : {
"type" : "long"
}
}
}
}
删除 logstash 的配置文件 db_path.log,然后再执行 logstash 命令,重新导入 movies 数据集
logstash.bat -f D:\elasticsearch\logstash-7.4.2\config\logstash.conf
执行推荐
GET movies/_search
{
"suggest": {
"title-suggest": {
# 查询前缀为min 有的话就返回,没有的话就自动补全,匹配含有min开头的
"prefix": "min",
"completion": {
"field": "title",
"skip_duplicates": true #忽略重复
}
}
}
}
6.5.4 SpringBoot整合Elasticsearch
6.5.4.1 简单整合
导入依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
注意es和spring等都会存在版本问题
由于SpringBoot可以自动选择版本
( 此次使用的是 springboot是2.3.X 版本,默认匹配7.6.X的elasticsearch )
RestClientConfig配置类
@Configuration
public class RestClientConfig extends AbstractElasticsearchConfiguration {
@Bean
public RestHighLevelClient elasticsearchClient() {
方式一 spring官网提供的客户端工具
final ClientConfiguration clientConfiguration = ClientConfiguration.builder()
.connectedTo("localhost:9200")
.build();
return RestClients.create(clientConfiguration).rest();
}
// 获取ElasticsearchRestTemplate模版对象(elasticsearch 6.x 使用的是ElasticsearchTemplate对象)
@Bean
public ElasticsearchRestTemplate elasticsearchRestTemplate() {
return new ElasticsearchRestTemplate(elasticsearchClient());
}
}
}
@Configuration
public class RestClientConfig extends AbstractElasticsearchConfiguration {
@Override
@Bean
public RestHighLevelClient elasticsearchClient() {
// 方式二 elasticsearch官网
RestHighLevelClient client = new RestHighLevelClient(
RestClient.builder(
new HttpHost("192.168.224.128", 9200, "http")));
return client;
}
// 获取ElasticsearchRestTemplate模版对象(elasticsearch 6.x 使用的是ElasticsearchTemplate对象)
@Bean
public ElasticsearchRestTemplate elasticsearchRestTemplate() {
return new ElasticsearchRestTemplate(elasticsearchClient());
}
}
注意:如果es的host
实体类
@Data
@AllArgsConstructor
@NoArgsConstructor
@Document(indexName = "movies")//以前是添加表名,现在是索引名
public class Movie {
private String id;
private String title;
private Integer year;
private List<String> genre;
}
MoviesController
@RestController
@RequestMapping("movies")
public class MoviesController {
@Autowired
private ElasticsearchRestTemplate elasticsearchRestTemplate;
@GetMapping("findAll")
public List<Movie> findAll() {
//构建查询条件
NativeSearchQuery query = new NativeSearchQueryBuilder().withQuery(new RangeQueryBuilder("year").from(1995, true)
.to(1996, true)).build();
//注意查询数据的结构,第一层hits是数据总数,第一层hits里包含的hits里才有数据
//这里获取的第一层的hits
SearchHits<Movie> movies = elasticsearchRestTemplate.search(query, Movie.class, IndexCoordinates.of("movies"));
//之后获取第一层hits里包含的hits
List<SearchHit<Movie>> searchHits = movies.getSearchHits();
//创建集合并保存数据
ArrayList<Movie> list = new ArrayList<Movie>();
for (SearchHit<Movie> searchHit:searchHits
) {
//获取第二层hits里的真正的数据
Movie content = searchHit.getContent();
System.out.println(content);
list.add(content);
}
return list;
}
}
访问
6.5.4.2 实现推荐搜索功能
@RestController
@RequestMapping("suggest")
public class MoviesSuggestSearchContorller {
@Autowired
private ElasticsearchRestTemplate elasticsearchRestTemplate;
// GET movies/_search
// {
// "suggest": {
// "title-suggest": {
// "prefix": "min",
// "completion": {
// "field": "title",
// "skip_duplicates": true
// }
// }
// }
// }
@RequestMapping("findAll")
//前端传过来的搜索字符text 有可能不全,所以需要推荐搜索,推荐相似的字符
public Object movieSuggest(String text) {
//创建推荐搜索的规范
CompletionSuggestionBuilder completionSuggestionBuilder = new CompletionSuggestionBuilder("title")
//注意两种写法
.prefix(text);
completionSuggestionBuilder.size(10); //展示条数
completionSuggestionBuilder.skipDuplicates(true); //跳过重复元素
//创建集合来抓取建议搜索出来自动补全的结果集
Set<String> suggestResult = new HashSet<String>();
//构建推荐条件
SuggestBuilder suggestBuilder = new SuggestBuilder();
//传入推荐名称和推荐规范
suggestBuilder.addSuggestion("suggest", completionSuggestionBuilder);
//开始处理 通过模板工具类
SearchResponse movies = elasticsearchRestTemplate.suggest(suggestBuilder, IndexCoordinates.of("movies"));
//获取Suggest对象
Suggest suggest = movies.getSuggest();
//获取对应的建议搜索的结果
Suggest.Suggestion suggesttion = suggest.getSuggestion("suggest");
//获取结果集
List entries = suggesttion.getEntries();
Object object = entries.get(0);
if (object instanceof CompletionSuggestion.Entry) {
CompletionSuggestion.Entry entry = (CompletionSuggestion.Entry) object;
//获取options
List<CompletionSuggestion.Entry.Option> options = entry.getOptions();
for (CompletionSuggestion.Entry.Option option : options) {
suggestResult.add(option.getText().toString());
}
}
System.out.println(movies);
return suggestResult;
}
}
调试看一下结构