MK
摩柯社区 - 一个极简的技术知识社区
AI 面试

ElasticSearch返回信息过滤API的灵活运用

2021-07-018.0k 阅读

ElasticSearch 返回信息过滤 API 的基础认知

在 ElasticSearch 中,返回信息过滤 API 是一项极为关键的功能,它允许用户精准地控制从 ElasticSearch 集群检索到的信息。通过灵活运用这些 API,开发者能够极大地提升数据获取的效率,减少不必要的数据传输,进而优化应用程序的性能。

源字段过滤(Source Filtering)

源字段过滤是 ElasticSearch 返回信息过滤中最为基础的部分。默认情况下,ElasticSearch 在返回搜索结果时会包含文档的完整 _source 字段内容。然而,在许多实际场景中,我们可能只需要其中的部分字段。例如,假设我们有一个存储博客文章的索引,文档结构如下:

{
    "title": "这是一篇精彩的博客文章",
    "author": "张三",
    "content": "这里是文章的详细内容,非常长……",
    "publish_date": "2023 - 01 - 01",
    "tags": ["技术", "生活"]
}

如果我们只是想获取文章的标题和作者,而不需要冗长的文章内容,就可以使用源字段过滤。在 ElasticSearch 的查询 DSL(Domain - Specific Language)中,通过 _source 参数来指定需要返回的字段。例如,使用 HTTP 请求的方式:

GET /blog_posts/_search
{
    "_source": ["title", "author"],
    "query": {
        "match_all": {}
    }
}

上述代码中,_source 数组内指定了我们希望返回的字段。如果只想排除某些字段,也可以使用否定语法:

GET /blog_posts/_search
{
    "_source": {
        "excludes": ["content"]
    },
    "query": {
        "match_all": {}
    }
}

在这个例子中,content 字段将不会出现在返回结果中。

脚本字段(Script Fields)

脚本字段允许我们基于文档中的现有字段,通过自定义脚本生成新的字段,并在返回结果中包含这些新字段。这在需要对数据进行一些计算或转换时非常有用。例如,我们有一个索引存储商品信息,其中包含商品价格 price 和折扣率 discount 字段,我们想在搜索结果中直接获取折扣后的价格。首先,确保 ElasticSearch 开启了脚本功能(在较新版本中默认开启,但某些安全配置下可能需要额外设置)。然后,使用如下查询:

GET /products/_search
{
    "script_fields": {
        "discounted_price": {
            "script": {
                "lang": "painless",
                "source": "doc['price'].value * (1 - doc['discount'].value)"
            }
        }
    },
    "query": {
        "match_all": {}
    }
}

上述代码中,script_fields 定义了一个新的字段 discounted_price。使用 painless 脚本语言,通过源文档中的 pricediscount 字段计算出折扣后的价格。脚本字段生成的结果会出现在返回结果的 fields 部分,而不会影响 _source 字段内容。

深度过滤:聚合结果的过滤

在 ElasticSearch 中,聚合(Aggregations)是一项强大的数据分析功能,它允许我们对搜索结果进行分组、统计等操作。同时,我们也可以对聚合结果进行过滤,以获取更有针对性的数据。

桶选择器(Bucket Selector)

桶选择器是一种在聚合桶层面进行过滤的工具。假设我们对博客文章按标签进行聚合,并且只想获取包含文章数量大于 10 的标签桶。我们可以这样实现:

GET /blog_posts/_search
{
    "size": 0,
    "aggs": {
        "by_tags": {
            "terms": {
                "field": "tags"
            },
            "aggs": {
                "filter_buckets": {
                    "bucket_selector": {
                        "buckets_path": {
                            "count": "_count"
                        },
                        "script": "params.count > 10"
                    }
                }
            }
        }
    }
}

在这个例子中,首先通过 terms 聚合按 tags 字段对文档进行分组。然后,使用 bucket_selector 对生成的桶进行过滤。buckets_path 定义了要引用的桶指标,这里 _count 表示每个标签桶中的文档数量。script 则定义了过滤条件,只有满足 count > 10 的桶才会出现在最终的聚合结果中。

管道聚合过滤(Pipeline Aggregation Filtering)

管道聚合允许我们基于其他聚合的结果进行进一步的聚合操作。同样也可以对管道聚合的结果进行过滤。例如,我们先按月份对销售数据进行聚合,然后计算每个月的销售总额占全年销售总额的比例,并且只保留占比大于 5% 的月份。

