此文根据【QCon高可用架构群】分享内容,由群内【编辑组】志愿整理,转发请注明出处。
王晓伟:2009年创办麦图科技,专注于电商行业的垂直搜索, 受到多家天使、Pre-A投资机构的关注。有10+年互联网、游戏、内核安全从业经验。历任软件工程师、高级软件工程师、技术经理和总监。目前主要从事手机游戏发行平台的构建,推进公司DevOps的培养和运维自动化的实施。
以下为王晓伟老师最近在微信群直播分享的全文记录。
垂直搜索的应用场景
场景1:基于拼音搜索联系人,可以从开始处搜索,也可以从中间搜索。
场景2:基于关键字搜索常见政府网站,存在的问题参见截图说明。
搜索业务场景归纳:
1、传统封闭系统
ERP、CRM、OA、知识管理系统、站内搜索、各种企业/电子政务系统
2、新型大众用户系统
移动应用、桌面应用、交互友好的WEB系统
3、技术应用
拼音-汉字的检索、自动补全、支持推荐系统
4、解救数据库的like查询
根据数据库数据量级和文本长度不同,性能可以提高数个量级
搜索应用场景举例:
很多政府信息公开网站的站内搜索使用数据库的like, 导致大量有用信息无法查询(场景2), 需要用垂直搜索技术提高查询的质量
拼音查询, 文字起始处查询和文字中间查询(场景1)
App本地搜索(场景1)
从应用场景上看,目前在大数据、用户产生数据的背景下,搜索的需求是刚性的。
技术选型
1. 检索引擎选型
彼时选型背景
Xapian相对完备的理论模型和技术实现,较早支持概率模型(BM25),彼时Lucene和Sphinx不支持BM25(事实上都要晚上一两年)。
Lucene早期版本二次开发工作量太大,该版本更像一个实验性的项目, 只是实现了一些主要功能。
Lucene早期版本索引和检索的性能远低于C++实现的Xapian。
Solr定制灵活性较差, 比较适合整站页面抓取和全文索引处理, 不能满足对于电商产品的索引的需求(重点索引, 不需要全文)。
Sphinx与MySQL结合过于紧密,定制侵入性太强,检索模型单一,更适合对于相关性排序和权重计算不那么高的全文检索。
主要思路:
选型一定要了解业务和业务的价值,垂直搜索的核心价值在于,要对用户输入关键字的搜索意图有充分理解,返回的结果的相关性、权重排序要与用户高度匹配,提高满意度,所以对于检索模型和权重计算要求较高。
比如彼时, Nokia N97很流行, iPhone也很流行,当用户输入N97时,京东,亚马逊,一号店等绝大多数网站搜索出来的前10个几乎都为N97的配件,而我们搜索的结果是N97手机。
这就是全文搜索和垂直搜索在本质上的差异。在这点上跟推荐系统有点像,只不过推荐系统是根据用户的历史足迹和行为推定出来,而垂直搜索是通过关键字。某些情况下, 两者可以相互融合。
最终,我们选择了Xapian。
2. 存储选型
这部分主要从笔者所接触的系统着眼,一些看法从今天来看也许不一定正确,主要分享一下思路。 当时我们选择了Cassandra+MongoDB,而不是HDFS/HBase。
几点原因:
HDFS处理小文件是个坑, 空间浪费大,文件一多,检索性能慢。彼时HDFS没有现在这么稳定,NameNode会因为莫名的原因阻塞。HDFS文件管理跟普通的文件系统原理相同,命名空间节点有限,需要多级目录管理,最终检索太费劲。
HBase基于HDFS,性能一般,检索困难,Scheme定义没有MongoDB灵活。不过现在好多了。同样都有单点故障,彼时创业公司运维能力和资源没有那么多,感觉维护不来。
Cassandra 和MongoDB搭档基本解决我们问题,包括扩容方便,检索便利,scheme自由度高;Cassandra是去中心化的,只要不是几个机架一起坏,一般不会有事;基于一致性hash的数据分布,扩容比较无忧,会自协调;根据配置可以做到多份 replica,有容灾能力。
我们最初用的MongoDB 1.x版本功能雏形已经全了,检索方便,支持js的mapreduce。 我们是基于Ruby和C++开发, 而MongoDB最早支持Ruby。
创业公司选型最大的问题是时间问题. 我们不能停下来选型而不做业务. 这是个比较大矛盾. 上面提到的几点经验希望对大家有所借鉴.
垂直搜索的引擎架构
分享一下5000W数据以内的垂直搜索引擎的架构模型,模型中也涉及到了流式计算,当然彼时流式计算并没有如此完整的模型和开源项目的支持。我们可谓是蛮干了一把!此处可以参考王新春老师的实时计算在点评中内容做个比较(当然结论是, 我们还是很山寨的)。
整体架构包含以下部分:
1. 种子发生器
用于入口页面的发布,可以根据需求定义粒度,比如整个 http://www.jd.com 是一个入口,夺宝岛http://auction.jd.com/index.action 也是一个入口。后面会介绍业务场景。
2. 抓取系统(Cralwer)
单进程单线程异步多工方式抓取,分布部署,容错性,健壮性为主。通用的模块与具体站点和业务无关,只负责抓取,抓取的URL最初由种子发生器发出,后面有Parser页面解析系统分析URL再填充。Crawler是一个不会停止的系统。Crawler的数据key和meta-data存储于MongoDB,页面的 RAW 数据存储于 Cassandra。Crawler除了容错性,健壮性之外,性能其实非常重要。
3. 页面解析器(Parser)
业务相关,从MongoDB 和 Cassandra 获取数据负责对页面进行符合业务需求的分析,根据关键字、URL特征和预置的规则将页面URL和部分数据提取出来,另外实现查重的功能(几亿数据查重,采用 bloomfilter + Redis 实现 )
4. 数据分析器(Analyser)
语料分析, 真正的业务核心。将HTML的页面数据提取成结构化的数据,存储到MongoDB。整个架构最精彩的部分也在这里,如何能做到处理几十家不同网站数据提取和规整,并能跟上源站更新的节奏(当时某东, 几乎天天更新),又便于多人并行开发和更新, 低耦合, 互相隔离, 持续部署是一个有趣的问题。
5. 索引器(Indexer)
完成从MongoDB取数据,分词,语法分析,部分语义分析,计算预置权重,完成索引。该模块完成一堆数据到信息的提炼,直接影响了用户查询结果的质量和满意度。比如判断语义,相关性排序等。索引工作从代码层面不难,调用几个函数把数据添加即可,但是其实功夫都花在前面了。索引和检索是垂直搜索引擎的大脑,是思考的部分,性能要好,结果要准。
6. 图片到文字的提取识别(Ocr)
最初测试过 Tesseract OCR ( 由HP开源, 后来由 Google 赞助的一个项目),但是后来发现不实用,样本学习过程繁琐,更新不方便,关键识别太慢。后来我们手动写了一套专门只针对数字的识别服务。OCR处理会在索引之前完成。这一部分很多时候不是必须的,当时主要针对某东和后来被鹅厂收购的某讯,现在某东已经不是图片了。
下面来一张图,大概表示一下这之间的逻辑,分层关系图中表现的不是特别准确。核心中间件是RabbitMQ。
后来有所改进,我们把CWS也单独抽取出来,自己写了一个简单的MQ Agent,是基于Redis实现的,性能比RabbitMQ要好很多。建议有条件可以使劲的Hack。
垂直搜索技术和业务细节
1. 如何提高垂直检索质量和语义识别
其实语义识别本身是很难的,但是一定的关键词集合里是可以做到和优化的。比如前面提到的,搜索”N97 手机”搜索出一堆手机配件的问题,因为手机和手机配件含有相同的关键字。传统的相关度,权重的模型是基于语料库TF/IDF做的。但是商品的名称文字是很短的,基本上都只会出现一次,名称相似度也没有可以参考的,那怎么办呢?
这种情况我们就需要预置权重,我们编写了一套学习的工具。通过分类、品类,建立了词干、词根的树形结构;同时设定每层的权重,那么用户在搜索的时候,匹配从根部开始,那么就避免了搜出树枝部分。这是个非常繁琐细致的工作,要分析整个商品库,人工很难。需要有一套启发式的词根更新方法和工具。
2. 如何应对不同众多异构数据模型
先发一张图, 图中列出了对于不同站点检索的模板文件的管理结构。
这是其下层目录的结构。
前面提到我们是做电商的垂直搜索的,需要从源站获取商品信息:商品名称、价格、图片、规则参数、详细介绍及评论。
显然每个源站都不一样,要适配40几家网站需要一种独立、易更新的、fast-fail的、错误自容的架构。这样能保证开发可以并行,源站更新可以快速适配,出现错误不影响其他模块。
后来,我们采取了基于模板语言的方法,把业务逻辑封装到一个模板文件,此文件结构本身是HTML,通过、<script>标签把我们业务代码放到HTML里。这样好处是:业务代码跟要解析的HTML在一起,方便代码提取数据的逻辑调试,同时业务代码运行环境局限在模板本身,即使出了问题也不会污染到其他代码。
代价是开发一套工具用于模板编写调试。
3 .关于算法选择
算法一定不是万能的。现在计算权重,相关性的算法很多。我们尝试过基本的布尔、BM25、SVM、Cosine similarity等等。
其实这些算法直接应用到现实的业务都不灵。那为啥会有这么高大上的算法存在呢?
我们的经验是:想要发挥算法奇效,要靠数据索引前的预处理。所以,踏踏实实做数据处理,分析业务,然后理解数据与算法的结合点;最终才能做到相得益彰,检索出符合用户心理的结果。
Q&A
Q1: 请问使用的中文分词器是什么,自研的还是其它?拼音搜索是如何实现的,是在新建索引时将转换为拼音存起来吗?
1、中文分词器:基于scws, 国人开源之作。自行开发了Ruby和Python的binding。
Q2: 数据分析器,分析HTML代码的时候,如何解决Ajax的问题?
解决Ajax的问题:我们遇到某宝是这样的, 一开始想开发一套基于Webkit的工具处理,后来发现性能太差,而且某宝Ajax请求基本稳定;所以在模板里直接发起HTTP搞定。或者也可以在Parser阶段取出相关URL进行处理。 这里我们没找到通用的解决方案。
Q3: 重新让你选择一次,目前情况下你会做哪些选型上的改进?
1、业务量不增加的情况下,索引和存储层面我可能不太会做更多改变。
2、技术架构上,对于Crawler及其他几个模块的实现要改;之前是自行开发的并行结构,健壮性还行, 但是维护难度高, 适合采用现在流行的并行计算的架构。
Q4: 基于Redis实现了一个MQ Agent,是基于Redis的队列实现的吗?为什么没有直接用Redis的Pub/Sub? 还有这个有做集群实现吗?
1、自己开发是将一些业务放进了Agent,便于集中优化。
2、未做集群, 做了主备。
Q5: 拼音库是怎么实现的?有支持模糊音匹配吗?
1、拼音库有现成的,如果觉得不可靠可以通过Character Map提供的数据自己做一份。
Q6: 用户的意图你们是怎样预期及理解的?
我们是靠语料的数量,我们直接能假定用户不是调戏我们的。然后再通过一些算法,比如纠错、拼音、引导提示和补全等猜测。还有最重要的是:
Q7: 请再稍微具体展开下权重计算和检索模型这块儿?N97手机的例子,怎样做到排除干扰词影响的?如N97手机和N97手机壳.
原理如下:
Q8: MongoDB的库级锁对吞吐量的影响是怎样优化或规避的?
1、这个我无法从MongoDB本身回答,我的经验是,mongodb driver for ruby(理论上其他的也是)是异步写的,所以写不会阻塞。
Q9: 基于模版的业务逻辑放到HTML的Script里面,业务开发人员在这个框架如何进行代码调试?
1、最重要的是我们API是详尽和傻瓜的, 用法明确, 完全满足数据操作的要求, 杜绝了开发人员用错API或者没有API可以用。
Q10: 爬虫获取的URL反垃圾怎么做的?比如URL陷阱,无效参数之类的?
1、我的回答可能会有点遗憾,垂直搜索对于URL的粒度的把控是很细的,所以我们从业务上就可以杜绝。
Q11: 单进程单线程设计是基于什么考虑的?
1、我纠正我的说法,我想说的是一个进程只有一个线程,部署其实是多进程的。
Q12: 从HTML中提取结构化数据是你们是用正则还是基于css or dom?
基于dom。css/xpath 正则建议不要用于复杂数据,变动性强的数据和大规模数据,不易调试和维护。
Q13: 排序会用到用户日志的学习及训练吗?
当时的设计是通过朋友收藏,社交上connection + 搜索权重训练。从日志学习没有,主要是没有想到好的防止作弊的方法。
Q14: 最近搜索遇到一个需要存储时间维度的需求,而且是几十天,能通过建索引解决吗?
我不是很明白问题的需求,我觉得应该可以。
Q15: 如果现在你选,你还是用RabbitMQ吗,有没有考虑Kaffka? 自己实现基于MQ Agent,是否类似于Github开源的resque?MongoDB写入不可靠的问题有碰到吗,有用Shard吗?
1、参照尽量Hack的理念,可能会换。