likes
comments
collection
share

ElasticSearch

作者站长头像
站长
· 阅读数 35

一、ElasticSearch简介

1. ElasticSearch是什么?

ElasticSearch,简称ES。ES是一款非常强大的开源搜索引擎,可以帮助我们从海量数据中快速找到需要的内容。如百度搜索、京东商品搜索、打车软件中搜附近的车辆等等。

2. ElasticSearch为什么快?

其中一个原因就是使用了倒排索引

2.1 正向索引

说明

传统RDBMS(例如MySql)采用的是正向索引,即:根据表的主键字段构建索引,在查询时要扫描到每个索引值对应的数据,从中筛选出符合查询条件的记录。

这样的检索方式构建索引简单,但是不适合复杂的文档检索。复杂的文档检索时,索引会失效,变成全表扫描

示例

要搜索包含“手机”的商品,执行SQL:select * from tb_goods where title like '%华为手机%'

MySql会扫描所有的数据记录,逐条判断 “title”的值是否包含“手机”,直到全表扫描,过滤出所有符合要求的结果

  • 先找每条数据
  • 再判断每条数据是否符合要求

ElasticSearch

2.2 倒排索引

说明

ElasticSearch采用的是倒排索引,即:以字或词为关键字构建索引,保存每个关键字所在的记录。当需要查询时,根据词条匹配查询条件,直接找到关联的记录。

倒排索引的建立和维护都比较复杂,但是在查询时可以和查询关键字关联的所有结果,并快速响应。

倒排索引中有两个非常重要的概念:

  • 文档(Document):用来搜索的数据,其中的每一条数据就是一个文档。
  • 词条(Term):对文档数据或用户搜索数据,利用某种算法分词,得到的具备含义的词语就是词条。

创建倒排索引是对正向索引的一种特殊处理和应用,流程如下:

  • 将每一个文档的数据利用分词算法根据语义拆分,得到一个个词条
  • 创建表,每行数据包括词条、词条所在文档id、位置等信息
  • 因为词条唯一性,可以给词条创建正向索引

示例

构建倒排索引:

词条(索引)文档id
小米1,3,4
手机1,2
华为2,3
充电器3
手环4

倒排索引的搜索流程如下(以搜索"华为手机"为例),如图:

ElasticSearch

3.ElasticSearch和MySQL

传统的RDBMS和ElasticSearch都可以增删改查,并且各有所长:

  • MySQL:MySQL擅长增删改 写操作。因为MySQL有事务,可以保证数据的一致性和完整性
  • ES:ES擅长查询搜索。因为ES使用了倒排索引,可以快速从海量数据里查找目标数据

实际开发中的应用:

  • 如果要增删改数据,找MySQL
  • 再把MySQL里的数据同步给ES
  • 我们再从ES里搜索查询数据

ElasticSearch

二、安装ElasticSearch

1. 使用docker安装

  1. 首先把《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
  1. 创建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
  1. 给ElasticSearch配置ik分词器

把ik分词器插件文件夹上传到 CentOS的/data/elasticsearch/plubins文件夹里,重启ElasticSearch容器

ElasticSearch

  1. 创建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. 本机直接安装

  1. 安装ES

目前我们安装的是ES的7.4.0版本,需要JDK8及以上

准备工作:Cpolar关闭或者卸载,因为它占用了9200端口。这个端口是ES使用的

关闭Cpolar的方式:

  • 打开“服务”窗口,找到Cpolar Service

ElasticSearch

  • 在服务上右键停止;
  • 在服务上右键-属性-启动类型,修改为“手动”;确定并关闭
  • 打开浏览器,输入地址 http://localhost:9200
    • 如果显示出来了一个登录页面,就按 ctrl + shift + delete,清除浏览器缓存
    • 再刷新页面,保证页面是不能访问的。(不能访问,说明Cpolar已经成功关闭了)

1.1 解压

把《elasticsearch-7.4.0-windows-x86_64.zip》解压到一个不含中文、空格、特殊字符的目录里

ElasticSearch

1.2 配置

  1. 配置存储路径

打开config/elasticsearch.yml文件,设置索引数据的存储路径,和日志的存储路径

ElasticSearch

  1. 配置虚拟机参数

ElasticSearch

1.3 启动

进入es的bin目录,直接双击 elasticsearch.bat即可启动

es服务要占用两个端口:

  • 9200:rest访问接口,我们稍后要通过这个端口连接es、操作es
  • 9300:用于es集群间通信的接口

