ElasticSearch
一、简介与安装
1、简介
ElasticSearch全文搜索引擎,高可用,实时存储数据,检索数据,本身扩展性好,通过RESTful API来隐藏Lucene的复杂性,让全文搜索变得简单
ElasticSearch与Solr性能比较
单纯对已有的数据进行检索,Solr更快
当建立索引时,Solr会产生阻塞,查询性能比较差,ES具有明显的优势
随着数据量增加,Solr的搜索效率会变低,而ES没有明显变化
两者区别总结
Solr利用Zookeeper进行分布式管理,而ES自带分布式协调管理功能
Solr支持更多格式的数据,比如JSON,XML,CSV,而ElasticSearch,仅支持json文件格式
Solr查询快,单更新索引时慢,即插入快删除慢,用于电商等查询多的应用,ES建立索引快,实时查询快,用于facebook新浪等搜索
Solr比较成熟,有更大更成熟的用户,开发者和社区贡献
https://zhuanlan.zhihu.com/p/567196335
2、安装
安装ES,目录中最好不要有空格等,不然后面安装ik分词器插件会有问题
下载地址:https://www.elastic.co/cn/downloads/past-releases#elasticsearch
解压即用,通过bin目录下的elasticsearch.bat启动,简单的连接工具,可以使用谷歌Multi ElasticSearch Heads插件
安装kibana
下载地址:https://www.elastic.co/cn/downloads/past-releases#kibana
解压即用,通过bin目录下的kibana.bat启动,注意需要node环境,可以通过conf目录下的kibana.yml修改默认配置,如将页面改成汉化版本
i18n.locale: "zh-CN"
二、基本操作
1. 基本概念
elasticsearch是面向文档的,类似数据库,两者概念类比,值得注意的是,es是以json格式存储数据的。
物理设计:
elasticsearch把索引划分成多个分片,以及分片对应的副本,这和kafka中主题,分区和副本概念类似,包括分区及其副本在集群中节点中如何存储的都很类似。
文档:
一个文档包含字段和值,形式类似map,当然字段也有它自身的类型
2. 倒排索引
elasticsearch使用倒排索引的结构,底层使用Lucene的倒排索引,这种结构适用于全文快速索引。首先理解一些什么是正排索引,这种结构适用于需要顺序访问文档内容的场景。考虑一个简化的博客集合,其中包含三篇博客:
博客1: 《LangChain学习笔记——Model I/O》
博客2: 《Docker存储驱动初探》
博客3: 《几种常见的消息队列介绍》
在正排索引中,每篇文章都按照其在文档集合中的顺序存储,每篇博客包含了其所包含的所有词汇。以下是一个简化的正排索引示例:
这个正排索引示例中,我们可以通过博客ID快速找到每篇文章的完整内容。例如,如果我们想查看文档2的内容,只需根据文档ID为2检索正排索引即可得到“正排索引的解析”。
但是如果我们需要进行搜索,比如搜索与“消息队列”相关的内容,就可能需要做全表的扫描,性能开销急剧提升。这就需要引入倒排索引来有效地处理用户的检索需求。
倒排索引(Inverted Index)是一种数据结构,用于在大规模文档集合中快速定位包含特定关键词的文档。相对于正排索引,倒排索引以关键词为中心,将每个关键词映射到包含该关键词的文档列表。这种颠倒的结构使得搜索引擎能够高效地响应用户的查询,快速返回相关的文档。
同样以上面的博客集合作为示例,
博客1:《LangChain学习笔记——Model I/O》
内容包含关键词:LangChain、学习笔记、Model I/O。
博客2:《Docker存储驱动初探》
内容包含关键词:Docker、存储驱动、初探。
博客3:《几种常见的消息队列介绍》
内容包含关键词:消息队列、介绍、常见。
倒排索引示例:
倒排索引示例:
通过这个倒排索引示例,我们可以看到每个关键词都与包含该关键词的博客的文档ID关联。例如,如果用户查询关键词“消息队列”,搜索引擎可以迅速找到文档ID列表为3的博客,即《几种常见的消息队列介绍》。这种方式使得搜索引擎能够快速过滤掉与查询无关的文档,提高检索效率。
倒排索引构建过程:
构建倒排索引是一个复杂而关键的过程,它涉及多个步骤,可以归纳为两个阶段:
文档预处理阶段
分词(Tokenization): 将文档拆分成单词或词汇单元。这个过程使用分词器,将文本切分成有意义的词语,形成一个词汇列表。
去停用词(Stopword Removal): 移除常见且在搜索中没有实际意义的词语,如“的”、“是”等。这有助于提高倒排索引的效率和准确性。
词干提取(Stemming): 将词语还原为其词干形式,去除词尾,以便将相关的词汇映射到同一词根,减少索引的大小。
倒排生成阶段
建立词汇表: 将预处理后的文档中的所有唯一词语构建成一个词汇表。每个词汇都有一个唯一的标识符。
映射关键词到文档ID: 遍历每个文档,对于文档中的每个关键词,将其映射到文档的唯一标识符(文档ID)。这样的映射关系通常以字典的形式保存。
生成倒排列表: 对于每个关键词,创建一个倒排列表,其中包含映射到该关键词的所有文档ID。倒排列表实际上是一个映射,将关键词与包含该关键词的文档关联起来。
3. ik 分词器插件
下载地址:https://github.com/infinilabs/analysis-ik/releases
解压,然后放到ES的plugin目录下,然后使用elasticsearch-plugin list
命令查看是否加载成功,注意,读取的是文件一级目录下的plugin-descriptor.properties
,失败看是否有多级目录。
分词:简单理解就是智能的将一段话,分成几个词语,便于建立倒排索引
包含两个算法
ik_smart为最少切分
ik_max_word为最细颗粒度切分
# 使用分析器分析字段
GET _analyze
{
"analyzer": "ik_max_word",
"text": "中国共产党"
}
4. 基本操作
补充:
postman方式参考连接https://blog.51cto.com/u_16213720/11004319,kibana控制台命令类似,以下以kibana操作为例
注意字段值都有其对应的字段类型,参考链接https://blog.csdn.net/qq_26664043/article/details/136101716
需要注意的是,text类型会被分析器处理单独的词项(term)适合全文搜索,keyword不会被分析器处理,适合精确搜索,如身份证,姓名等
当索引已创建时,修改索引字段的mapping映射,方式参考:https://blog.csdn.net/jdcdev_/article/details/141063672
基础命令
新增数据一般使用PUT方式,修改数据使用POST + /_update的方式,因为PUT是覆盖的方式更新,意味着如果只更新一个字段也必须附加上其他字段,不然其他字段没有了,而POST方式则不需要。
精确查询
# 创建索引
PUT /test
{
"mappings": {
"properties": {
"name": {
"type": "keyword"
},
"nicheng": {
"type": "text"
},
"age": {
"type": "integer",
"null_value": -1
}
}
}
}
# 获取索引信息
GET /test
# 删除索引
DELETE test
# PUT方式新增
PUT /test/_doc/1
{
"name": "张三",
"nicheng": "法外狂徒",
"age": 99
}
# POST方式新增
PUT /test/_doc/2
{
"name": "李四",
"age": 100
}
# POST方式新增,随机文档ID
POST /test/_doc
{
"name": "王五",
"nicheng": "钻石王老五",
"age": 100
}
# POST方式查询
POST /test/_doc/_search
# POST更新文档
POST /test/_doc/2/_update
{
"doc":{
"name": "褒姒",
"nicheng": "烽火戏诸侯"
}
}
# 根据文档ID查询
GET test/_doc/1
# 精准匹配查询
GET test/_doc/_search?q=name:张三
5. 复杂查询
高级搜索,参考链接https://blog.51cto.com/u_93011/11724660
查询结果中的文档都会包含一个分数值,这个值越大,代表越符合查询条件,有很多因素会影响评分,如bool查询中的
must:相当于JAVA中的 && 操作符,必须匹配,支持评分。
should:相当于 JAVA 中的 || 操作符,选择性匹配,支持评分。
must_not:相当于JAVA中的 !,必须不匹配,不支持评分。
filter:简单的过滤,不支持评分。
# 查询,多条件复合查询,指定查询字段,排序,分页查询,分组中的数据查询,精确查询,多个值精确查询,高亮
# 查询
GET /test/_doc/_search
{
"query": {
"match": {
"name": "王五"
}
}
}
# 多条件查询
GET /test/_doc/_search
{
"query": {
"bool": {
"must": [{
"match": {
"name": "王五"
}
}],
"filter":[{
"term": {
"age": 100
}
}]
}
}
}
# 范围查询
GET /test/_doc/_search
{
"query": {
"range": {
"age": {
"gte": 0,
"lte": 200
}
}
}
}
# 指定查询字段,排序,分页查询
GET /test/_doc/_search
{
"query": {
"range": {
"age": {
"gte": 0,
"lte": 200
}
}
},
"_source": ["name", "age"],
"sort": [{
"age":{
"order": "desc"
}
}],
"from":0,
"size":1
}
# POST更新文档
POST /test/_doc/1/_update
{"doc":{"tags": ["技术宅", "红", "男"]}}
POST /test/_doc/2/_update
{"doc":{"tags": ["皇后", "白", "女"]}}
POST /test/_doc/tCQ5f5EB4R2zXQlvUJoz/_update
{"doc":{"tags": ["技术宅", "黑", "男"]}}
# 匹配分组中的数据
GET /test/_doc/_search
{
"query": {
"match": {
"tags": "男 红"
}
}
}
# 多个值精确查询
GET /test/_doc/_search
{
"query": {
"bool":{
"should":[
{"term": {"age": 99}},
{"term": {"age": 100}}
]
}
}
}
# POST更新文档
POST /test/_doc/1/_update
{"doc":{"nicheng": "法外狂徒测试"}}
POST /test/_doc/2/_update
{"doc":{"nicheng": "烽火戏诸侯测试"}}
# 高亮查询,使用highlight,可以给条件中的文字加样式
GET /test/_doc/_search
{
"query": {
"match": {
"nicheng" : "测试"
}
},
"highlight": {
"pre_tags": "<p class='key' style='color:red'>",
"post_tags": "</p>",
"fields": {
"nicheng":{}
}
}
}
三、CRUD实践
1. 集成SpringBoot
导入依赖
<!-- ES -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
添加配置
spring:
elasticsearch:
rest:
uris: http://127.0.0.1:9200
简单查询实例
@Test
public void testQueryDocument() {
QueryBuilder queryBuilder = QueryBuilders.matchQuery("name", "名字");
String indexName = "spring_boot_es";
Integer pageNum = 1;
Integer pageSize = 2;
//1、条件搜索 参数 索引
SearchRequest searchRequest = new SearchRequest(indexName);
//2、构建搜索条件
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
searchSourceBuilder.from((pageNum - 1) * pageSize);
searchSourceBuilder.size(pageSize);
//3、注入执行查询条件
searchSourceBuilder.query(queryBuilder);
//4、设置查询超时时间
searchSourceBuilder.timeout(new TimeValue(60, TimeUnit.SECONDS));
//5、执行查询 返回结果
searchRequest.source(searchSourceBuilder);
SearchResponse response = null;
try {
response = client.search(searchRequest, RequestOptions.DEFAULT);
} catch (IOException e) {
e.printStackTrace();
}
List<Map<String, Object>> list = Arrays.stream(response.getHits().getHits()).map(o -> o.getSourceAsMap()).collect(Collectors.toList());
list.forEach(s -> {
if(!Objects.isNull(s)) {
log.info(s.get("name").toString());
}
});
}
2. 复杂查询
参考文档:https://www.elastic.co/guide/en/elasticsearch/client/java-rest/current/java-rest-high.html
3. 聚合查询
类似于SQL里的group,max,avg等聚合查询,对查询结果进行统计分析
参考链接:https://blog.csdn.net/chuige2013/article/details/129635792