Elasticsearch Query DSL入门

Elasticsearch Query DSL入门

列举几个最基础的 DSL 语句,所有长达成百上千行的 DSL 都是由这些基础语法组合起来的。

一、环境

  • Ubuntu 14.04/16、04
  • JDK1.8
  • Elasticsearch 5.3
  • Kibana 5.3.2

二、DSL介绍

Query DSL 又叫查询表达式,是一种非常灵活又富有表现力的查询语言,采用 JSON 接口的方式实现丰富的查询,并使你的查询语句更灵活、更精确、更易读且易调试。

我平时喜欢借助 Kibana 来执行 DSL 语句,它能够辅助自己开发,也能用于 debug 和研究。

实际项目中一般封装一下第三方引擎来实现业务,而这些语法转换成 DSL 后经常有成百上千行,但其实一点也不复杂。不管是过滤、聚合还是嵌套,只要理解了最基本的语法,都是很好解读的。

这篇文章主要还是记录下我平时常用的几个 DSL,防止隔得时间久了,裸写 DSL 不一定写得出来。

三、全文查询

1. match_all

/_search 查找整个ES中所有索引的内容,/ 前面可以加上索引名,多个索引名之前用英文逗号分割。
下面这个语法是 match_all 查询,Kibana 能够自动补全代码,最简单。

bash
GET /_search
{
  "query": {
    "match_all": {}
  }
}

2. match

下边的例子就表示查找 host 为 wenyuanblog.com 的所有记录。

bash
POST /wenyuanblog-2018.03.02/_search
{
  "query": {
    "match": {
      "host": "wenyuanblog.com"
    }
  }
}

3. multi_match

在多个字段上执行相同的 match 查询,下边的例子就表示查询 host 或 http_referer 字段中包含 wenyuanblog.com 的记录。

bash
GET /wenyuanblog-2018.03.02/_search
{
  "query": {
    "multi_match": {
      "query": "wenyuanblog.com",
      "fields": [
        "host",
        "http_referer"
      ]
    }
  }
}

4. query_string

可以在查询里边使用 AND 或者 OR 来完成复杂的查询。

下边的例子表示查找 host 为 a.wenyuanblog.com 或者 b.wenyuanblog.com 的所有记录。

bash
GET /wenyuanblog-2018.03.02/_search
{
  "query": {
    "query_string": {
      "query": "(a.wenyuanblog.com) OR (b.wenyuanblog.com)",
      "fields": [
        "host"
      ]
    }
  }
}

也可以组合更多的条件完成更复杂的查询请求。

下边的例子表示查询(host 为 a.wenyuanblog.com)或者是(host 为 b.wenyuanblog.com 且 status 为 404)的所有记录。

bash
GET /wenyuanblog-2018.03.02/_search
{
  "query": {
    "query_string": {
      "query": "host:a.wenyuanblog.com OR (host:b.wenyuanblog.com AND status:404)"
    }
  }
}

与其相类似的还有个 simple_query_string 的关键字,可以将 query_string 中的 AND 或 OR 用 + 或 | 这样的符号替换掉。

5. term

term 可以用来精确匹配,精确匹配的值可以是数字、时间、布尔值或者是设置了 not_analyzed 不分词的字符串。

bash
GET /wenyuanblog-2018.03.02/_search
{
  "query": {
    "term": {
      "status": {
        "value": 404
      }
    }
  }
}

term 对输入的文本不进行分析,直接精确匹配输出结果,如果要同时匹配多个值可以使用 terms。

bash
GET /wenyuanblog-2018.03.02/_search
{
  "query": {
    "terms": {
      "status": [
        403,
        404
      ]
    }
  }
}

6. range

range 用来查询落在指定区间内的数字或者时间。
下边的例子表示搜索所有状态为 200 到 399 之间的数据,这里的操作符主要有四个 gt 大于,gte 大于等于,lt 小于,lte 小于等于。

bash
GET /wenyuanblog-2018.03.02/_search
{
  "query": {
    "range": {
      "status": {
        "gte": 200,
        "lte": 399
      }
    }
  }
}

当使用日期作为范围查询时,我们需要注意下日期的格式,官方支持的日期格式主要有两种:

① 时间戳,注意是毫秒粒度。

bash
GET /wenyuanblog-2018.03.02/_search
{
  "query": {
    "range": {
      "@timestamp": {
        "gte": 1519920000000,
        "lte": 1519956000000,
        "format": "epoch_millis"
      }
    }
  }
}

② 日期字符串。

bash
GET /wenyuanblog-2018.03.02/_search
{
  "query": {
    "range": {
      "@timestamp": {
        "gte": "2018-03-02 00:00:00",
        "lte": "2018-03-03",
        "format": "yyyy-MM-dd HH:mm:ss||yyyy-MM-dd",
        "time_zone": "+08:00"
      }
    }
  }
}

选择哪种方式根据实际情况决定,我们业务中用时间戳的情况居多。

