Elasticsearch
回顾
如果要实现一个搜索功能,用来匹配用户需要查看的内容,如何实现。我们当时学习数据库的时候,进行过大量的匹配,比如查询所有的所有姓张的人,可以这样查询SELECT * FROM person WHERE NAME LIKE '张%';
这种简单的查询可以直接通过数据库的模糊查询实现,但是如果是查询这种效果呢?
例如:在京东搜索栏中中搜索罗技蓝牙鼠标
会查询到这种情况!!!
不难发现,搜索栏相对于模糊查询更加灵活,可以进行关键词匹配。
通过数据库的模糊查询来实现搜索功能有下面的缺点:
1.搜索内容是一个整体,不能拆分
2.效率较低,没有使用索引
简介
Elasticsearch,简称ES,是一款开源的高扩展的分布式全文搜索引擎,可以帮助我们存储(只负责存储需要查询显示的内容)和检索数据,还可以帮助我们实现日志统计,分析,系统监控等功能。
上述的存储功能,我们只需将商品的展示信息存储到Elasticsearch中,例如上述从京东搜索栏的查询情况来看,基本信息有(商品id,商品图片,商品价格,商品名,商品评论数,所在店铺),将这些数据存储到Elasticsearch中之后便可以通过Elasticsearch来快速检索数据。
环境搭建
安装Elasticsearch
ES下载地址:https://www.elastic.co/cn/downloads/elasticsearch 默认下载的是最新版本
下载之后解压系统环境的文件夹中,之后进入 bin 目录,双击启动 elasticsearch.bat
最后访问:http://127.0.0.1:9200/ 会见到如下画面
到此ES下载成功
安装数据可视化界面elasticsearch head
此前端项目只能看数据不能操作数据
需要提前安装nodejs
然后下载可视化界面,是一个前端项目
github 下载: https://github.com/mobz/elasticsearch-head/
github 加速器: https://github.ur1.fun/
下载完成后解压至指定文件夹中
这里还需要修改 elasticsearch 中 config 中的 elasticsearch.yml 文件中配置
# 开启跨域
http.cors.enabled: true
# 所有人访问
http.cors.allow-origin: "*"
然后进入项目目录,下载前端项目依赖并启动项目
使用 npm install
下载依赖,使用npm run start
启动项目
这里使用npm下载依赖可能无法成功,我们可以使用cnpm命令下载
最后项目运行成功是这样的界面
如果在下载以来过程中出现问题,可以使用淘宝镜像
npm config set registry https://registry.npm.taobao.org/
安装cnpm
npm install -g cnpm --registry=https://registry.npm.taobao.org
安装kibana组件
kibana是一个针对Elasticsearch的开源分析及可视化平台,用来搜索,查看交互存储在Elasticsearch索引中的数据。kibana可以通过各种图表进行高级数据分析和展示,可以使得海量数据刚容易理解
下载地址下载地址: https://www.elastic.co/cn/downloads/kibana 默认打开是最新版本
7.6.1 下载版:https://artifacts.elastic.co/downloads/kibana/kibana-7.6.1-windows-x86_64.zip
汉化kibana:修改 config 目录下的 kibana.yml 文件 i18n.locale: “zh-CN”
双击 bin 目录下的 kibana.bat 启动,最后访问 http://127.0.0.1:5601
最后会看到下图所示:
安装ik分词器
为什么需要 IK 分词器?
在搜索时,需要对用户输入的内容进行分词。默认的分词规则对中文处理并不友好。
在kibana的 DevTools中测试
下面测试英文分词
下面测试标准分词
上述分词器无法对中文进行有效分词,我们可以下载ik分词器,使得搜索结果更好的匹配用户的输入
ik分词器下载地址
https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.6.1/elasticsearchanalysis-ik-7.6.1.zip
解压,在elasticsearch-7.6.1\plugins目录下创建名称为ik的文件夹,将解压后的文件复制到ik目录
添加分词器之后就可以得到更适合中文分词的分析器
最少切分分
最细粒度切分,下图只展示了部分结果,总之分词比较细致
自定义ik分词(可以根据实际情况进行配置)
在 elasticsearch-7.6.1\plugins\ik\config
添加 xxx.dic 文件 定义词组, .dic 文件必须是 utf-8 编码格式,否则启动报错
在 IKAnalyzer.cfg.xml 文件添加自定义分词器文件
最后进行测试
可以明显观察到与上次切分的结果不同,就是我们在my.dic中定义了“我是”词组
ES的基本概念
Elasticsearch是面向文档存储的,可以是数据库中的一条记录。文档数据会被序列化成json格式后存储在elasticsearch中。
例如下图:
索引:同类型文档的集合,可以理解成数据库中的一张表,一张表中存储同种类型的数据
文档:一条数据就是一个文档,ES中是json格式,对应数据库表中的一行记录
字段:对应数据库中的一列
映射:索引中文档的约束,比如字段名称、类型
关系型数据库MySQL和Elasticsearch对比
MySQL擅长事务类型操作,可以保证数据的安全和一致性
Elasticsearch擅长海量数据的搜索,分析和计算。
正向索引和倒排索引
MySQL采用正向索引:基于id创建索引,查找必须先找到文档,而后判断是否包含搜索内容
正向索引的工作原理非常简单,但检索效率非常低下。
Elasticsearch采用倒排索引:以关键字为索引,关键字对应和该关键字相关的所有文档
由于每个关键字对应的文档数量在动态变化,索引倒排索引的建立与维护较为复杂,但是由于可以一次性得到查询关键字所对应的所有文档,所以效率高于正向索引。在全文检索中,检索的快速响应是一个关键的性能,而索引的创建在后台运行,尽管效率相对较低一些,但不会影响搜索的效率
为了更加清楚的理解倒排索引,先看一下这张图
我们可以看到倒排索引会将关键字拆分,将某个关键字与它对应的所有文档绑定,并且根据相关的文档数进行降序排列,所以称为倒排索引
ES索引库的基本操作
mapping是对索引库中文档的约束,常见的mapping属性包括:
type:字段数据类型,常见的简单类型有:
1.字符串:text(可分词的文本),keyword(精确值,例如:品牌,国家,邮箱)
2.数值:long,integer,short,byte,double,float
3.布尔:boolean
4.日期:date
5.对象:object
index:是否创建索引参与搜索,默认为true,如果不参与搜索设置为false
analyzer:使用哪种分词器
创建索引库和mapping的语法如下:
PUT /索引库名
{
"mappings": {
"properties": {
"字段1": {
"type": "数据类型",
"index": false
},
"title": {
"type": "数据类型",
"analyzer": "ik_max_word"
},
"time": {
"type": "数据类型",
"index": false
}
}
}
}
例如:创建一个新闻索引库:
查看索引库
语法: GET /索引库名
实例: GET /news
删除索引库
语法: DELETE /索引库名
实例: DELETE /news
修改索引库
索引库和mapping一旦创建无法修改,但是可以添加新的字段,语法如下:
例如我们向上述新增的新闻索引库添加观看人数字段:
PUT /news/_mapping
{
"properties": {
"count": {
"type": "long"
}
}
}
新增之后查询新闻索引库,可以看到明显多了一个字段
ES文档操作
新增文档
语法:
POST /索引库名/_doc/文档 id
{
“字段名 1”:”值 1”
“字段名 2”:”值 2”
.....
}
查询文档
语法:
GET /索引库名/_doc/文档 id
删除文档
语法:
DELETE /索引库名/_doc/文档 id
修改文档
POST /索引库名/_update/文档 id
{
"doc":{
"要修改的字段":"新值"
}
}
搜索文档
GET /news/_search
{
"query":{
"match":{
"title":"美国"
}
}
}
SpringBoot集成ES
搭建,官网地址:https://www.elastic.co/guide/en/elasticsearch/client/index.html
1.首先导入依赖
指定版本,版本必须与安装的 ES 版本一致
<properties>
<java.version>1.8</java.version>
<elasticsearch.version>7.6.1</elasticsearch.version>
</properties>
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-high-level-client</artifactId>
</dependency>
2.添加初始化 RestHighLevelClient 的配置类
@Configuration
public class ElasticSearchConfig {
@Bean
public RestHighLevelClient restHighLevelClient(){
RestHighLevelClient client = new RestHighLevelClient(
RestClient.builder(
new HttpHost("localhost", 9200, "http")));
return client;
}
}
索引库操作
创建索引库
CreateIndexRequest request = new CreateIndexRequest("users");
CreateIndexResponse createIndexResponse = restHighLevelClient.indices().create(request,
RequestOptions.DEFAULT);
判断索引库是否存在
GetIndexRequest request = new GetIndexRequest("users");
boolean exists = restHighLevelClient.indices().exists(request, RequestOptions.DEFAULT);
删除索引库
DeleteIndexRequest indexRequest = new DeleteIndexRequest("users");
AcknowledgedResponse delete = restHighLevelClient.indices().delete(indexRequest,
RequestOptions.DEFAULT);
delete.isAcknowledged();//返回 true 删除成功,返回 false 删除失败
文档操作
添加文档
//将新闻添加到 mysql 的同时,将数据同步更新到 ES,为搜索提供数据
News news = new News();
news.setId(3);
news.setTitle("美国今年要总统选择,拜登着急了");
news.setImg("aaaaasssss.jpg");
IndexRequest indexRequest = new IndexRequest("news").id(news.getId().toString());
//将对象转为 json 存进 ES
indexRequest.source(new ObjectMapper().writeValueAsString(news),XContentType.JSON);
restHighLevelClient.index(indexRequest,RequestOptions.DEFAULT);
修改文档
News news = new News();
news.setId(3);
news.setTitle("中国航母开往美国,准备开战,拜登着急了");
news.setImg("dddddddddddd.jpg");
UpdateRequest updateRequest = new UpdateRequest("news",news.getId().toString());
updateRequest.doc(new ObjectMapper().writeValueAsString(news), XContentType.JSON);
restHighLevelClient.update(updateRequest,RequestOptions.DEFAULT);
查询文档
GetRequest getRequest = new GetRequest("news","1");
GetResponse getResponse = restHighLevelClient.get(getRequest, RequestOptions.DEFAULT);
//获取查询的内容,返回 json 格式
String json = getResponse.getSourceAsString();
//使用 jackson 组件将 json 字符串解析为对象
News news = new ObjectMapper().readValue(json, News.class);
删除文档
DeleteRequest deleteRequest = new DeleteRequest("news","1");
DeleteResponse delete = restHighLevelClient.delete(deleteRequest, RequestOptions.DEFAULT);
搜索文档
SearchRequest searchRequest = new SearchRequest("news");
SearchRequest searchRequest = new SearchRequest("news");
//精确条件查询
searchRequest.source().query(QueryBuilders.termQuery("title","美国"));
//发送查询请求
SearchResponse search = restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
//接收查询结果
SearchHits hits = search.getHits();
//组装查询结果
ArrayList<News> list = new ArrayList<>();
//取出结果集
for (SearchHit searchHit : hits.getHits()){
String json = searchHit.getSourceAsString();
News news = new ObjectMapper().readValue(json,News.class);
list.add(news);
}
高亮显示
searchRequest.source().highlighter(new
HighlightBuilder().field("title").requireFieldMatch(false));
//组装查询结果
ArrayList<News> list = new ArrayList<>();
for (SearchHit searchHit : hits.getHits()){
String json = searchHit.getSourceAsString();
News news = new ObjectMapper().readValue(json,News.class);
//获取高亮名称
Map<String, HighlightField> highlightFields = searchHit.getHighlightFields();
HighlightField titleField = highlightFields.get("title");
String highttitle = titleField.getFragments()[0].string();
news.setTitle(highttitle);//用添加了高亮的标题替换原始文档标题内容
list.add(news);
}