平时项目开发中,经常会遇到模糊搜索的需求。通常当需要模糊搜索的数据库字段不大,我们可以简单通过 字段名 like '%搜索值%'
实现,搜索效率不高,而且就算加索引也无法生效。对于数据库字段很大的,mysql还提供全文索引,开销也很大。
有没有一种专门做搜索的“数据库”呢?不仅可以实现高效的模糊搜索,而且还能像百度、谷歌这类搜索引擎一样,从我输入的一段文字中,自动识别关键词进行搜索。下面介绍的elasticsearch就是这方面的行家。
1. 简介
全文搜索属于最常见的需求,开源的 Elasticsearch是目前全文搜索引擎的首选。它可以快速地储存、搜索和分析海量数据。维基百科、Stack Overflow、Github 都采用它。Elasticsearch 的底层是开源库 Lucene。但是,你没法直接用 Lucene,必须自己写代码去调用它的接口。Elasticsearch 是 Lucene 的封装,提供了 REST API 的操作接口,开箱即用。
index 索引
elasticsearch 数据管理的顶层单位叫做 Index(索引)。它是单个数据库的同义词。每个 Index (即数据库)的名字必须是小写。下面的命令可以查看当前节点的所有 Index。
curl -X GET 'http://localhost:9200/_cat/indices?v'
document 文档
Index 里面单条的记录称为 Document(文档)。许多条 Document 构成了一个 Index,Document 使用 JSON 格式表示。
同一个 Index 里面的 Document,不要求有相同的结构(scheme),但是最好保持相同,这样有利于提高搜索效率。
type (已移除)
type 是之前存在的概念,elasticsearch 7.x 就不在使用。Document 可以分组,比如weather这个 Index 里面,可以按城市分组(北京和上海),这种分组就叫做 Type,它是虚拟的逻辑分组,用来过滤 Document。
但是不同的分组中,Document 的数据结构应当尽量保持一致,否则会影响搜索效率,type的定位就很鸡肋。因此elasticsearch就直接做强制限制,在6.X 版本中,一个index下只能存在一个type;在 7.X 版本中,直接去除了 type 的概念,就是说 index 不再会有 type。从前的那些写法,可以直接用index下的_doc
代替。
倒排索引
什么是倒排索引: 倒排索引也叫反向索引,通俗来讲正向索引是通过key找value,反向索引则是通过value找key。Elasticsearch 使用一种称为倒排索引的结构,它适用于快速的全文搜索。一个倒排索引由文档中所有不重复词的列表构成,对于其中每个词,有一个包含它的文档列表。倒排索引建立的是分词(Term)和文档(Document)之间的映射关系,在倒排索引中,数据是面向词(Term)而不是面向文档的。
假设我们有两个文档,每个文档的content域包含如下内容:
The quick brown fox jumped over the lazy dog
Quick brown foxes leap over lazy dogs in summer
为了创建倒排索引,我们首先将每个文档的content域拆分成单独的词(我们称它为词条或tokens),创建一个包含所有不重复词条的排序列表,然后列出每个词条出现在哪个文档。
现在,如果我们想搜索quick brown,我们只需要查找包含每个词条的文档。两个文档都匹配,但是第一个文档比第二个匹配度更高。如果我们使用仅计算匹配词条数量的简单相似性算法,那么,我们可以说,对于我们查询的相关性来讲,第一个文档比第二个文档更佳。
分词
Elasticsearch在倒排索引时会文本会使用设置的分析器,而输入的检索语句也会首先通过设置的相同分析器,然后在进行查询。
es内置很多分词器,但是对中文分词并不友好,例如使用standard分词器对一句中文话进行分词,会分成一个字一个字的。这时可以使用第三方的Analyzer插件,比如 ik、pinyin等,本文用的是ik。
2. 安装
本次除了安装elasticsearch
以外,我们还会安装kibana
(可视化展示elasticsearch数据的产品),以及给elasticsearch预装一个ik
中文分词器的插件。因为elasticsearch选用的版本是 7.9.3 ,为了保证兼容,因此kibana和ik插件都使用该对应版本。
先查看下面容器化安装脚本:
# 启动 Elasticsearch
docker run -d \
--name elasticsearch \
--restart=on-failure:3 \
-p 9200:9200 \
-p 9300:9300 \
-e "discovery.type=single-node" \
-v /Volumes/elasticsearch/data/:/usr/share/elasticsearch/data/ \
-v /Volumes/elasticsearch/config/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml \
-v /Volumes/elasticsearch/plugins/:/usr/share/elasticsearch/plugins/ \
elasticsearch:7.9.3
#启动 Kibana
docker run -d \
--name kibana \
--link elasticsearch:es \
-p 5601:5601 \
-e ELASTICSEARCH_URL=es:9200 \
kibana:7.9.3
elasticsearch部分
单节点执行,暴露9200和9300端口,值得关注的是有三个挂载卷:
- /data: elasticsearch的数据库都在该目录,可以直接挂载该目录。
/config/elasticsearch.yml: 主要是安装后要修改
elasticsearch.yml
文件,在文件末尾加上下列两行,以解决跨域问题。为了方便,就直接挂载该文件了。http.cors.enabled: true http.cors.allow-origin: "*"
- /plugins: 这是elasticsearch安装插件的目录,默认是空的,通常将插件解压后放入该目录,重启后即可使用。关于分词器的插件,下文再说。
kibana部分
因为kibana的环境变量中是要配置elasticsearch地址的,但是docker不同容器之间默认网络隔离。可以通过配置同一网络环境的方式解决,这里使用的方式,是通过--link
给elasticsearch设置了一个别名,相当于容器内部维护了一个DNS域名映射。
ik分词器插件
ik中文分词器插件的下载地址,最好找elasticsearch对应版本的插件。
通常来说是启动elasticsearch服务后再安装插件的,重启之后生效。我为了简单,先将插件zip下载到本地,elasticsearch-analysis-ik-7.9.3.zip 解压到 /Volumes/elasticsearch/plugins/analysis-ik/ 目录,再对 /Volumes/elasticsearch/plugins/ 目录做挂载,当elasticsearch 容器启动后,插件就已经生效了。
3. API示例
这里我们通过REST API,从零开始做一个完整的示例。我们通过创建一个articles的索引,使用ik中文分词器,创建完数据后,再做查询。本文不会介绍所有的API,但好在是 RESTFul 风格,很多用法都能自己推敲出来。
创建索引
(PUT) http://localhost:9200/articles
更新索引mapping
(POST) http://localhost:9200/articles/_mapping
{
"properties": {
"title": {
"type": "text",
"analyzer": "ik_max_word",
"search_analyzer": "ik_max_word"
},
"content": {
"type": "text",
"analyzer": "ik_max_word",
"search_analyzer": "ik_max_word"
}
}
}
查看索引mapping
(GET) http://localhost:9200/articles/_mapping
新建文档
(POST) http://localhost:9200/articles/_doc
{
"title": "都江堰",
"content": "一位年迈的老祖宗,没有成为挂在墙上的画像,没有成为写在书里的回忆..."
}
搜索文档
(GET) http://localhost:9200/articles/_search
{
"query": {
"match": {
"content": "画像回忆"
}
}
}
4. spring开发
实际spring结合elasticsearch的开发还是比较复杂的,如果详细的讲这块内容,得单独讲几篇文章。本文的目的主要是简要介绍elasticsearch的应用,因此本章只是做一个很简单的demo,实际开发还得查阅相关文档。
下文使用的是spring-boot-starter-data-elasticsearch,还好目前4.2版本是兼容7.9.x版本elasticsearch的。因为都是属于 spring data jpa 体系的,所以dao层的语法还是容易理解的。另外一些复杂的操作,可以通过注入 ElasticsearchRestTemplate 来实现。
下列代码中的 /articles
接口,和上文中的 http://localhost:9200/articles/_search
一样,都是对文章正文的模糊搜索。
pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
application.yml
spring:
data:
elasticsearch:
client:
reactive:
endpoints: localhost:9200
ArticlesEO.java
@Data
@Document(indexName = "articles")
public class ArticlesEO {
@Id
private String id;
@Field(type = FieldType.Text,analyzer = "ik_max_word")
private String title;
@Field(type = FieldType.Text,analyzer = "ik_max_word")
private String content;
}
ArticlesRepository.java
@Repository
public interface ArticlesRepository extends ElasticsearchRepository<ArticlesEO,String> {
Page<ArticlesEO> findByContent(String content, Pageable pageable);
}
ArticleController.java
@RestController
@RequestMapping
public class ArticleController {
private Pageable pageable = PageRequest.of(0,10);
private final ArticlesRepository articlesRepository;
private final ElasticsearchRestTemplate elasticsearchRestTemplate;
public ArticleController(ArticlesRepository articlesRepository,ElasticsearchRestTemplate elasticsearchRestTemplate) {
this.articlesRepository = articlesRepository;
this.elasticsearchRestTemplate=elasticsearchRestTemplate;
}
/**
* 查询 document
* @param content
* @return
*/
@GetMapping("/articles")
public Page<ArticlesEO> searchArticle(@RequestParam("content")String content){
return articlesRepository.findByContent(content,pageable);
}
}
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。