ElasticSearch

1.4 验证

在浏览器上直接输入地址 http://localhost:9200/, 如果看到以下界面,说明es启动成功了

ElasticSearch

  1. 安装Kibana

Kibana是一个ES索引库数据统计工具,可以利用ES的聚合功能,生成各种图表,如柱形图,线状图,饼图等。

而且还提供了操作ES索引数据的控制台,并且提供了一定的API提示,非常有利于我们学习ES的语法。

2.1 安装nodeJs

Kibana依赖于nodeJs,需要在windows下先安装Node.js,然后才能安装Kibana。

双击nodejs的安装包,按照提示一步步安装即可

安装成功后,打开cmd 输入:node -v,如果能看到版本号,说明node安装成功

ElasticSearch

2.2 安装Kibana

  1. 解压

把《kibana-7.4.0-windows-x86_64.zip》解压到不含中文、空格、特殊字符的目录里

ElasticSearch

  1. 配置

修改kibana的config/kibana.yml,配置es的地址

ElasticSearch

  1. 启动

进入到kibana的bin目录,双击 kibana.bat 启动。启动稍微有些慢,耐心等待一会

ElasticSearch

  1. 访问

打开浏览器输入 http://localhost:5601/

ElasticSearch

ElasticSearch

3. ik分词器

为了解决中文词语拆分的问题,可以安装一个IK分词器。IK分词器是一款适合于中文分词习惯的、优秀的中文分词器,具有60万字/秒的高速处理能力。

它支持两种粒度的拆分:

  • ik_smart:做粗粒度的拆分
  • ik_max_word:将文本做细粒度的拆分,拆分出尽可能多的词条(建议用这种)
  1. 解压

把分词器《elasticsearch-analysis-ik-7.4.0.zip》解压到es的plugins目录下,重命名为“ik”

ElasticSearch

  1. 重启
  1. 重启es
  2. 重启kibana
  1. 测试

在浏览器的kibana开发工具界面,输入并执行

三、操作索引

1.核心概念

MySQLES说明
TableIndex索引Index,就是文档的集合,类似数据库表。注意:索引名必须小写
RowDocument文档Document,就是一条数据,类似于数据库表中一行记录。文档是json格式的
ColumnField字段Field,是json中的字段,类似于数据库中的列column
SchemaMapping映射Mapping,是索引中文档的约束,例如字段类型约束。类似于数据库的表结构
SQLDSLDSL是ES提供的json格式的请求语句,用来操作ES,实现CURD Domain Spefical Language

2. 索引库映射mapping

说明

映射mapping:目的是为了给索引的每个Field设置类型(字符串,数字,日期,对象,数组……)

在MySql里,我们在插入一条记录之前,需要提前设置好表里每个每个字段的类型。

而类似的,在ES里,我们需要给每个索引库设置映射信息

参考:www.elastic.co/guide/en/el…

映射类型

每个字段field要设置的 常见的maping属性有:

  • type:字段的类型,常见的简单类型有
    • text:可分词的文本字符串。如果是text,需要设置analyzer分词器
    • keyword:不分词的文本字符串。某些字符串一旦分词就失去意义了,例如品牌、国家、ip地址、url地址等
    • byte,short,integer,long,double,float等数值
    • boolean
    • date日期。ES可以把日期格式化为字符串存储,但是为了节省空间,建议使用long存储毫秒值
    • object对象。如果是object,需要设置properties
  • 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 算分函数(了解)

原理:基于原始查询条件计算的关联度得分,进行再运算

语法

ElasticSearch

function score的运行流程如下:

  1. 根据原始条件查询搜索文档,并且计算相关性算分,称为原始算分(query score)
  2. 根据过滤条件,过滤出符合过滤条件的文档,基于算分函数运算,得到函数算分(function score)
  3. 原始算分(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 // 希望获取的聚合结果的最大数量
      }
    }
  }
}

结果如图

ElasticSearch

聚合结果排序

默认情况下,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
      }
    }
  }
}

这次,聚合得到的品牌明显变少了:

ElasticSearch

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的聚合内部嵌套的子聚合。因为我们需要在每个桶分别计算。

另外,我们还可以给聚合结果做个排序,例如按照每个桶的酒店平均分做排序:

ElasticSearch

转载自:https://juejin.cn/post/7364418751482101794
评论
请登录