纯 MongoDB 实现中文全文搜索

您所在的位置:网站首页 在wikihow中,我们可以使用中文检索 纯 MongoDB 实现中文全文搜索

纯 MongoDB 实现中文全文搜索

2024-07-13 12:51| 来源: 网络整理| 查看: 265

本文来自获得《2021MongoDB技术实践与应用案例征集活动》最佳应用案例奖作品

作者:赖勇浩

 

摘要

MongoDB在2.4版中引入全文索引后几经迭代更新已经比较完美地支持以空格分隔的西语,但一直不支持中日韩等语言,社区版用户不得不通过挂接ElasticSearch等支持中文全文搜索的数据库来实现业务需求,由此引入了许多业务限制、安全问题、性能问题和技术复杂性。作者独辟蹊径,基于纯MongoDB社区版(v4.x和v5.0)实现中文全文搜索,在接近四千万个记录的商品表搜索商品名,检索时间在200ms以内,并使用Change Streams技术同步数据变化,满足了业务需要和用户体验需求。

本文首先描述遇到的业务需求和困难,介绍了MongoDB和Atlas Search对全文搜索的支持现状,然后从全文搜索原理讲起,结合MongoDB全文搜索实现,挂接中文分词程序,达到纯MongoDB社区版实现中文全文搜索的目标;针对性能需求,从分词、组合文本索引、用户体验、实时性等多方面给出了优化实践,使整个方案达到商业级的实用性。

 

业务需求和困难

电商易是作者公司的电商大数据工具品牌,旗下多个产品都有搜索商品的业务需求。早期的时候,我们的搜索是直接用$regex去匹配的,在数据量比较大的时候,需要耗时十几秒甚至几分钟,所以用户总是反馈说搜不出东西来。其实不是搜不出来,而是搜的时间太长,服务器掐断连接了。加上我们普遍使用极简风格的首页,像搜索引擎那样,有个框,右侧是一个“一键分析”的按钮,用户点击后显示相关的商品的数据。搜索成为用户最常用的功能,搜索性能的问题也就变得更加突出了,优化搜索成为了迫在眉睫的任务。

MongoDB在2.4版中引入文本索引(Text Index)实现了全文搜索(Full Text Search,下文简称FTS),虽然后来在2.6和3.2版本中两经改版优化,但一直不支持中日韩等语言。MongoDB官网推出服务Atlas Search,也是通过外挂Lucene的方式支持的,这个服务需要付费,而且未在中国大陆地区运营,与我们无缘,所以还是要寻找自己的解决之道。

那么能否仅仅基于MongoDB社区版实现中文全文搜索呢?带着这个问题,作者深入到MongoDB文本索引的文档、代码中去,发现了些许端倪,并逐步实现和优化了纯MongoDB实现中文全文搜索的方案,下文将从全文搜索的原理讲起,详细描述这个方案。

 

过程

全文搜索原理

倒排索引是搜索引警的基础。倒排是与正排相对的,假设有一个 ID 为 1 的文档,内容为“ My name is LaiYonghao.“,那么通过 ID 1 总能找到这个文档所有的词。通过文档 ID 找包含的词,称为正排;反过来通过词找到包括该词的文档 ID,称为倒排,词与文档ID的对应关系称为倒排索引。下面直接引用一下维基百科上的例子。

0 “it is what it is” 1 “what is it” 2 “it is a banana”

上面 3 个文档的倒排索引大概如下:

“a”:      {2} “banana”: {2} “is”:     {0, 1, 2} “it”:     {0, 1, 2} “what”:   {0, 1}

这时如果要搜索banana的话,利用倒排索引可以马上查找到包括这个词的文档是ID为2的文档。而正排的话,只能一个一个文档找过去,找完3个文档才能找到(也就是$regex的方式),这种情况下的耗时大部分是无法接受的。

倒排索引是所有支持全文搜索的数据库的基础,无论是PostgreSQL还是MySQL都是用它来实现全文搜索的,MongoDB也不例外,这也是我们最终解决问题的基础底座。简单来说,倒排索引类似MongoDB里的多键索引(Multikey Index),能够通过内容元素找到对应的文档。文本索引可以简单类比为对字符串分割(即分词)转换为由词组成的数组,并建立多键索引。虽然文本索引还是停止词、同义词、大小写、权重和位置等信息需要处理,但大致如此理解是可以的。

西文的分词较为简单,基本上是按空格分切即可,这就是MongoDB内置的默认分词器:当建立文本索引时,默认分词器将按空格分切句子。而CJK语言并不使用空格切分,而且最小单位是字,所以没有办法直接利用MongoDB的全文搜索。那么如果我们预先将中文句子进行分词,并用空格分隔重新组装为“句子”,不就可以利用上MongoDB的全文搜索功能了吗?通过这一个突破点进行深挖,实验证明,这是可行的,由此我们的问题就转化为了分词问题。

一元分词和二元分词

从上文可知,数据库的全文搜索是基于空格切分的词作为最小单位实现的。中文分词的方法有很多,最基础的是一元分词和二元分词。

所谓一元分词:就是一个字一个字地切分,把字当成词。如我爱北京天安门,可以切分为我爱北京天安门,这是最简单的分词方法。这种方法带来的问题就是文档过于集中,常用汉字只有几千个,姑且算作一万个,如果有一千万个文档,每一个字会对应到10000000/10000*avg_len(doc)个。以文档内容是电商平台的商品名字为例,平均长度约为 60 个汉字,那每一个汉子对应 6 万个文档,用北京两字搜索的话,要求两个长度为6万的集合的交集,就会要很久的时间。所以大家更常使用二元分词法。

所谓二元分词:就是按两字两个分词。如我爱北京天安门,分词结果是我爱爱北北京京天天安安门。可见两个字的组合数量多了很多,相对地一个词对应的文档也少了许多,当搜索两个字的时候,如北京不用再求交集,可以直接得到结果。而搜索三个字以上的话,如天安门也是由天安和安门两个不太常见的词对应的文档集合求交集,数量少,运算量也小,速度就很快。下面是纯中文的二元分词Python代码,实际工作中需要考虑多语言混合的处理,在此仅作示例:

def bigram_tokenize(word): return' '.join( word[i:i+2]for i inrange(len(word))if i+2


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3