概述
es是一个基于Lucene的搜索引擎。对于初学者来说,可以将其看作一款NoSQL。es一般可以用作项目中的搜索、检索模块,提供关键词检索、条件过滤、聚合等功能。
es在单独使用时,可以实现的功能有比如:淘宝、京东等的商品搜索,根据品牌、分类的商品过滤,根据价格等的排序。数据聚合则是可以统计如:某一价格段的商品的销量,等类似的数据。
es中的数据是以文档(docment)为单位保存的,可以看作是数据库中的一行数据。文档保存在类型(type)中,可以看作是数据库中的表。而存储以上这些的称为索引(index),可以看作是数据库中的某一个库。 但是需要注意的是,es7后就不推荐使用type了,默认每个索引只有一个type,名字为“_doc”,而在es8中则会删除type。
这里主要是对于es的入门使用的介绍,以帮助初学者。
对于一些基础概念,则推荐阅读官方文档:https://www.elastic.co/guide/cn/elasticsearch/guide/current/index.html
中文官方文档对应版本较低,但是底层的一些概念是没有改变的。
分片、副本:https://www.elastic.co/guide/cn/elasticsearch/guide/current/_add-an-index.html
聚合:https://www.elastic.co/guide/cn/elasticsearch/guide/current/aggregations.html
安装
需要提前安装:elasticsearch7.x、kibana,教程太多,这里不贴出。
kibana可以可视化的对es进行操作。
使用
增删改查
es提供了非常多的Rest风格的接口,可以直接通过http请求对其进行操作,而请求的数据格式则为json。
添加
put请求为添加。使用postman对一下地址进行请求,结果如下。
http://localhost:9200/customer/external/1
也可以使用post请求,不加id(标识)的话每次请求会随机生成一个唯一id,加了就和put一样
查询
- 使用get请求即可
get:http://localhost:9200/customer/external/1
更新
更新有两种方法:1、就是使用添加的api进行请求,如果请求的id相同,则为添加。在此不进行介绍。
2、post请求:http://localhost:9200/customer/external/1/_update
而此种方法请求时,请求的json格式要如下:
{
"doc":{
"name":"1111"
}
}
两种方法的区别在于,第二种方式,若数据相同,则不会执行更新,而第一种,无论数据与否,都会用新数据将旧数据覆盖,那么这个不覆盖如何体现呢,我们看一下es返回的数据。
{
"_index": "customer",
"_type": "external",
"_id": "1",
"_version": 8,
"result": "noop",
"_shards": {
"total": 0,
"successful": 0,
"failed": 0
},
"_seq_no": 16,
"_primary_term": 29
}
可以看到第6行,result为noop,代表没有执行更新。
另外,还有两个字段值得注意: _seq_no、 _primary_term。
-
_seq_no是数据每更新一次就会+1的字段
-
_primary_term是es服务端每重启一次会+1的字段
而第二种方法更新请求的若为已有的数据,则_seq_no不会进行+1操作。
而且我们可以根据这两个字段,来为更新请求加上乐观锁。
先将数据查出,再将查出的_seq_no和 _primary_term 放入请求中:
使用put请求: http://localhost:9200/customer/external/1?if_seq_no=14&if_primary_term=29
,若_seq_no和 _primary_term对应不上,则会更新失败。
删除
发送DELETE请求即可
- DELETE:
http://localhost:9200/customer/external/1
也可以删除整个index,但是删除type只能是删除type下的所有doc
- DELETE:
http://localhost:9200/customer
批量
一个个的添加、删除非常耗时耗力,我们还可以进行批量的操作,使用bluk接口
此时需要使用kibana 的 devtools
语法格式:
两行为一个整体(删除操作因为不需要请求的json数据,所以一行)
{action:{metadata}}\n
{request body }\n
{action:{metadata}}\n
{request body }\n
这里的批量操作,当发生某一条执行发生失败时,其他的数据仍然能够接着执行,也就是说彼此之间是独立的。
bulk api以此按顺序执行所有的action(动作)。如果一个单个的动作因任何原因失败,它将继续处理它后面剩余的动作。当bulk api返回时,它将提供每个动作的状态(与发送的顺序相同),所以您可以检查是否一个指定的动作是否失败了。
示例1:
可以在请求的时候,在path里指定index和type
POST: customer/external/_bulk
{"index":{"_id":"1"}}
{"name":"John Doe"}
{"index":{"_id":"2"}}
{"name":"John Doe"}
以上的请求数据,两行为一个整体,第一行为操作:比如index为添加、delete为删除。第二行为操作的数据。
{“index”:{"_id":“1”}}
{“name”:“John Doe”}
即表示,添加{"name":"John Doe"}
数据 ,id为1。
示例2:
也可以在action的metadata中指定index、type
index和create是同一个操作,delete不需要请求体,所以为一行
POST: /_bulk
{"delete":{"_index":"website","_type":"blog","_id":"123"}}
{"create":{"_index":"website","_type":"blog","_id":"123"}}
{"title":"my first blog post"}
{"index":{"_index":"website","_type":"blog"}}
{"title":"my second blog post"}
{"update":{"_index":"website","_type":"blog","_id":"123"}}
{"doc":{"title":"my updated blog post"}}
Query DSL
以上的简单查询,是无法帮助我们实现检索等功能的,所以就引出了DSL查询语句。
首先,先导入es官方的测试数据:https://github.com/elastic/elasticsearch/blob/6d42e197b82aa1c98a1796d547e373f7029ee27a/docs/src/test/resources/accounts.json
POST请求bank/account/_bulk
地址,向bank索引中批量添加数据
DSL语句的基本结构为:
{
QUERY_NAME:{
FIELD_NAME:{
ARGUMENT:VALUE,
ARGUMENT:VALUE,...
}
}
}
查询
查询后,默认会根据匹配程度生成一个评分_score
,也会根据此评分进行排序
match_all 查询全部
GET /bank/_search
{
"query": {
"match_all": {}
}
}
match 关键词查询
- 查询非字符串(keyword、long)的会查询相等的
GET bank/_search
{
"query": {
"match": {
"account_number": 20
}
}
}
match返回account_number=20的数据。
- 查询字符串(text)的会查询包含的
GET bank/_search
{
"query": {
"match": {
"address": "kings"
}
}
}
全文检索,最终会按照评分进行排序,会对检索条件进行分词匹配。
- 查询text时以keyword的特性查询,即匹配完全相等的
GET bank/_search
{
"query": {
"match": {
"address.keyword": "440 King Street"
}
}
}
字段.keyword:则会匹配这个字段相等的,而不是包含
match_phrase 短句匹配
将需要匹配的值当成一整个单词(不分词)进行检索
与match的区别在于:如下两个查询,match_phrase会查询包括Kings Place
这两个连续单词的,而match会查询地址包括Kings
或 Place
的,而包括Kings Place
这两个连续单词的评分会更高
GET bank/_search
{
"query": {
"match_phrase": {
"address": "Kings Place"
}
}
}
GET bank/_search
{
"query": {
"match": {
"address": "Kings Place"
}
}
}
multi_math 多字段匹配
GET bank/_search
{
"query": {
"multi_match": {
"query": "mill",
"fields": [
"state",
"address"
]
}
}
}
state或者address中包含mill,并且在查询过程中,会对于查询条件进行分词。
term 精确值查找
只对非text字段生效,必须完全匹配才会被查找到。
GET bank/_search
{
"query": {
"term": {
"age" : 20
}
}
}
bool 复杂查询
一个 bool
过滤器由这几部分组成:
{
"bool" : {
"must" : [],
"should" : [],
"must_not" : [],
"filter": [],
}
}
must
所有的语句都 必须(must) 匹配
must_not
所有的语句都 不能(must not) 匹配
should
应该包含(如果到达会增加相关文档的评分,并不会改变查询的结果。如果query中只有should且只有一种匹配规则,那么should的条件就会被作为默认匹配条件二区改变查询结果。)
filter
过滤条件,使用上和查询一样,但是更匹配的不计入分数。
举例:
// 查询 address中有Street,年龄不为18,最好lastname有Wallace的(有的加分,没有的不影响),最后产生的结果再过滤(不记分数)gender为M的(此处使用的是term,term只对非text生效 .keyword是提前设置的,后面会说到,这里的作用是此次检索当作keyword来使用,所以才可以用term)
GET bank/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"address": "Street"
}
}
],
"must_not": [
{
"match": {
"age": "18"
}
}
],
"should": [
{
"match": {
"lastname": "Wallace"
}
}
],
"filter": [
{
"term": {
"gender.keyword": "M"
}
}
]
}
}
}
filter 过滤
并不是所有的查询都需要产生分数,特别是哪些仅用于filtering过滤的文档。为了不计算分数,elasticsearch会自动检查场景并且优化查询的执行。
{
"query": {
"bool": {
"must": [
{
"range": {
"balance": {
"gte": 10000,
"lte": 20000
}
}
},
{
"match": {
"address": "Street"
}
}
]
}
}
}
GET bank/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"address": "Street"
}
}
],
"filter": [
{
"range": {
"balance": {
"gte": 10000,
"lte": 20000
}
}
}
]
}
}
}
以上两种查询方式,一个是使用filter来匹配工资10000-20000的 最终查询出的分数为:
{
"_index" : "bank",
"_type" : "account",
"_id" : "51",
"_score" : 0.95395315, // 《------
"_source" : {
"account_number" : 51,
"balance" : 14097,
"firstname" : "Burton",
"lastname" : "Meyers",
"age" : 31,
"gender" : "F",
"address" : "334 River Street",
"employer" : "Bezal",
"email" : "burtonmeyers@bezal.com",
"city" : "Jacksonburg",
"state" : "MO"
}
}
一个是通过must匹配工资,最终查询出的分数:
{
"_index" : "bank",
"_type" : "account",
"_id" : "51",
"_score" : 1.9539531, // 《------
"_source" : {
"account_number" : 51,
"balance" : 14097,
"firstname" : "Burton",
"lastname" : "Meyers",
"age" : 31,
"gender" : "F",
"address" : "334 River Street",
"employer" : "Bezal",
"email" : "burtonmeyers@bezal.com",
"city" : "Jacksonburg",
"state" : "MO"
}
}
分页
在与query平行的一层,使用from 和 size 即可
GET bank/_search
{
"query": {
"match_all": {}
},
"from": 0,
"size": 5,
"_source": ["balance","firstname"]
}
// 表示从0开始,展示5个,即分页的第一页、页面大小为5
// _source的意思是,只展示firstname和balance两个字段
排序
GET bank/_search
{
"query": {
"match_all": {}
},
"from": 0,
"size": 5,
"sort": [
{
"account_number": {
"order": "desc"
}
}
],
"_source": ["balance","firstname"]
}
mapping 映射
指定映射
说白了就是字段的类型,主要有:keyword、text、long、integer、date、object几种
上面的数据,是直接添加数据,es根据数据自动生成的映射
我们还可以在创建索引时指定映射
## 创建index时指定
PUT /my_index
{
"mappings": {
"properties": {
"age": {
"type": "integer"
},
"email": {
"type": "keyword"
},
"name": {
"type": "text",
"fields" : {
// 指定 name.keyword时,字段以下面配置进行检索,此处的keyword无意义,可以任意更换其他名字
"keyword" : {
// 指定还可以作为keyword类型进行检索
"type" : "keyword",
// 表示作为keyword类型时,长度最多为256
"ignore_above" : 256
}
}
}
}
}
}
// ------------------------------------------------------------
{
"mappings" : {
"properties" : {
"createTime" : {
// type为date时需要指定日期的格式
// 具体见文档:https://www.elastic.co/guide/reference/mapping/date-format/
"type" : "date",
"format" : "yyyy-MM-dd HH:mm:ss"
},
"id" : {
"type" : "keyword"
},
// 这里的memeber就是object类型,注意其下一级有“properties”
"member" : {
"properties" : {
"id" : {
"type" : "keyword"
},
"name" : {
"type" : "text",
"analyzer" : "standard"
}
}
},
"title" : {
"type" : "text",
// 此处为指定分词器,一般需要安装ik分词器,后面会提到
"analyzer" : "ik_smart"
}
}
}
}
类型介绍
keyword
使用比较多的一个,主要可以用来储存不分词的字段,比如电话、邮箱这些只可以被整个检索到的
"id" : {
"type" : "keyword"
}
text
一般的字符串类型,主要用来储存分词检索的字段,比如商品标题、博客简介等。可以指定分词器,比如搜索“小米手机”,就可能被分词为“小米”、“手机”两个词语,同事拥有“小米手机”的标题会评分较高,排位靠前,当然也可能你指定的分词器不会将“小米”和“手机分开”。
"title" : {
"type" : "text",
// 此处为指定分词器,一般需要安装ik分词器,后面会提到
"analyzer" : "ik_smart"
}
date
日期时间类型,搭配format字段,统一日期格式,一般可以用于排序
"createTime" : {
// type为date时需要指定日期的格式
// 具体见文档:https://www.elastic.co/guide/reference/mapping/date-format/
"type" : "date",
"format" : "yyyy-MM-dd HH:mm:ss"
}
arrays
数组类型
- an array of strings: [
"one"
,"two"
] - an array of integers: [
1
,2
] - an array of arrays: [
1
, [2
,3
]] which is the equivalent of [1
,2
,3
] - an array of objects: [
{ "name": "Mary", "age": 12 }
,{ "name": "John", "age": 10 }
]
PUT my-index-000001/_doc/1
{
"message": "some arrays in this document...",
"tags": [ "elasticsearch", "wow" ],
"lists": [
{
"name": "prog_list",
"description": "programming list"
},
{
"name": "cool_list",
"description": "cool stuff list"
}
]
}
PUT my-index-000001/_doc/2
{
"message": "no arrays in this document...",
"tags": "elasticsearch",
"lists": {
"name": "prog_list",
"description": "programming list"
}
}
GET my-index-000001/_search
{
"query": {
"match": {
"tags": "elasticsearch"
}
}
}
以上第三个请求,会检索tags中有elasticsearch的,数组内的数据也可以被检索到,所以最终两个doc都会被检索出来
object
对象类型
// 这里的memeber就是object类型,注意其下一级有“properties”
"member" : {
"properties" : {
"id" : {
"type" : "keyword"
},
"name" : {
"type" : "text",
"analyzer" : "standard"
}
}
}
检索的时候通过 对象名
.字段名
进行检索。如 "member.age":20
来检索 年龄==20的
boolean
布尔类型 储存true false
其他类型
建议查看官方文档。
https://www.elastic.co/guide/en/elasticsearch/reference/current/mapping-types.html
Aggregation聚合
官方中文文档写的非常好https://www.elastic.co/guide/cn/elasticsearch/guide/current/aggregations.html
概述
先说聚合是干嘛的
聚合可以用来查询诸如:2021-06-01这一天有多少注册人数,这些注册人数的男女性别分别为多少,他们的平均工资,以及男女的平均工资。
这类需要统计、计算的问题。(当然这是比较简单的使用,还有更加复杂的聚合
再说几个概念
桶聚合(Buckets):满足特定条件的文档的集合,如根据男女、月份、工资范围进行分桶。
指标(度量)聚合(Metrics):对桶内的文档进行统计计算,如计算count、平均、sum等。
桶在概念上类似于 SQL 的分组(GROUP BY),而指标(度量)则类似于
COUNT()
、SUM()
、MAX()
等统计方法。
桶聚合
概述
桶简单来说就是满足特定条件的文档的集合:
- 一个员工属于 男性 桶或者 女性 桶
- 故宫属于 北京 桶,也可以属于中国 桶
- 日期2020-10-28属于 十月 桶,当然也可以属于21世纪 桶
当聚合时,通过一条数据中的某个字段的值来确定该条数据属于哪个桶。
比如,我们创建一个条件为分桶字段为gender的,若gender字段只保存了两个值 男or女 ,则该桶则会分为两个,男和女
桶也可以进行嵌套,比如上面的两个男女桶,经过嵌套可以再根据年龄划分,得出不同年龄的桶。当然桶内也可以嵌套度量聚合,比如求不同年龄的平均工资。
terms
terms聚合的条件,就是根据这个字段有多少种不同的值,则生成多少个桶,如:性别的男女,生成两个桶,若保存的数据种还有“人妖”,则还会生成人妖桶
## 查询全部的人,根据年龄进行桶聚合
GET bank/_search
{
"query": {
"match_all": {}
},
"aggs": {
"ageAgg": {
"terms": {
"field": "age"
}
}
},
"size": 0
}
## 返回结果:
"aggregations" : {
"ageAgg" : {
"doc_count_error_upper_bound" : 0,
"sum_other_doc_count" : 463,
"buckets" : [
{
# 31岁桶
"key" : 31,
# 数量61
"doc_count" : 61
},
{
"key" : 39,
"doc_count" : 60
}
# 此处省略一些其他年龄
]
}
}
以上的查询聚合结果,可以看到 “buckets” 为一个数组,其中有多个对象。而对象的key字段的意思即为分桶的依据,如第一个意思是31岁的数据有61条
range/date range
根据范围来分桶
GET /_search
{
"aggs": {
"price_ranges": {
"range": {
"field": "price",
"ranges": [
{ "to": 100.0 },
{ "from": 100.0, "to": 200.0 },
{ "from": 200.0 }
]
}
}
}
}
https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-range-aggregation.html
根据时间范围来分桶
POST /blog/_search?size=0
{
"aggs": {
"range": {
"date_range": {
"field": "createTime",
"format": "yyyy-MM-dd",
"ranges": [
{ "from": "2020-01-10" } ,
{ "to": "2021-06-10" }
]
}
}
}
}
https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-daterange-aggregation.html
histogram
直方图聚合
请求:
interval 字段就是每间隔多少生成一个桶
min_doc_count 字段就是最小的数量,如果低于这个数量就不显示
POST /sales/_search?size=0
{
"aggs": {
"prices": {
"histogram": {
"field": "price",
"interval": 50,
"min_doc_count": 1
}
}
}
}
最后看返回结果:
可以根据key作为x轴,doc_count作为y轴,生成一张直方图
{
...
"aggregations": {
"prices": {
"buckets": [
{
"key": 0.0,
"doc_count": 1
},
{
"key": 50.0,
"doc_count": 1
},
{
"key": 150.0,
"doc_count": 2
},
{
"key": 200.0,
"doc_count": 3
}
]
}
}
}
https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-bucket-histogram-aggregation.html
度量聚合
顾名思义,即求和、求平均之类的聚合
可以和桶聚合一起使用
avg
POST /exams/_search?size=0
{
"aggs": {
"avg_agg": { "avg": { "field": "grade" } }
}
}
sum
POST /sales/_search?size=0
{
"query": {
"constant_score": {
"filter": {
"match": { "type": "hat" }
}
}
},
"aggs": {
"hat_prices": { "sum": { "field": "price" } }
}
}
嵌套
## 查出所有年龄分布,并且这些年龄段中M的平均薪资和F的平均薪资以及这个年龄段的总体平均薪资
GET bank/_search
{
"query": {
"match_all": {}
},
"aggs": {
"ageAgg": {
"terms": {
"field": "age",
"size": 100
},
"aggs": {
"genderAgg": {
"terms": {
"field": "gender.keyword",
"size": 2
},
"aggs": {
"blanceAvg": {
"avg": {
"field": "balance"
}
}
}
},
"blanceAvg":{
"avg": {
"field": "balance"
}
}
}
}
},
"size": 0
}
分词器
安装分词器
安装常用的ik分词器
1、下载
https://github.com/medcl/elasticsearch-analysis-ik/releases
注意要下载与你的es对应版本的分词器。
2、解压
解压到es目录下的plugins文件夹即可,如图所示
使用
安装完成后重启es
GET my_index/_analyze
{
"analyzer": "ik_smart",
"text":"我是中国人"
}
## 分出 我、是、中国人 ,三个词
GET my_index/_analyze
{
"analyzer": "ik_max_word",
"text":"我是中国人"
}
## 分出 我、是、中国人、中国、国人 ,五个词
大部分时候创建索引时指定mapping为text需要分词的字段的分词所用的分词器。
自定义词库
有的比较新的网络用语没办法分词,需要自定义词库
1、进入plugins–>ik–>config目录,新建一个分词的文件,如:fenci.txt
并在这一个文件中写入词汇,每个词以 回车 分割,可以参考 main.dic
文件(自定义分词的文件后缀也可以是dic)
2、在config目录下的IKAnalyzer.cfg.xml
文件中配置好自定义的词典
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE properties SYSTEM "http://java.sun.com/dtd/properties.dtd">
<properties>
<comment>IK Analyzer 扩展配置</comment>
<!--用户可以在这里配置自己的扩展字典 -->
<entry key="ext_dict">fenci.txt</entry>
<!--用户可以在这里配置自己的扩展停止词字典-->
<entry key="ext_stopwords"></entry>
<!--用户可以在这里配置远程扩展字典 -->
<!-- <entry key="remote_ext_dict">http://127.0.0.1/es/fenci.txt</entry>-->
<!--用户可以在这里配置远程扩展停止词字典-->
<!-- <entry key="remote_ext_stopwords">words_location</entry> -->
</properties>