如果采用日期字符串的方式,那么可以使用 format 字段指定匹配的日期格式,如果格式有多个就用 || 分开,像例子中那样,不过建议用同样的日期格式。

如果日期中缺少年月日这些内容,那么缺少的部分会用 unix 的开始时间(即 1970年1月1日)填充,当你将 “format”:”dd” 指定为格式时,那么 “gte”:10 将被转换成 1970-01-10T00:00:00.000Z。

Elasticsearch 中默认使用的是 UTC 时间,所以我们在使用时要通过 time_zone 来设置好时区,以免出错。

7. exists

查询出存在某字段的文档。

bash
GET /wenyuanblog-2018.03.02/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "exists": {
            "field": "visitor.name"
          }
        }
      ]
    }
  },
  "from": 0,
  "size": 100
}

8. bool组合查询

通常我们可能需要将很多个条件组合在一起查出最后的结果,这个时候就需要使用 ES 提供的 bool 来实现了。
布尔查询支持的子查询类型共有四种,分别是:must,should,must_not 和 filter。

must:类似于 SQL 中的 AND ,必须包含;
must_not:类似于 SQL 中的 NOT,必须不包含;
should:文档应该匹配 should 子句查询的一个或多个;
filter:过滤器,文档必须匹配该过滤条件,跟 must 子句的唯一区别是,filter 不会对结果进行相关性评分 _score,换言之当我们的业务中无相关性的要求时,建议查询的过程中多用 filter。

下面是一个组合查询的例子,我们要查询 host 为 wenyuanblog.com 且 http_x_forworded_for 为 47.97.12.69 且 status 不为 200 的所有数据。

bash
GET /wenyuanblog-2018.03.02/_search
{
  "query": {
    "bool": {
      "filter": [
        {
          "match": {
            "host": "wenyuanblog.com"
          }
        },
        {
          "match": {
            "http_x_forwarded_for": "47.97.12.69"
          }
        }
      ],
      "must_not": {
        "match": {
          "status": 200
        }
      }
    }
  }
}

这里再说一下 should。通常情况下,should 子句是数组字段,包含多个 should 子查询,默认情况下,匹配的文档必须满足其中一个子查询条件。

但我们可以通过显式设置布尔查询的参数 minimum_should_match 的值,从而改变默认匹配行为。该参数控制一个文档必须匹配的 should 子查询的数量,它有很多种配置方式:

如果设置为数字 3,表示至少需要匹配 3 个 should 子句;如果设置为一个百分比,例如 “minimum_should_match”:75%,则至少满足 75% 且向下取整(5个 should 子句,5*75%=3.75,向下取整为 3,也就是至少匹配 3 个 should 子句)。

下面是个例子。(注:在 bool query 中 minimum_should_match 只能紧跟在 should 的后面,放其他地方会出异常)

bash
GET /wenyuanblog-2018.03.02/_search
{
  "query": {
    "bool": {
      "should": [
        {
          "match": {
            "host": "a.wenyuanblog.com"
          }
        },
        {
          "match": {
            "host": "b.wenyuanblog.com"
          }
        },
        {
          "match": {
            "host": "c.wenyuanblog.com"
          }
        }
      ],
      "minimum_should_match": 2
    }
  }
}

9. sort

sort 是排序,也是很常用的查询,这里我举个按时间(@timestamp)倒叙查询的例子。

bash
GET /wenyuanblog-2018.03.02/_search
{
  "query": {
    "match_all": {}
  },
  "sort": [
    {
      "@timestamp": {
        "order": "desc"
      }
    }
  ]
}

四、聚合查询

1. 分桶

根据 host 字段的值进行分桶(有点类似于 SQL 中的 group by),这里的 host_bucket 是我给该桶起的名字。

bash
GET /wenyuanblog-2018.03.02/_search
{
  "query": {
    "match_all": {}
  },
  "aggs": {
    "host_bucket": {
      "terms": {
        "field": "host"
      }
    }
  }
}

2. 度量

计算出 latency 字段的最大值(metric 有点类似于 SQL 的 avg、max、min),这里的 max_latency 是我给该度量起的名字。

bash
GET /wenyuanblog-2018.03.02/_search
{
  "query": {
    "match_all": {}
  },
  "aggs": {
    "max_latency": {
      "max": {
        "field": "latency"
      }
    }
  }
}

五、业务应用

实际业务中的一些需求,属于较综合的查询语法。

1. 聚合结果进行排序

关键词:aggregations,terms,order。

先过滤出 host 字段值为 “wenyuanblog.com” 的记录,然后对 源IP 进行分桶聚合,最后根据聚合查询到的 文档数量倒序排序。

bash
GET /wenyuanblog-2019.05.*/_search
{
  "query": {
    "bool": {
      "filter": [
        {
          "range": {
            "@timestamp": {
              "to": 1557503999000,
              "from": 1557417600000
            }
          }
        },
        {
          "term": {
            "host": "wenyuanblog.com"
          }
        }
      ]
    }
  },
  "from": 0,
  "size": 0,
  "aggregations": {
    "src_ip_bucket": {
      "terms": {
        "field": "http.src_ip.dotted",
        "size": 10,
        "order": {
          "_count": "desc"
        }
      }
    }
  }
}

