说起 ElasticSearch,往往大家想到的都是 ELK 的一套,但是作为 NoSQL,ES 有极快的响应速度,强大的聚合功能,支持复杂的查询条件,应对高并发的复杂查询的业务场景其实也是非常强力的。

You Know, for Search

我们团队就一直使用 ES 作为主力数据库, 从一开始做全文检索,到现在承担全部的商品列表页查询。近几个月将查询系统的 qps 从 1k 优化到了 10k+,其中 ES 的优化占了很重要一部分,准确的来说,应该是对 ES 特性的扬长避短起到了非常大的作用。

数组 & 嵌套结构

ES 没有 join,很多人直接就会认为 ES 无法处理一对多的情况,其实还有数组嵌套结构可以应付常见的业务场景。

比如一个商品拥有多种属性,都存放在一个数组字段中,使用 must 和 must_not 就可以灵活地进行查询筛选。

比如同款不同色的几件T恤,使用嵌套结构保存,搜索时只需要其中一件满足筛选条件,便可以全部带出来,在页面上以多个小色块展示,而无需占用多个展示位。并且还可以拿满足筛选条件的商品中的某属性最大值/最小值等进行排序,如官网给出的示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
{
"query": {
"nested": {
"path": "parent",
"query": {
"bool": {
"must": {"range": {"parent.age": {"gte": 21}}},
"filter": {
"nested": {
"path": "parent.child",
"query": {"match": {"parent.child.name": "matt"}}
}
}
}
}
}
},
"sort" : [
{
"parent.child.age" : {
"mode" : "min",
"order" : "asc",
"nested": {
"path": "parent",
"filter": {
"range": {"parent.age": {"gte": 21}}
},
"nested": {
"path": "parent.child",
"filter": {
"match": {"parent.child.name": "matt"}
}
}
}
}
}
]
}

聚合

商品列表页面能用到聚合的场景非常多,比如聚合出分类下(可能多达数万个商品)的各子分类,各属性的数量,并且需要支持复杂的筛选条件,比如库存,价格范围等等,并且这种查询速度远比 RDS 的 join + group by + count 快。

又比如需要查出最近10天内有新商品的日期列表,那就可以用到 date_histogram 聚合函数。

动态字段

动态字段的设计也为我们的业务提供了很大便利,由于与具体业务关联性太强,就不详细展开了。

ES能支持的动态字段数量非常的多,不过这里要留意的就是动态字段一个比较容易出问题的地方,就是瞬时写入大量的动态字段会导致集群索引的元数据大量变动,master 节点负载暴涨甚至挂掉。

缺陷

  1. 没有 join。ES 的查询速度非常的快,但是不能 join 毕竟还是有一些业务场景无法使用。当然话又说回来,在高并发量下,多表 join 能不能抗得住也是个问题。对于查询,我们一贯的原则还是:把数据离线准备成便于查询的结构,线上实时查询尽可能的简单,一步到位
  2. 由于要把数据离线准备好,这便带来了数据同步更新的问题,数据的时效性、准确性都需要保证,数组与嵌套结构的数据更新也不够方便高效,这些都会增加很多的工作量。