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

ElasticSearch API返回信息过滤的要点

2021-10-213.0k 阅读

ElasticSearch API 返回信息过滤的重要性

在使用 ElasticSearch 进行数据检索时,我们往往并不需要返回所有的字段和信息。过多不必要的数据不仅会增加网络传输的负担,延长响应时间,还可能对系统性能产生负面影响。通过合理地过滤 ElasticSearch API 返回的信息,我们可以精准地获取所需的数据,提高系统的效率和响应速度。

减少网络传输

在许多应用场景中,ElasticSearch 集群与客户端可能处于不同的网络环境,甚至跨地域分布。返回大量不必要的数据意味着在网络上传输更多的字节数,这会显著增加网络带宽的消耗,尤其是在网络带宽有限的情况下,可能导致其他重要业务的网络请求受到影响。例如,在移动应用中,用户通过移动网络访问后端 ElasticSearch 服务,如果每次返回的数据量过大,会造成用户流量的浪费,并且加载数据的时间变长,影响用户体验。

提升性能

ElasticSearch 本身在处理大量数据时性能出色,但如果返回过多不必要的数据,ElasticSearch 需要花费额外的时间和资源来序列化这些数据,客户端也需要更多的时间来解析和处理这些数据。通过过滤返回信息,ElasticSearch 可以更快地返回结果,客户端也能更快地进行后续操作,从而提升整个系统的性能。

保护数据安全

有时候,某些敏感字段不应该返回给所有的客户端请求。通过信息过滤,我们可以确保敏感数据不会被意外泄露。例如,在用户信息检索中,用户的密码字段绝对不应该返回给前端应用,即使密码是加密存储的,也不应暴露在返回结果中,避免潜在的安全风险。

字段级别的过滤

包含特定字段

在 ElasticSearch API 中,我们可以通过 _source 参数来指定返回的字段。以下是使用 ElasticSearch 的 RESTful API 进行字段过滤的示例:

假设我们有一个名为 products 的索引,其中文档包含 namepricedescription 等字段。如果我们只需要获取 nameprice 字段,可以使用以下请求:

GET /products/_search
{
    "_source": ["name", "price"],
    "query": {
        "match_all": {}
    }
}

在这个例子中,_source 数组中指定了我们希望返回的字段 nameprice。这样,ElasticSearch 返回的结果中只会包含这两个字段的信息,大大减少了返回的数据量。

如果使用 ElasticSearch 的 Python 客户端 elasticsearch-py,代码示例如下:

from elasticsearch import Elasticsearch

es = Elasticsearch()

query = {
    "_source": ["name", "price"],
    "query": {
        "match_all": {}
    }
}

response = es.search(index='products', body=query)
for hit in response['hits']['hits']:
    print(hit['_source'])

排除特定字段

除了指定包含的字段,我们也可以通过在 _source 中使用 ! 前缀来排除特定字段。例如,我们想排除 description 字段,可以这样写:

GET /products/_search
{
    "_source": ["!description"],
    "query": {
        "match_all": {}
    }
}

这样,返回的结果中将不包含 description 字段。

elasticsearch-py 中实现排除字段的过滤:

from elasticsearch import Elasticsearch

es = Elasticsearch()

query = {
    "_source": ["!description"],
    "query": {
        "match_all": {}
    }
}

response = es.search(index='products', body=query)
for hit in response['hits']['hits']:
    print(hit['_source'])

嵌套字段过滤

当文档包含嵌套结构时,过滤变得稍微复杂一些,但仍然是可行的。假设我们有一个 orders 索引,每个订单文档包含客户信息和订单商品列表,订单商品列表是一个嵌套字段。文档结构如下:

{
    "customer_name": "John Doe",
    "order_items": [
        {
            "product_name": "Product A",
            "quantity": 2,
            "price": 10.0
        },
        {
            "product_name": "Product B",
            "quantity": 1,
            "price": 15.0
        }
    ]
}

包含嵌套字段特定子字段

如果我们只想获取订单商品的 product_nameprice 字段,可以这样写查询:

