Elasticsearch - 让你的搜索飞起来

接触Elasticsearch是两年前因为项目的需要。客户位于西雅图,对于新技术有着敏锐的洞察力,正好项目需要实现搜索功能,客户就想尝试采用Elasticsearch来实现整个网站的检索功能,然后就被迫去学习了一下。一开始我是排斥的,但还是忍不住想看一下为什么客户要推荐它。随着深入的使用,Duang~~,才发现这货果然是个神器,只需要轻轻松松的简单配置一下,就可以让你的程序的搜索功能插上翅膀,带着用户在你的应用里翱翔。

1.揭开Elasticsearch的面纱

既然这货这么厉害,那它究竟是个什么东东呢?首先我们不妨假设这样一个需求,在我们的程序中需要实现一个搜索功能,需要支持全文检索,模糊匹配,按相关度排序,并高亮显示关键词。

一种做法是基于数据库,可以写许多存储过程来实现复杂的查询,并且有些数据库本身已提供强大的全文检索和模糊搜索功能,但是这样会带来一个问题,且不说复杂的存储过程难写,难懂,难维护,首先数据库是一个程序性能的瓶颈,一个需要高响应的网站,是不应该将数据的计算与聚合之类的操作放在数据库层面上来做的,对于这类网站来说,数据库应该只是一个存放数据的地方。

还有一种做法,就是采用一些搜索引擎技术,比如很成熟的Lucene、Solr、Sphinx,这些技术已经诞生很久并被广泛采用,但是他们有一些共同的缺点,比如难配置,难扩展,搜索代码难学难写,如下面这段代码,当你需要组合一个查询的时候,你需要初始化很多不同的实例,然后组合成一个查询,这还只是一个很简单的查询,在不懂其API的情况下去看这样一段代码是很难理解其目的的。

接下来我们看下如果在Elasticsearch里,你如何去写一段查询语句:

上面这段代码只有简单的几行,我想只要懂这几个英语单词的人都能够猜这段代码是要干什么。没错,咱们是要查询postDate在“2011-12-10”到“2011-12-12”日之间的所有blog记录,瞬间颠覆了人生观有没有?一个查询竟然可以如此简单清晰,你只需要会写JSON就可以写出各种你想得到的查询,不需要任何程序基础。

看到了这里我想你应该对Elasticsearch有一些兴趣了,那么简单介绍一下Elasticsearch,它是一个基于Lucene的搜索服务器,但它针对于Lucene做了许多改进,解决了许多在实现搜索技术的过程中很头疼的问题:

  • 高效的实时搜索
  • 零配置,安装后即可用,不需要对你的搜索服务器做任何配置
  • Restful的web接口,只需要简单的使用JSON,就可以创建索引和搜索
  • 易于扩展,当你需要扩展你的搜索服务器的时候,你只需要将新机器纳入同一个集群名,Elasticsearch会自动扫描新加入的机器,并自动扩展
  • 分布式模型,如果你想让你的搜索支持分布式,你无需额外的配置,Elasticsearch的设计初衷就是为了解决分布式的存储与搜索

2. Elasticsearch的一些概念

在我们开始一个实例之前,需要先了解一下Elasticsearch的几个核心概念:

1. Mapping

我们需要在一段文本中搜索某句话的时候,首先需要将这段文本存储到索引服务器, 建立好索引供我们随后的搜索,如这样一段话“The quick brown fox jumps over the lazy dog”,在索引服务器上可能是如下形式:

[“the”, “quick”, ”brown”, “fox”, “jumps”, “over”,“the”, “lazy”, “dog”]

大写的字母被转换成小写,并以空格作为分隔符将整句话拆成单个的单词。这样就需要有一个规则定义如何去转换这样一句话成单个的搜索单元,也就是Mapping的作用。一个简单的mapping定义如下:


在上面这个mapping的例子中,定义了tweet这个文档包含的字段user、message等等,其中user是string类型,postDate是时间类型,priority是integer类型,index设置成not_analyzed则表示该字段在文本被分析的时候不进行分词处理,比如“Gary Gao”直接以两个单词的形式存储,不拆分成”Gary”和”Gao”。Mapping在某种意义上可以类似于数据库表结构的定义,在数据库定义时我们需要定义每个字段的类型,长度,是否可空等等,而在这里的mapping这定义每个字段的类型,分词时如何处理,为空的时候如何处理等等。

2. Tokenizer

