全文搜索
數據結構
- 結構化:指具有固定格式或有限長度的數據,如數據庫、元數據等。
- 非結構化:指不定長或無固定格式的數據,如郵件、word文檔等。
非結構化數據的檢索
- 順序掃描法(Serial Scanning):如操作系統中文件搜索,適合小數據量
- 全文搜索(Full-text Search):將非結構化數據的一部分轉爲結構的數據,然後創建索引,實現搜索的目的
概念
全文搜索是一種將文件中所有文本與搜索項匹配的文字資料檢索方法
全文搜索實現原理
建文本庫 》 建立索引 》 執行搜索 》 過濾結果
全文搜索實現技術
基於Java的開源實現
- Lucene (引擎)
- ElasticSearch :基於Lucene,
- Solr:和ElasticSearch同類型的一個搜索技術
ElasticSearch與Solr對比
- Solr 利用在分佈式中利用Zookeeper實現分佈式管理,支持非常量多的數據格式,Json,Xml,Csv,提供非常多功能,傳統應用中表現優於ElasticSearch,但在實時搜索中的效率低於ElasticSearch
- ElasticSearch自身就帶了分佈式管理系統,數據格式只支持Json,主要爲了提供RestFull接口,可以使用第三方插件進行功能擴展或增強
ElasticSearch簡介
ElasticSearch是什麼?
- 高度可擴展的開源全文搜索和分析引擎
- 快速地、近實時地對大數據進行存儲、搜索和分析
- 用來支撐有複雜的數據搜索需求的企業級應用
ElasticSearch特點
- 分佈式 (每個索引都使用可配置數量的分片,每個分片又可以有多個副本,在任何一個副本分片上執行讀取和搜索操作)
- 高可用(正是因爲分佈式的特點,促成了高可用)
- 多數據類型
- 多API (Http RestFull、JavaAPI)
- 面向文檔
- 異步寫入(寫入性能好)
- 近實時(搜索性能非常高)
- 基於Lucene搜索引擎
- Apache 開源協議
ElasticSearch核心概念
- 近實時
ES每隔N秒自動做一次索引刷新,可配置 - 集羣
每個集羣有一個唯一個集羣名稱 - 節點
集羣中的單一節點,節點默認使用UUID(可自定義配置節點名稱)標識,在集羣啓動時分配,通過集羣名稱加入指定集羣 - 索引
加快搜索速度,相似文檔的集合,每個索引都有一個名稱,類似於Mysql中的‘庫’,單位集羣中,可以根據需要定義任意需要的索引 - 類型
對索引文檔中進一步的細分,類似全Mysql中的‘表’ - 文檔
是進行索引的基本單位,相當於Mysql中表的‘行’,使用Json格式表示 - 分片
企業應用中,一般存儲的數據比較大,可能會超出單個節點所能處理的範圍,ES允許將索引分成多個分片來存儲這個索引的部分數據,ES會負責這個分片的分配和聚合,對於數據的可靠性,ES還會對這個分片建立副本,這樣ES每個索引就會分配多個分片和多個副本,ES自動管理這些節點中的分片和副本。設置分片主要原因是一方面進行水平的分隔,其次,數據分配在多個節點中可以並行查詢,提高性能和吞吐量 - 副本
第一:提供高可用
第二:提高搜索量和吞吐量
默認情況下,ES每個索引會分配5個分片,每個分片有1個副本。相當於一個集羣10個分片
Elasticsearch 與 Spring Boot集成
配置環境
- Spring Boot 2.2.2.RELEASE
- Elasticsearch 6.1.1
https://www.elastic.co/cn/downloads/past-releases - Spring Data Elasticsearch 2.2.2.RELEASE
- JNA 4.3.0 (Elasticsearch 需要此依賴)
ElasticSearch實戰
pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
<dependency>
<groupId>net.java.dev.jna</groupId>
<artifactId>jna</artifactId>
<version>4.3.0</version>
</dependency>
application.properties
#Elasticsearch服務地址
spring.data.elasticsearch.cluster-nodes=localhost:9300
#設置連接超時時間
spring.data.elasticsearch.properties.transport.tcp.connect_timeout=120s
EsBlog.Java
package com.example.elasticsearchinaction.blog.domain.es;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.Document;
import java.io.Serializable;
/**
* Created by Axin in 2019/12/20 22:11
*/
@Document(indexName = "blog", type="blog")
public class EsBlog implements Serializable {
private static final long serialVersionUID = 1L;
/**
* ID
*/
@Id
private String id;
/**
* 標題
*/
private String title;
/**
* 摘要
*/
private String summary;
/**
* 內容
*/
private String content;
/*
*protected 修飾 ,JPA規範要求;防止直接使用
*/
protected EsBlog() {
}
public EsBlog(String title, String summary, String content) {
this.title = title;
this.summary = summary;
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 getSummary() {
return summary;
}
public void setSummary(String summary) {
this.summary = summary;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
@Override
public String toString() {
return "EsBlog{" +
"id='" + id + '\'' +
", title='" + title + '\'' +
", summary='" + summary + '\'' +
", content='" + content + '\'' +
'}';
}
}
EsBlogRepository.java 接口
package com.example.elasticsearchinaction.blog.repository.es;
import com.example.elasticsearchinaction.blog.domain.es.EsBlog;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;
/**
* Created by Axin in 2019/12/20 22:50
*/
public interface EsBlogRepository extends ElasticsearchRepository<EsBlog,String> {
/**
* 分頁查詢博客(去重)
* @param title
* @param summary
* @param content
* @return
*/
Page<EsBlog> findDistinctEsBlogByTitleContainingOrSummaryContainingOrContentContaining(String title, String summary, String content, Pageable pageable);
}
編寫測試代碼 EsBlogRepositoryTest.java
package com.example.elasticsearchinaction.repository.es;
import com.example.elasticsearchinaction.blog.domain.es.EsBlog;
import com.example.elasticsearchinaction.blog.repository.es.EsBlogRepository;
import org.junit.Assert;
import org.junit.Before;
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 java.util.List;
/**
* Created by Axin in 2019/12/20 22:50
*/
@RunWith(SpringRunner.class)
@SpringBootTest
public class EsBlogRepositoryTest {
@Autowired
private EsBlogRepository esBlogrepository;
@Before
public void initRRepositoryData(){
//清除所有數據
esBlogrepository.deleteAll();
esBlogrepository.save(new EsBlog("關山月","李白的關山月","明月出天山,蒼茫雲海間。\n" +
"長風幾萬裏,吹度玉門關。"));
esBlogrepository.save(new EsBlog("望月懷遠","張九齡的望月懷遠","海上生明月,天涯共此時。\n" +
"情人怨遙夜,竟夕起相思。"));
esBlogrepository.save(new EsBlog("靜夜思","李白《靜夜思》","舉頭望明月,低頭思故鄉"));
}
/**
* 分頁查詢博客(去重)
*
*/
@Test
public void testFindDistinctEsBlogByTitleContainingOrSummaryContainingOrContentContaining(){
Pageable pageable = PageRequest.of(0,20);
String title = "月";
String summary = "月";
String content = "月";
Page<EsBlog> page = this.esBlogrepository.findDistinctEsBlogByTitleContainingOrSummaryContainingOrContentContaining(title, summary, content,pageable);
int numberOfElements = page.getNumberOfElements();
//斷言
Assert.assertEquals(3,numberOfElements);
page.get();
System.out.println("============================");
List<EsBlog> content1 = page.getContent();
for (EsBlog esBlog : content1) {
System.out.println(esBlog);
}
System.out.println("============================");
}
}
啓動本地ElasticSearch服務
運行測試代碼,打印結果
============================
EsBlog{id='GSTyKG8BGy-dUfJgRiB3', title='關山月', summary='李白的關山月', content='明月出天山,蒼茫雲海間。
長風幾萬裏,吹度玉門關。'}
EsBlog{id='GiTyKG8BGy-dUfJgRyAV', title='望月懷遠', summary='張九齡的望月懷遠', content='海上生明月,天涯共此時。
情人怨遙夜,竟夕起相思。'}
EsBlog{id='GyTyKG8BGy-dUfJgRyCz', title='靜夜思', summary='李白《靜夜思》', content='舉頭望明月,低頭思故鄉'}
============================
BlogController.java
package com.example.elasticsearchinaction.blog.controller;
import com.example.elasticsearchinaction.blog.domain.es.EsBlog;
import com.example.elasticsearchinaction.blog.repository.es.EsBlogRepository;
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.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
* Created by Axin in 2019/12/20 23:26
*/
@RestController
@RequestMapping("/blog")
public class BlogController {
@Autowired
private EsBlogRepository repository;
@RequestMapping("/list")
public List<EsBlog> list (@RequestParam(value="title") String title,
@RequestParam(value="summary") String summary,
@RequestParam(value="content") String content,
@RequestParam(value="pageIndex",defaultValue="0") int pageIndex,
@RequestParam(value="pageSize",defaultValue="10") int pageSize){
Pageable pageable = PageRequest.of(pageIndex,pageSize);
Page<EsBlog> page = repository.findDistinctEsBlogByTitleContainingOrSummaryContainingOrContentContaining(title, summary, content,pageable);
return page.getContent();
}
}
啓動項目
運行 ElasticsearchInActionApplication.java
瀏覽器訪問
瀏覽器地址輸入:http://localhost:8080/blog/list?title=月&summary=月&content=月