24、Elasticsearch 搜索功能拓展

Elasticsearch 搜索功能拓展

1. 排序与地理搜索

在地理搜索中,Elasticsearch 能展示用于排序的值信息。例如,在一组包含城市位置信息的数据中,通过排序可以看到不同城市与特定地点(如巴黎)的距离。以下是部分数据示例:

{
    "_index": "map",
    "_type": "poi",
    "_id": "2",
    "_score": null,
    "_source": {
        "name": "London",
        "location": [-0.1275, 51.507222]
    },
    "sort": [1453.6450747751787]
},
{
    "_index": "map",
    "_type": "poi",
    "_id": "3",
    "_score": null,
    "_source": {
        "name": "Moscow",
        "location": {
            "lat": 55.75,
            "lon": 37.616667
        }
    },
    "sort": [2486.2560754763977]
}

从这些数据可以看出,伦敦与巴黎的距离约为 343 公里,这与地图信息相符。

2. 边界框过滤

边界框过滤可以将搜索结果限定在一个由给定矩形界定的区域内。这在地图展示结果或允许用户标记地图区域进行搜索时非常有用。以下是使用边界框过滤的查询示例:

{
    "filter": {
        "geo_bounding_box": {
            "location": {
                "top_left": "52.4796, -1.903",
                "bottom_right": "48.8567, 2.3508"
            }
        }
    }
}

在这个示例中,通过提供左上角和右下角的坐标,选择了伯明翰和巴黎之间的地图区域。Elasticsearch 会根据这些坐标进行计算。执行该查询后,返回的结果如下:

{
    "took": 9,
    "timed_out": false,
    "_shards": {
        "total": 5,
        "successful": 5,
        "failed": 0
    },
    "hits": {
        "total": 1,
        "max_score": 1.0,
        "hits": [
            {
                "_index": "map",
                "_type": "poi",
                "_id": "2",
                "_score": 1.0,
                "_source": {
                    "name": "London",
                    "location": [-0.1275, 51.507222]
                }
            }
        ]
    }
}

可以看到,只有伦敦符合该区域的条件,这与地图信息一致。

3. 距离限制

距离限制可以将搜索结果限定在距离基点指定距离内的地点。例如,要将结果限制在距离巴黎 500 公里半径内的所有城市,可以使用以下过滤器:

{
    "filter": {
        "geo_distance": {
            "location": "48.8567, 2.3508",
            "distance": "500km"
        }
    }
}

如果一切正常,Elasticsearch 应该只返回一条记录,即伦敦,你可以自行验证。

4. 任意地理形状

有时候,单一的地理点或矩形可能不够用,Elasticsearch 允许定义自定义形状。为了使用自定义形状限制,需要修改索引并引入 geo_shape 类型。新的映射如下:

{
    "poi": {
        "properties": {
            "name": {
                "type": "string",
                "index": "not_analyzed"
            },
            "location": {
                "type": "geo_shape"
            }
        }
    }
}

同时,需要修改示例数据以匹配新的索引结构:

{
    "index": {
        "_index": "map2",
        "_type": "poi",
        "_id": 1
    }
}
{
    "name": "New York",
    "location": {
        "type": "point",
        "coordinates": [-73.938611, 40.664167]
    }
}
{
    "index": {
        "_index": "map2",
        "_type": "poi",
        "_id": 2
    }
}
{
    "name": "London",
    "location": {
        "type": "point",
        "coordinates": [-0.1275, 51.507222]
    }
}

4.1 地理形状类型

在查询时,可以使用以下几种地理形状类型:
| 形状类型 | 描述 | 示例 |
| ---- | ---- | ---- |
| Point | 由经度和纬度定义的点 |

{
    "location": {
        "type": "point",
        "coordinates": [-0.1275, 51.507222]
    }
}

|
| Envelope | 由左上角和右下角坐标定义的矩形 |

{
    "type": "envelope",
    "coordinates": [
        [
            -0.087890625,
            51.50874245880332
        ],
        [
            2.4169921875,
            48.80686346108517
        ]
    ]
}

|
| Polygon | 由一系列连接的点组成的多边形,第一个和最后一个点必须相同以闭合形状 |

{
    "type": "polygon",
    "coordinates": [
        [
            -5.756836,
            49.991408
        ],
        [
            -7.250977,
            55.124723
        ],
        [
            1.845703,
            51.500194
        ],
        [
            -5.756836,
            49.991408
        ]
    ]
}

|
| Multipolygon | 由多个多边形组成的形状 |