2. IP范围过滤+分桶+度量+脚本+排序

关键词:range,aggregations,terms,metric,script,order。

先根据 IP范围 过滤,同时排除指定 host;
接着对 源IP 进行分桶聚合,再计算 进流量、出流量 在该时间段内的总和,用脚本计算出 总流量(进流量+出流量) ,最后根据 总流量(进流量+出流量) 数值大小进行倒序排序。

使用groovy:

bash
GET /wenyuanblog-2019.05.*/_search
{
  "query": {
    "bool": {
      "filter": [
        {
          "bool": {
            "must": [
              {
                "range": {
                  "@timestamp": {
                    "to": 1557503999000,
                    "from": 1557417600000
                  }
                }
              },
              {
                "range": {
                  "http.src_ip.dotted": {
                    "to": "10.255.255.255",
                    "from": "10.0.0.0"
                  }
                }
              },
              {
                "bool": {
                  "must_not": [
                    {
                      "terms": {
                        "host": [
                          "a.wenyuanblog.com",
                          "b.wenyuanblog.com"
                        ]
                      }
                    }
                  ]
                }
              }
            ]
          }
        }
      ]
    }
  },
  "from": 0,
  "size": 0,
  "aggs": {
    "appid_bucket": {
      "terms": {
        "field": "http.src_ip.dotted",
        "order": {
          "sum_total_bytes": "desc"
        },
        "size": 100
      },
      "aggs": {
        "sum_in_bytes": {
          "sum": {
            "field": "http.in_bytes"
          }
        },
        "sum_out_bytes": {
          "sum": {
            "field": "http.out_bytes"
          }
        },
        "sum_total_bytes": {
          "sum": {
            "script": {
              "lang": "groovy",
              "inline": "doc['http.in_bytes'].value + doc['http.out_bytes'].value"
            }
          }
        }
      }
    }
  }
}

使用 painless,修改 script 部分如下:

bash
"sum_total_bytes": {
  "sum": {
    "script": {
      "lang": "painless",
      "inline": "doc['tcp.in_bytes'].value + doc['tcp.out_bytes'].value"
    }
  }
}

以前一直用 groovy,因为比较好用。但最近两年的某个版本开始将其 Deprecation 了,官方推荐使用 painless。
painless 也能实现一些脚本语法,具体可以上官网查询。

3. IP范围过滤+聚合过滤+度量

关键词:range,aggregations,filter,metric。

先根据 IP范围 过滤,然后开始聚合,筛选出 目的端口 是 80、443 的数据,再计算这些数据中(80、443端口) 进流量、出流量 在该时间段内的总和。

至于为什么要在聚合中进行过滤而不是在聚合前就过滤,是因为下面语句只是完整 dsl 的一部分,该查询业务还要同时对其它端口进行聚合操作。

bash
GET /wenyuanblog-2019.05.*/_search
{
  "query": {
    "bool": {
      "filter": [
        {
          "bool": {
            "must": [
              {
                "range": {
                  "@timestamp": {
                    "to": 1557503999000,
                    "from": 1557417600000
                  }
                }
              },
              {
                "range": {
                  "http.src_ip.dotted": {
                    "to": "10.255.255.255",
                    "from": "10.0.0.0"
                  }
                }
              }
            ]
          }
        }
      ]
    }
  },
  "from": 0,
  "size": 0,
  "aggs": {
    "dport_80_443": {
      "filter": {
        "bool": {
          "must": [
            {
              "terms": {
                "http.dport": [
                  80,
                  443
                ]
              }
            }
          ]
        }
      },
      "aggs": {
        "sum_in_bytes": {
          "sum": {
            "field": "http.in_bytes"
          }
        },
        "sum_out_bytes": {
          "sum": {
            "field": "http.out_bytes"
          }
        }
      }
    }
  }
}

六、总结

ES 很强大,它支持的查询有很多,这里我只是列举了平时在 Kibana 中经常调试的 DSL。

还有很多实际业务中经常用到的模糊查询(wildcard、regexp、prefix)、nested 查询、多层聚合等等,基本上都是先封装第三方引擎,然后开发时转成 DSL 并 copy 到 Kibana 进行验证和查错。

更复杂的应用这里就不列举出来了,必要时候官方文档是最好的资料。

作者:
您可以发表评论,也可以在保留原文地址及作者的情况下引用到您的网站或博客
除特别说明以外,原创文章版权所有,无论从何处转载请注明原文出处(RP比PR重要):
http://www.eavea.com/blog/index.php/elasticsearchquerydslrumen.html | 易博云天
标签:
【上一篇】
【下一篇】

您可能感兴趣的文章:

发表评论

您必须 登录 后才能发表评论。