ElasticSearch
一、ElasticSearch简介
1. ElasticSearch是什么?
ElasticSearch,简称ES。ES是一款非常强大的开源搜索引擎,可以帮助我们从海量数据中快速找到需要的内容。如百度搜索、京东商品搜索、打车软件中搜附近的车辆等等。
2. ElasticSearch为什么快?
其中一个原因就是使用了倒排索引
2.1 正向索引
说明
传统RDBMS(例如MySql)采用的是正向索引,即:根据表的主键字段构建索引,在查询时要扫描到每个索引值对应的数据,从中筛选出符合查询条件的记录。
这样的检索方式构建索引简单,但是不适合复杂的文档检索。复杂的文档检索时,索引会失效,变成全表扫描
示例
要搜索包含“手机”的商品,执行SQL:select * from tb_goods where title like '%华为手机%'
MySql会扫描所有的数据记录,逐条判断 “title”的值是否包含“手机”,直到全表扫描,过滤出所有符合要求的结果
- 先找每条数据
- 再判断每条数据是否符合要求
2.2 倒排索引
说明
ElasticSearch采用的是倒排索引,即:以字或词为关键字构建索引,保存每个关键字所在的记录。当需要查询时,根据词条匹配查询条件,直接找到关联的记录。
倒排索引的建立和维护都比较复杂,但是在查询时可以和查询关键字关联的所有结果,并快速响应。
倒排索引中有两个非常重要的概念:
- 文档(
Document
):用来搜索的数据,其中的每一条数据就是一个文档。 - 词条(
Term
):对文档数据或用户搜索数据,利用某种算法分词,得到的具备含义的词语就是词条。
创建倒排索引是对正向索引的一种特殊处理和应用,流程如下:
- 将每一个文档的数据利用分词算法根据语义拆分,得到一个个词条
- 创建表,每行数据包括词条、词条所在文档id、位置等信息
- 因为词条唯一性,可以给词条创建正向索引
示例
构建倒排索引:
词条(索引) | 文档id |
---|---|
小米 | 1,3,4 |
手机 | 1,2 |
华为 | 2,3 |
充电器 | 3 |
手环 | 4 |
倒排索引的搜索流程如下(以搜索"华为手机"为例),如图:
3.ElasticSearch和MySQL
传统的RDBMS和ElasticSearch都可以增删改查,并且各有所长:
- MySQL:MySQL擅长增删改 写操作。因为MySQL有事务,可以保证数据的一致性和完整性
- ES:ES擅长查询搜索。因为ES使用了倒排索引,可以快速从海量数据里查找目标数据
实际开发中的应用:
- 如果要增删改数据,找MySQL
- 再把MySQL里的数据同步给ES
- 我们再从ES里搜索查询数据
二、安装ElasticSearch
1. 使用docker安装
- 首先把《es7.4.0.tar》和《kibana7.4.0.tar》上传到CentOS的/root目录里
然后执行以下命令:
#创建文件夹。用于挂载到ElasticSearch容器上
mkdir -p /data/elasticsearch/config
mkdir -p /data/elasticsearch/data
#创建es配置文件,设置允许任意主机访问ElasticSearch
echo "http.host: 0.0.0.0" >> /data/elasticsearch/config/elasticsearch.yml
#设置文件夹的权限
chmod -R 777 /data/elasticsearch
- 创建ElasticSearch容器
#创建ElasticSearch容器并启动
docker run --name elasticsearch -p 9200:9200 -p 9300:9300 \
-e "discovery.type=single-node" \
-e ES_JAVA_OPTS="-Xms512m -Xmx512m" \
-v /data/elasticsearch/config/elasticsearch.yml:/usr/share/elasticsearch/config/elasticsearch.yml \
-v /data/elasticsearch/data:/usr/share/elasticsearch/data \
-v /data/elasticsearch/plugins:/usr/share/elasticsearch/plugins \
-d elasticsearch:7.4.0
#如果想要查看ElasticSearch是否启动成功,可以打开浏览器访问 http://ip地址:9200
- 给ElasticSearch配置ik分词器
把ik分词器插件文件夹上传到 CentOS的/data/elasticsearch/plubins
文件夹里,重启ElasticSearch容器
- 创建Kibana容器
注意:一定要把命令里的ip地址,修改成ElasticSearch的访问ip
#安装Kibana:注意一定要把ip地址设置为ElasticSearch的ip
docker run --name kibana -e ELASTICSEARCH_HOSTS=http://192.168.119.129:9200 -p 5601:5601 \
-d kibana:7.4.0
访问测试
打开浏览器访问:http://192.168.119.129:9200:5601 如果能正常打开,说明已经完全启动成功了
注意:ElasticSearch和Kibana的启动,都需要花费一定时间,如果浏览器页面打不开,就耐心等待一会
2. 本机直接安装
- 安装ES
目前我们安装的是ES的7.4.0版本,需要JDK8及以上
准备工作:Cpolar关闭或者卸载,因为它占用了9200端口。这个端口是ES使用的
关闭Cpolar的方式:
- 打开“服务”窗口,找到Cpolar Service
- 在服务上右键停止;
- 在服务上右键-属性-启动类型,修改为“手动”;确定并关闭
- 打开浏览器,输入地址 http://localhost:9200
- 如果显示出来了一个登录页面,就按 ctrl + shift + delete,清除浏览器缓存
- 再刷新页面,保证页面是不能访问的。(不能访问,说明Cpolar已经成功关闭了)
1.1 解压
把《elasticsearch-7.4.0-windows-x86_64.zip》解压到一个不含中文、空格、特殊字符的目录里
1.2 配置
- 配置存储路径
打开config/elasticsearch.yml文件,设置索引数据的存储路径,和日志的存储路径
- 配置虚拟机参数
1.3 启动
进入es的bin目录,直接双击 elasticsearch.bat即可启动
es服务要占用两个端口:
- 9200:rest访问接口,我们稍后要通过这个端口连接es、操作es
- 9300:用于es集群间通信的接口
1.4 验证
在浏览器上直接输入地址 http://localhost:9200/, 如果看到以下界面,说明es启动成功了
- 安装Kibana
Kibana是一个ES索引库数据统计工具,可以利用ES的聚合功能,生成各种图表,如柱形图,线状图,饼图等。
而且还提供了操作ES索引数据的控制台,并且提供了一定的API提示,非常有利于我们学习ES的语法。
2.1 安装nodeJs
Kibana依赖于nodeJs,需要在windows下先安装Node.js,然后才能安装Kibana。
双击nodejs的安装包,按照提示一步步安装即可
安装成功后,打开cmd 输入:node -v
,如果能看到版本号,说明node安装成功
2.2 安装Kibana
- 解压
把《kibana-7.4.0-windows-x86_64.zip》解压到不含中文、空格、特殊字符的目录里
- 配置
修改kibana的config/kibana.yml,配置es的地址
- 启动
进入到kibana的bin目录,双击 kibana.bat 启动。启动稍微有些慢,耐心等待一会
- 访问
打开浏览器输入 http://localhost:5601/
3. ik分词器
为了解决中文词语拆分的问题,可以安装一个IK分词器。IK分词器是一款适合于中文分词习惯的、优秀的中文分词器,具有60万字/秒的高速处理能力。
它支持两种粒度的拆分:
- ik_smart:做粗粒度的拆分
- ik_max_word:将文本做细粒度的拆分,拆分出尽可能多的词条(建议用这种)
- 解压
把分词器《elasticsearch-analysis-ik-7.4.0.zip》解压到es的plugins目录下,重命名为“ik”
- 重启
- 重启es
- 重启kibana
- 测试
在浏览器的kibana开发工具界面,输入并执行
三、操作索引
1.核心概念
MySQL | ES | 说明 |
---|---|---|
Table | Index | 索引Index,就是文档的集合,类似数据库表。注意:索引名必须小写 |
Row | Document | 文档Document,就是一条数据,类似于数据库表中一行记录。文档是json格式的 |
Column | Field | 字段Field,是json中的字段,类似于数据库中的列column |
Schema | Mapping | 映射Mapping,是索引中文档的约束,例如字段类型约束。类似于数据库的表结构 |
SQL | DSL | DSL是ES提供的json格式的请求语句,用来操作ES,实现CURD Domain Spefical Language |
2. 索引库映射mapping
说明
映射mapping:目的是为了给索引的每个Field设置类型(字符串,数字,日期,对象,数组……)
在MySql里,我们在插入一条记录之前,需要提前设置好表里每个每个字段的类型。
而类似的,在ES里,我们需要给每个索引库设置映射信息
映射类型
每个字段field要设置的 常见的maping属性有:
- type:字段的类型,常见的简单类型有
- text:可分词的文本字符串。如果是text,需要设置
analyzer
分词器 - keyword:不分词的文本字符串。某些字符串一旦分词就失去意义了,例如品牌、国家、ip地址、url地址等
- byte,short,integer,long,double,float等数值
- boolean
- date日期。ES可以把日期格式化为字符串存储,但是为了节省空间,建议使用long存储毫秒值
- object对象。如果是object,需要设置
properties
- text:可分词的文本字符串。如果是text,需要设置
- index:是否创建索引,默认true。如果字段值不参与搜索,要设置为false。比如图片url
- analyzer:使用哪种分词器。text类型时需要设置,其它类型的字段不需要设置
- properties:字段的子字段。如果字段是object时,需要设置子字段
3. DSL命令操作索引
3.1 创建索引库
PUT /索引名
{
"mappings":{
"properties":{
"字段名":{
"type": 类型, #类型常用的有:text,keyword,byte,short,integer,long,double,float, boolean, date, geo_point
"index": 是否参与搜索,
"analyzer": 分词器 如果类型是text,就需要设置分词器 #ik_smart, ik_max_word
},
....
}
}
}
3.2 查看索引库
#查询所有索引
GET /_cat/indices
#查看某一索引
GET /索引
3.3 修改索引库
注意: 不能修改已有字段,只能增加新字段
因为创建好索引库后,es会构建倒排索引,这个过程是比较消耗性能的。一旦索引库的某个字段被修改,可能会导致原本的倒排索引失效,影响太大,所以es不能修改索引库中已有的字段。
PUT /索引/_mapping
{
"properties":{
"字段名":{
"type": 类型, #类型常用的有:text,keyword,byte,short,integer,long,double,float, boolean, date, geo_point
"index": 是否参与搜索,
"analyzer": 分词器 如果类型是text,就需要设置分词器 #ik_smart, ik_max_word
},
....
}
}
3.4 删除索引
DELETE /索引
四、操作文档
1. DSL命令操作文档
1.1 新增文档
注意: 如果唯一标识对应的文档不存在,是新增。如果已存在,会覆盖掉
PUT /索引/_doc/唯一标识
{
"字段": 值,
"字段": 值,
...
}
1.2 查看文档
#查看某一文档
GET /索引/_doc/唯一标识
#查看文档列表
POST /索引/_search
1.3 增量修改
#修改一条文档:增量修改,即在原本文档基础上做变更
POST /索引/_update/唯一标识
{
"doc":{
"字段": 值,
"字段": 值,
...
}
}
1.4 删除文档
#删除文档
DELETE /索引/_doc/唯一标识
2. DSL查询文档
2.1 查询语法
GET /索引库名/_search
{
"query":{}, #查询条件
"sort":{}, #排序条件
"from": 起始索引,#分页的起始索引
"size": 查询数量,#分页查询几条
"highlight": {},#高亮字段
"aggs":{}, #聚合分组条件
"suggest":{}, #搜索提示条件
}
2.2 query查询条件
match模糊查询,即文本检索,会分词检索查询结果
term等值查询,即相当于SQL里 where 字段=值
range范围查询,即相当于SQL里 where 字段>值 and 字段<值
geo_distance地理坐标查询,比如搜附近
function_score算分函数,用于影响查询结果排名【了解】
bool多条件组合查询
2.3 文本检索
常见的全文检索查询包括:
-
match查询:单字段查询
-
multi_match查询:多字段查询,任意一个字段符合条件就算符合条件查询。
注意: 多字段检索的性能问题:使用多个字段检索时,字段越多,检索的性能越差
解决方案: 使用copy_to
,把多个字段值拷贝到一个字段里,对这个字段用match
检索,可以达到同样效果,而且效率更高
语法
POST /索引库名/_search
{
"query":{
"match":{
"字段名": "搜索关键词"
}
}
}
示例
# 查询北京酒店
POST /hotel/_search
{
"query": {
"match": {
"all": "北京酒店"
}
}
}
2.4 精确查询
精确查询一般是查找keyword、数值、日期、boolean等类型字段。所以不会对搜索条件分词。常见的有:
-
term:根据词条精确值查询
因为精确查询是不分词的,所有查询的条件也必须是不分词的词条。查询时,用户输入的内容跟自动值完全匹配时才认为符合条件。如果用户输入的内容过多,反而搜索不到数据。
-
range:根据值的范围查询
范围查询,一般应用在对数值类型做范围过滤的时候。比如做价格范围过滤。
语法
# 根据词条精确值查询
POST /索引库名/_search
{
"query":{
"term":{
"字段名": "值"
}
}
}
# 根据值的范围查询
POST /索引库名/_search
{
"query":{
"range":{
"字段名": { #包含 gte, gt, lte, lt
"gte": 最小值,
"lt": 最大值
}
}
}
}
示例
# 查询品牌为希尔顿的酒店
POST /hotel/_search
{
"query": {
"term": {
"brand": {
"value": "希尔顿"
}
}
}
}
# 价格为100-300之间的酒店[100,300)
POST /hotel/_search
{
"query": {
"range": {
"price": {
"gte": 100,
"lt": 300
}
}
}
}
2.5 地理坐标查询
语法
POST /索引库名/_search
{
"query":{
"geo_distance":{
"字段名": "圆心的坐标 纬度在前经度在后 英文逗号分隔",
"distance": "搜索距离半径 比如 1km"
}
}
}
示例
# 搜索天安门广场附近的酒店(圆形范围查询)。
#划一个圆形区域,搜索区域内的数据。需要指定圆心坐标和半径距离
POST /hotel/_search
{
"query": {
"geo_distance":{
"location":"39.909652,116.404177",#圆心坐标
"distance":"1km" #半径。单位:km千米,m米
}
}
}
2.6 算分函数(了解)
原理:基于原始查询条件计算的关联度得分,进行再运算
语法
function score的运行流程如下:
- 根据原始条件查询搜索文档,并且计算相关性算分,称为原始算分(query score)
- 根据过滤条件,过滤出符合过滤条件的文档,基于算分函数运算,得到函数算分(function score)
- 将原始算分(query score)和函数算分(function score)基于运算模式做运算,得到最终结果,作为相关性算分。
示例
# 将品牌为希尔顿的酒店_score乘以100
POST /hotel/_search
{
"query": {
"function_score": {
"query": {
"match": {
"all": "北京酒店"
}
},
"functions": [
{
"filter": {
"term": {
"brand": "希尔顿"
}
},
"weight": 100
}
],
"boost_mode": "multiply"
}
}
}
2.7 多条件组合查询 ★★★★★
多条件组合查询:bool
-
must:主搜索条件放这里,直接影响关联得分和结果数量。
通常是搜索框的条件
-
should:偏好条件。只影响得分,不影响结果的数量
符合条件的数据,得分更高,排名更靠前
不符合条件的数据,得分较低,排名靠后
-
filter:过滤条件。只保留符合条件的数据,不符合条件的剔除掉
只影响结果数量,不影响关联的得分
-
must_not:剔除条件。直接剔除符合条件的数据
注意:搜索时,参与打分的字段越多,查询的性能也越差。因此这种多条件查询时,建议这样做:
- 搜索框的关键字搜索,是全文检索查询,使用must查询,参与算分
- 其它过滤条件,采用filter查询。不参与算分
语法
POST /索引库名/_search
{
"query":{
"bool":{
"must":[
{
"match":{ "字段名":值 }
}
],
"should":[],
"filter":[],
"must_not":[]
}
}
}
示例
POST /hotel/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"all": "北京酒店"
}
}
],
"should": [
{
"term": {
"brand": {
"value": "速8"
}
}
}
],
"filter": [
{
"range":{
"price":{
"lt": 500
}
}
}
],
"must_not": [
{
"range": {
"score": {
"lte": 45
}
}
}
]
}
}
}
3. DSL处理结果
3.1 排序
elasticsearch默认是根据相关度算分(_score)来排序,但是也支持自定义方式对搜索结果排序。可以排序字段类型有:keyword类型、数值类型、地理坐标类型、日期类型等。
简单排序:按某字段值升序或降序排列
简单排序语法
GET /索引库名/_search
{
"sort":[
{"字段1":{"order": "排序规则"}}, #排序规则:DESC,ASC
...
{"字段2":{"order": "排序规则"}}
]
}
简单排序示例
# 酒店数据按照用户评价升序排序,评价相同按照价格升序排序
POST /hotel/_search
{
"sort": [
{
"price": {
"order": "asc"
}
},
{
"score": {
"order": "asc"
}
}
]
}
距离排序:按geo计算距离进行排序
距离排序语法
GET /索引名称/_search
{
"sort": [
{
"_geo_distance" : {
"字段" : "纬度, 经度", # 字段名、目标坐标点
"order" : "asc", # 排序方式
"unit" : "km" # 排序的距离单位
}
}
]
}
这个查询的含义是:
- 指定一个坐标,作为目标点
- 计算每一个文档中,指定字段的坐标 到目标点的距离是多少
- 根据距离排序
距离排序示例
# 距离排序:按geo计算距离进行排序。把所有酒店按照与天安门的距离升序
POST /hotel/_search
{
"sort": [
{
"_geo_distance": {
"location": "39.909652, 116.404177",
"order": "asc",
"unit": "km"
}
}
]
}
3.2 分页
elasticsearch的分页与mysql数据库非常相似,都是指定两个值
- from:起始索引 (from值 = (页码-1) * 每页几条)
- size:查询几条
语法
GET /索引库名/_search
{
"from": 起始索引,
"size": 查询几条
}
示例
#分页查询:每页五条。查询第一页
POST /hotel/_search
{
"from": 0,
"size": 5
}
3.3 高亮
高亮需要前后端配合实现高亮效果
我们把需要高亮的内容,使用HTML标签标起来
前端开发人员编写css样式代码,选中高亮的标签设置样式
注意:
-
高亮是对关键字高亮,因此搜索条件必须带有关键字,而不能是范围这样的查询
-
默认情况下,高亮的字段,必须与搜索指定的字段一致,否则无法高亮
-
如果检索字段和高亮字段不同,就必须添加
require_field_match
设置为false
语法
POST /索引库名/_search
{
"query":{
"match":{
"检索字段名":"搜索条件"
}
},
"highlight":{
"fields":{
"高亮字段名":{
"pre_tags":"前置标签",
"post_tags":"后置标签",
}
},
"require_field_match":"false"
}
}
示例
POST /hotel/_search
{
"query": {
"match": {
"all": "北京酒店"
}
},
"highlight": {
"fields": {
"name": {
"pre_tags": "<em>",
"post_tags": "</em>"
}
},
"require_field_match": "false"
}
}
4. 数据聚合
4.1 聚合的种类
注意:参加聚合的字段必须是keyword、日期、数值、布尔类型。 text类型不允许参与聚合
聚合常见的有三类:
- **桶(Bucket)**聚合--聚合成桶,把数据进行归类分组:用来对文档做分组
- TermAggregation:按照文档字段值分组,例如按照品牌值分组、按照城市分组
- Date Histogram:按照日期阶梯分组,例如一周为一组,或者一月为一组
- Range Aggregation:按范围分组,指定开始和结束,然后按段分组
- ……
- **度量(Metric)**聚合-桶内度量:用以计算一些值,比如:最大值、最小值、平均值等
- Avg:求平均值
- Max:求最大值
- Min:求最小值
- Sum:求和
- Stats:同时求max、min、avg、sum、count等
- Percentiles:求百分比
- Top hits:求前几
- ……
- **管道(pipeline)**聚合:其它聚合的结果为基础做聚合
4.2 聚合基础语法
4.2.1 Bucket聚合为桶
聚合基本语法
GET /hotel/_search
{
"size": 0, // 设置size为0,结果中不包含文档,只包含聚合结果
"aggs": { // 定义聚合
"brandAgg": { //给聚合起个名字
"terms": { // 聚合的类型,按照品牌值聚合,所以选择term
"field": "brand", // 参与聚合的字段
"size": 20 // 希望获取的聚合结果的最大数量
}
}
}
}
结果如图
聚合结果排序
默认情况下,Bucket聚合会统计Bucket内的文档数量,记为_count
,并且按照_count
降序排序。
我们可以指定order属性,自定义聚合的排序方式:
GET /hotel/_search
{
"size": 0,
"aggs": {
"brandAgg": {
"terms": {
"field": "brand",
"order": {
"_count": "asc" // 按照_count升序排列
},
"size": 20
}
}
}
}
限定聚合范围
默认情况下,Bucket聚合是对索引库的所有文档做聚合,但真实场景下,用户会输入搜索条件,因此聚合必须是对搜索结果聚合。那么聚合必须添加限定条件。
我们可以限定要聚合的文档范围,只要添加query条件即可:
GET /hotel/_search
{
"query": {
"range": {
"price": {
"lte": 200 // 只对200元以下的文档聚合
}
}
},
"size": 0,
"aggs": {
"brandAgg": {
"terms": {
"field": "brand",
"size": 20
}
}
}
}
这次,聚合得到的品牌明显变少了:
4.2.2 Metric桶内度量
刚刚我们对酒店按照品牌分组,形成了一个个桶。现在我们需要对桶内的酒店做运算,获取每个品牌的用户评分的min、max、avg等值。
这就要用到Metric聚合了,例如stat聚合:就可以获取min、max、avg等结果。
语法如下:
GET /hotel/_search
{
"size": 0,
"aggs": {
"brandAgg": {
"terms": {
"field": "brand",
"size": 20
},
"aggs": { // 是brands聚合的子聚合,也就是分组后对每组分别计算
"score_stats": { // 聚合名称
"stats": { // 聚合类型,这里stats可以计算min、max、avg等
"field": "score" // 聚合字段,这里是score
}
}
}
}
}
}
这次的score_stats聚合是在brandAgg的聚合内部嵌套的子聚合。因为我们需要在每个桶分别计算。
另外,我们还可以给聚合结果做个排序,例如按照每个桶的酒店平均分做排序:
转载自:https://juejin.cn/post/7364418751482101794