使用elasticsearch-rails在Rails中运行Elasticsearch【初学者专用】

你好。我是@katsuhisa__。
这篇文章是「Elastic stack(Elasticsearch)Advent Calendar 2017」的12/18(周一)的文章。

引导词

在第21届Elasticsearch研讨会上,我发表了有关将Elastic Stack作为日志分析基础设施引入的演讲。在那个时候,我宣布说:“下一步我们将使用Elasticsearch作为全文搜索引擎!”
不幸的是,我还没有在实际工作中使用它,但是不久前我根据这篇文章的参考,在Rails中实现了使用Elasticsearch的基本步骤。

所以,我重新整理了在Rails中运行Elasticsearch所需的步骤。如果需要,可以在这里找到我实施时的代码。

目标读者

    • RDBMS 以外を使用した検索機能の実装経験が一度もない方

 

    • 自分にもElasticsearch って使えるんだろうか・・・と、不安な方

 

    実装の全体観だけをまずは知りたい方

尽管具备全文搜索引擎的实施经验者阅读此文可能得到的收获并不多。
当我模糊地思考使用全文搜索引擎能够充实搜索功能的好处时,我从那些过于平凡以至于没有人教授的内容开始进行拆解,因此对于初学者来说,这可能是一篇有用的文章。


版本

Rails 5.1.4
弹性搜索 5.6.3

由于elasticsearch-rails 的 gem 只支持到5系,所以我使用的是5系而不是6系的Elasticsearch。稍稍后悔,因为我应该尝试一下6系的版本。另外,顺便提一下,前面介绍的那篇文章是2年前的,使用的是Rails 4.2.3和Elasticsearch 1.7.2。

使用全文检索引擎的思路

    1. 在全文搜尋引擎中,包含了搜尋目標的資料。

 

    1. 當在應用程式中進行搜尋時,會向搜尋引擎提出查詢,並返回結果。

 

    當應用程式中的搜尋目標資料被更新時,這些更新也會正確反映到搜尋引擎中的資料上。

我认为,对于完全初学者来说,在脑海中存在这种思维地图会使后续的理解更加顺利。

在Rails中使用Elasticsearch运行。

Well, let’s break down steps 1 to 3 that we mentioned earlier and explain them in detail. Of course, please keep in mind that “application = Rails application” and “full-text search engine = Elasticsearch.”
In addition, this time, let’s consider the assumption of incorporating Elasticsearch into an existing Rails application.

1. 搜索引擎中包含了要搜索的数据

首先,我们需要准备一个用来存储搜索数据的盒子来进行管理。在关系型数据库中,这个盒子被称为数据库,在Elasticsearch中则称为索引。因此,我们首先需要在Elasticsearch中创建一个索引。

准备创建 Elasticsearch 索引…

正如之前提到的,假设现有的应用程序已经完成,那么你想要在 Elasticsearch 中处理 Rails 的 Model,对吧?嗯嗯。

关于这样的要求,Elastic公司的人们为了实现Rails工程师们希望实现的功能,他们发布了一个gem。

请将以下内容写入Gemfile文件,并执行bundle install命令。

gem 'elasticsearch-model', git: 'git://github.com/elasticsearch/elasticsearch-rails.git'
gem 'elasticsearch-rails', git: 'git://github.com/elasticsearch/elasticsearch-rails.git'

嗯?你对于两个宝石感兴趣吗?如果是的话,请查阅 README 中的使用说明。这次不会进行详细解释。

创建一个 Elasticsearch 索引

这次让我们给Elasticsearch创建一个索引吧。

在你想要搜索的模型中,include Elasticsearch::Model。
※根据elasticsearch-rails和elasticsearch-model的示例代码,模型被称为Article。在本文中,我打算统一使用Article作为用于说明的示例代码中的模型。


class Article < ActiveRecord::Base
  include Elasticsearch::Model
end

现在,我们已经准备好通过模型来处理 Elasticsearch了。因此,让我们创建一个索引吧。可以通过以下代码来创建索引。


Article.__elasticsearch__.create_index! force: true

如果想要了解这些行为的更多细节,请查看elasticsearch-model的README,里面有详细的解释。
https://github.com/elastic/elasticsearch-rails/tree/master/elasticsearch-model#index-configuration

順便提一句,正如笔记中所指出的,

当文档被索引时,Elasticsearch会自动创建一个带有默认设置和映射的索引。

如果使用默认的索引设置和映射,您可以不必明确地创建索引。
但这次我认为通过明确地讲解创建索引的操作,可以促进理解,所以特意写了出来。(如果反而导致混淆的话,请谅解。)

将文档插入到Elasticsearch中

接下来,让我们尝试将数据存入Elasticsearch索引中。这里所指的数据,当然是我们想要作为搜索对象的数据,而在Elasticsearch中,我们称其为文档(相当于RDB中的记录概念)。