GET /orders/_search
{
    "_source": {
        "includes": ["customer_name", "order_items.product_name", "order_items.price"]
    },
    "query": {
        "match_all": {}
    }
}

这里通过在 _source.includes 数组中指定嵌套字段路径 order_items.product_nameorder_items.price,实现了对嵌套字段特定子字段的过滤。

elasticsearch-py 中的实现:

from elasticsearch import Elasticsearch

es = Elasticsearch()

query = {
    "_source": {
        "includes": ["customer_name", "order_items.product_name", "order_items.price"]
    },
    "query": {
        "match_all": {}
    }
}

response = es.search(index='orders', body=query)
for hit in response['hits']['hits']:
    print(hit['_source'])

排除嵌套字段特定子字段

同样,我们也可以排除嵌套字段中的特定子字段。例如,排除订单商品的 quantity 字段:

GET /orders/_search
{
    "_source": {
        "excludes": ["order_items.quantity"]
    },
    "query": {
        "match_all": {}
    }
}

elasticsearch-py 中:

from elasticsearch import Elasticsearch

es = Elasticsearch()

query = {
    "_source": {
        "excludes": ["order_items.quantity"]
    },
    "query": {
        "match_all": {}
    }
}

response = es.search(index='orders', body=query)
for hit in response['hits']['hits']:
    print(hit['_source'])

脚本字段过滤

有时候,我们需要根据文档中的现有字段进行计算,然后返回计算结果作为一个新的字段,这时候就可以使用脚本字段。

使用脚本字段计算新字段

假设我们有一个 products 索引,每个产品文档包含 pricediscount 字段,我们想计算出折扣后的价格并返回。可以使用以下脚本字段的方式:

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

在这个例子中,script_fields 定义了一个新的字段 discounted_price,通过脚本计算得出。脚本中使用 doc['field_name'].value 的方式获取文档中的字段值,并进行计算。

elasticsearch-py 中的实现:

from elasticsearch import Elasticsearch

es = Elasticsearch()

query = {
    "script_fields": {
        "discounted_price": {
            "script": {
                "source": "doc['price'].value * (1 - doc['discount'].value)"
            }
        }
    },
    "query": {
        "match_all": {}
    }
}

response = es.search(index='products', body=query)
for hit in response['hits']['hits']:
    print(hit['fields']['discounted_price'])

结合脚本字段与现有字段过滤

我们也可以将脚本字段与前面提到的字段包含或排除过滤结合使用。例如,我们只想返回产品的 name 字段和计算出的 discounted_price 字段:

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

elasticsearch-py 中:

from elasticsearch import Elasticsearch

es = Elasticsearch()

query = {
    "_source": ["name"],
    "script_fields": {
        "discounted_price": {
            "script": {
                "source": "doc['price'].value * (1 - doc['discount'].value)"
            }
        }
    },
    "query": {
        "match_all": {}
    }
}

response = es.search(index='products', body=query)
for hit in response['hits']['hits']:
    print(hit['_source']['name'])
    print(hit['fields']['discounted_price'])

高亮字段过滤

高亮显示搜索结果中的关键词是 ElasticSearch 的一个常用功能。我们可以对高亮显示的字段进行过滤,只显示我们需要的高亮部分。

高亮特定字段

假设我们在 articles 索引中搜索包含关键词 ElasticSearch 的文章,并只对 titlecontent 字段进行高亮显示:

GET /articles/_search
{
    "query": {
        "match": {
            "content": "ElasticSearch"
        }
    },
    "highlight": {
        "fields": {
            "title": {},
            "content": {}
        }
    }
}

在这个请求中,highlight.fields 定义了我们希望高亮的字段 titlecontent。ElasticSearch 会在返回结果中为这两个字段中的关键词添加高亮标记。

elasticsearch-py 中的实现:

from elasticsearch import Elasticsearch

es = Elasticsearch()

query = {
    "query": {
        "match": {
            "content": "ElasticSearch"
        }
    },
    "highlight": {
        "fields": {
            "title": {},
            "content": {}
        }
    }
}

response = es.search(index='articles', body=query)
for hit in response['hits']['hits']:
    print(hit['_source']['title'])
    print(hit['highlight']['content'])

