ElasticSearch 是什么?
ElasticSearch是一个分布式、Restfui 风格的搜索和数据分析引擎,国内简称ES, ElasticSearch 是用Java开发的,底层基于Lucene,Lucene是一种全文的索引库,直接采用Lucene是比较麻烦的,Elasticsearch在Lucene的基础上开发了一个强大的搜索引擎。
- ELK是什么?
使用到ES, 那么ELK是绕不过去的一环, ELK代表了Elasticsearch + Logstash + Kibana 三套软件,他们的作用如下:
- Elasticsearch - 前面简介提到过,解决海量数据搜索问题。
- Logstash - 解决数据同步问题,因为我们数据一般存储在Mysql之类的数据库中,需要将数据导入到ES中,Logstash就支持数据同步、数据过滤、转换功能。
- Kibana - Elasticsearch数据可视化支持,例如:通过各种图表展示ES的查询结果,也可以在Kibana通过ES查询语句分析数据,起到类似ES Web后台的作用。
- ES的应用场景
- 各种搜索场景,例如:订单搜索、商品搜索。
- 日志处理和分析,例如:通过ELK搭建日志处理和分析方案。
- 地理空间数据搜索,例如:查询距离最近的店铺、查询某个空间范围内的店铺。
基本概念
- index(索引)
在Elasticsearch中索引(index)类似mysql的表,代表文档数据的集合,文档指的是ES中存储的一条数据。
- type(文档类型)
在新版的Elasticsearch中,已经不使用文档类型了,在Elasticsearch老的版本中文档类型,代表一类文档的集合,index(索引)类似mysql的数据库、文档类型类似Mysql的表。既然新的版本文档类型没什么作用了,那么index(索引)就类似mysql的表的概念,ES没有数据库的概念了。(针对老的涉及ES的项目,可以将索引看作是数据库,索引类型看作表)。
- Document(文档)
Elasticsearch是面向文档的数据库,文档是最基本的存储单元,文档类似mysql表中的一行数据,简单的说在ES中,文档指的就是一条JSON数据。
- Field(文档字段)
文档由多个json字段(Field)组成, 这里的字段类似mysql中表的字段。
ES中涉及到的常用的数据类型有:
- 数值类型(包括: long、integer、short、byte、double、float)
- text - 支持全文搜索
- keyword - 不支持全文搜索,例如:email、电话这些数据,作为一个整体进行匹配就可以,不需要分词处理。
- date - 日期类型
- boolean
- mapping(映射)
Elasticsearch的mapping (映射)类似mysql中的表结构定义,每个索引都有一个映射规则,我们可以通过定义索引的映射规则,提前定义好文档的json结构和字段类型,如果没有定义索引的映射规则,Elasticsearch会在写入数据的时候,根据我们写入的数据字段推测出对应的字段类型,相当于自动定义索引的映射规则。
- ES 基本概念和MySQL比较
Elasticsearch存储结构 | MYSQL存储结构 |
---|---|
index(索引) | 行,一行数据 |
文档 | 单元格 |
Field(字段) | 表字段 |
mapping (映射)) | 表结构定义 |
ES中api操作
- 文档元数据
- _index - 代表当前JSON文档所属的文档名字
- _type - 代表当前JSON文档所属的类型,虽然新版ES废弃了type的用法,但是元数据还是可以看到。
- _id - 文档唯一Id, 如果我们没有为文档指定id,系统会自动生成
- _source - 代表我们插入进去的JSON数据
- _version - 文档的版本号,每修改一次文档数据,字段就会加1, 这个字段新版的ES已经不使用了
- _seq_no - 文档的版本号, 替代老的_version字段
- _primary_term - 文档所在主分区,这个可以跟_seq_no字段搭配实现乐观锁。
- index(索引)
- 插入index
PUT /{indexName}
服务器返回一个 JSON 对象,里面的acknowledged字段表示操作成功。
{
"acknowledged":true,
"shards_acknowledged":true
}
- 删除index
DELETE /{indexName}
- 查询当前结点下的所有index
GET /_cat/indices?v
- document(文档)
- 插入文档
PUT /{index}/{type}/{id}
{
"field": "value",
...
}
{index} - 索引名
{type} - 文档类型名 - 新版的Elasticsearch为了兼容老版本,只允许指定一个类型,随便设置一个类型名就行。
{id} - 文档的唯一id, 可以不指定, 如果不指定id, 需要使用POST发送请求
- 查询文档
GET /{index}/{type}/{id}
- 更新文档
POST /{index}/_update/{id}
{
"doc":{ // 在doc字段中指定需要更新的字段
// 需要更新的字段列表
}
}
如果我们只想更新json文档的某些字段,可以使用局部更新
- 删除文档
DELETE /{index}/{type}/{id}
查询语法
- 查询的基本语法结构
GET /{索引名}/_search
{
"from" : 0, // 返回搜索结果的开始位置
"size" : 10, // 分页大小,一次返回多少数据
"_source" :[ ...需要返回的字段数组... ],
"query" : { ...query子句... },
"aggs" : { ..aggs子句.. },
"sort" : { ..sort子句.. }
}
{索引名},支持支持一次搜索多个索引,多个索引使用逗号分隔,例子:GET /order1,order2/_search
或者按照前缀匹配索引名称:
GET /order*/_search
当我们执行查询语句时,返回的json数据格式如下:
{
"took" : 5, // 查询消耗时间,单位毫秒
"timed_out" : false, // 查询是否超时
"_shards" : { // 本次查询参与的ES分片信息,查询中参与分片的总数,以及这些分片成功了多少个失败了多少个
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : { // hits字段包含我们搜索匹配的结果
"total" : { // 匹配到的文档总数
"value" : 1, // 找到1个文档
"relation" : "eq"
},
"max_score" : 1.0, // 匹配到的最大分值
"hits" : [
// 这里就是我们具体的搜索结果,是一个JSON文档数组
]
}
}
- 查询语句
- query子句查询
query子句主要用来编写类似SQL的Where语句,支持布尔查询(and/or)、IN、全文搜索、模糊匹配、范围查询(大于小于)。
- aggs子句查询
aggs子句,主要用来编写统计分析语句,类似SQL的group by语句
- sort子句查询
sort子句,用来设置排序条件,类似SQL的order by语句
- 分页查询
ES查询的分页主要通过from和size参数设置,类似MYSQL 的limit和offset语句
例如:
GET /order_v2/_search
{
"from": 0,
"size": 20,
"query": {
"match_all": {}
}
}
- _source
_source用于设置查询结果返回什么字段,类似Select语句后面指定字段。
GET /order_v2/_search
{
"_source": ["order_no","shop_id"],
"query": {
"match_all": {}
}
}
Query查询语法
- 匹配单个字段
{FIELD} - 就是我们需要匹配的字段名
{TEXT} - 就是我们需要匹配的内容
GET /{索引名}/_search
{
"query": {
"match": {
"{FIELD}": "{TEXT}"
}
}
}
GET /article/_search
{
"query": {
"match" : {
"title" : "ES教程"
}
}
}
- 精准匹配单个字段
如果我们想要类似SQL语句中的等值匹配,不需要进行分词处理,例如:订单号、手机号、时间字段,不需要分值处理,只要精确匹配。
{FIELD} - 就是我们需要匹配的字段名
{VALUE} - 就是我们需要匹配的内容,除了TEXT类型字段以外的任意类型。
GET /{索引名}/_search
{
"query": {
"term": {
"{FIELD}": "{VALUE}"
}
}
}
GET /order_v2/_search
{
"query": {
"term": {
"order_no": "202003131209120999"
}
}
}
select * from order_v2 where order_no = “202003131209120999”
- SQL中的in语法
如果我们要实现SQL中的in语句,一个字段包含给定数组中的任意一个值就匹配。
{FIELD} - 就是我们需要匹配的字段名
{VALUE1}, {VALUE2} … {VALUE N} - 就是我们需要匹配的内容,除了TEXT类型字段以外的任意类型。
GET /order_v2/_search
{
"query": {
"terms": {
"{FIELD}": [
"{VALUE1}",
"{VALUE2}"
]
}
}
}
GET /order_v2/_search
{
"query": {
"terms": {
"shop_id": [123,100,300]
}
}
}
select * from order_v2 where shop_id in (123,100,300)
- 范围查询
通过range实现范围查询,类似SQL语句中的>, >=, <, <=表达式。
参数说明:
{FIELD} - 字段名
gte范围参数 - 等价于>=
lte范围参数 - 等价于 <=
范围参数可以只写一个,例如:仅保留 “gte”: 10, 则代表 FIELD字段 >= 10
参数范围:
gt - 大于 ( > )
gte - 大于且等于 ( >= )
lt - 小于 ( < )
lte - 小于且等于 ( <= )
GET /{索引名}/_search
{
"query": {
"range": {
"{FIELD}": {
"gte": 10,
"lte": 20
}
}
}
}
GET /order_v2/_search
{
"query": {
"range": {
"shop_id": {
"gte": 10,
"lte": 200
}
}
}
}
select * from order_v2 where shop_id >= 10 and shop_id <= 200
- 组合查询(bool)
前面的例子都是设置单个字段的查询条件,如果需要编写类似SQL的Where语句,组合多个字段的查询条件,可以使用bool语句。在ES中bool查询就是用来组合布尔查询条件,布尔查询条件,就是类似SQL中的and (且)、or (或)。在SQL中,我们需要and和or,还有括号来组合查询条件,在ES中使用bool查询可用做到同样的效果。
GET /{索引名}/_search
{
"query": {
"bool": { // bool查询
"must": [], // must条件,类似SQL中的and, 代表必须匹配条件
"must_not": [], // must_not条件,跟must相反,必须不匹配条件
"should": [] // should条件,类似SQL中or, 代表匹配其中一个条件
}
}
}
- SQL中的and查询(must)
类似SQL的and,代表必须匹配的条件。
GET /{索引名}/_search
{
"query": {
"bool": {
"must": [
{匹配条件1},
{匹配条件2},
...可以有N个匹配条件...
]
}
}
}
GET /order_v2/_search
{
"query": {
"bool": {
"must": [
{
"term": {
"order_no": "202003131209120999"
}
},
{
"term": {
"shop_id": 123
}
}
]
}
}
}
select * from order_v2 where order_no=“202003131209120999” and shop_id=123
- SQL中的不等于条件(must_not)
跟must的作用相反。
GET /{索引名}/_search
{
"query": {
"bool": {
"must_not": [
{匹配条件1},
{匹配条件2},
...可以有N个匹配条件...
]
}
}
}
GET /order_v2/_search
{
"query": {
"bool": {
"must_not": [
{
"term": {
"shop_id": 1
}
},
{
"term": {
"shop_id": 2
}
}
]
}
}
}
select * from order_v2 where shop_id != 1 and shop_id != 2
- SQL中的or条件(should)
类似SQL中的 or, 只要匹配其中一个条件即可
GET /{索引名}/_search
{
"query": {
"bool": {
"should": [
{匹配条件1},
{匹配条件2},
…可以有N个匹配条件…
]
}
}
}
GET /order_v2/_search
{
"query": {
"bool": {
"should": [
{
"match": {
"order_no": "202003131209120999"
}
},
{
"match": {
"order_no": "22222222222222222"
}
}
]
}
}
}
select * from order_v2 where order_no=“202003131209120999” or order_no=“22222222222222222”
- bool综合条件
GET /order_v2/_search
{
"query": {
"bool": {
"should": [
{
"bool": {
"must": [
{
"term": {
"order_no": "2020031312091209991"
}
},
{
"range": {
"shop_id": {
"gte": 10,
"lte": 200
}
}
}
]
}
},
{
"terms": {
"tag": [
1,
2,
3,
4,
5,
12
]
}
}
]
}
}
}
select * from order_v2 where (order_no=‘202003131209120999’ and (shop_id>=10 and shop_id<=200)) or tag in (1,2,3,4,5)
- MySQL中的order by(排序子句)
GET /{索引名}/_search
{
"query": {
...查询条件....
},
"sort": [
{
"{Field1}": { // 排序字段1
"order": "desc" // 排序方向,asc或者desc, 升序和降序
}
},
{
"{Field2}": { // 排序字段2
"order": "desc" // 排序方向,asc或者desc, 升序和降序
}
}
....多个排序字段.....
]
}
GET /order_v2/_search
{
"query": {
"match_all": {}
},
"sort": [
{
"order_no": {
"order": "desc"
}
},
{
"shop_id": {
"order": "asc"
}
}
]
}
select * from order_v2 order by order_no desc, shop_id asc
- MySQL中的(sum/count/avg/group by) 聚合统计
{
"aggregations" : {
"<aggregation_name>" : {
"<aggregation_type>" : {
<aggregation_body>
}
[,"aggregations" : { [<sub_aggregation>]+ } ]? // 嵌套聚合查询,支持多层嵌套
}
[,"<aggregation_name_2>" : { ... } ]* // 多个聚合查询,每个聚合查询取不同的名字
}
}
- aggregations - 代表聚合查询语句,可以简写为aggs
- <aggregation_name> - 代表一个聚合计算的名字,可以随意命名,因为ES支持一次进行多次统计分析查询,后面需要通过这个名字在查询结果中找到我们想要的计算结果。
- <aggregation_type> - 聚合类型,代表我们想要怎么统计数据,主要有两大类聚合类型,桶聚合和指标聚合,这两类聚合又包括多种聚合类型,例如:指标聚合:sum、avg, 桶聚合:terms、Date histogram等等。
- <aggregation_body> - 聚合类型的参数,选择不同的聚合类型,有不同的参数。
- aggregation_name_2 - 代表其他聚合计算的名字,意思就是可以一次进行多种类型的统计。
案例:
GET /order/_search
{
"size" : 0, // 设置size=0的意思就是,仅返回聚合查询结果,不返回普通query查询结果。
"aggs" : { // 聚合查询语句的简写
"popular_colors" : { // 给聚合查询取个名字,叫popular_colors
"terms" : { // 聚合类型为,terms,terms是桶聚合的一种,类似SQL的group by的作用,根据字段分组,相同字段值的文档分为一组。
"field" : "color" // terms聚合类型的参数,这里需要设置分组的字段为color,根据color分组
}
}
}
}
select count(color) from order group by color
- MySQL中聚合函数(指标聚合)
ES指标聚合,就是类似SQL的统计函数,指标聚合可以单独使用,也可以跟桶聚合一起使用。
-
Value Count - 类似sql的count函数,统计总数
-
Cardinality - 类似SQL的count(DISTINCT 字段), 统计不重复的数据总数
-
Avg - 求平均值
-
Sum - 求和
-
Max - 求最大值
-
Min - 求最小值
-
MySQL中的count
GET /sales/_search?size=0
{
"aggs": {
"types_count": { // 聚合查询的名字,随便取个名字
"value_count": { // 聚合类型为:value_count
"field": "type" // 计算type这个字段值的总数
}
}
}
}
{
...
"aggregations": {
"types_count": { // 聚合查询的名字
"value": 7 // 统计结果
}
}
}
值聚合,主要用于统计文档总数,类似SQL的count函数。
select count(type) from sales
- MySQL中的distinct(Cardinality)
基数聚合,也是用于统计文档的总数,跟Value Count的区别是,基数聚合会去重,不会统计重复的值,类似SQL的count(DISTINCT 字段)用法。
前面提到基数聚合的作用等价于SQL的count(DISTINCT 字段)的用法,其实不太准确,因为SQL的count统计结果是精确统计不会丢失精度,但是ES的cardinality基数聚合统计的总数是一个近似值,会有一定的误差,这么做的目的是为了性能,因为在海量的数据中精确统计总数是非常消耗性能的,但是很多业务场景不需要精确的结果,只要近似值,例如:统计网站一天的访问量,有点误差没关系。
POST /sales/_search?size=0
{
"aggs" : {
"type_count" : { // 聚合查询的名字,随便取一个
"cardinality" : { // 聚合查询类型为:cardinality
"field" : "type" // 根据type这个字段统计文档总数
}
}
}
}
{
...
"aggregations" : {
"type_count" : { // 聚合查询的名字
"value" : 3 // 统计结果
}
}
}
select count(DISTINCT type) from sales
- Avg
POST /exams/_search?size=0
{
"aggs": {
"avg_grade": { // 聚合查询名字,随便取一个名字
"avg": { // 聚合查询类型为: avg
"field": "grade" // 统计grade字段值的平均值
}
}
}
}
{
...
"aggregations": {
"avg_grade": { // 聚合查询名字
"value": 75.0 // 统计结果
}
}
}
- Sum
POST /sales/_search?size=0
{
"aggs": {
"hat_prices": { // 聚合查询名字,随便取一个名字
"sum": { // 聚合类型为:sum
"field": "price" // 计算price字段值的总和
}
}
}
}
{
...
"aggregations": {
"hat_prices": { // 聚合查询名字
"value": 450.0 // 统计结果
}
}
}
- Max
POST /sales/_search?size=0
{
"aggs": {
"max_price": { // 聚合查询名字,随便取一个名字
"max": { // 聚合类型为:max
"field": "price" // 求price字段的最大值
}
}
}
}
{
...
"aggregations": {
"max_price": { // 聚合查询名字
"value": 200.0 // 最大值
}
}
}
- Min
POST /sales/_search?size=0
{
"aggs": {
"min_price": { // 聚合查询名字,随便取一个
"min": { // 聚合类型为: min
"field": "price" // 求price字段值的最小值
}
}
}
}
{
...
"aggregations": {
"min_price": { // 聚合查询名字
"value": 10.0 // 最小值
}
}
}
- group by
Elasticsearch桶聚合,目的就是数据分组,先将数据按指定的条件分成多个组,然后对每一个组进行统计。 组的概念跟桶是等同的,在ES中统一使用桶(bucket)这个术语。ES桶聚合的作用跟SQL的group by的作用是一样的,区别是ES支持更加强大的数据分组能力,SQL只能根据字段的唯一值进行分组,分组的数量跟字段的唯一值的数量相等,例如: group by 店铺id, 去掉重复的店铺ID后,有多少个店铺就有多少个分组。
桶聚合如下:
- Terms聚合 - 类似SQL的group by,根据字段唯一值分组
- Histogram聚合 - 根据数值间隔分组,例如: 价格按100间隔分组,0、100、200、300等等
- Date histogram聚合 - 根据时间间隔分组,例如:按月、按天、按小时分组
- Range聚合 - 按数值范围分组,例如: 0-150一组,150-200一组,200-500一组。
terms聚合的作用跟SQL中group by作用一样,都是根据字段唯一值对数据进行分组(分桶),字段值相等的文档都分到同一个桶内。
GET /order/_search?size=0
{
"aggs": {
"shop": { // 聚合查询的名字,随便取个名字
"terms": { // 聚合类型为: terms
"field": "shop_id" // 根据shop_id字段值,分桶
}
}
}
}
{
...
"aggregations" : {
"shop" : { // 聚合查询名字
"buckets" : [ // 桶聚合结果,下面返回各个桶的聚合结果
{
"key" : "1", // key分桶的标识,在terms聚合中,代表的就是分桶的字段值
"doc_count" : 6 // 默认的指标聚合是统计桶内文档总数
},
{
"key" : "5",
"doc_count" : 3
},
{
"key" : "9",
"doc_count" : 2
}
]
}
}
}
select shop_id, count(*) from order group by shop_id
参考资料
-
https://www.ruanyifeng.com/blog/2017/08/elasticsearch.html
-
https://www.tizi365.com/archives/628.html