让我们尝试将文档导入到Elasticsearch中。

Article.import

这很简单易懂。 至此,已在 Elasticsearch 索引中注册了文档。

(参考)https://github.com/elastic/elasticsearch-rails/tree/master/elasticsearch-model#导入数据

当在应用程序中进行搜索时,我们向搜索引擎提交查询,并获取结果的返回。

接下来,在Rails应用程序中进行搜索时,我们需发送查询到Elasticsearch。

可以通过以下方式,在Rails中向Elasticsearch发送查询请求。

response = Article.search 'fox dogs'

换句话说,为了在Rails中向Elasticsearch发出查询请求的实现,只需将应用程序端的搜索字符串直接放入查询参数中即可。


def index
    @articles = Article.search(params)
end

这就是结束了。顺便说一下,获取的数据可以这样处理。

response.took
# => 3

response.results.total
# => 2

response.results.first._score
# => 0.02250402

response.results.first._source.title
# => "Quick brown fox"

当应用程序的搜索对象数据更新时,相应地更新搜索引擎内的数据。

到目前为止,我们已经成功将Elasticsearch作为搜索引擎使用了。但是,考虑到实际服务的运营,当搜索对象的数据发生更新时,Elasticsearch中的文档也必须相应地更新。
因此,最后,我将介绍一下如何从Rails更新Elasticsearch的文档的方法。

现在,首先只需使用以下代码即可更新 Elasticsearch 文档。

Article.first.__elasticsearch__.update_document

由于还有一个名为delete_document的函数,所以你可以使用它来删除文档。

好了,那么我们只需要在进行记录更新操作的前后实现这些方法就可以了吗?当然,并不需要这样做。
在 elasticsearch-model 中,只需在 Model 中包含 Elasticsearch::Model::Callbacks ,在更新记录时,它会自动向 Elasticsearch 发送更新文档的查询。


class Article
  include Elasticsearch::Model
  include Elasticsearch::Model::Callbacks
end

以非同步的方式更新文件

然而,仅仅如此还不够。如果在数据库事务期间频繁地发送HTTP请求,肯定会惹恼运维人员。
是的,我们希望将其异步化。

所以,最后我会介绍一种异步处理的方法来结束这篇文章。可以按照以下方式进行编写以实现异步处理。在这个例子中,我们使用了 Sidekiq。(由于这超出了初学者的范围,我会跳过解释。)


class Article
  include Elasticsearch::Model

  after_save    { Indexer.perform_async(:index,  self.id) }
  after_destroy { Indexer.perform_async(:delete, self.id) }
end

class Indexer
  include Sidekiq::Worker
  sidekiq_options queue: 'elasticsearch', retry: false

  Logger = Sidekiq.logger.level == Logger::DEBUG ? Sidekiq.logger : nil
  Client = Elasticsearch::Client.new host: 'localhost:9200', logger: Logger

  def perform_async(operation, record_id)
    logger.debug [operation, "ID: #{record_id}"]

    case operation.to_s
      when /index/
        record = Article.find(record_id)
        Client.index  index: 'articles', type: 'article', id: record.id, body: record.__elasticsearch__.as_indexed_json
      when /delete/
        Client.delete index: 'articles', type: 'article', id: record_id
      else raise ArgumentError, "Unknown operation '#{operation}'"
    end
  end
end

总结

以上是关于在Rails中作为全文搜索引擎使用Elasticsearch的初级思路的总结。

本文中,我们将全文搜索引擎集成到应用程序中的必要步骤分为三个,并详细介绍了Rails × Elasticsearch的实现方法。

非常感谢您的陪伴。

如果有任何事情让你在意,随时都可以告诉我!(虽然不确定我是否能给出准确答案!)

我也期待着其他的「Elastic Stack(Elasticsearch)Advent Calendar 2017」。

隨便聊聊

Elasticsearch的术语和RDB的术语并不是彼此的完全子集。因此,我认为如果在Elasticsearch的术语中有不理解的地方,最好是每次都去查找。这样可以避免产生一些奇怪的期望或误解,比如“在关系数据库中可以做到的功能,在Elasticsearch中也能做到吗?”这是我作为初学者的想法。

因为我是个怕事的人,所以遇到不懂的事情就会去阅读文档…
Elasticsearch的官方文档非常详尽,这次我也是边自己实现边遇到问题(根据不同版本Filterd query的写法不同),关于这些变更点的解释都很清楚地写在了官方文档中。

附加註2

我在写这篇文章的同时发现了 README 中的错误,所以我发送了 PR 并成功地合并了。
https://github.com/elastic/elasticsearch-rails/pull/764#event-2316774905

bannerAds