提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
ElasticSearch全文搜索引擎
前言
全文搜索是一种用于在大规模文本集合中查找特定关键词或短语的技术。它是一种常见的信息检索方法,广泛应用于搜索引擎、文档管理系统、电子邮件客户端等各种应用程序中。
全文搜索通过将文本集合的每个文档转换为可搜索的表示形式来实现。这个表示形式通常是以倒排索引的形式存储,其中每个单词或短语都与包含它的文档相关联。当用户输入一个查询词或短语时,全文搜索引擎会根据倒排索引快速找到相关文档,并按照相关性进行排名和返回。
全文搜索技术通常包括以下几个关键过程:
1.文本分词:将文档拆分为单词或短语的过程,常用的方法包括基于空格、标点符号或自然语言处理技术。
2.建立倒排索引:将分词后的单词或短语与对应的文档建立关联,构建出一个倒排索引表。
3.查询处理:当用户输入一个查询词或短语时,全文搜索引擎会将查询进行相同的分词处理,并在倒排索引中快速找到相关文档。
4.相关性排名:根据一定的算法和指标对匹配到的文档进行排序,以便按照相关性返回最有可能的文档。
一、Lucene概述
Lucene是apache下的一个开源的全文检索引擎工具包(一堆jar包)。它为软件开发人员提供一个简单易用的工具包(类库),以方便的在小型目标系统中实现全文检索的功能。
主要功能
Lucene的主要功能包括:
1.文本索引:Lucene可以将文本数据转换为倒排索引的形式,以支持快速的全文搜索。它能够处理各种类型的文本数据,包括文档、网页、电子邮件等。
2.分词和分析:Lucene提供了强大的文本分析功能,可以将文本数据拆分为单词或短语,并进行词干化、停用词过滤、同义词处理等预处理步骤,以提高搜索的准确性和效率。
3.查询解析:Lucene支持使用查询语法来构建查询条件,可以灵活地定义搜索规则,包括布尔逻辑、通配符、范围查询等。
4.相关性排名:Lucene根据一定的算法和指标对匹配到的文档进行相关性排名,以确保搜索结果的质量和准确性。它使用了TF-IDF(词频-逆文档频率)等算法来评估文档的相似度。
Lucene索引原理(倒排索引创建原理)
Lucene的索引原理是基于倒排索引(Inverted Index)的概念。倒排索引是一种数据结构,用于将文档中的单词或短语与包含它们的文档关联起来。倒排索引的主要目的是支持快速的全文搜索。
核心:创建索引、搜索索引
1.索引的创建
Lucene通过使用分词器将文档拆分为词项,并将词项进行大小写转换、按字母顺序排序等预处理操作。就可以得到以下结果:倒排索引文档
每个倒排索引项包含了一个词项及其相关的文档列表。在这个列表中,记录了包含该词项的文档的标识符(如文档ID)或其他元数据。倒排索引表的主要优势是它能够快速确定一个词项在哪些文档中出现,从而实现快速查询和高效的全文搜索。通过倒排索引表,搜索引擎可以根据用户查询的词项快速定位到相关的文档,并根据相似度进行排序和返回结果。
举个例子来说,假设有三个文档:
文档1: “hello java world”
文档2: “hello lucene world”
文档3: “java proxy has a method called newProxyInstance”
对于词项"java",倒排索引表的一个倒排索引项可能是:
词项:“java”
文档列表:[1, 3]
这样,当用户搜索包含"quick"的文档时,搜索引擎可以快速地查找到倒排索引表中的对应倒排索引项,并返回文档1和文档3作为搜索结果。
2.索引的搜索
就是得到用户的查询请求,搜索创建的索引,然后返回结果的过程。
搜索java world两个关键词,符合java的有1,3两个文档,符合world的有1,2两个文档,在搜索引擎中直接这样排列两个词他们之间是OR的关系,出现其中一个都可以被找到,所以这里3个都会出来。全文检索中是有相关性排序的,那么结果在是怎么排列的呢?hello java world中包含两个关键字排在第一,另两个都包含一个关键字,得到结果,hello lucene world排在第二,java在最长的句子中占的权重最低排在结果集的第三。从这里可以看出相关度排序还是有一定规则的。
二、ElasticSearch相关概念
什么是ElasticSearch
ES是一个分布式的全文搜索引擎,为了解决原生Lucene使用的不足,优化Lucene的调用方式,并实现了高可用的分布式集群的搜索方案,ES的索引库管理支持依然是基于Apache Lucene™的开源搜索引擎。
ES的特点
- 分布式的实时文件存储
- 分布式全文搜索引擎,每个字段都被索引并可被搜索
- 能在分布式项目/集群中使用
- 本身支持集群扩展,可以扩展到上百台服务器
- 处理PB级结构化或非结构化数据
- 简单的 RESTful API通信方式
- 支持各种语言的客户端
- 基于Lucene封装,使操作简单
ES和lucene的区别
- Lucene只支持Java,ES支持多种语言
- Lucene非分布式,ES支持分布式
- Lucene非分布式的,索引目录只能在项目本地 , ES的索引库可以跨多个服务分片存储
- Lucene使用非常复杂 , ES屏蔽了Lucene的使用细节,操作更方便
- 单体/小项目使用Lucene ,大项目,分布式项目使用ES
三、ElasticSearch基础
基本概念
1.Near Realtime(NRT)
近实时,两个意思,从写入数据到数据可以被搜索到有一个小延迟(大概1秒);基于es执行搜索和分析可以达到秒级
2.Index:索引库
包含一堆有相似结构的文档数据,比如可以有一个客户索引,商品分类索引,订单索引,索引有一个名称。一个index包含很多document,一个index就代表了一类类似的或者相同的document。比如说建立一个product index,商品索引,里面可能就存放了所有的商品数据,所有的商品document。
3.Type:类型
每个索引里都可以有一个或多个type,type是index中的一个逻辑数据分类,·`一个type下的document,都有相同的field,比如博客系统,有一个索引,可以定义用户数据type,博客数据type,评论数据type。
4.Document&field
文档,es中的最小数据单元,一个document可以是一条客户数据,一条商品分类数据,一条订单数据,通常用JSON数据结构表示,每个index下的type中,都可以去存储多个document。一个document里面有多个field,每个field就是一个数据字段。
操作索引库
1.增加索引库
PUT 索引库名
2.查询索引库
查询所有索引库
GET _cat/indices?v
查看指定索引库
GET _cat/indices/索引库名
3.删除索引库
DELETE 索引库名
4.修改索引库
删除再添加
操作文档
1.添加文档
PUT 索引库/文档类型/文档id
{
JSON格式,文档原始数据
}
2.获取文档
GET 索引库/类型/文档ID
3.修改文档
全局修改
PUT 索引库/类型/文档ID
{
JSON格式,要修改的数据
}
注意:上面的修改会把ES中的数据全部覆盖
局部修改
POST 索引库/类型/文档ID/_update
{
"doc":{
JSON格式,要修改的数据
}
}
4.删除文档
DELETE 索引库/类型/文档ID
文档查询
查询所有
GET _search
查询指定索引库
GET 索引库名/_search
查询指定文档
GET 索引库/类型/文档ID
分页查询
&size=每页条数&from=从多少条数据开始查
字符串查询
GET 索引库/类型/_search?q=要查询的内容
q - 表示查询字符串
四、DSL查询与DSL过滤
DSL查询
1.什么是DSL查询
DSL查询是由ES提供丰富且灵活的查询语言叫做DSL查询(Query DSL),它允许你构建更加复杂、强大的查询
。DSL(Domain Specific Language特定领域语言)以JSON请求体的形式出现。DSL有两部分组成:DSL查询和DSL过滤。
2.DSL查询案例
GET /crm/user/_search
{
"query" : {
"match" : {
"username" : "Hello Java"
}
}
}
match
指的是“标准查询”,该查询方式会对查询的内容进行分词;DSL查询可以支持的查询方式很多,如term
词元查询 ,该查询不会对内容进行分词。
DSL过滤
1.什么是DSL过滤
DSL过滤语句和DSL查询语句非常相似,但是它们的使用目的却不同:DSL过滤文档的方式更像是对于我的条件"有"或者"没有"(等于 ;不等于),而DSL查询语句则像是"有多像"(模糊查询)。
2.查询与过滤的区别
-
过滤结果可以缓存并应用到后续请求。
-
查询语句同时匹配文档,计算相关性,所以更耗时,且不缓存。
-
过滤语句可有效地配合查询语句完成文档过滤。
3.DSL查询+过滤案例
GET /crm/user/_search
{
"query": {
"bool": { //booleanQuery 组合
"must": [{ //与(must) 或(should) 非(must not)
"match": { //match : 匹配,吧查询的内容分词后去查询
"username": "zs"
},
}],
"filter": {
"term": {
"name": "zs ls"
}
}
}
},
"from": 20,
"size": 10,
"_source": ["name", "age", "username"],
"sort": [{
"join_date": "desc"
}, {
"age": "asc"
}]
}
解释:
-
query : 查询,所有的查询条件在query里面
-
bool : 组合搜索bool可以组合多个查询条件为一个查询对象,这里包含了 DSL查询和DSL过滤的条件
-
must : 必须匹配 :与(must) 或(should) 非(must_not)
-
match:分词匹配查询,会对查询条件分词 , multi_match :多字段匹配
-
filter: 过滤条件
-
term:词元查询,不会对查询条件分词
-
from,size :分页
-
_source :查询结果中需要哪些列
-
sort:排序
查询方式
1.全匹配(match_all)
GET _search
{
"query": {
"bool": {
"must": [
{
"match_all": {}
}
],
"filter": {
"term": {
"name": "zs"
}
}
}
}
}
match_all查询通常用于一些特定的场景,如获取索引的所有文档、统计文档总数、导出所有文档等。
2.单词搜索与过滤(Term和Terms)
{
"query": {
"bool": {
"must": {
"match_all": {}
},
"filter": {
"term": {
"username": "Steven King"
}
}
}
}
}
- must子句中包含了一个match_all查询,即匹配所有文档。
- filter子句中包含了一个term查询,用于精确匹配username字段为"Steven King"的文档。
3.范围查询与过滤(range)
{
"query": {
"range": {
"age": {
"gte": 20,
"lt": 30
}
}
}
}
- gte代表大于或等于(greater than or equal to),即age字段的值大于或等于20。
- lt代表小于(less than),即age字段的值小于30。
范围定义:gt:>;gte:>= ;lt:<;lte:<=
4.存在和缺失过滤器(exists和missing)
{
"query": {
"bool": {
"must": [
{
"match_all": {}
}
],
"filter": {
"exists": {
"field": "zs"
}
}
}
}
}
- must子句中包含了一个match_all查询,即匹配所有文档。
- filter子句中包含了一个exists查询,用于匹配具有指定字段存在的文档。在该示例中,要检查字段zs是否存在。
提示:exists和missing只能用于过滤结果。
5.前匹配搜索与过滤(prefix)
{
"query": {
"prefix": {
"fullName": "张"
}
}
}
在实例中,使用prefix查询来匹配fullName字段以"张"开头的文档;执行查询后,Elasticsearch将返回匹配fullName字段以"张"开头的所有文档。
6.通配符搜索(wildcard)
{
"query": {
"wildcard": {
"fullName": "刘*菲"
}
}
}
在实例中,使用wildcard查询来匹配fullName字段符合通配符模式"刘*菲"的文档;执行完查询后,ELasticsearch将返回匹配fullName字段复合通配符匹配的所有文档。
五、文档类型映射
基本概念
ES的文档映射(mapping)机制用于进行字段类型确认,将每个字段匹配为一种确定的数据类型。就如果Mysql创建表时候指定的每个column列的类型。 为了方便字段的检索,我们会指定存储在ES中的字段是否进行分词,但是有些字段类型可以分词,有些字段类型不可以分词,所以对于字段的类型需要我们自己去指定。
默认的字段类型
字符串 | text(分词) ; | keyword(不分词) ; | StringField(不分词文本); | TextFiled(要分词文本) | |
---|---|---|---|---|---|
数字 | long | integer | short | double | float |
日期 | date | ||||
逻辑 | boolean |
添加映射案例
put aigou/goods/_mapping
{
"goods": {
"properties": {
"id": {
"type": "long"
},
"name": {
"type": "text",
"analyzer": "ik_smart",
"search_analyzer": "ik_smart"
}
}
}
}
解释:给aigou索引库中的是goods类型创建映射 ,id指定为long类型 , name指定为text类型(要分词),analyzer分词使用ik,查询分词器也使用ik
六、在SpringBoot使用ES
1.导入依赖
代码如下(示例):
<parent>
<groupId> org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.2.5.RELEASE</version>
</parent>
<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-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
</dependencies>
2.编写启动类和配置yml
代码如下(示例):
// 启动类
@SpringBootApplication
public class AppStart {
public static void main(String[] args) {
SpringApplication.run(AppStart.class,args);
}
}
#yml配置
spring:
elasticsearch:
rest:
uris: http://localhost:9200
3.编写实体类对象
代码如下(示例):
@Data
// 该注解可以表示该对象是一个ES的文档对象 - indexName:索引库的名字、type:索引库的类型
@Document(indexName = "animal",type = "_doc")
public class AnimalDoc {
// 文档的Id
@Id
private Long id;
// 该字段的类型为Text(要分词),使用ik分词器
@Field(type = FieldType.Text,analyzer = "ik_smart",searchAnalyzer = "ik_smart")
private String name;
// 该字段的类型为Keyword(不分词)
@Field(type = FieldType.Keyword)
private String type;
// 该字段的类型为Integer
@Field(type = FieldType.Integer)
private Integer age;
// 该字段的类型为Double
@Field(type = FieldType.Double)
private Double price;
}
4.编写接口继承ElasticsearchRepository实现CRUD
代码如下(示例):
@Service
public interface EsRepository extends ElasticsearchRepository<AnimalDoc,Long> {
}
5.在测试类中编写测试代码进行测试
代码如下(示例):
@RunWith(SpringRunner.class)
@SpringBootTest(classes = AppStart.class)
public class EsTest {
@Autowired
private ElasticsearchRestTemplate restTemplate;
@Autowired
private EsRepository repository;
@Test
public void addIndex(){
// 创建索引库
restTemplate.createIndex(AnimalDoc.class);
// 添加映射规则
restTemplate.putMapping(AnimalDoc.class);
}
// 往索引库中添加数据
@Test
public void addData(){
for (long i = 1;i <= 50;i++){
AnimalDoc animalDoc = new AnimalDoc();
animalDoc.setId(i);
animalDoc.setAge(Integer.valueOf(i+1+""));
animalDoc.setName(i%2==0?"小猪佩奇" + i:"HelloKitty"+i);
animalDoc.setPrice(Double.valueOf(i*2));
animalDoc.setType(i%2==0?"猪":"猫");
repository.save(animalDoc);
}
// 查询id为22的这条数据
Optional<AnimalDoc> optional = repository.findById(22L);
System.out.println("optional.get() = " + optional.get());
// 删除id为14的数据
repository.deleteById(14L);
}
// 修改索引库中的数据
@Test
public void testUpdate(){
Optional<AnimalDoc> optional = repository.findById(22L);
AnimalDoc animalDoc = optional.get();
animalDoc.setName("纯情哈士奇");
repository.save(animalDoc);
optional = repository.findById(22L);
System.out.println("optional.get() = " + optional.get());
}
// 查询索引库中的数据
@Test
public void testSearch(){
// new ,自己有创建自己的方法 ; 用它子类 ; 通过build创建 ; 通过工具类创建
// 使用NativeSearchQueryBuilder类创建一个搜索查询的构建器对象
NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
// 使用withPageable方法设置分页,这里设置为第一页,每页10条记录
queryBuilder.withPageable(PageRequest.of(0,10));
// 使用FieldSortBuilder类创建一个排序构建器对象,以价格(price)字段降序排序
SortBuilder sortBuilder = new FieldSortBuilder("price").order(SortOrder.DESC);
// 通过withSort方法将排序构建器添加到查询构建器中
queryBuilder.withSort(sortBuilder);
// 使用BoolQueryBuilder创建一个布尔查询构建器对象作为查询的主体
BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();
// DSL查询
queryBuilder.withQuery(boolQueryBuilder.must(new MatchQueryBuilder("name","小猪")));
// DSL过滤
boolQueryBuilder.filter(new TermQueryBuilder("type","猪"))
.filter(new RangeQueryBuilder("price").gte(10).lte(100));
// 通过builder构建查询对象
SearchQuery searchQuery = queryBuilder.build();
Page<AnimalDoc> search = repository.search(searchQuery);
// 总条数
long totalElements = search.getTotalElements();
List<AnimalDoc> animalDocs = search.getContent();
System.out.println("totalElements = " + totalElements);
animalDocs.forEach(System.out :: println);
}
}
总结
综上所述,Elasticsearch是一个功能强大的全文搜索引擎,具有高性能、分布式架构、强大的查询和分析功能等特点。它在大数据搜索、日志分析、企业搜索、电子商务等各种场景中得到广泛应用,并通过丰富的生态系统提供了更多扩展和集成的可能性。