传统数据库是为了解决结构化存储而产生的,如关系型数据库、键值存储、操作磁盘文件的map-reduce(映射-规约)引擎,图引擎等。 传统型数据库的缺点:
搜索引擎是为了解决传统数据库的缺点而产生的。它主要是用来搜索大量非结构化文本,并返回最相关的搜索文本。
Solr是搜索引擎的一种,主要用来文档存储与检索。提交给solr处理的每一份数据都是一个文档,文档可以是一篇新闻报道、一份简历、社交用户信息,甚至是一本书。
Solr会通过以下四个步骤对内容和查询进行文本分析:
Solr之所以能完成上述工作,是因为使用了索引将内容映射到文档的方式,这与传统数据库模型-文档映射至内容的方式不同。倒排索引是搜索引擎运作的核心。
假设我们有若干图书,我们来看下如何将索引中的词项映射到文档。
文档编号 | 内容字段 |
---|---|
1 | A Fun Guide to Cooking |
2 | Decorating Your Home |
3 | How to Raise a Child |
4 | Buying a New Car |
5 | Buying a New Home |
6 | The Beginner’s Guide to Buying a House |
7 | Purchasing a Home |
8 | Becoming a New Home Owner |
9 | How to Buy Your First House |
原始文档
Lucene倒排索引
现在可以看到,倒排索引将语料库中的每个单词与它们出现的文档对应起来。
本节较少分析查询如何使用索引找到匹配的文档。
假设用户要查询“new house”,Solr默认配置是将词项或短语视为可选的,在单查询上可进行配置,使用URL里的q.op
参数配置多种查询句柄。
/select/?q=new house&q.op=OR versus /select?q=new house&q.op=AND
常见的布尔查询运算的图形化表示
在Lucene索引上除了可以查询词项之外,还可以查询短语。但是索引只包含单个的词项,那么如何搜索完整的短语呢? 短语中的每个词项依然在Lucene索引中分别检索,就好像提交的查询是两个查询词组合new home,而不是“new home”整个短语。一旦发现重叠的文档集,就会通过另一项倒排索引特征:词项位置(它会记录词项在文档中的相对位置),利用词项位置来确定最终结果集。
带有术语位置的倒排索引
下表显示了new和home两个词项交集的倒排索引。
词项 | 文档编号 | 词项位置 |
---|---|---|
home | 5 | 4 |
8 | 4 | |
new | 5 | 3 |
8 | 3 |
搜索以offi开头的文档: * Query: offi* 匹配 office, officer, official, 等 * Query: off*r 匹配 offer, officer, officiator, and so on * Query: off?r 匹配 offer, 但是不匹配 officer
注意:不适用于短语内的通配符查询
Solr还提供了在已知区间值中进行搜索的功能,适用于在一个区间内搜索特定的文档子集。 匹配2012年2月2日到2012年8月2日期间创建的文档,可以执行以下搜索:
solr通过编辑距离搜索解决了80%以上的人为拼写错误。
注意: 2个以上的编辑距离会使得搜索速度大幅下降。
编辑距离适用于词项字符的替换和短语内词项的变形,而邻近搜索适用可视为传统短语搜索的“马虎”版本。
Solr出色地实现了搜索结果排序中最佳匹配文档位于搜索结果列表的顶端,这是它的开箱即用功能之一。它会计算每个文档的相关度得分,并从最高分到最低分对搜索结果进行排序。本节介绍相关度得分的计算方法及影响得分的因素。
solr的相关度得分是基于Similarity类的。在solr的schema.xml中,这个类被定义为一个预置字段。Similarity是一个java类,它根据给定查询了搜索结果相关度得分的计算方法。 此类通过两段式检索来计算相似度。首先,使用布尔模型过滤出不符合用户查询的所有文档。然后,使用向量空间模型通过计算和绘制将查询和文档转换为向量,在此基础上计算相似度得分。
词项向量的余弦相似度
给定查询(q)和文档(d),查询对应的文档相似度的得分计算如下所示:
Score(q,d) = ∑ ( tf(t in d) • idf(t)^2 • t.getBoost() • norm(t,d) ) • coord(q,d) • queryNorm(q)
其中:
评分算法分解
上图为相关度计算的主要概念,包括词项频次(term frequency, tf)、反向文档频次、词项权重、规范化因子
词项频率是指特定词项在待匹配文档中出现的次数,表示了文档与该词项的匹配程度。 这个是Solr默认相关度公式中tf的基本前提。查询词项在某一文档中出现次数越多,则该文档被视为越相关。
反向文档频次是查询词项罕见程度的度量,根据文档频次(含有该查询词项的总文档数)计算它的逆。计算公式为:idf(t) = 1 + log (numDocs / (docFreq +1))
。
因为idf表示词同时出现在查询和文档中,因此相关度计算公式中需要求平方。
词项频次与反向文档频次在相关度计算中起到了相互平衡作用。 词项频次“奖励”了在一个文档中出现多次的词项,而反向文档频次“惩罚”了在多个文档中普遍出现的词项。因此,例如the、an、和of等在任何文档中都会频繁出现的词汇,最终会拉低相关度得分。
我们可以通过自己调整内容文档中特定字段或词项的重要性,来调整相应字段和词项在索引阶段或查询阶段的权重。 查询阶段权重设置,可是使用如下的语法进行设置:
Solr默认的相关度公式计算了三种规范因子:字段规范、查询规范和协调因子。
字段规范计算公式
norm(t,d) = d.getBoost() • lengthNorm(f) • f.getBoost()
字段规范由匹配文档的权重、匹配字段的权重以及惩罚长文档的长度归一因子组成。这三个完全独立的数据以单个字节储存在Solr引擎中,这是组合为一个字段规范变量的唯一依据。d.getBoost()
分量表示发送至Solr的文档权重,f.getBoost()
分量表示字段的权重。
信息检索中的查准率*Procision(精确性的度量)与查全率Recall(全面性的度量)主要是在返回相关结果与尽可能的结果之间作出权衡。*
查准率主要是为了回答这样一个问题:返回的这些文档是不是我要寻找的?
查准率的计算公式如下(介于0.0和1.0之间):正确匹配的文档数量/返回的文档数量
查全率衡量的是返回的搜索结果是否正确。查全率衡量的则是搜索结果的全面性。
查准率的计算公式如下(介于0.0和1.0之间):正确匹配的文档数量/(正确匹配的文档数+错误的匹配文档数)
最大限度提升查准率与查全率是绝大多数搜索相关度优化的终极目标。 Solr中平衡查全率和查准率的一种常见方式:在整个结果集上计算查全率,仅在搜索结果第一页(或少数页)上计算查准率。根据这一模型,调节Solr相关度评分的计算方式,让更好的结果被提升到搜索结果的顶部,而许多不良的匹配出现在现在搜索结果的底部。
此部分我们将介绍Solr的存储方式,以探讨如何可以拓展到处理数十亿文档和无限查询请求数量。
Solr的核心概念是所有文档去除规范化。非规范化文档指文档中的所有字段是自包含的,允许这些字段的值在多个文档中重复出现。下面通过和关系型的存储结构来对比二者的差异。
关系型数据库存储方式
上图展示的信息表示在同一家公司(Code Monkeys R Us, LLC.)任职的两个用户。Solr文档不遵从传统的关系型数据库的规范化模型,下面展示在Solr文档中的表示方式:
<doc>
<field name="id">123</field>
<field name="username">John Doe</field>
<field name="about">Senior Software Engineer with 10 years of
experience with java, ruby, and .net
</field>
<field name="usercity">Atlanta</field>
<field name="userstate">Georgia</field>
<field name="companyname">Code Monkeys R Us, LLC</field>
<field name="companydescription">we write lots of code</field>
<field name="companycity">Decatur</field>
<field name="companystate">Georgia</field>
<field name="lastmodified">2013-06-05T12:25:12Z</field>
</doc>
<doc>
<field name="id">456</field>
<field name="username">Coco</field>
<field name="about">I’m a real monkey</field>
<field name="usercity">Norcross</field>
<field name="userstate">Georgia</field>
<field name="companyname">Code Monkeys R Us, LLC</field>
<field name="companydescription">we write lots of code</field>
<field name="companycity">Decatur</field>
<field name="companystate">Georgia</field>
<field name="lastmodified">2013-06-01T15:26:37Z</field>
</doc>
当Solr部署在单台服务器上时,同时发出过多的查询请求,或者需要在单台服务器上处理太多的搜索数据,这些都会导致搜索服务器超载。在这种情况下,我们可以将内容拆分到两个单独的solr索引中,每一个索引包含单独的一部分数据。每次搜索运行时,会自动被同时发送到两台服务器上,分别进行处理后汇总在一起后再返回给搜索引擎。 以下语法可以实现多个Solr内核的聚合搜索:
http://box1:8983/solr/core1/select?q=*:*&shards=box1:8983/solr/core1,box2:8983/solr/core2,box2:8983/solr/core3
以上例子有4个特点:
当只使用分布式搜索的方式来提高搜索性能时,如果在搜索的过程中,其中的一台机器出现故障,会导致整个Solr的响应出现故障。 这是因为在这种方式下的服务器是相互依存的,所以一台无法被搜索,它们就都不能搜索,导致整体出错。因此在构建solr解决方案时,要采用服务器集群的方式取代单一服务器,由这些服务器组成一个计算资源来提供服务。