高亮字段片段过滤

有时候,高亮字段的内容可能很长,我们可以通过设置 fragment_sizenumber_of_fragments 来控制高亮片段的大小和数量。例如,我们只希望获取 content 字段中高亮部分的一个片段,且片段大小为 100 个字符:

GET /articles/_search
{
    "query": {
        "match": {
            "content": "ElasticSearch"
        }
    },
    "highlight": {
        "fields": {
            "content": {
                "fragment_size": 100,
                "number_of_fragments": 1
            }
        }
    }
}

elasticsearch-py 中:

from elasticsearch import Elasticsearch

es = Elasticsearch()

query = {
    "query": {
        "match": {
            "content": "ElasticSearch"
        }
    },
    "highlight": {
        "fields": {
            "content": {
                "fragment_size": 100,
                "number_of_fragments": 1
            }
        }
    }
}

response = es.search(index='articles', body=query)
for hit in response['hits']['hits']:
    print(hit['highlight']['content'])

聚合结果过滤

在 ElasticSearch 中进行聚合操作时,我们也可以对聚合结果进行过滤,只返回我们关心的聚合数据。

桶聚合结果过滤

假设我们在 products 索引中按 category 进行桶聚合,并只想获取数量大于 10 的类别:

GET /products/_search
{
    "size": 0,
    "aggs": {
        "product_categories": {
            "terms": {
                "field": "category",
                "min_doc_count": 10
            }
        }
    }
}

在这个请求中,terms 聚合的 min_doc_count 参数设置为 10,这意味着只有文档数量大于等于 10 的类别会出现在聚合结果中。

elasticsearch-py 中的实现:

from elasticsearch import Elasticsearch

es = Elasticsearch()

query = {
    "size": 0,
    "aggs": {
        "product_categories": {
            "terms": {
                "field": "category",
                "min_doc_count": 10
            }
        }
    }
}

response = es.search(index='products', body=query)
for bucket in response['aggregations']['product_categories']['buckets']:
    print(bucket['key'], bucket['doc_count'])

指标聚合结果过滤

对于指标聚合,例如计算平均值、总和等,我们也可以根据条件过滤结果。假设我们计算每个 category 的平均价格,并只返回平均价格大于 50 的类别:

GET /products/_search
{
    "size": 0,
    "aggs": {
        "product_categories": {
            "terms": {
                "field": "category"
            },
            "aggs": {
                "average_price": {
                    "avg": {
                        "field": "price"
                    }
                },
                "filtered_average_price": {
                    "bucket_selector": {
                        "buckets_path": {
                            "avg_price": "average_price"
                        },
                        "script": "params.avg_price > 50"
                    }
                }
            }
        }
    }
}

在这个例子中,bucket_selector 用于过滤聚合桶,通过脚本判断平均价格是否大于 50,只有满足条件的类别会出现在最终结果中。

elasticsearch-py 中的实现:

from elasticsearch import Elasticsearch

es = Elasticsearch()

query = {
    "size": 0,
    "aggs": {
        "product_categories": {
            "terms": {
                "field": "category"
            },
            "aggs": {
                "average_price": {
                    "avg": {
                        "field": "price"
                    }
                },
                "filtered_average_price": {
                    "bucket_selector": {
                        "buckets_path": {
                            "avg_price": "average_price"
                        },
                        "script": "params.avg_price > 50"
                    }
                }
            }
        }
    }
}

response = es.search(index='products', body=query)
for bucket in response['aggregations']['product_categories']['buckets']:
    print(bucket['key'], bucket['average_price']['value'])

通过以上对 ElasticSearch API 返回信息过滤的各个要点的介绍,包括字段级别过滤、嵌套字段过滤、脚本字段过滤、高亮字段过滤以及聚合结果过滤等,我们可以更加灵活和高效地获取所需的数据,优化系统性能,确保数据安全和减少不必要的资源消耗。在实际应用中,需要根据具体的业务需求和数据特点,合理选择和组合这些过滤方式,以达到最佳的效果。同时,不断地进行性能测试和优化,以适应不同规模和复杂度的应用场景。