{
    "type": "multipolygon",
    "coordinates": [
        [
            [
                -5.756836,
                49.991408
            ],
            [
                -7.250977,
                55.124723
            ],
            [
                1.845703,
                51.500194
            ],
            [
                -5.756836,
                49.991408
            ]
        ],
        [
            [
                -0.087890625,
                51.50874245880332
            ],
            [
                2.4169921875,
                48.80686346108517
            ],
            [
                3.88916015625,
                51.01375465718826
            ],
            [
                -0.087890625,
                51.50874245880332
            ]
        ]
    ]
}

|

4.2 示例用法

现在有了包含 geo_shape 字段的索引,可以查询哪些城市位于英国。查询如下:

{
    "filter": {
        "geo_shape": {
            "location": {
                "shape": {
                    "type": "polygon",
                    "coordinates": [
                        [
                            -5.756836,
                            49.991408
                        ],
                        [
                            -7.250977,
                            55.124723
                        ],
                        [
                            -3.955078,
                            59.352096
                        ],
                        [
                            1.845703,
                            51.500194
                        ],
                        [
                            -5.756836,
                            49.991408
                        ]
                    ]
                }
            }
        }
    }
}

执行该查询后,Elasticsearch 返回的结果如下:

{
    "hits": [
        {
            "_index": "map2",
            "_type": "poi",
            "_id": "2",
            "_score": 1,
            "_source": {
                "name": "London",
                "location": {
                    "type": "point",
                    "coordinates": [
                        -0.1275,
                        51.507222
                    ]
                }
            }
        }
    ]
}

结果显示只有伦敦符合条件,这与实际情况相符。

4.3 在索引中存储形状

通常,形状定义比较复杂,且定义的区域变化不频繁,这时可以将形状定义存储在索引中并在查询中使用。首先,需要创建合适的映射:

{
    "country": {
        "properties": {
            "name": {
                "type": "string",
                "index": "not_analyzed"
            },
            "area": {
                "type": "geo_shape"
            }
        }
    }
}

示例数据如下:

{
    "index": {
        "_index": "countries",
        "_type": "country",
        "_id": 1
    }
}
{
    "name": "UK",
    "area": {
        "type": "polygon",
        "coordinates": [
            [
                -5.756836,
                49.991408
            ],
            [
                -7.250977,
                55.124723
            ],
            [
                -3.955078,
                59.352096
            ],
            [
                1.845703,
                51.500194
            ],
            [
                -5.756836,
                49.991408
            ]
        ]
    }
}
{
    "index": {
        "_index": "countries",
        "_type": "country",
        "_id": 2
    }
}
{
    "name": "France",
    "area": {
        "type": "polygon",
        "coordinates": [
            [
                [
                    3.1640625,
                    42.09822241118974
                ],
                [
                    -1.7578125,
                    43.32517767999296
                ],
                [
                    -4.21875,
                    48.22467264956519
                ],
                [
                    2.4609375,
                    50.90303283111257
                ],
                [
                    7.998046875,
                    48.980216985374994
                ],
                [
                    7.470703125,
                    44.08758502824516
                ],
                [
                    3.1640625,
                    42.09822241118974
                ]
            ]
        ]
    }
}

修改查询以使用索引中的形状:

{
    "filter": {
        "geo_shape": {
            "location": {
                "indexed_shape": {
                    "index": "countries",
                    "type": "country",
                    "path": "area",
                    "id": "1"
                }
            }
        }
    }
}

与之前的查询相比, shape 对象改为 indexed_shape ,需要告诉 Elasticsearch 在哪里查找形状,包括索引、类型、路径和形状的 id

5. 滚动 API

5.1 问题定义

当索引中有数百万个文档时,使用分页查询可能会导致性能问题。例如,当查询第 10 页时,Elasticsearch 需要获取前 10 页的所有文档,然后丢弃前 9 页的文档,这会增加查询时间。这种情况不仅在 Elasticsearch 中存在,在使用优先队列的数据库系统中也会出现。

5.2 滚动解决方案

为了解决这个问题,可以使用滚动 API。具体操作步骤如下:
1. 像平常一样查询 Elasticsearch,但添加 scroll 参数,指定使用滚动功能以及希望 Elasticsearch 保留结果信息的时间。示例查询如下:

