为什么要学习ElasticSearch
- 用SQL进行模糊查询:like %%,如果是大数据,就十分的慢。
Lucene
Lucene是一套信息检索工具包,jar包,不包含搜索引擎系统,它包含索引结构,读写索引的工具、排序、搜索规则。
Lucene和ElasticSearch的关系
ElasticSearch是基于Lucene做了一些封装和增强
什么是ElasticSearch
ElasticSearch,简称为es,es是一个开源的高扩展的分布式全文检索引擎,它可以实现存储、检索数据,扩展性很好,可以扩展到上百台服务器,处理PB级别的数据。es也使用java开发并使用lucene作为其核心来实现所有索引和搜索的功能,它通过简单的restful api来隐藏lucene的复杂性,从而让全文搜索变得简单。
在2016年,ElasticSearch已超过Solr,成为排名第一的搜索引擎。
历史
多年前,Shay Banon 一个刚结婚不久的失业开发者,由于他老婆要去伦敦学习厨师,他便跟着去了,在他找工作的过程中,为了给他妻子构建一个食谱的搜索引擎,他开始构建一个早期版本的lucene。
直接基于lucene工作会比较困难,所以Shay开始抽象Lucene代码以便java程序员可以在应用中添加搜索功能,他发布了他的第一个开源项目,叫做Compass。
后来Shay找到一份工作,这份工作处在高性能和内存数据网络的分布式环境中,因此高性能的,分布式的搜索引擎也是理所当然的,然后他决定重写Compass库使其成为一个独立的服务叫做ElasticSearch。
第一个公开版出现在2010年,在那之后ElasticSearch已经成为Github上最受欢迎的项目之一,代码贡献者超过300人,一家主营ElasticSearch的公司就此成立,他们一边提供商业支持一边开发新功能,不过ElasticSearch将永远开源且对所有人可用。
ElasticSearch和Solr的区别
ElasticSearch介绍
ElasticSearch是一个分布式搜索和分析引擎,它让你以前所未有的速度处理大数据成为可能。
它用于全文搜索、结构化搜索、分析以及将这三种混合使用。
百科使用ElasticSearch提供全文搜索并高亮关键字,以及输入实时搜索和搜索纠错等搜索建议。
英国卫报使用ElasticSearch结合用户日志和社交网络数据提供给他们的编辑以实时的反馈,以便及时了解对新发表的文章的回应。
StackOverflow结合全文搜索与地理位置查询,以及more-like-this功能来找到相关的问题和答案。
Github使用ElasticSearch检索1300亿行的代码。
ElasticSearch是一个基于Apache Lucene的开源搜索引擎。无论在开源还是专有领域,Lucene可以被认为是迄今为止最先进性能最好的、功能最全的搜索引擎库,es也使用java开发并使用lucene作为其核心来实现所有索引和搜索的功能,它通过简单的restful api来隐藏lucene的复杂性,从而让全文搜索变得简单。
Solr介绍
Solr采用java开发,它是基于Lucene的全文搜索服务器,Solr提供了比Lucene更为丰富的查询语言,同时实现了可配置、可扩展、并对索引、搜索性能进行了优化。
Solr可以独立运行,运行在jetty,tomcat等这些servlet容器中,Solr索引的实现方法很简单,用post方法向solr服务器发送一个描述field及其内容的xml文档,solr根据xml文档添加、删除,更新索引。
ElasticSearch和Solr比较
- 当单纯的对已经有的数据进行搜索时,Solr更快。
- 当实时建立索引时,Solr会产生io阻塞,查询性能比较差,ElasticSearch具有明显的优势。
- 随着数据量的增加,Solr的搜索效率会变得更低,而ElasticSearch却没有明显的变化。
- es基本是开箱即用,非常简单,Solr安装复杂一点。
- Solr利用Zookeeper进行分布式管理,而es自身带有分布式协调管理功能
- Solr支持更多格式的数据,而es本身更注重于核心功能,高级功能多有第三方插件支持,例如图形化界面需要kibana友好支撑。
- Solr查询快,但更新索引时慢,用于电商等查询多的应用。
- es建立索引快,即实时查询快,用于facebook新浪等搜索。
- Solr是传统搜索应用的有力解决方案,但es更适用于新兴的实时搜索应用,
- Solr比较成熟,有一个更大,更成熟的用户,开发和贡献者社区,而es相对开发维护者较少,更新太快,学习使用成本较高。
ElasticSearch安装
最低要求:JDK1.8
下载
window 下安装!
1.解压即可使用
2.熟悉目录
- bin :启动文件
- config:配置文件、log4j2:日志配置文件、jvm.options :java虚拟机相关的配置、elasticsearch.yml :elasticsearch的配置文件! 默认端口9200!跨域
- lib相关jar包
- logs:日志
- modules:功能模块
- plugins:插件
3.启动
- 访问9200
安装可视化界面 es head的插件
下载地址:https://github.com/mobz/elasticsearch-head/
- 下载后解压
-
先安装一下依赖
-
依赖已下好
-
这里有个跨域的问题
-
配置跨域
-
重启ElasticSearch
-
新建索引 ,索引就相当于一个数据库
这个head就把它当做数据展示工具!我们后面所有的查询,都用kibana来做。
什么是ELK
ELK是ElasticSearch,Logstash,Kibana三大开源框架字母大写简称,市面上也被称为Elastic Stack,其中ElasticSearch是一个基于Lucene,分布式,通过Restful方式进行交互的搜索平台框架,类似百度、谷歌这种大数据全文搜索引擎的场景都可以使用Elasticsearch作为底层支持框架,提供的搜索能力确实强大。
Logstash是ELK的中央数据流引擎用于从不同目标收集不同格式的数据,经过过滤后支持输出到不同目的地。
Kibana可以将ElasticSearch的数据通过友好的页面展示出来,提供实时分析的功能。
市面上很多开发只要提到ELK,都说它是一个日志分析架构技术栈总称,但实际上ELK不仅仅适用于日志分析,它还可以支持其它任何数据分析和收集的场景,日志分析和收集只是更具代表性,并非唯一性。
安装kibana
Kibana是一个针对于ElasticSearch的开源分析及可视化平台,用来搜索,查看交互存储在ElasticSearch索引中的数据,使用kibana可以通过各种图表进行高级数据分析及展示,kibana让海量数据更容易理解,它操作简单,基于浏览器的用户界面可以快速创建仪表板,实时显示ElasticSearch查询动态,设置kibana非常简单,无需编码或者额外的基础架构,几分钟内就可以完成kibana安装并启动ElasticSearch索引监测。
下载地址:https://www.elastic.co/cn/downloads/kibana
- 下载后解压
启动测试
- 注意启动kibana的时候,ElasticSearch也要启动
- 访问端口测试
- 之后所有的操作都在这里进行编写
- 汉化
- 重启kibana
ElasticSearch核心概念
概述
在前面的学习中,我们已经掌握了es是什么,同时也把es的服务已经安装启动,那么es是如何去存储数据,数据结构是什么,又是如何实现搜索的呢,我们先来聊聊ElasticSearch的相关概念把
集群、节点、索引、类型、文档、分片、映射是什么?
elasticsearch是面向文档,关系型数据库和elasticsearch客观的对比!
Relational DB | ElasticSearch |
---|---|
数据库(database) | 索引(indices) |
表(tables) | types |
行(rows) | documents |
字段(columns) | fields |
elasticsearch中可以包含多个索引,每个索引中可以包含多个类型,每个类型下又包含多个文档,每个文档中又包含多个字段
IK分词器插件
分词:把一段中文或者别的划分成一个个的关键字,我们在搜索的时候会把自己的信息进行分词,会把数据库中或者索引库中的数据进行分词,然后进行一个匹配操作,默认的中文分词是将每个字看成一个词。
IK提供了两个分词算法:ik_smart,最少切分和ik_max_word,为最细粒度划分。
安装IK分词器
下载地址:https://github.com/medcl/elasticsearch-analysis-ik
- 下载完毕后,放入到elasticsearch即可
- 重启观察ElasticSearch,插件被加载了
- 也可以通过 elasticsearch-plugin list这个命令来查看加载进来的插件
- 使用kibana进行测试
不同分词器的效果
- ik_smart,最少切分
- ik_max_word,为最细粒度划分。
- 发现问题:聂礼兵这个名字被拆开了!
- 这种需要的词,需要自己加到我们的分词器的字典中!
ik分词器增加自己的配置
- 重启es,加载了刚刚咱自己加的那个字典
-现在就可以识别了
Rest风格
一种软件架构风格,而不是标准,只是提供了一组设计原则和约束条件,它主要用于客户端和服务器交互类的软件,基于这个风格设计的软件可以更简洁,更有层次,更易于实现缓存等机制。
- 基本Rest命令说明:
method | url地址 | 描述 |
---|---|---|
PUT | localhost9200/索引名称/类型名称/文档id | 创建文档(指定文档id) |
POST | localhost9200/索引名称/类型名称 | 创建文档(随机文档id) |
POST | localhost9200/索引名称/类型名称/文档id/_update | 修改文档 |
DELETE | localhost9200/索引名称/类型名称/文档id | 删除文档 |
GET | localhost9200/索引名称/类型名称/文档id | 查询文档通过文档id |
POST | localhost9200/索引名称/类型名称/文档id | 查询所以数据 |
基础测试
1.创建一个索引
- 启动 head-master
- 2.访问
PUT /索引名/类型名/文档id
{
请求体
}
- 3.索引创建成功!
- 4.数据浏览,完成了自动增加了索引,在初期可以把它当做数据库学习
那么name这个字段没有指定类型,在我们数据库关系型数据库,是需要指定类型的。
- 字符串类型:text,keyword
- 数值类型:long,integer,short,byte,double,float,halffloat,scaledfloat
- 日期类型:date
- 布尔值类型:boolean
- 二进制类型:binary
5.指定字段的类型
- 6.获得指定类型的规则,可以通过GET请求获取具体的信息
- 7.查看默认的信息
- 如果自己的文档字段没有指定,那么es就会给我们默认配置字段类型!
- 通过get _cat/可以获得es的当前的很多信息
修改 提交还是使用PUT即可! 然后覆盖!
删除索引
通过delete命令实现删除,根据你的请求来判断是删除索引还是删除文档记录
使用restful风格是es推荐使用的
关于文档 基本操作
基本操作
1.添加数据
POST nie/user/1
{
"name":"清石",
"age":3,
"desc": ["明月松间照","清泉石上流"]
}
2.获取数据 GET
3.更新数据 PUT
4.POST _update,推荐使用这种更新方式!
5.简单的搜索
- GET nie/user/1
- 简单的条件查询,可以根据默认 的映射规则,产生基本的查询!
6.复杂操作搜索 select (排序,分页,高亮,模糊查询,精准查询!)
- 输出结果,不想要那么多!
- 之后使用java操作es,所有的方法和对象就是这里面的key!
排序
分页查询
布尔值查询
must (and) ,所有的条件都要符合
- should(or) ,一部分的条件符合
- must_not,不包括查询条件的信息
- 过滤器 filter
- 匹配多个条件
精确查询
term 查询是直接通过倒排序索引指定的词条进程精确的查找的!
关于分词:
- term,直接查询精确的
- match,会使用分词器解析!(先分析文档,然后在通过分析的文档进行查询!)
两个类型:
- text:会被分词器解析
- keyword:不会被分词器解析
高亮查询
- 自定义高亮
集成SpringBoot
1.找到依赖
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
<version>7.10.2</version>
</dependency>
2.找对象
3.分析类中的方法
配置基本的项目
注意: 一定要保证我们的导入的依赖和我们的es版本一致
- pom.xml
<properties>
<java.version>1.8</java.version>
<!--自己定义es版本依赖,保证和本地一致-->
<elasticsearch.version>7.10.2</elasticsearch.version>
</properties>
<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-devtools</artifactId>
<scope>runtime</scope>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<excludes>
<exclude>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
</exclude>
<exclude>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</exclude>
</excludes>
</configuration>
</plugin>
</plugins>
</build>
- ElasticSearchClientConfig
@Configuration
public class ElasticSearchClientConfig {
@Bean
public RestHighLevelClient restHighLevelClient(){
RestHighLevelClient client = new RestHighLevelClient(
RestClient.builder(
new HttpHost("127.0.0.1", 9200, "http")));
return client;
}
}
具体的api测试
1.创建索引
@SpringBootTest
class NieEsApiApplicationTests {
@Autowired
private RestHighLevelClient restHighLevelClient;
//索引的创建
@Test
public void testCreateIndex() throws IOException {
//1.创建索引请求
CreateIndexRequest request=new CreateIndexRequest("niu_index");
//2.客户端执行请求,indicesClient,请求后获得响应
CreateIndexResponse createIndexResponse=
restHighLevelClient.indices().create(request, RequestOptions.DEFAULT);
System.out.println(createIndexResponse);
}
}
//测试获取索引
@Test
public void testExistIndex() throws IOException{
GetIndexRequest request=new GetIndexRequest("niu_index");
//判断索引是否存在
boolean exists=restHighLevelClient.indices().exists(request, RequestOptions.DEFAULT);
System.out.println("是否存在:"+exists);
}
//测试删除索引
@Test
public void testDeleteIndex() throws IOException {
DeleteIndexRequest request=new DeleteIndexRequest("niu_index");
//删除
AcknowledgedResponse delete=restHighLevelClient.indices().delete(request, RequestOptions.DEFAULT);
System.out.println(delete.isAcknowledged());
}
爬虫
数据问题? 数据库获取,消息队列中获取中,都可以成为数据源,爬虫
爬取数据:获取请求返回的页面信息,筛选出我们想要的数据就可以了!
jsoup包!
1.导入依赖
@Component
public class HtmlParseUtil {
public List<Content>parseJD (String keywords) throws Exception {
//获取请求 https://search.jd.com/Search?keyword=java
String url = "https://search.jd.com/Search?keyword="+keywords;
//解析网页
Document document = Jsoup.parse(new URL(url), 30000);
//所有你在js中可以使用的方法,这里都能用!
Element element = document.getElementById("J_goodsList");
//获取所有的li元素
Elements elements = element.getElementsByTag("li");
//获取元素中的内容,这里el就是每一个li标签了
ArrayList<Content> goodsList = new ArrayList<>();
for (Element el : elements) {
String img = el.getElementsByTag("img").eq(0).attr("data-lazy-img");
String price = el.getElementsByClass("p-price").eq(0).text();
String title = el.getElementsByClass("p-name").eq(0).text();
Content content=new Content();
content.setImg(img);
content.setPrice(price);
content.setTitle(title);
goodsList.add(content);
}
return goodsList;
}
}
//业务编写
@Service
public class ContentService {
@Autowired
private RestHighLevelClient restHighLevelClient;
//1.解析数据放入es索引中
public Boolean parseContent(String keywords) throws Exception{
List<Content> contents=new HtmlParseUtil().parseJD(keywords);
//把查询到的数据放入es中
BulkRequest bulkRequest=new BulkRequest();
bulkRequest.timeout("2m");
for(int i=0;i<contents.size();i++){
bulkRequest.add(new IndexRequest("jd_goods").source(JSON.toJSONString(contents.get(i)), XContentType.JSON));
}
BulkResponse bulk=restHighLevelClient.bulk(bulkRequest, RequestOptions.DEFAULT);
return !bulk.hasFailures();
}
//2.获取这些数据实现搜索功能
public List<Map<String,Object>> searchPage(String keyword,int pageNo,int pageSize) throws IOException {
if(pageNo<=0){
pageNo=1;
}
//条件搜索
SearchRequest searchRequest=new SearchRequest("jd_goods");
SearchSourceBuilder sourceBuilder=new SearchSourceBuilder();
//分页
sourceBuilder.from(pageNo);
sourceBuilder.size(pageSize);
//精准匹配
TermQueryBuilder termQueryBuilder= QueryBuilders.termQuery("title",keyword);
sourceBuilder.query(termQueryBuilder);
sourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS));
//执行搜索
searchRequest.source(sourceBuilder);
SearchResponse searchResponse=restHighLevelClient.search(searchRequest,RequestOptions.DEFAULT);
//解析结果
ArrayList<Map<String,Object>> list=new ArrayList<>();
for (SearchHit documentFields:searchResponse.getHits().getHits()){
list.add(documentFields.getSourceAsMap());
}
return list;
}
//2.获取这些数据实现搜索高亮功能
public List<Map<String,Object>> searchPage1(String keyword,int pageNo,int pageSize) throws IOException {
if(pageNo<=0){
pageNo=1;
}
//条件搜索
SearchRequest searchRequest=new SearchRequest("jd_goods");
SearchSourceBuilder sourceBuilder=new SearchSourceBuilder();
//分页
sourceBuilder.from(pageNo);
sourceBuilder.size(pageSize);
//精准匹配
TermQueryBuilder termQueryBuilder= QueryBuilders.termQuery("title",keyword);
sourceBuilder.query(termQueryBuilder);
sourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS));
//高亮
HighlightBuilder highlightBuilder=new HighlightBuilder();
highlightBuilder.field("title");
highlightBuilder.requireFieldMatch(false);
highlightBuilder.preTags("<span style='color:red'>");
highlightBuilder.postTags("</span>");
sourceBuilder.highlighter(highlightBuilder);
//执行搜索
searchRequest.source(sourceBuilder);
SearchResponse searchResponse=restHighLevelClient.search(searchRequest,RequestOptions.DEFAULT);
//解析结果
ArrayList<Map<String,Object>> list=new ArrayList<>();
for (SearchHit hit:searchResponse.getHits().getHits()){
Map<String, HighlightField> highlightFields=hit.getHighlightFields();
HighlightField title=highlightFields.get("title");
Map<String,Object> sourceAsMap=hit.getSourceAsMap();//原来的结果
//解析高亮的字段,将原来的自字段换成我们高亮的字段即可!
if(title!=null){
Text[] fragments=title.fragments();
String n_title="";
for (Text text:fragments){
n_title +=text;
}
sourceAsMap.put("title",n_title);
}
list.add(sourceAsMap);
}
return list;
}
}