目录
6.5.1 ElasticSearch概述
6.5.1.1 什么是ElasticSearch
6.5.1.2 Lucene
6.5.1.3 Elastic Stack
6.5.1.4 Solr与ES
6.5.1.4.1 背景
6.5.1.4.2 区别
6.5.1.5 正向索引与倒排索引
6.5.1.5.1 正向索引
6.5.1.5.2 倒排索引
6.5.2 Elasticsearch安装
6.5.3 Elasticsearch操作
6.5.3.1 索引、文档、类型
6.5.3.2 CRUD
6.5.3.2.1 增加和更新
6.5.3.2.2 查询
6.5.3.2.3 删除
6.5.3.3 URI查询
6.5.3.4 Request Body查询
6.5.3.5 自动补全功能
6.5.4 SpringBoot整合Elasticsearch
6.5.4.1 简单整合
6.5.4.2 实现推荐搜索功能
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.6 工具-ELK安装》
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;
}
}
调试看一下结构