《大话搜索引擎》-第一季(2)聊聊工程切分、倒排、分词

本季内容概览:大概会花费4篇左右的文章为大家讲解一个普适率80%左右的垂直搜索引擎,内容涵盖需求分析、架构设计、模块切分、各种填坑、效果评测 直至上线运行。     

本篇知识点概览:工程切分、倒排、分词


一、放水篇(如果时间少请直接跳过阅读 干货篇 ;  特别声明:如果有时间建议放水篇也阅读一下,无水万物枯萎凋零 搜索亦然) 

   上篇话说架构模块切分完毕,按照国际惯例我以为一切准备就绪只等光膀子开始码砖了,然而如大家所想我以为我以为的就是我以为的!就像帝都雾霾来袭站在国贸看到的天空中飘着一个小三角板似的怪物,走近一看才知道是这么大一个大裤衩!!
     进入A公司的前一个周基本上都是在熟悉一些东西(对我来说应该是调研,因为对搜索啥都不知道),记得杨总当时安排了3人日让我把solr tutorial看一遍并且将里面的demo也做一遍。对于我大学四年英文考试是否需要补考全看阅卷老师心情的水平来说当时面对满篇的e文网页也是压力山大, 现在想象也是醉了,本身不多的内容我竟然吭哧了三天才算理清楚solr到底是个啥玩意!但是一路走过来才发现自己是幸运的,solr tutorial寥寥数页文章无疑是对搜索小白最好的入门教材,它使你能快速上手感知搜索是个什么东东而又不至于一开始就陷入各种烧脑无聊的理论知识甚至算法无法自拔。
     这几年遇见过刚入搜索的许多朋友直接一上来就要来高大上的比如自己写分词、自己定制排序、几十条数据用solrcloud,其中理由大都很有趣 比如领导要求、公司要求、xxx公司就是这么搞的、还有的更搞笑的是好不容易有一次自己做主的机会自己想挑战一把极限!其实按照他们实际需求来说大部分并无必要 。最后的结果大部分可能是搜索做的焦头烂额,被挑战的头晕眼花在搜索的职业发展道路上中途退场了,凡此种种不免深感惋惜。
     solr tutorial熟悉完毕后我迎来了第一个3人日的小任务LogCleaner, 对 你没看错是LogCleaner(日志清理工),也许你会纳闷这是什么鬼和搜索有毛关系? 咱们看看这个LogCleaner工程要实现的功能:A 将指定目录下的日志删除、 B 日志名字可以配置并支持正则表达式、C 可以指定日志文件保留的天数(天、时、分都可配置)、D 可以指定日志的开始或者结束时间进行删除。是的这个工程本身和搜索没什么关系,我当时也很纳闷 这难道是进少林寺学武术先要做一年扫地僧考验一下不成。当天回家想了一下四个人的团队除了杨总外地主和老曹都是五年以上的搬砖老司机,这些打扫卫生的活当然非我莫属了,于是也便释然了。接下来如你所猜,又来了LogStorer(日志存储工)、LogDumper(日志转储工) ,就这样我在日志中死扛度过了两个月(后来才知道这些项目也都是整个搜索中的一块块积木,是不是有点像武侠了`(*∩_∩*)′)。当然在这两个月期间课余时间也没敢懈怠,几乎是在公交车上看完了《搜索引擎lucene+Heritix》,然后又拉着老曹跑人大打印店把《lucene in action》的英文第一版打印了厚厚一摞继续啃(当时lucene in action只有第一版英文版,并且一直缺货,老曹说人民大学的打印店打印很便宜就索性下个电子版的去打印了)这两本书跟随我在北京搬家搬了无数次,直到最近才把它们拿去送给后来者了。
     由于项目紧张晚上加班经常一块吃饭,私下里我也好奇地主和老曹都在做什么高大上的东西,这一看也是惊呆了。  老曹一直在倒腾词(那时候对词库没概念),将老的银杏搜索的热搜词一堆一堆的弄过来然后从网上找一些餐饮相关的词合并进来,还有天天分析餐厅标题,将那些和餐饮八竿子都打不找的词肉眼一个个找出来去掉,天天满屏词乱飞也是头要炸了。开了几次会议后终于确定我们选择自己做分词采用正向匹配最小的算法(餐饮行业的词大部分是菜谱和餐厅名,大部分都在2-5个汉字左右 2-3个的占到80%以上,并且根据老曹整理的实际餐厅数据场景和杨总跟大家的分析正向匹配最小算法效率相对较高并且歧义很小即使有歧义也可以自己修改词库控制)。这样也就带来了一个问题如果ABC是个词 AB也是个词,则ABC永远不会被切分出来,那么词库中到底是留AB呢还是留ABC呢?只有把这类的词找出来看具体场景才能人工判断决定留谁。于是老曹开始写一个脚本处理大约20w的词库将这种包含关系的词找出来,比较清晰的记得当时老曹特意让我们等一下他让脚本跑起来再一块去吃晚饭,吃完晚饭回来跑了一个多小时的脚本还是没有跑完,一直到晚上九点半大家准备撤了回家的时候老曹突发警告说“脚本还没跑完大家都别懂我电脑啊!”,第二天一大早大家好奇的聚集在一起准备看看脚本跑出的结果,这一看心里拔凉拔凉的, word 哥 竟然还在跑!!!疯掉了! 后来大家一块查原因才知道由于是临时脚本没怎么估计性能问题,结果脚本里有几个for循环,计算量超万万亿了!!!
      与此同时地主正在设计一个mysql大表并和业务人员去理各种字段关系以及triger设计,由于涉及的数据源和表都超多所以工作也是不轻松,这工作也持续了近两个月中间也开了无数次的评审会。杨总一直在辅导我们每个人应该如何做各自模块和工程并时刻做着救火队员的工作。
      是的,如大家所见我们前两个月并没有大家期望的高大上的搜索技术,相反大都是些世俗的脏活累活,甚至以后也很少会有多么高大上的技术工程。现在回过头来看,一个好的搜索的确离不开甚至80%以上的活都是很平常的脏活累活。每个细节、模块、工程的精细雕琢才有可能做出一些还不错的成绩。搜索就像生活,相对于诗和远方更多的还是些柴米油盐酱醋茶,时不时的换个尿不湿也是常事o(* ̄▽ ̄*)o。

二、干货篇友情提醒:如果您是一名搜索初学者这部分请仔细阅读,以免出现整个系列文章读完后搜索技术点忘得一干二净,而每篇里面扯的蛋都历历在目、栩栩如生的情况!

工程拆分细节

架构中模块拆分和工程拆分大部分时候会不一样,一般来说模块拆分更抽象将意思表达出来即可,更侧重宏观上的控制;工程拆分则要侧重团队之间的沟通协作方便和代码依赖交叉最小化,以及大块的代码如何解耦和复用上 相对来说会细一些。上篇里提到过的架构图中的模块大家可能还有印象,下面这幅图里是整个工程图,给大家一个直观的感受后接下来会对每个工程做详细描述。

   如上图所示,我把大概15个工程按优先级的维度分成了三大部分,因为业务需求的不同大部分第一次做垂直搜索的人员可能只需要用到其中的一部分或两部分,这样整理应该会更通俗易懂一些。

core(核心): 这部分是必须有的,也就是作为一个搜索引擎必须具备的工程组件,就像作为一辆汽车起码要有四个轮子,作为一个人起码要有四肢和大脑一样。

extend(扩展) :这部分可有可无,按照我接触的一些初次需要搜索功能的平台来说真实需求几乎90%的情况不需要,然而你懂的,实际上大部分情况下需求会被放大。现在回过头来看这些就像我们的生活,有车有房的并不比月光族的生活质量和幸福感会高一样,同样对于搜索刚刚起步的阶段做了这一部分扩展如果驾驭不了往往会使得整个搜索更差,甚至整个项目远远超期或者中途夭折。

assistant(辅助):这部分比较难以形容也相对杂乱些,几个词形容一下大家自己悟吧:锦上添花、打酱油、工匠精神、吃瓜群众、未雨绸缪、杞人忧天。(整个《大话搜索引擎》系列会有好多工程会属于这部分,也许只能系列文章全写完后才能确切讲清楚这部分是干啥的)


 core(核心):

1.DataCollector(数据采集工程)  将变更餐厅相关信息近实时的采集到搜索库中。 输入:分属于两个类型的数据库(mysql,oracle)的20多张餐厅相关信息表。输出:整理好的单表数据到SeachDB。此工程在部署的时候是一个单独的后台守护进程运行。

2.IndexBuilder(索引创建工程)  负责将采集好的变更的餐厅数据近实时的发给solr让其创建索引。输入:SearchDB中的数据源; 输出:整理好的xml文档发给solr。 此工程在部署的时候是一个单独的后台守护进程运行。

3.SearchService(搜索服务工程)  接受web发来的搜索请求执行搜索操作,并做一些业务转换。 输入:搜索请求参数、业务配置文件、solr返回的源数据; 输出:整理好的规范化的xml数据。  此工程在部署的时候是一运行在web容器中的标准http服务。

4.triger(数据触发器)  数据源DB上的触发器,负责将变更的数据触发到对应的表中。输入:20多张餐厅信息表的变更事件;输出:变更信息表中的数据(只标识增删改和餐厅业务主键)。此“工程”是DB上的机制,只是开发些triger脚本即可。
 extend(扩展):

1.WordSegmentation(分词工程)  将索引和查询的字符串分词,包含正向匹配最小的分词器(主分词器)、特殊符号分词器、儿话音分词器、全半角转化分词器。输入:字符串 ; 输出:一个个词。比如输入:“西直门肯德基餐厅” 输出:“西直门” ”肯德基“  “餐厅”。此工程在部署的时候是以jar包的形式内嵌到solr中的。(分词这块具体在搜索中是啥意思文章后半部分会细讲)

2.SearchProxy(搜索代理工程)  接受SearchService的业务api参数进行审核解析为solr的复杂语法,并查询solr数据返回原生xml信息。 输入:SearchService查询请求参数 输出:solr返回的原生xml数据;此工在部署的时候是以jar包的形式内嵌到SearchService中的。

3.UpdateFacetCfg(切面数据配置更新工程)  负责将数据库变更的需要做切面统计的数据从数据库下载好组织好生成xml文件供SearchService使用,例如:区域、菜系、价格区间、地铁沿线 等等。 输入:数据库原始信息或者运营人员提的数据变更; 输出:树状facet.xm结构文件。此工程在部署的时候是一个单独的后台守护进程运行,定期向SearchService中输送最新的切面数据文件facet.xml,SearchService则有自动检测文件变更的线程将最新文件加载到内存中使用。两个工程通过文件的形式解耦。

4.SearchLogic(搜索逻辑功能)   扩展solr功能提供一些灵活的查询模型。例如And, OR, Mix(分词查询语句之间先And查询,无结果自动切换到OR查询)。输入:模型参数;输出:改写的查询语法解析。 这块要改写不少solr的代码,至于当时为什么要做这块原因大概这么几点。A:SearchService提供给多个业务系统用,查询精确度要求不一样 B:solr当时的版本(1.4)在语法解析上会有一些错误在配置同义词的情况下 And和OR关系解析不正确导致同义词配置形同虚设(餐饮行业好多同义词 比如 菜花<->花菜  土豆<->山药蛋 等等)。C:对于先And后Or的查询在solr层hack会减少一次网络传输,性能会更好些。此工程在部署的时候是以jar包的形式内嵌到solr中的。

5.SearchSimilarity(搜索相关度工程)   修改solr的相关度函数代码使其更适合垂直行业搜索。其实改动的很少,但是效果很不错。这块暂时不细讲,因为涉及的内容太多,后续的文章中会花费大的篇幅去讲解。感兴趣的同学可以提前看看了解一下下图的lucene评分公式,在没讲解这个公式前很难把修改的东西起了什么作用讲明白。

score(q,d)=(overlap/maxOverlap)·(1/(q.getBoost()^2·∑(idf(t)·t.getBoost())^2))·∑(tf(t in d)·idf(t)^2·t.getBoost()·doc.getBoost()·lengthNorm·∏f.getBoost())


6.Monitor(搜索监控工程)监控系统主要负责两个工作,一个是发现任何solr服务不可用则自动发邮件通知搜索团队每个人,另一个则是将solr服务下线通知LB让SearchService端立即停止请求不可用的solr,并同时周期性检测服务是否已经恢复,恢复的话则会自动通知LB上线并发邮件通知搜索团队每个人。此工程在部署的时候是一个单独的后台守护进程运行。


 assistant(辅助):

1. LoadBalance(负责均衡工程)  load balance这个模块是用来做负载均衡,比如三个solr slave可能有一台机器配置好,则在流量分配上可以多分一些。另外比如solr服务挂掉了 LB则不会上SearchService上的请求打过来。此工在部署的时候是以jar包的形式内嵌到SearchService中的。          

2. ResParticiple(餐饮行业分词测试工程)  这个工程本来可以不提的,本身上线的时候也不会用到,但是它的确又扮演了非常重要的角色。这个工程在分词器进行修改后会拿餐厅标题、热搜字符串等经常被检索的字段分类随机跑出来一定数量(上万)的分词效果供大家检测分词是否合理词库是否完善。这几乎也是初次建立自己词库和分词器的必经之路,尽管繁琐甚至大部分是人肉工作,但是相当有必要!! 此工程不需要部署,通俗点讲是良心活、工匠活。3.LogCleaner(日志清理工程)主要负责 A 将指定目录下的日志删除、 B 日志名字可以配置并支持正则表达式、C 可以指定日志文件保留的天数(天、时、分都可配置)、D 可以指定日志的开始或者结束时间进行删除。 上线部署的时候是多部署,多个运行在后台的守护进程,主要工作是给各个搜索工程吐出的搜日志做清理或保留工作。(其实现在来看这块的功能更常规的做法应该是log4j+shell脚本去处理)                                                                              4.LogStorer(日志存储工程)   将SearchService的文件日志读取存储到关系数据库中,以备后续统计分析使用。主要是针对搜索参数的比如:搜索词、ip、用户id、选择的筛选条件、排序条件等等。这块其实是搜索用户数据采集的雏形,后面的经历中会对这块做更深入讲解,其实大部分搜索的后期优化来源于和搜索相关甚至不相关的数据仓库数据。此工程在部署的时候是一个单独的后台守护进程运行。                                                                                         5.LogDumper(日志转储工程)   将一个月前的搜索日志信息转储到搜索日志历史表中,这块就不多解释了把,你懂的。此工程在部署的时候是一个单独的后台守护进程行。          

                                                                                     
搜索引擎基本术语

文档(doc):搜索引擎中的一条数据记录,比如一个餐厅、一个商品。

文档id(docId):搜索引擎内部唯一标识一篇文档的标记,就像我们的身份证编号一样。

词:查询、索引 存储的最小原子单位。比如汉字的最细粒度一个字也可以说是一个词。

分词器:将文本或字符串分解成一个一个词的工具。

倒排索引表:以词为索引记录每个词都在哪些文档中出现过。

正排索引表:以文档id为索引,记录相应的文档存储的位置。


正排倒排讲解

    搜索引擎中常听的的两个术语是正排索引和倒排索引(高大上的称呼可以是 forward index和 inverted index),到底是什么意思呢?大家对索引这个词应该并不陌生,比如说目录索引,邮编索引等等。通俗的讲我们经常阅读书籍的目录索引就是一种正排索引,比如你有天想唱《儿歌三百首》中的“葫芦娃”这首歌,通过目录很快找到了“葫芦娃”的页码(docId:文档id)在36页,于是很快找到了整个歌曲。再比如有天你的领导发你的紧急邮件中有一字“擦”,你认识但是不知道什么意思,于是拿起了祖传的《新华字典》查到了这个"擦"字在第585页,此处的585即可理解场倒排索引id,字典的组织结构是比较像搜索引擎的倒排索引结构的。

ok? 如果这个不太好理解,拿我们就那更正规的讲法讲解一遍。

假如有一天你的女神脑洞大开让你将包含“妈妈”这个词的儿歌全部唱一遍,那我们看如果根据正排索引来找出所有歌曲的话则只能一个一个歌曲看,如果那天《儿歌三百首》增长到《儿歌三亿首》是不是这种场景要崩溃香菇的感觉?那如果我们用倒排索引来查呢? “妈妈”这个词对应的倒排列表(文档Id列表)为 1、6、9   而根据这三个文档id再到正排表中快速查到 “妈妈带我沙滩跑”、“傻妈妈”、“喂妈妈” so fast !so easy!咱们再加点难度,又假设你女神突然想起一首歌叫 ”xxx小雪人“而xxx以及记不清具体是啥了,现在我们使用正排索引无疑还是蓝廋香菇!那看看伟大的倒排表如何呢:“小雪人”分词为 “小” “雪人”,对应的倒排docid列表分别是 “小”:2、5、7 ; “雪人“:2,两个docid列表取交集得到docid列表为2,则根据正排表快速找到”门口有个小雪人“  于是你的女神向你投来了崇拜的目光,如此估计你下个双十一不用一个人过了。

分词讲解

    聊搜索引擎,分词就不可能避开,也许有人说外文比如英文搜索引擎应该不用分词吧,是的你猜错了!分词的好坏直接影响到召回率(查询出来的真正相关的文档数/总相关的文档数)和查准率(查询出来的真正相关的文档数/查询出来的文档总数)。词是在搜索引擎中的最小原子单位,在lucene的体系又称之为term或token。既然是最小的原子单位就意味着不可再分,不管是查询还是索引都是以这个最小单位为基础。 那么什么叫分词的,就是将一个长字符串通过一定的规则或者词库分解成多个小字符串的过程而这里被分解后的小字符串即是搜索引擎中的最小单位的一个实体--词。所以搜索引擎中的”词“和我们生活中说的”词“并不是同一个概念!! ”张三买了一张三角桌子“   可以分词为  ”张三、买了、一张、三角、桌子“ , 也可以分词为”张三、买了、一、张三、桌子“ ,如果乐意也可以分词为”张三买了、一张三角、桌子“ , 上面提到的“张三” “张三买了”都是一种词,至于是不是词全是分词器说了算,就像每年评优秀员工一样,领导说你是你就是不是也是! 
     好,接下来我们做个智力题,说:”兔妈妈出门前让小白兔把一个胡萝卜切成了10小段放进锅里煮,结果兔妈妈回来了要它从锅里捞出来一条完好无损的整根萝卜,请问小白兔能做到吗?“,是的你回答正确显然不能, 那么难度加大说:”兔妈妈出门前让小白兔把一个胡萝卜切成了无数萝卜丝放进锅里煮,结果兔妈妈回来了要它从锅里捞出来10小段萝卜,请问小白兔能做到吗?“,是的你又答对了肯定不可能做到。然而初入搜索者经常会犯懵,时不时的去让自己扮演兔妈妈,让搜索引擎扮演小白兔,让搜索引擎蓝瘦的香菇!不信你看下面这个例子:我文档里面有"中国东北大学“,为什么搜索“中国大学”搜不出来, 反而搜索“北大”把 中国东北大学搜索出来了!  

如何选择分词器?分词的问题也是搜索技术社区里面经常甚至每天都会问到的一类问题。典型问题如:“我用什么分词器好  ik、庖丁、mmseg、ansj还是hanlp?” 这个就好比问ruby、java、python、php那个更牛X?再打个比方这些开源的分词器就像做好的鞋子(大部分有其固定特点),你直接问柜员要最漂亮的鞋子,结果给你推荐一个红色女士高跟鞋结果哪知道你是个大老爷们并且有着48的大脚!分词的选择脱离开业务场景和自己的数据场景无异于缘木求鱼,关于如何选择分词我以后会根据自己趟过的坑让大家一步步去理解加深,实际中没有最好的只有合适不合适的!


今天就先罗嗦到这里吧,感谢各位看官看到了文章末尾,若是无意挑战中挑战了各位亲的忍耐力还请海涵。其实本身不打算把文章搞这么长并让大家等这么久的,但是权衡再三还是感觉放在一篇中知识点相对比较完整独立,前后篇耦合度也低一些,作为一个码农的洁癖,你是知道的O(∩_∩)O。

----------------下篇预告---------------------

如何选择分词

----------------------------------------------------

喜欢的朋友请扫描下方二维码关注公众号:gold_data (金沙数据)  您将会及时得到最新的文章。


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值