写在最前
- 黑马视频地址:https://www.bilibili.com/video/BV1LQ4y127n4/
- 想获得最佳的阅读体验,请移步至我的个人博客
- SpringCloud学习笔记
- 消息队列MQ学习笔记
- Docker学习笔记
- 分布式搜索ElasticSearch学习笔记
初识ElasticSearch
了解ES
ElasticSearch的作用
ElasticSearch
是一款非常强大的开源搜素引擎,具备非常强大的功能,可以帮助我们从海量数据中快速找到需要的内容- 例如在电商平台搜索商品,搜索
4090显卡
会以红色标识 - 在搜索引擎搜索答案,搜索到的内容同样会以红色标识,也可以实现搜索时的自动补全功能
ELK技术栈
ElasticSearch
结合kibana
、Logstash
、Beats
,也就是elastic stack
(ELK)。被广泛应用在日志数据分析、实时监控等领域- 而
ElasticSearch
是elastic stack
的核心,负责存储、搜索、分析数据
ElasticSearch和Lucene
-
ElasticSearch底层是基于Lucene来实现的
-
Lucene是一个Java语言的搜索引擎类库,是Apache公司的顶级项目,由DougCutting于1999年研发,官网地址:https://lucene.apache.org/
-
Lucene的优势
- 易扩展
- 高性能(基于倒排索引)
-
Lucene的缺点
- 只限于Java语言开发
- 学习曲线陡峭
- 不支持水平扩展
-
ElasticSearch的发展史
- 2004年,Shay Banon基于Lucene开发了Compass
- 2010年,Shay Banon重写了Compass,取名为ElasticSearch,官网地址:https://www.elastic.co/cnl/
-
相比于Lucene,ElasticSearch具备以下优势
- 支持分布式,可水平扩展
- 提供Restful接口,可以被任意语言调用
总结
- 什么是ElasticSearch?
- 一个开源的分布式搜索引擎,可以用来实现搜索、日志统计、分析、系统监控等功能
- 什么是Elastic Stack(ELK)?
- 它是以ElasticSearch为核心的技术栈,包括beats、Logstash、kibana、elasticsearch
- 什么是Lucene?
- 是Apache的开源搜索引擎类库,提供了搜索引擎的核心API
倒排索引
- 倒排索引的概念是基于MySQL这样的正向索引而言的
正向索引
- 为了搞明白什么是倒排索引,我们先来看看什么是正向索引,例如给下表中的id创建索引
id | title | price |
---|---|---|
1 | 小米手机 | 3499 |
2 | 华为手机 | 4999 |
3 | 华为小米充电器 | 49 |
4 | 小米手环 | 49 |
- 如果是基于id查询,那么直接走索引,查询速度非常快。
- 但是实际应用里,用户并不知道每一个商品的id,他们只知道title(商品名称),所以对于用户的查询方式,是基于title(商品名称)做模糊查询,只能是逐行扫描数据
select id, title, price from tb_goods where title like %手机%
- 1
- 具体流程如下
- 用户搜索数据,搜索框输入手机,那么条件就是title符合
%手机%
- 逐行获取数据
- 判断数据中的title是否符合用户搜索条件
- 如果符合,则放入结果集,不符合则丢弃
- 用户搜索数据,搜索框输入手机,那么条件就是title符合
- 逐行扫描,也就是全表扫描,随着数据量的增加,其查询效率也会越来越低。当数据量达到百万时,这将是一场灾难
倒排索引
- 倒排索引中有两个非常重要的概念
- 文档(Document):用来搜索的数据,其中的每一条数据就是一个文档。例如一个网页、一个商品信息
- 词条(Term):对文档数据或用户搜索数据,利用某种算法分词,得到的具备含义的词语就是词条。例如:我最喜欢的FPS游戏是Apex,就可以分为我、我最喜欢、FPS游戏、最喜欢的FPS、Apex这样的几个词条
- 创建倒排索引是对正向索引的一种特殊处理,流程如下
- 将每一个文档的数据利用算法分词,得到一个个词条
- 创建表,每行数据包括词条、词条所在文档id、位置等信息
- 因为词条唯一性,可以给词条创建索引,例如hash表结构索引
词条(term) | 文档id |
---|---|
小米 | 1,3,4 |
手机 | 1,2 |
华为 | 2,3 |
充电器 | 3 |
手环 | 4 |
- 以搜索
华为手机
为例- 用户输入条件
华为手机
,进行搜索。 - 对用户输入的内容分词,得到词条:华为、手机。
- 拿着词条在倒排索引中查找,可以得到包含词条的文档id为:1、2、3。
- 拿着文档id到正向索引中查找具体文档
- 用户输入条件
- 虽然要先查询倒排索引,再查询正向索引,但是无论是词条还是文档id,都建立了索引,所以查询速度非常快,无需全表扫描
正向和倒排
- 那么为什么一个叫做正向索引,一个叫做倒排索引呢?
正向索引
是最传统的,根据id索引的方式。但是根据词条查询是,必须先逐条获取每个文档,然后判断文档中是否包含所需要的词条,是根据文档查找词条的过程
- 而
倒排索引
则相反,是先找到用户要搜索的词条,然后根据词条得到包含词条的文档id,然后根据文档id获取文档,是根据词条查找文档的过程
- 那么二者的优缺点各是什么呢?
正向索引
- 优点:可以给多个字段创建索引,根据索引字段搜索、排序速度非常快
- 缺点:根据非索引字段,或者索引字段中的部分词条查找时,只能全表扫描
倒排索引
- 优点:根据词条搜索、模糊搜索时,速度非常快
- 缺点:只能给词条创建索引,而不是字段,无法根据字段做排序
ES的一些概念
ElasticSearch中有很多独有的概念,与MySQL中略有差别,但也有相似之处
文档和字段
- ElasticSearch是面向文档(Document)存储的,可以是数据库中的一条商品数据,一个订单信息。文档数据会被序列化为json格式后存储在ElasticSearch中
{
"id": 1,
"title": "小米手机",
"price": 3499
}
{
"id": 2,
"title": "华为手机",
"price": 4999
}
{
"id": 3,
"title": "华为小米充电器",
"price": 49
}
{
"id": 4,
"title": "小米手环",
"price ": 299
}
- 而Json文档中往往包含很多的字段(Field),类似于数据库中的列
索引和映射
-
索引(Index),就是相同类型的文档的集合
-
例如
- 所有用户文档,可以组织在一起,成为用户的索引
{ "id": 101, "name": "张三", "age": 39 } { "id": 102, "name": "李四", "age": 49 } { "id": 103, "name": "王五", "age": 69 }
- 1
- 所有商品的文档,可以组织在一起,称为商品的索引
{ "id": 1, "title": "小米手机", "price": 3499 } { "id": 2, "title": "华为手机", "price": 4999 } { "id": 3, "title": "苹果手机", "price": 6999 }
- 1
- 所有订单的文档,可以组织在一起,称为订单的索引
{ "id": 11, "userId": 101, "goodsId": 1, "totalFee": 3999 } { "id": 12, "userId": 102, "goodsId": 2, "totalFee": 4999 } { "id": 13, "userId": 103, "goodsId": 3, "totalFee": 6999 }
- 1
-
因此,我们可以把索引当做是数据库中的表
-
数据库的表会有约束信息,用来定义表的结构、字段的名称、类型等信息。因此,索引库就有
映射(mapping)
,是索引中文档的字段约束信息,类似于表的结构约束
MySQL与ElasticSearch
- 我们统一的把MySQL和ElasticSearch的概念做一下对比
MySQL | Elasticsearch | 说明 |
---|---|---|
Table | Index | 索引(index),就是文档的集合,类似数据库的表(Table) |
Row | Document | 文档(Document),就是一条条的数据,类似数据库中的行(Row),文档都是JSON格式 |
Column | Field | 字段(Field),就是JSON文档中的字段,类似数据库中的列(Column) |
Schema | Mapping | Mapping(映射)是索引中文档的约束,例如字段类型约束。类似数据库的表结构(Schema) |
SQL | DSL | DSL是elasticsearch提供的JSON风格的请求语句,用来操作elasticsearch,实现CRUD |
-
二者各有自己擅长之处
MySQL
:产长事务类型操作,可以保证数据的安全和一致性ElasticSearch
:擅长海量数据的搜索、分析、计算
-
因此在企业中,往往是这二者结合使用
- 对安全性要求较高的写操作,使用MySQL实现
- 对查询性能个较高的搜索需求,使用ElasticSearch实现
- 二者再基于某种方式,实现数据的同步,保证一致性
安装ES、Kibana
部署单点ES
- 因为我们还需要部署Kibana容器,因此需要让es和kibana容器互联,这里先创建一个网络(使用compose部署可以一键互联,不需要这个步骤,但是将来有可能不需要kbiana,只需要es,所以先这里手动部署单点es)
docker network create es-net
- 1
- 拉取镜像,这里采用的是ElasticSearch的7.12.1版本镜像
docker pull elasticsearch:7.12.1
- 1
- 运行docker命令,部署单点ES
docker run -d \
--name es \
-e "ES_JAVA_OPTS=-Xms512m -Xmx512m" \
-e "discovery.type=single-node" \
-v es-data:/usr/share/elasticsearch/data \
-v es-plugins:/usr/share/elasticsearch/plugins \
--privileged \
--network es-net \
-p 9200:9200 \
elasticsearch:7.12.1
- 1
-
命令解释:
-e "ES_JAVA_OPTS=-Xms512m -Xmx512m"
:配置JVM的堆内存大小,默认是1G,但是最好不要低于512M-e "discovery.type=single-node"
:单点部署-v es-data:/usr/share/elasticsearch/data
:数据卷挂载,绑定es的数据目录-v es-plugins:/usr/share/elasticsearch/plugins
:数据卷挂载,绑定es的插件目录-privileged
:授予逻辑卷访问权--network es-net
:让ES加入到这个网络当中-p 9200
:暴露的HTTP协议端口,供我们用户访问的
-
成功启动之后,打开浏览器访问:http://192.168.128.130:9200/, 即可看到elasticsearch的响应结果
部署kibana
- 同样是先拉取镜像,注意版本需要与ES保持一致
docker pull kibana:7.12.1
- 1
- 运行docker命令,部署kibana
docker run -d \
--name kibana \
-e ELASTICSEARCH_HOSTS=http://es:9200 \
--network=es-net \
-p 5601:5601 \
kibana:7.12.1
- 1
- 2
- 3
- 4
- 5
- 6
- 命令解释
--network=es-net
:让kibana加入es-net
这个网络,与ES在同一个网络中-e ELASTICSEARCH_HOSTS=http://es:9200
:设置ES的地址,因为kibana和ES在同一个网络,因此可以直接用容器名访问ES-p 5601:5601
:端口映射配置
- 成功启动后,打开浏览器访问:http://192.168.128.130:5601/ ,即可以看到结果
DevTools
- kibana中提供了一个DevTools界面,在这个界面中我们可以编写DSL来操作ElasticSearch,并且有对DSL语句的自动补全功能
安装IK分词器
- 默认的分词对中文的支持不是很好,所以这里我们需要安装IK插件
- 在线安装IK插件
# 进入容器内部
docker exec -it elasticsearch /bin/bash
# 在线下载并安装
./bin/elasticsearch-plugin install https://github.com/medcl/elasticsearch-analysis-ik/releases/download/v7.12.1/elasticsearch-analysis-ik-7.12.1.zip
#退出
exit
#重启容器
docker restart elasticsearch
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- IK分词器包含两种模式
ik_smart
:最少切分ik_max_word
:最细切分
- 下面我们分别测试这两种模式
{% tabs 测试两种分词模式 %}
GET /_analyze
{
"analyzer": "ik_smart",
"text": "青春猪头G7人马文不会梦到JK黑丝兔女郎铁驭艾许"
}
- 1
- 2
- 3
- 4
- 5
- 结果
{
"tokens" : [
{
"token" : "青春",
"start_offset" : 0,
"end_offset" : 2,
"type" : "CN_WORD",
"position" : 0
},
{
"token" : "猪头",
"start_offset" : 2,
"end_offset" : 4,
"type" : "CN_WORD",
"position" : 1
},
{
"token" : "G7",
"start_offset" : 4,
"end_offset" : 6,
"type" : "LETTER",
"position" : 2
},
{
"token" : "人",
"start_offset" : 6,
"end_offset" : 7,
"type" : "COUNT",
"position" : 3
},
{
"token" : "不会",
"start_offset" : 7,
"end_offset" : 9,
"type" : "CN_WORD",
"position" : 4
},
{
"token" : "梦到",
"start_offset" : 9,
"end_offset" : 11,
"type" : "CN_WORD",
"position" : 5
},
{
"token" : "jk",
"start_offset" : 11,
"end_offset" : 13,
"type" : "ENGLISH",
"position" : 6
},
{
"token" : "黑",
"start_offset" : 13,
"end_offset" : 14,
"type" : "CN_CHAR",
"position" : 7
},
{
"token" : "丝",
"start_offset" : 14,
"end_offset" : 15,
"type" : "CN_CHAR",
"position" : 8
},
{
"token" : "兔女郎",
"start_offset" : 15,
"end_offset" : 18,
"type" : "CN_WORD",
"position" : 9
},
{
"token" : "铁",
"start_offset" : 18,
"end_offset" : 19,
"type" : "CN_CHAR",
"position" : 10
},
{
"token" : "驭",
"start_offset" : 19,
"end_offset" : 20,
"type" : "CN_CHAR",
"position" : 11
},
{
"token" : "艾",
"start_offset" : 20,
"end_offset" : 21,
"type" : "CN_CHAR",
"position" : 12
},
{
"token" : "许",
"start_offset" : 21,
"end_offset" : 22,
"type" : "CN_CHAR",
"position" : 13
}
]
}
- 1
GET /_analyze
{
"analyzer": "ik_max_word",
"text": "青春猪头G7人马文不会梦到JK黑丝兔女郎铁驭艾许"
}
- 1
- 2
- 3
- 4
- 5
- 结果
{
"tokens" : [
{
"token" : "青春",
"start_offset" : 0,
"end_offset" : 2,
"type" : "CN_WORD",
"position" : 0
},
{
"token" : "猪头",
"start_offset" : 2,
"end_offset" : 4,
"type" : "CN_WORD",
"position" : 1
},
{
"token" : "G7",
"start_offset" : 4,
"end_offset" : 6,
"type" : "LETTER",
"position" : 2
},
{
"token" : "G",
"start_offset" : 4,
"end_offset" : 5,
"type" : "ENGLISH",
"position" : 3
},
{
"token" : "7",
"start_offset" : 5,
"end_offset" : 6,
"type" : "ARABIC",
"position" : 4
},
{
"token" : "人马",
"