在我们做的调研产品中,最近有个词频分析的需求,其希望能从用户的文本作答中提取关键词并按照频次排序展示,同时可以从关键词得到其来源的完整作答。
![在这里插入图片描述](https://i-blog.csdnimg.cn/blog_migrate/1535376abce476988619f253e436cfef.png#pic_center)
预期的效果是这样的,当然前端如何展示我们并不是那么关心。从后端的角度,可以将需求做如下拆分:
- 分词:将文本作答进行分词处理得到关键词;
- 存储:需要将分词结果进行持久化的存储;
- 查询:对分词的结果进行聚合并按频次排序,根据关键词召回其来源的作答;
在技术选型上,说起分词,首先想到的其实就是es。除了内置的分词功能外,es的聚合功能和搜索功能也满足对关键词进行聚合以及作答召回的要求。目前的作答数据是存储在mongoDB中,所以也考虑在业务中使用分词库对文本作答进行分词,并将关键词存储在mongoDB的arrary字段中,使用mongoDB的聚合和搜索能力来实现需求。最后,技术选型并不是非此即彼的,如果有需要,可以使用es和mongoDB共同实现功能。
下面我们将对三种方案进行详细的描述并分析其可行性以及优劣。
方案一:使用es进行存储
将文本作答直接存储在es中,利用指定的分词器进行分词,利用es的聚合能力对文本作答字段进行聚合分析,利用es的搜索能力进行作答的召回。
es的分词及搜索功能是基本不需要讨论的,因为es本身的定位就是一个搜索引擎。但是es的聚合功能尚需要调研一下其性能如何。我们都知道es搜索功能的实现是基于倒排索引的数据结构。倒排索引其本质就是一个K-V结构,K为term,V为docId。这种结构对于搜索(找到哪些文档包含固定关键词)非常有利,但是对于聚合(文档中包含了哪些关键词)是非常不利的。所以es除了广为人知的倒排索引外还有正排索引的数据结构。为了聚合功能,es在索引时还会建立正排索引,其名为doc_values,是一种列式存储的数据结构(实际上es的正排索引有列式和行式两种,行式主要用来聚合)。但是doc_values只对not_analyzed字段生效,对于需求中的analyzed字段,聚合时会生成fielddata并加载到内存中,这其实非常损耗性能和资源的。所以完全基于es来实现该功能存在性能问题。
方案二:使用mongoDB进行存储
利用开源分词库在业务代码中对文本作答进行分词,将关键词存入文档的arrary字段。对关键词字段建立索引,聚合时将关键词字段unwind后进行group by操作。召回时,对关键词字段进行match操作。需要注意的是,在召回时其实已经由模糊查询的搜索变为精确匹配的查询。
在该方案中,我们主要关注的是聚合时对关键词字段unwind后进行group by + sort操作,以及召回时对关键词字段进行match操作时的效率问题。基于对mongoDB多键索引的了解以及实际的测试,上面的两种操作都会用到索引。在业务场景下,最大参与人数为10W量级(在所有调研产品中这个量级也完全够用),假设平均每个作答有10个关键词,那么上面的两个操作可转化为:100w量级的数据在B+树索引下的group by操作以及查询操作,其性能上完全可以接受的。
方案三:使用mongoDB和es
在方案一中,我们提到es用来做text字段的聚合操作是不合适的。所以有考虑数据在mongo中存储一份,同时mongo来做聚合操作,es做召回操作。但是方案二中我们明确用mongo来做召回操作也是完全可以的。那么我们简单来比较下es和mongo各自的特点,以确定是否有必要同时使用mongo和es。
如果你对es和mongo都有一定的了解,那么es相比mongo而言,资源消耗更多,读写性能一般也不如mongo,其优势就在于牛逼的模糊查询和复杂搜索能力。在该场景下我们显然可以完全使用mongo来胜任需求。但是如果需求有升级,比如在召回的时候可以随机选择多个关键字,那么似乎用es更加合适。
如果觉得本文对您有帮助,可以请博主喝杯咖啡~