在对一个语句进行分词处理的时候,有一个很重要的角色,就是分词器,分词器的定义直接会影响你最终搜索结果的质量。分词器的工作是将一串的文本切成 tokens,这些 token 一般是文本的子集。比如有这样一句话“今天是个好日子”,在进行分词的时候“今天”肯定不能被分成“今”和“天”,而应该被当做一个词语。又比如“今天天气不错,我们出去转转吧”,语句中的逗号应该被作为一个分词的符号。

Elasticseaerch内置的Tokenizer有很多,常用的有keyword tokenizer,将字段当作一个关键词,不做任何分词处理;lowercase analyzer将字段里面所有的字母进行小写化处理,whitespace analyzer则以空格作为分隔符进行分词,还有许多有用的分词器,这里就不一一的介绍。当然我们也可以自己根据具体的需求来定义分词器,也可以将不同功能的分词器进行组合,形成一个功能更强大的分词器。

3. Filter

过滤器可以对文本进行一些清理工作,文本在进行分词处理前,可能存在一些影响分词的“噪音”数据,或者需要对从分词器中出来的tokens进行二次处理时,都可以通过过滤器来实现。如在对一句英文分词时,语句中的”the”, “a”, “an”实际上对搜索可能是没有意义,咱们就可以定义一个过滤器来对特定的单词或对搜索无意义的词组进行清洗。和Tokenizer一样,Elasticsearch也有许多内置的filter,你可以任意的组合,总有一款可以满足你的需求。

通过上面的介绍,我们对创建一个索引的过程有了一个基本的认识,从一段文本输入到输出存储的过程来看,可以通过下面这个图更直观的理解整个过程。

 

一段文本在输入之后先经过CharFilter进行初次清理,去掉文本中的 HTML元素,然后进过多个分词器,最终以索引最小单元存储。

3.一个搜索示例

假定我们有一个需求,在某个网站上需要根据关键词搜索全国的大学,同时也需要根据坐标信息搜索坐标周围的大学。那我们一步步的来看在Elasticsearch中如何实现这样的功能。

首先当然是需要安装Elasticsearch,你只需要去官网https://www.elastic.co/下载一个安装包解压,然后在解压目录中启动elastcisearch就可以了,成功启动之后可以看到如下的信息:

Elasticsearch默认的启动端口是9200,启动之后访问http://localhost:9200,会返回一段关于当前版本及运行状态的信息。

接下来我们创建schools的索引,只需向localhost的9200端口发送一段httpput请求就可以创建出来:

索引创建成功之后,我们需要创建索引的mapping,这里我们只是对字段进行了一些简单的定义,其中location字段是geo_point类型,表示这是一个坐标,被定义为geo_point类型的字段在搜索的时候可以使用一些距离计算函数,来实现根据距离搜索的功能。

在mapping创建成功之后,我们就可以开始往elasticsearch服务器保存数据,保存数据的方式也是通过http的方式,直接post到索引的地址即可:

在创建出更多的数据后,我们就可以开始进行搜索了,第一个搜索比较简单,我们搜索大学的founded字段在1900到1910之间的:

上面的搜索中,query是一个搜索语句的主体,range表示搜索语句的类型是范围搜索,这是Elasticsearch提供的许多搜索类型中的一种,关于各种搜索类型的定义和使用方法,在Elasticsearch官网的文档中都有详尽的说明。

咱们的第二个搜索会稍微复杂一些,根据关键词来全文查找,并高亮显示关键词:

在上面的搜索中,用到了bool搜索,它的子语句可以是should、must、must_not,如果是should的话,在should里包含的查询条件不一定都要满足,只需满足其中之一即可,如上面的搜索中所要表达的意思是查询wuhan这个单词在city中出现,或者在description这个字段中出现。这里靠boost来控制字段的权重,也就是说如果数据A中在city里找到了wuhan,数据B在description找到了wuhan,boost值大的在搜索结果中权重更高。另外highlight定义了在city和description中高亮显示搜索的关键词。

最后一个搜索是基于坐标搜索附近的大学,在Elasticsearch中内置了关于计算距离的函数,在查询时我们只需要指定查询的类型是 geo_distance,然后传入关于距离的参数,如下表示查询“30.5619,114.3404”周围200公里范围的所有大学:

通过以上的一些例子,我想大家应该对Elastcisearch如何去建立索引,如何去实现查询有了一些基本认识,Elasticsearch 搜索功能的强大与简洁,远不是这一篇文章能够介绍的了的,如果你正在为你所在项目的性能问题而头疼,不妨尝试一下,它真的会为你的搜索插上翅膀。

Categories: 
up
0 users have voted.

Add new comment