前言
近十年间,Elasticsearch 凭借其卓越性能,已成为市场上备受青睐的开源搜索和数据分析引擎。它不仅在离线数据仓库、实时检索以及面向企业客户的搜索服务中扮演着核心角色,而且积累了丰富的实战经验和优化成果。
尽管如此,在高并发、高可用性以及处理大规模数据等面向消费者端的复杂场景中,相关的优化资料仍相对稀缺。为此,我们希望通过在大屏投影搜索领域的具体优化案例,为同行提供宝贵的参考,以启发大家在 Elasticsearch 优化方面的创新思维和实践。
当贝在影视搜索领域广泛应用了 Elasticsearch 作为其核心检索引擎,并成功应对了过去几年春晚期间的海量搜索请求。然而,随着内容和数据量的飞速增长,业务处理时间和 CPU 负载也随之增加。经过深入分析,我们发现性能瓶颈主要出现在前缀搜索环节。为解决这一问题,我们采用了基于 edge n-gram 的分词策略来优化前缀搜索,大幅提升了查询效率。
背景
在当前的影视内容搜索业务中,Elasticsearch 作为主要的检索引擎,承担着至关重要的角色。为了提供更加灵活和便捷的搜索体验,业务逻辑中特别设计了针对用户输入的三个字及以下的查询条件,采用了一种特别的搜索机制,即通配符前缀搜索。
具体来说,当用户在搜索框中输入三个字或更少的字符时,系统会自动执行一个基于输入内容的前缀匹配查询,迅速检索出与用户输入的前缀相匹配的影片名称。这种设计充分考虑了用户在快速输入时的需求,使得搜索结果能够即时反映用户的查询意图,从而极大地提升了用户体验和搜索效率。通过这种方式,用户能够更加快速地找到他们所期望的影视内容,而无需完整输入整个影片名称,这在用户进行快速浏览和选择时显得尤为重要。
前缀搜索的一些痛点
查询语句示例如下:
POST /xy_test_pinyin_ik/_search
{
"query": {
"wildcard": {
"text": {
"value": "杭州当贝*"
}
}
}
}
这样搜索通常会带来如下问题:
- 通配符查询,尤其是那些在字符串开头使用通配符的查询,会导致 Elasticsearch 遍历大量索引项,这可能会导致查询速度变慢。
- 由于通配符查询的复杂性,它们通常无法利用索引的优化特性,如倒排索引和缓存。
- 当使用前缀查询时,如果前缀的长度越长,那么能匹配的文档相对越少,性能会好一些,如果前缀太短,只有一个字符,那么匹配数据量太多,会影响性能。
- Elasticsearch 的查询缓存(如查询缓存和过滤器缓存)通常无法用于通配符查询,因为通配符查询的结果可能会因索引的更改而频繁变化。
- 通配符查询可能会导致 Elasticsearch 使用更多的内存,因为它需要保持更多的状态来处理复杂的匹配逻辑。
什么是 n-gram
n-gram 是一种语言模型,它通过将文本划分为连续的 n 个字符序列来工作。
例如,对于单词 “Elasticsearch”:
- 2-gram ("bigram") 会将其划分为 “El”, “la”, “as”, “si”, “ic”, “ch”, “he”, “es”, “se”。
- 3-gram ("trigram") 则会划分为 “Ela”, “las”, “asi”, “sic”, “ich”, “che”, “hes”, “ese”。
什么是 edge_ngram
edge_ngram 是一种专为前缀匹配设计的 n-gram 分词器,它仅从文本的开头生成 n-gram。这种方法对于提升搜索性能、缩小索引体积以及增强用户体验非常有效,尤其是在自动完成和建议功能中,能够迅速返回与用户输入文本开头匹配的词语。
edge_ngram 的优势主要包括:
- 快速前缀匹配:仅索引每个词的前缀部分,使得 Elasticsearch 能够迅速定位所有以用户输入字符开头的词语。
- 提升搜索性能:生成的索引项数量较少,前缀查询因此更快,只需搜索索引的子集。
- 减少索引大小:与存储整个词或使用常规 n-gram 分词器相比,edge_ngram 生成的索引更小,节省磁盘空间和内存。
- 优化的自动完成体验:为快速响应用户输入并提供相关建议而设计,确保即时反馈。
- 提高搜索相关性:专注于前缀匹配,确保只返回以用户输入词语开头的搜索结果。
- 易于配置:通过调整 min_gram 和 max_gram 参数,轻松控制 n-grams 的大小,满足特定搜索需求。
- 多语言支持:不依赖于特定语言的规则,适用于任何语言的前缀生成。
- 降低不必要的匹配:通过精确的前缀匹配,减少无关搜索结果的出现,提高搜索精度。
- 通过这些优势,edge_ngram分词器成为提高搜索效率和用户体验的理想选择。
通过应用 edge_ngram 分词器,我们可以将单词 "Elastic" 分解为一组以单词开头为基础的子字符串,具体如下:
- 长度1:E
- 长度2:El
- 长度3:Ela
- 长度4:Elas
- 长度5:Elast
- 长度6:Elasti
- 长度7:Elastic
使用 edge_ngram 进行前缀搜索
1. 创建一个带有 edge ngram 分词器的索引
PUT xy_test_pinyin_ik
{
"settings": {
"number_of_shards": 1,
"number_of_replicas": 0,
"index": {
"max_ngram_diff": 10
},
"analysis": {
"analyzer": {
"custom_analyzer": {
"tokenizer": "custom_tokenizer"
}
},
"tokenizer": {
"custom_tokenizer": {
"type": "edge_ngram",
"min_gram": 1,
"max_gram": 7
}
}
}
},
"mappings": {
"properties": {
"text": {
"type": "text",
"analyzer": "custom_analyzer"
}
}
}
}
过滤器会生成一个最小固定值为1,最大为7的ngram。
2. 使用已经创建好的custom_tokenizer分词器查看分词结果
得到的结果符合预期
POST /xy_test_pinyin_ik/_analyze
{
"text": "杭州当贝网络科技有限公司",
"analyzer": "custom_analyzer"
}
结果:
{
"tokens" : [
{
"token" : "杭",
...
"position" : 0
},
{
"token" : "杭州",
...
"position" : 1
},
{
"token" : "杭州当",
...
"position" : 2
},
{
"token" : "杭州当贝",
...
"position" : 3
},
{
"token" : "杭州当贝网",
...
"position" : 4
},
{
"token" : "杭州当贝网络",
...
"position" : 5
},
{
"token" : "杭州当贝网络科",
...
"position" : 6
}
]
}
3. 写入数据
POST /xy_test_pinyin_ik/_doc/1
{"text": "杭州当贝网络科技有限公司"}
{"text": "杭州"}
4. 查询数据
如果用 match,只有杭州的也会出来,全文检索,只是分数比较低。
推荐使用 match_phrase,要求每个 term 都有,而且 position 刚好靠着 1 位,符合我们的期望的。
GET /xy_test_pinyin_ik/_search
{
"query": {
"match_phrase": {
"text": "杭州当贝"
}
}
}
结果:
{
...
"hits" : {
...
"hits" : [
{
"_index" : "xy_test_pinyin_ik",
...
"_source" : {
"text" : "杭州当贝网络科技有限公司"
}
}
]
}
}
总结
本文主要针对搜索业务场景中遇到的问题,进行问题分析、技术选型、选择合适的解决方案、集成、验证。我们最终使用了edge n-gram,彻底解决了这个场景上的性能瓶颈。本文希望能提供一个思路,让其他同学在遇到 Elasticsearch 相关的性能问题时,也能遵循相同的路径,解决业务上的问题。
参考链接
- https://www.elastic.co/guide/en/elasticsearch/reference/current/analysis-edgengram-tokenizer.html
- https://segmentfault.com/a/1190000022100153
- https://blog.csdn.net/tiancityycf/article/details/114847911
来源-微信公众号:当贝技术团队
出处:https://mp.weixin.qq.com/s/tr9e6jzrP6A6IU3T3wb85g