curl 'localhost:9200/library/_search?pretty&scroll=5m' -d '{
    "query": {
        "match_all": {}
    }
}'
  1. Elasticsearch 的响应中会包含 _scroll_id ,这是后续查询需要使用的句柄。响应示例如下:
{
    "_scroll_id": "cXVlcnlUaGVuRmV0Y2g7NTsxMDI6dklNMlkzTG1RTDJ2b25oTDNENmJzZzsxMDU6dklNMlkzTG1RTDJ2b25oTDNENmJzZzsxMDQ6dklNMlkzTG1RTDJ2b25oTDNENmJzZzsxMDE6dklNMlkzTG1RTDJ2b25oTDNENmJzZzsxMDM6dklNMlkzTG1RTDJ2b25oTDNENmJzZzswOw==",
    "took": 9,
    "timed_out": false,
    "_shards": {
        "total": 5,
        "successful": 5,
        "failed": 0
    },
    "hits": {
        "total": 1341211,
        ...
    }
}
  1. 使用 _search/scroll 端点和 _scroll_id 进行后续查询,每次调用该端点都会返回下一页的结果。示例查询如下:
curl -XGET 'localhost:9200/_search/scroll?scroll=5m&pretty&scroll_id=cXVlcnlUaGVuRmV0Y2g7NTsxMjg6dklNlkzTG1RTDJ2b25oTDNENmJzZzsxMjk6dklNMlkzTG1RTDJ2b25oTDNENmJzZzsxMzA6dklNMlkzTG1RTDJ2b25oTDNENmJzZzsxMjc6dklNMlkzTG1RTDJ2b25oTDNENmJzZzsxMjY6dklNMlkzTG1RTDJ2b25oTDNENmJzZzswOw=='

需要注意的是, _scroll_id 只有在定义的不活动时间内有效,过期后查询会返回错误响应。滚动 API 不适用于对各种结果的随机页面有大量请求或请求时间难以确定的情况,但在需要获取大量结果集(如在多个系统之间传输数据)时非常有用。

6. 术语过滤器

6.1 基本用法

术语过滤器可以过滤文档,只返回与给定术语之一匹配且不进行分析的文档。示例查询如下:

{
    "query": {
        "constant_score": {
            "filter": {
                "terms": {
                    "title": [
                        "crime",
                        "punishment"
                    ]
                }
            }
        }
    }
}

该查询会返回标题字段中包含 crime punishment 术语的文档。术语过滤器的工作方式是遍历提供的术语,找到匹配的文档,并将匹配的文档标识符加载到 bitset 结构中进行缓存。

6.2 执行参数

可以通过提供 execution 参数来改变术语过滤器的默认行为,参数值有以下几种:
| 执行参数值 | 描述 |
| ---- | ---- |
| plain | 默认方法,遍历所有提供的术语,将结果存储在 bitset 中并缓存 |
| fielddata | 生成使用字段数据缓存比较术语的术语过滤器,在过滤已加载到字段数据缓存的字段时非常高效 |
| bool | 为每个术语生成一个术语过滤器,并构建一个布尔过滤器,构建的布尔过滤器不缓存 |
| and | 与 bool 类似,但构建 and 过滤器 |
| or | 与 bool 类似,但构建 or 过滤器 |

示例查询如下:

{
    "query": {
        "constant_score": {
            "filter": {
                "terms": {
                    "title": [
                        "crime",
                        "punishment"
                    ],
                    "execution": "and"
                }
            }
        }
    }
}

7. 术语查找

7.1 功能介绍

术语查找功能是在 Elasticsearch 0.90.6 中添加的,它可以从提供的源加载术语,而不是显式传递术语列表。

7.2 示例操作

以下是使用术语查找功能的具体步骤:
1. 创建一个新索引并索引三个文档:

curl -XPOST 'localhost:9200/books/book/1' -d '{
    "id": 1,
    "name": "Test book 1",
    "similar": [
        2,
        3
    ]
}'
curl -XPOST 'localhost:9200/books/book/2' -d '{
    "id": 2,
    "name": "Test book 2",
    "similar": [
        1
    ]
}'
curl -XPOST 'localhost:9200/books/book/3' -d '{
    "id": 3,
    "name": "Test book 3",
    "similar": [
        1,
        3
    ]
}'
  1. 假设要获取与标识符为 3 的书相似的所有书籍,可以使用术语查找功能,查询如下:
curl -XGET 'localhost:9200/books/_search?pretty' -d '{
    "query": {
        "filtered": {
            "query": {
                "match_all": {}
            },
            "filter": {
                "terms": {
                    "id": {
                        "index": "books",
                        "type": "book",
                        "id": "3",
                        "path": "similar"
                    },
                    "_cache_key": "books_3_similar"
                }
            }
        }
    },
    "fields": [
        "id",
        "name"
    ]
}'
  1. 执行查询后,返回的结果如下:
{
    "took": 2,
    "timed_out": false,
    "_shards": {
        "total": 5,
        "successful": 5,
        "failed": 0
    },
    "hits": {
        "total": 2,
        "max_score": 1.0,
        "hits": [
            {
                "_index": "books",
                "_type": "book",
                "_id": "1",
                "_score": 1.0,
                "fields": {
                    "id": 1,
                    "name": "Test book 1"
                }
            },
            {
                "_index": "books",
                "_type": "book",
                "_id": "3",
                "_score": 1.0,
                "fields": {
                    "id": 3,
                    "name": "Test book 3"
                }
            }
        ]
    }
}

7.3 术语查找查询结构

术语查找过滤器的结构如下:

{
    "filter": {
        "terms": {
            "id": {
                "index": "books",
                "type": "book",
                "id": "3",
                "path": "similar"
            },
            "_cache_key": "books_3_similar"
        }
    }
}

使用了简单的过滤查询,通过 id 字段过滤文档。除了 id 字段,还使用了以下属性:
| 属性 | 描述 |
| ---- | ---- |
| index | 指定从哪个索引加载术语,示例中是 books 索引 |
| type | 指定感兴趣的类型,示例中是 book 类型 |
| id | 指定要获取术语列表的文档标识符,示例中是标识符为 3 的文档 |
| path | 指定要加载术语的字段名,示例中是 similar 字段 |

此外,还可以使用 routing cache 两个属性:
- routing :指定 Elasticsearch 加载术语到过滤器时使用的路由值。
- cache :指定 Elasticsearch 是否缓存从加载的文档构建的过滤器,默认值为 true

需要注意的是,使用术语查找功能时, _source 字段需要存储,并且 execution 属性不被考虑。

通过以上介绍,可以看到 Elasticsearch 在地理搜索、分页查询、文档过滤等方面提供了丰富的功能和灵活的操作方式,能够满足不同场景下的搜索需求。

7. 术语过滤器与术语查找总结

术语过滤器和术语查找功能为 Elasticsearch 的文档过滤提供了强大的支持。它们的操作流程可以用以下 mermaid 流程图表示:

graph LR
    classDef startend fill:#F5EBFF,stroke:#BE8FED,stroke-width:2px;
    classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px;
    classDef decision fill:#FFF6CC,stroke:#FFBC52,stroke-width:2px;

    A([开始]):::startend --> B(创建查询):::process
    B --> C{是否使用术语查找}:::decision
    C -->|是| D(指定索引、类型、ID、路径):::process
    C -->|否| E(指定术语列表):::process
    D --> F(构建术语过滤器):::process
    E --> F
    F --> G(执行查询):::process
    G --> H(返回匹配文档):::process
    H --> I([结束]):::startend

这个流程图展示了术语过滤器和术语查找的基本操作流程。在实际使用中,可以根据具体需求选择合适的方法。

7.4 术语过滤器与术语查找的对比

功能 优点 缺点 适用场景
术语过滤器 简单直接,可通过执行参数调整行为 需显式提供术语列表 过滤条件明确,术语数量较少的情况
术语查找 可从源加载术语,减少手动操作 需确保源文档存在且 _source 字段存储 术语列表动态变化,需从其他文档获取术语的情况

8. 地理搜索总结

地理搜索是 Elasticsearch 的重要功能之一,它提供了多种方式来筛选和排序地理数据。以下是地理搜索相关功能的总结:

8.1 地理搜索功能对比

功能 描述 示例查询
排序 展示用于排序的值信息,可查看不同城市与特定地点的距离 见排序与地理搜索部分示例
边界框过滤 将搜索结果限定在由给定矩形界定的区域内
{
    "filter": {
        "geo_bounding_box": {
            "location": {
                "top_left": "52.4796, -1.903",
                "bottom_right": "48.8567, 2.3508"
            }
        }
    }
}

|
| 距离限制 | 将搜索结果限定在距离基点指定距离内的地点 |

{
    "filter": {
        "geo_distance": {
            "location": "48.8567, 2.3508",
            "distance": "500km"
        }
    }
}

|
| 任意地理形状 | 允许定义自定义形状,支持多种地理形状类型 | 见任意地理形状部分示例 |

8.2 地理搜索操作流程

地理搜索的操作流程可以用以下 mermaid 流程图表示:

graph LR
    classDef startend fill:#F5EBFF,stroke:#BE8FED,stroke-width:2px;
    classDef process fill:#E5F6FF,stroke:#73A6FF,stroke-width:2px;
    classDef decision fill:#FFF6CC,stroke:#FFBC52,stroke-width:2px;

    A([开始]):::startend --> B(选择地理搜索功能):::process
    B --> C{排序}:::decision
    C -->|是| D(获取排序信息):::process
    C -->|否| E{边界框过滤}:::decision
    E -->|是| F(指定矩形边界):::process
    E -->|否| G{距离限制}:::decision
    G -->|是| H(指定基点和距离):::process
    G -->|否| I{任意地理形状}:::decision
    I -->|是| J(定义地理形状):::process
    D --> K(执行搜索):::process
    F --> K
    H --> K
    J --> K
    K --> L(返回匹配结果):::process
    L --> M([结束]):::startend

这个流程图展示了地理搜索的不同功能选择和操作流程。在实际应用中,可以根据具体需求选择合适的地理搜索功能。

9. 滚动 API 应用建议

滚动 API 为处理大量数据的分页查询提供了有效的解决方案。在使用滚动 API 时,有以下几点建议:
- 合理设置滚动时间 :滚动时间设置过短,可能导致在获取数据过程中 _scroll_id 过期,需要重新发起查询;设置过长,会占用 Elasticsearch 的资源。应根据数据量和处理速度合理设置滚动时间。
- 避免随机访问 :滚动 API 不适合对结果的随机页面进行大量请求。如果需要随机访问结果,建议使用其他分页方式。
- 及时清理资源 :在完成数据获取后,应及时清理滚动上下文,释放 Elasticsearch 的资源。可以通过发送 _clear_scroll 请求来清理滚动上下文,示例如下:

curl -XDELETE 'localhost:9200/_search/scroll' -d '{
    "scroll_id": [
        "cXVlcnlUaGVuRmV0Y2g7NTsxMDI6dklNMlkzTG1RTDJ2b25oTDNENmJzZzsxMDU6dklNMlkzTG1RTDJ2b25oTDNENmJzZzsxMDQ6dklNMlkzTG1RTDJ2b25oTDNENmJzZzsxMDE6dklNMlkzTG1RTDJ2b25oTDNENmJzZzsxMDM6dklNMlkzTG1RTDJ2b25oTDNENmJzZzswOw=="
    ]
}'

10. 综合应用示例

假设我们有一个包含书籍信息和地理位置信息的索引,需要实现以下功能:
1. 查找标题包含特定术语的书籍。
2. 筛选出位于特定地理区域内的书籍相关地点。
3. 处理大量书籍数据的分页查询。

10.1 实现步骤

步骤 1:创建索引和映射
{
    "book_info": {
        "properties": {
            "title": {
                "type": "string",
                "index": "not_analyzed"
            },
            "location": {
                "type": "geo_shape"
            }
        }
    }
}
步骤 2:插入示例数据
{
    "index": {
        "_index": "book_index",
        "_type": "book_info",
        "_id": 1
    }
}
{
    "title": "Crime and Punishment",
    "location": {
        "type": "point",
        "coordinates": [-0.1275, 51.507222]
    }
}
{
    "index": {
        "_index": "book_index",
        "_type": "book_info",
        "_id": 2
    }
}
{
    "title": "Another Book",
    "location": {
        "type": "point",
        "coordinates": [-73.938611, 40.664167]
    }
}
步骤 3:查找标题包含特定术语的书籍
{
    "query": {
        "constant_score": {
            "filter": {
                "terms": {
                    "title": [
                        "crime",
                        "punishment"
                    ]
                }
            }
        }
    }
}
步骤 4:筛选出位于特定地理区域内的书籍相关地点
{
    "filter": {
        "geo_bounding_box": {
            "location": {
                "top_left": "52.4796, -1.903",
                "bottom_right": "48.8567, 2.3508"
            }
        }
    }
}
步骤 5:处理大量书籍数据的分页查询
curl 'localhost:9200/book_index/_search?pretty&scroll=5m' -d '{
    "query": {
        "match_all": {}
    }
}'

10.2 总结

通过以上步骤,我们展示了如何综合使用 Elasticsearch 的术语过滤器、地理搜索和滚动 API 来实现复杂的搜索需求。在实际应用中,可以根据具体业务场景进行调整和扩展。

Elasticsearch 提供了丰富的搜索功能和灵活的操作方式,能够满足各种复杂的搜索需求。通过合理运用这些功能,可以提高搜索效率和准确性,为用户提供更好的搜索体验。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值