GET /sales/_search
{
    "size": 0,
    "aggs": {
        "sales_by_month": {
            "date_histogram": {
                "field": "sale_date",
                "calendar_interval": "month"
            },
            "aggs": {
                "total_sales_per_month": {
                    "sum": {
                        "field": "amount"
                    }
                }
            }
        },
        "total_sales": {
            "sum": {
                "field": "amount"
            }
        },
        "percentage_of_total": {
            "bucket_script": {
                "buckets_path": {
                    "monthly_total": "sales_by_month>total_sales_per_month"
                },
                "script": {
                    "source": "params.monthly_total / params._agg.total_sales.value * 100",
                    "lang": "painless"
                }
            },
            "aggs": {
                "filter_percentage": {
                    "bucket_selector": {
                        "buckets_path": {
                            "percentage": "_value"
                        },
                        "script": "params.percentage > 5"
                    }
                }
            }
        }
    }
}

在这段代码中,首先通过 date_histogram 按月份对销售数据进行聚合,并计算每个月的销售总额。然后,通过 sum 聚合计算全年销售总额。接着,使用 bucket_script 计算每个月销售总额占全年销售总额的百分比。最后,使用 bucket_selector 对百分比结果进行过滤,只保留占比大于 5% 的月份。

多层嵌套文档的返回信息过滤

当文档结构较为复杂,存在多层嵌套关系时,返回信息过滤需要更多的技巧和特定的语法。

嵌套字段过滤(Nested Field Filtering)

假设我们有一个索引存储电商订单信息,每个订单可能包含多个商品,商品信息以嵌套文档的形式存储。文档结构如下:

{
    "order_id": "12345",
    "customer": "李四",
    "order_date": "2023 - 02 - 15",
    "products": [
        {
            "product_name": "手机",
            "price": 5000,
            "quantity": 1
        },
        {
            "product_name": "耳机",
            "price": 500,
            "quantity": 2
        }
    ]
}

如果我们只想获取订单中包含价格大于 1000 的商品信息,并且在返回结果中体现。可以使用如下查询:

GET /orders/_search
{
    "query": {
        "nested": {
            "path": "products",
            "query": {
                "range": {
                    "products.price": {
                        "gt": 1000
                    }
                }
            }
        }
    },
    "nested": [
        {
            "path": "products",
            "inner_hits": {
                "name": "matching_products",
                "_source": ["product_name", "price"]
            }
        }
    ]
}

在这个例子中,nested 查询用于匹配包含价格大于 1000 的商品的订单。inner_hits 则用于在返回结果中展示匹配的嵌套文档(商品信息),并且通过 _source 过滤只返回 product_nameprice 字段。

父子文档过滤(Parent - Child Document Filtering)

父子文档关系在 ElasticSearch 中也是常见的结构。例如,我们有一个博客文章索引,文章和评论是父子关系。假设文章文档如下:

{
    "title": "技术分享",
    "author": "王五",
    "content": "……",
    "_id": "article1"
}

评论文档如下:

{
    "comment": "写得真好",
    "author": "赵六",
    "_parent": "article1"
}

如果我们想获取某篇文章及其点赞数大于 10 的评论。可以这样查询:

GET /blog/_search
{
    "query": {
        "has_child": {
            "type": "comment",
            "query": {
                "range": {
                    "likes": {
                        "gt": 10
                    }
                }
            }
        }
    },
    "inner_hits": {
        "name": "matching_comments",
        "type": "comment",
        "_source": ["comment", "author", "likes"]
    }
}

这里 has_child 查询用于匹配包含点赞数大于 10 的评论的文章。inner_hits 用于在返回结果中展示匹配的评论,并通过 _source 过滤评论的相关字段。

基于地理空间数据的返回信息过滤

随着地理空间应用的增多,ElasticSearch 对地理空间数据的支持也越来越完善。在处理地理空间数据时,也可以对返回信息进行过滤。

地理距离过滤(Geo - Distance Filtering)

假设我们有一个索引存储店铺信息,每个店铺包含地理位置信息(经纬度)。我们想获取距离某个坐标点(例如北京天安门)10 公里以内的店铺,并且只返回店铺名称和地址。可以使用如下查询:

GET /shops/_search
{
    "query": {
        "geo_distance": {
            "distance": "10km",
            "location": {
                "lat": 39.9042,
                "lon": 116.4074
            }
        }
    },
    "_source": ["shop_name", "address"]
}

