Elasticsearch 启动

作为一枚每天都应当是 Swift 启动的 Swifter,也需要做一些数据分析,不得已用了一段时间的 Elasticsearch (opens new window)

暂时没有 Demo。

个人目前对 Elasticsearch 的感觉就是:这是一个超快搜索聚合大数据工具。

世界上没有什么搜索是一层不能解决的,如果有,那就两层。

本文是对查询和聚合上的一些总结和感受。数据如何收集不是本文的重点。

一起来感受一下 Elasticsearch 设计的巧妙的语法。

# 查询

查询相关都在 query 字段描述,直接查询全部:

{
  "from": 0,
  "size": 10,
  "query": {
    "match_all": {}
  }
}

from 字段类似平时定义的 offsetsize 类似 limit。以上是一个搜索全部数据的例子。

来个简单的匹配?

{
  "query": {
    "match": {
      "name": "Tony"
    }
  }
}

以上是一个查询名字为 Tony 的数据。

一个拥有千亿日活 App 会有多个 Tony 用户,我们现在需要关注 18 岁的 Tony 们一些数据。 那么我们需要两个匹配条件:

  • name 为 Tony
  • age 为 18

Elasticsearch 为我们提供了一个 bool 参数:

{
  "query": {
    "bool": {
      // ... 匹配条件
    }
  }
}

bool 下面可以传入各种想要的匹配条件,比如 mustmust_notshould

满足所有的条件,那就匹配成功!

18 岁的 Tony 来了:

{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "name": "Tony"
          }
        },
        {
          "match": {
            "age": 18
          }
        }
      ]
    }
  }
}

当然我们也可以关注一个年龄段的 Tony,这是 18 到 28 的 Tony:

{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "name": "Tony"
          }
        },
        {
          "range": {
            "age": {
              "gte": 18,
              "lte": 28
            }
          }
        }
      ]
    }
  }
}

失策,有人给小姐姐起名 Tony 了,我们需要过滤掉,增加一个 must_not 即可:

{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "name": "Tony"
          }
        },
        {
          "range": {
            "age": {
              "gte": 18,
              "lte": 28
            }
          }
        }
      ],
      "must_not": [
        {
          "match": {
            "sex": "male"
          }
        }
      ]
    }
  }
}

一顿分析之后我们发现 18-28 的 Tony 和 28-38 的 Mike 似乎有什么关联,我们需要一起拿出来看看。 匹配条件为:(18-28 & Tony) | (28-38 & Mike)。

世界上没有什么搜索是一层 bool 不能解决的,如果有,那就两层 bool

{
  "query": {
    "bool": {
      "minimum_should_match": 1,
      "should": [
        {
          "bool": {
            "must": [
              {
                "match": {
                  "name": "Tony"
                }
              },
              {
                "range": {
                  "age": {
                    "gte": 18,
                    "lte": 28
                  }
                }
              }
            ]
          }
        },
        {
          "bool": {
            "must": [
              {
                "match": {
                  "name": "Mike"
                }
              },
              {
                "range": {
                  "age": {
                    "gte": 28,
                    "lte": 38
                  }
                }
              }
            ]
          }
        }
      ]
    }
  }
}

这个嵌套层数理论上可以无限层,不用担心遇到各种负载的匹配规则。 这些已经满足我常用的各种标签搜索。

# 聚合

预告:世界上没有什么聚合是一层不能解决的,如果有,那就两层。

我们的用户都提交了哪些心仪的发型?

{
  "aggs": {
    "hairstyles": { // 定义任意自己喜欢的变量名
      "terms": {
        "field": "hairstyle",
        "size": 100 // 配置需要聚合出多少种发型,默认似乎为 10,喜欢可以直接 10000
      }
    }
  }
}

这是一个 buckets 聚合,得到的结果为:

{
  "aggregations": {
    "hairstyles": {
      "doc_count_error_upper_bound": 0,
      "sum_other_doc_count": 0,
      "buckets": [
        {
          "key": "地中海",
          "doc_count": 397863
        },
        {
          "key": "最亮",
          "doc_count": 23423
        },
        // ...
      ]
    }
  }
}

为了设计更贴心的发型,我们需要知道每种发型是 18-28 期待的人数多还是 28-38 的人数多。 再加一层 aggs 即可:

{
  "aggs": {
    "hairstyles": {
      "terms": {
        "field": "hairstyle",
        "size": 100
      },
      "aggs": {
        "age_distribution": {
          "range": {
            "field": "age",
            "keyed": true,
            "ranges": [
              { "key": "18-28", "from": 18, "to": 28 },
              { "key": "28-38", "from": 28, "to": 38 }
            ]
          }
        }
      }
    }
  }
}

这样我们就可以拿到 hairstyles 中每个 bucket 对应的 age_distribution 结果。