本博客从全文搜索、ES简介、ES核心概念、ES与SpringBoot集成以及ES实战共五个方面进行详细介绍和应用。
1、全文搜索介绍
全文搜索搜索的对象主要有两种:
1、结构化数据:具有固定格式或固定长度的数据,例如,数据库,元数据
2、非结构化数据:无固定格式或者无固定长度的数据,例如:Word,图片等
非结构化数据的检索主要方法:
1、顺序扫描法(Serial Scanning):从头到尾一个字一个字的匹配数据,例如:Window操作系统中的文件都是采用这种方式
这种扫描法适合使用小文件的数据
2、全文搜索(Full-text Search):就是把非结构化的数据重新抽取出来,部分变成结构化数据,来创建索引,实现全文检索
索引:是全文搜索的关键,如何抽取非结构化数据中的关键字,抽取哪些关键字,是创建索引的关键。
全文搜索就是一种将文件中所有文本与搜索项匹配的文字资料检索方法。
全文搜索实现原理,一共分四个步骤:
1、首先建立文本库(搜索源) 2、第二步是建立索引(就是规则) 3、执行搜索(一般情况下由用户发起,由系统解析请求) 4、过滤结果(比如:根据规划展示不同的内容,或者采用分页的形式过滤,或者过滤掉敏感内容)
基于JAVA的开源实现有哪些:
1、Lucene 2、ElasticSearch:是基于Lucene而设计,建立的 3、Solr
ES与Solr进行对比:
(1)二者安装都很简单。
(2)Solr 利用 Zookeeper 进行分布式管理,而 Elasticsearch 自身带有分布式协调管理功能。
(3)Solr 支持更多格式的数据,比如JSON、XML、CSV,而 Elasticsearch 仅支持json文件格式。
(4)Solr 官方提供的功能更多,而 Elasticsearch 本身更注重于核心功能,高级功能多有第三方插件提供
(5)Solr 在传统的搜索应用中表现好于 Elasticsearch,但在处理实时搜索应用时效率明显低于 Elasticsearch。
(6)Solr 是传统搜索应用的有力解决方案,但 Elasticsearch 更适用于新兴的实时搜索应用。
2、ElasticSearch简介
1、高度可扩展的开源全文搜索和分析引擎
2、快速地、近实时地对大数据进行存储、探索和分析
3、用来支持有复杂的数据搜索需求的企业级应用
ES特点:
1、分布式:ES中的索引是由N个分片组成,分片分布到不同的服务器中 2、高可用:由于分布式造就了高可用 3、多类型 4、多API 5、面向文档 6、异步写入 7、近实时 8、基于Lucene 9、Apache协议:开源协调
3、ElasticSearch核心概念
近实时:就是搜索文档到产生结果,由一个小的延迟,大概1秒左右(Lucene是实时全文搜索),而ES是需要刷新索引,占用大概1秒时间
集群:是一个或多个节点的集合,是用来保存应用的全部数据,并提供基于节点的索引和搜索功能,每一个集群要有一个唯一的名称,节点加入集群是根据集群名称来的。
节点:就是单台的服务器,是用唯一标识符来标识。
索引:相似文档的一个集合,是用来快速搜索数据。
类型:就是索引中包含一些文档的一个细分,比如:产品相关的文档,有不同类型的文档,这些就是产品的类型
文档:就是最小的单位,对于非结构化数据来说,文档就是一个文件,对于结构化数据来说,就是指数据表中的一行数据,一般用Json来表示
分片 :就是存储索引的部分数据,就是说每个索引都可以有多个分片,同时也有多个副本。
副本:分片可以设置不同的副本,设置副本的目的是提高系统的高可用性;增加副本能增加吞吐量,把负载能分配到不同的副本中。
4、ElasticSearch与SpringBoot集成
需要的配置环境 :
1、ElasticSearch 2.4.4
2、Spring Data ElasticSearch 2.1.3 Release
3、JNA 4.3.0
添加依赖:
两个依赖:spring-boot-starter-data-elasticsearch 和 jna
5、ES实战
一、在application.properties中增加ES配置类
修改application.properties
有两项配置:1、配置ES服务器的地址 2、配置传输超时时间
二、后台编码
后台编码需要做工作有如下(以搜索博客内容为例):
第一:先定义一个文档类:EsBlog
第二:先定义一资源库:EsBlogRepository 这个资源库其实就是Dao层
第三:测试测试库接口,编写测试类
第四:编写控制器:BlogContorller
创建文档类EsBlog代码如下:
package com.waylau.spring.boot.elasticsearch.domain;
import java.io.Serializable;
import javax.xml.bind.annotation.XmlRootElement;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.Document;
@Document(indexName = "blog", type = "blog", shards = 1, replicas = 0, refreshInterval = "-1")
@XmlRootElement // MediaType 转为 XML
public class Blog implements Serializable {
private static final long serialVersionUID = 1L;
@Id // 主键
private String id; // 用户的唯一标识
private String title;
private String content;
protected Blog() { // JPA 的规范要求无参构造函数;设为 protected 防止直接使用
}
public Blog(String name, String content) {
this.title = name;
this.content = content;
}
public Blog(String id, String name, String content) {
this.id = id;
this.title = name;
this.content = content;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
@Override
public String toString() {
return String.format(
"User[id=%d, title='%s', content='%s']",
id, title, content);
}
}
资源类:BlogRepository 编码如下:
package com.waylau.spring.boot.elasticsearch.repository;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
import com.waylau.spring.boot.elasticsearch.domain.Blog;
public interface BlogRepository extends ElasticsearchRepository<Blog, String> {
/**
* 根据用户名分页查询用户列表
* @param name
* @param pageable
* @return
*/
Page<Blog> findByTitleLikeOrContentLike(String title, String content, Pageable pageable);
}
编写测试类:
package com.waylau.spring.boot.elasticsearch.repository;
import static org.assertj.core.api.Assertions.assertThat;
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.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.test.context.junit4.SpringRunner;
import com.waylau.spring.boot.elasticsearch.domain.Blog;
/**
* BlogRepository 测试类
*/
@RunWith(SpringRunner.class)
@SpringBootTest
public class BlogRepositoryTest {
@Autowired
private BlogRepository blogRepository;
@Test
public void testFindByTitleLikeOrContentLike() {
// 清空所有
blogRepository.deleteAll();
blogRepository.save(new Blog("1","老卫跟你谈谈安装 Elasticsearch",
"关于如何来安装 Elasticsearch,这个请看我的博客 https://waylau.com"));
blogRepository.save(new Blog("2","老卫跟你谈谈 Elasticsearch 的几个用法",
"关于如何来用 Elasticsearch,还是得看我的博客 https://waylau.com,妹")); // 关键字"妹"
blogRepository.save(new Blog("3","老卫和你一起学 Elasticsearch",
"如何来学习 Elasticsearch,最终看我的博客 https://waylau.com,酒")); // 关键字"酒"
blogRepository.save(new Blog("4","03-05 用大白话聊聊分布式系统",
"一提起“分布式系统”,大家的第一感觉就是好高大上啊,深不可测"));
blogRepository.save(new Blog("5","02-19 Thymeleaf 3 引入了新的解析系统",
"如果你的代码使用了 HTML5 的标准,而Thymeleaf 版本来停留在 2.x ,那么如果没有把闭合"));
blogRepository.save(new Blog("6","02-19 使用 GFM Eclipse 插件时,不在项目里面生成 HTML 文件",
"GFM 是 GitHub Flavored Markdown Viewer 的简称,是一款对 GitHub 友好的 Markdown 编辑器 。"));
Pageable pageable = new PageRequest(0, 20);
Page<Blog> page = blogRepository.findByTitleLikeOrContentLike("妹", "酒", pageable);
assertThat(page.getTotalElements()).isEqualTo(2);
}
}
编写controller,代码如下:
package com.waylau.spring.boot.elasticsearch.controller;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import com.waylau.spring.boot.elasticsearch.domain.Blog;
import com.waylau.spring.boot.elasticsearch.repository.BlogRepository;
/**
* Blog 控制器
*/
@RestController
@RequestMapping("/blogs")
public class BlogController {
@Autowired
private BlogRepository blogRepository;
@GetMapping
public List<Blog> list(@RequestParam(value="title",required=false,defaultValue="") String title,
@RequestParam(value="content",required=false,defaultValue="") String content,
@RequestParam(value="pageIndex",required=false,defaultValue="0") int pageIndex,
@RequestParam(value="pageSize",required=false,defaultValue="10") int pageSize) {
// 数据在 Test 里面先初始化了,这里只管取数据
Pageable pageable = new PageRequest(pageIndex, pageSize);
Page<Blog> page = blogRepository.findByTitleLikeOrContentLike(title, content, pageable);
return page.getContent();
}
}
到此ES实战结束,启动ES,就可以直接进行测试了。