在这个例子中,geo_distance 查询用于筛选距离指定坐标点 10 公里以内的店铺。_source 则用于过滤返回的字段,只保留店铺名称和地址。

地理边界框过滤(Geo - Bounding Box Filtering)

如果我们想获取位于某个矩形区域内的店铺,可以使用地理边界框过滤。例如,获取位于某个城市特定区域内的店铺:

GET /shops/_search
{
    "query": {
        "geo_bounding_box": {
            "location": {
                "top_left": {
                    "lat": 39.95,
                    "lon": 116.35
                },
                "bottom_right": {
                    "lat": 39.85,
                    "lon": 116.45
                }
            }
        }
    },
    "_source": ["shop_name", "rating"]
}

这里 geo_bounding_box 查询通过指定矩形区域的左上角和右下角坐标,筛选出位于该区域内的店铺。_source 过滤只返回店铺名称和评分。

高级过滤技巧:结合多种过滤方式

在实际应用中,往往需要结合多种过滤方式来满足复杂的业务需求。

源字段过滤与聚合过滤结合

假设我们有一个新闻文章索引,文章包含标题、内容、发布时间、分类等字段。我们想获取科技类文章中,按年份聚合后发布数量大于 100 的年份,并且在返回结果中只显示年份和文章标题。可以这样实现:

GET /news/_search
{
    "size": 0,
    "_source": ["title"],
    "query": {
        "match": {
            "category": "科技"
        }
    },
    "aggs": {
        "by_year": {
            "date_histogram": {
                "field": "publish_date",
                "calendar_interval": "year"
            },
            "aggs": {
                "filter_buckets": {
                    "bucket_selector": {
                        "buckets_path": {
                            "count": "_count"
                        },
                        "script": "params.count > 100"
                    }
                }
            }
        }
    }
}

在这个例子中,首先通过 match 查询筛选出科技类文章,并通过 _source 过滤只保留标题字段。然后,对筛选后的文章按年份进行聚合,并使用 bucket_selector 对聚合结果进行过滤,只保留文章数量大于 100 的年份。

嵌套文档过滤与地理空间过滤结合

假设我们有一个索引存储旅游景点信息,每个景点可能包含多个推荐路线,路线信息以嵌套文档形式存储,并且景点包含地理位置信息。我们想获取位于某个区域内,且推荐路线中距离小于 5 公里的景点及其路线信息。可以使用如下查询:

GET /tourist_spots/_search
{
    "query": {
        "bool": {
            "must": [
                {
                    "geo_bounding_box": {
                        "location": {
                            "top_left": {
                                "lat": 30.5,
                                "lon": 104.0
                            },
                            "bottom_right": {
                                "lat": 30.4,
                                "lon": 104.1
                            }
                        }
                    }
                },
                {
                    "nested": {
                        "path": "routes",
                        "query": {
                            "range": {
                                "routes.distance": {
                                    "lt": 5
                                }
                            }
                        }
                    }
                }
            ]
        }
    },
    "nested": [
        {
            "path": "routes",
            "inner_hits": {
                "name": "matching_routes",
                "_source": ["route_name", "distance"]
            }
        }
    ]
}

在这个例子中,通过 bool 查询结合地理边界框过滤和嵌套文档过滤。先通过地理边界框筛选出位于指定区域内的景点,再通过嵌套文档过滤筛选出推荐路线中距离小于 5 公里的景点。inner_hits 用于在返回结果中展示匹配的路线信息,并通过 _source 过滤只返回路线名称和距离。

通过灵活运用上述各种 ElasticSearch 返回信息过滤 API,开发者能够根据具体的业务场景,精准地获取所需的数据,提升应用程序的性能和用户体验。无论是简单的源字段过滤,还是复杂的多层嵌套、地理空间等多种过滤方式的结合,都为数据的高效利用提供了强大的支持。在实际开发中,需要根据数据结构和业务需求,不断探索和优化过滤策略,以达到最佳的效果。同时,也要注意在使用脚本等功能时的安全性和性能问题,合理配置 ElasticSearch 集群,确保系统的稳定运行。例如,在使用脚本时,要避免复杂度过高的脚本,防止性能瓶颈;在处理大量数据时,要合理设置分页和批量处理参数,避免内存溢出等问题。只有综合考虑各方面因素,才能充分发挥 ElasticSearch 返回信息过滤 API 的优势。