Elasticsearch 和 Ruby 入门
轮到你了!
最近,DEV 开始从 Algolia 迁移到 Elasticsearch。由于经常有人问我,开始使用 Elasticsearch 的最佳方式是什么,所以我想分享一下我们迁移的过程。希望如果您将来决定在 Rails 或 Ruby 应用程序中实现 Elasticsearch,可以将这篇文章作为模板。
在开始之前,我想先声明一下,本文假设您已经了解 Elasticsearch 的基础知识。由于我们将要讲解索引、映射和文档,您应该熟悉这些术语。如果您需要复习一下,或者想了解 Elasticsearch 的工作原理,我强烈推荐Elastic 文档!
1)安装Elasticsearch
其实,事情没那么简单。在开始修改代码之前,你需要先启动并运行 Elasticsearch,这样才能与它交互。根据你的环境,安装 Elasticsearch 的方法有很多,所以我建议你参考Elasticsearch 安装文档来入门。
我们 DEV 的很多人都用 Mac,最后都从存档里安装,因为 Homebrew 的安装对我们大多数人来说似乎都不太好用。Elasticsearch 启动并运行后,下一步就是让你的代码与它交互。
2)安装 Elasticsearch Ruby gem
相关拉取请求
Elasticsearch ruby gem 的安装方式与其他 gem 一样,您所要做的就是在 Gemfile 中添加一行。
gem "elasticsearch", "~> 7.4"
需要注意的一点是您计划使用的 Elasticsearch 版本。Gem 的版本号与 Elasticsearch 的版本号一致。如果您使用的是 Elasticsearch 5,则需要使用最新的 5.x 版本的 gem。
您可能在我上面引用的拉取请求中注意到的另一件事是我们还安装了 Typhoeus gem。
gem "typhoeus", "~> 1.3.1"
Elasticsearch gem 文档建议使用 HTTP 库(例如 Typhoeus)以获得最佳性能,因为它支持持久(“保持活动”)连接。
一旦 gem 成功安装,你就需要在代码中创建一个客户端来与 Elasticsearch 通信。我们选择通过初始化文件来实现,config/initializers/elasticsearch.rb
如下所示。
require "elasticsearch"
SearchClient = Elasticsearch::Client.new(
url: ApplicationConfig["ELASTICSEARCH_URL"],
retry_on_failure: 5,
request_timeout: 30,
adapter: :typhoeus,
log: Rails.env.development?,
)
让我们回顾一下这里传递的参数。
url
:(必填)我们向客户端传递一个 URL 参数。您通过 HTTP 与 Elasticsearch 通信,因此需要一个客户端可以用来发送请求的 URL。在开发环境中,默认情况下,该 URL 为http://localhost:9200
其余参数是可选的。
retry_on_failure
:客户端放弃之前重试的次数request_timeout
:设置请求响应的时间限制。任何响应时间超过 30 秒的请求都将超时。adapter
:我们希望使用 Ruby 中的 HTTP 库来帮助我们发出这些请求。如上所述,理想情况下,您应该使用 Typhoeus,因为它支持 Keep Alive 连接。log
:确定您的客户端是否为您发出的每个请求输出日志。
您还可以将许多其他选项传递给客户端,但这些是我们使用的基本的选项。现在,有些人可能会倾向于编写代码来将数据写入 Elasticsearch。我不是那种人。
每当我添加新的外部依赖项(例如数据库)时,我都喜欢单独部署使用它的接口(在本例中是 gem)。这样,您可以部署后直接进入控制台,确保所有组件都已正确连接,然后再在代码中使用它。如果需要进行任何配置调整,您可以直接进行,而不必担心代码崩溃。
为了验证集群是否正确连接,您可以跳转到 Rails 控制台并使用新命令发出以下命令SearchClient
:
[1] pry(main)> SearchClient.info
ETHON: Libcurl initialized
ETHON: performed EASY effective_url=http://localhost:9200/ response_code=200 return_code=ok total_time=0.392646
=> {"name"=>"mollys_computer",
"cluster_name"=>"elasticsearch",
"cluster_uuid"=>"123abc456",
"version"=>
{"number"=>"7.5.2",
"build_flavor"=>"default",
"build_type"=>"tar",
"build_hash"=>"8bec50e1e0ad29dad5653712cf3bb580cd1afcdf",
"build_date"=>"2020-01-15T12:11:52.313576Z",
"build_snapshot"=>false,
"lucene_version"=>"8.3.0",
"minimum_wire_compatibility_version"=>"6.8.0",
"minimum_index_compatibility_version"=>"6.0.0-beta1"},
"tagline"=>"You Know, for Search"}
如果您收到类似上述的 200 响应,则说明所有配置均已正确完成。正确设置好 gem 后,下一步就是开始使用 Elasticsearch,我们将通过创建第一个索引来实现!
2)设置标签索引
相关拉取请求
在这个例子中,我将向您展示如何设置一个非常简单的标签索引。Elasticsearch 的功能非常强大,但我希望在本例中尽量简化,以便您有一个良好的基础来开始使用。

首先,我们需要做几件事。首先,我们需要创建索引。
index_settings = { number_of_shards: 1, number_of_replicas: 0 }
settings = { settings: { index: index_settings } }
SearchClient.indices.create(index: "tag_development", body: settings)
这里,我们创建了一个包含 1 个分片和 0 个副本的简单索引。在开发环境中,您通常只有一个节点,因此将索引保留在单个分片中通常是可行的。然而,在生产环境中,根据数据大小和请求数量,您可能需要为索引设置更多分片。
您可以在控制台中运行上述命令来查看其运行情况。成功的响应将如下所示:
[37] pry(main)> SearchClient.indices.create(index: "molly", body: settings)
ETHON: performed EASY effective_url=http://localhost:9200/molly response_code=200 return_code=ok total_time=0.65619
2020-02-24 16:00:54 -0500: PUT http://localhost:9200/molly [status:200, request:0.660s, query:n/a]
{"acknowledged":true,"shards_acknowledged":true,"index":"molly"}
创建索引后,接下来需要做的就是定义映射。在这里,您将定义要搜索的字段。
我强烈建议您在应用程序中使用 Elasticsearch 进行集成搜索时,将映射dynamic
值设置为“严格”。将值设置为“严格”意味着,如果您尝试索引不在映射中的字段,Elasticsearch 将报错。进行集成搜索时,您需要保持文档精简,这可以确保您不会因为可能的索引错误而出现任何意外字段。
以下是我们的标签索引的映射。
{
"dynamic": "strict",
"properties": {
"id": {
"type": "keyword"
},
"name": {
"type": "text",
"fields": {
"raw": {
"type": "keyword"
}
}
},
"hotness_score": {
"type": "integer"
},
"supported": {
"type": "boolean"
},
"short_summary": {
"type": "text"
},
"rules_html": {
"type": "text"
}
}
}
在继续之前,我想指出几点。你可能注意到了,我们将id
字段映射为关键字而不是整数。这是因为关键字针对术语查询进行了优化,而我们将对 ID 字段进行优化。但是,对于像 这样的字段hotness_score
,我们希望使用整数,因为我们将使用范围查询(例如大于或小于)来搜索它。
您会注意到的另一件事是name
有两种类型。text
数据类型意味着我们将分析该字段并将其分解为标记,以便于全文搜索。关键字数据类型通过调用 来查看name.raw
。我们的原始字段将名称按原样存储在一个完整的字符串中。拥有两种字段类型使我们能够搜索标签名称的标记或整个名称本身。
好的,现在你对映射关系有了一点了解,让我们来谈谈如何将它们应用到我们新创建的索引中。为了让我们的 linters 满意,我们将映射关系存储在一个 JSON 文件中,然后像下面这样将它们导入到我们的 Ruby 文件中:
MAPPINGS = JSON.parse(File.read("config/elasticsearch/mappings/tags.json"), symbolize_names: true).freeze
设置好映射后,下一步就是将它们应用到我们刚刚创建的新索引中。您可以通过执行以下代码来实现:
SearchClient.indices.put_mapping(index: "tags_development", body: MAPPINGS)
如果请求成功,您应该收到如下响应
[38] pry(main)> SearchClient.indices.put_mapping(index: "tags_development", body: MAPPINGS)
ETHON: performed EASY effective_url=http://localhost:9200/tags_development/_mapping response_code=200 return_code=ok total_time=0.079915
2020-02-24 16:45:56 -0500: PUT http://localhost:9200/tag_development/_mapping [status:200, request:0.095s, query:n/a]
2020-02-24 16:45:56 -0500: > {"dynamic":"strict","properties":{"id":{"type":"keyword"},"name":{"type":"text","fields":{"raw":{"type":"keyword"}}},"hotness_score":{"type":"integer"},"supported":{"type":"boolean"},"short_summary":{"type":"text"},"rules_html":{"type":"text"}}}
2020-02-24 16:45:56 -0500: < {"acknowledged":true}
即使你收到了 200 响应,你仍然可能需要再次检查索引是否已正确创建。同样,你可以在控制台中执行此操作,如下所示:
[2] pry(main)> SearchClient.indices.get(index: "tags_development")
ETHON: performed EASY effective_url=http://localhost:9200/tag_development response_code=200 return_code=ok total_time=0.048122
=> {"tags_development"=>
"mappings"=>
{"dynamic"=>"strict",
"properties"=>
{"hotness_score"=>{"type"=>"integer"},
"id"=>{"type"=>"keyword"},
"name"=>{"type"=>"text", "fields"=>{"raw"=>{"type"=>"keyword"}}},
"rules_html"=>{"type"=>"text"},
"short_summary"=>{"type"=>"text"},
"supported"=>{"type"=>"boolean"}}},
"settings"=>
{"index"=>
{"creation_date"=>"1581527116462", "number_of_shards"=>"1", "number_of_replicas"=>"0", "uuid"=>"kO-MGUiFSJObSMY_22mrzg", "version"=>{"created"=>"7050299"}, "provided_name"=>"tag_development"}}}}
现在我们已经验证索引已创建并具有正确的映射,现在是时候开始用数据填充它了!

3)索引标签文档
相关拉取请求
在将数据发送到 Elasticsearch 之前,我们必须先将其序列化为正确的格式。为了处理 ActiveRecord 模型的序列化,我们使用Fast JSON API 序列化器。
module Search
class TagSerializer
include FastJsonapi::ObjectSerializer
attributes :id, :name, :hotness_score, :supported, :short_summary, :rules_html
end
end
一旦你找到了序列化模型数据的方法,剩下要做的就是发出请求将其发送到 Elasticsearch。以下是我们使用的方法SearchClient
:
tag = Tag.find(id)
serialized_data = Search::TagSerializer.new(tag).serializable_hash.dig(:data, :attributes)
SearchClient.index(id: tag.id, index: "tags_development", body: serialized_data)
上述索引请求的成功响应如下所示:
{"_index"=>"tags_development", "_type"=>"_doc", "_id"=>"39", "_version"=>10, "result"=>"created", "_shards"=>{"total"=>1, "successful"=>1, "failed"=>0}, "_seq_no"=>351, "_primary_term"=>3}
我们可以验证索引是否正确工作的另一种方法是使用 GET 请求向 Elasticsearch 询问标签文档。
SearchClient.get(id: tag.id, index: "tags_development")
上述请求将为您提供一个响应,其中包含_source
响应哈希的参数中的所有标签数据。
{"_index"=>"tags_development",
"_type"=>"_doc",
"_id"=>"39",
"_version"=>10,
"_seq_no"=>351,
"_primary_term"=>3,
"found"=>true,
"_source"=>
{"id"=>39,
"name"=>"coolbean",
"hotness_score"=>4,
"supported"=>false,
"short_summary"=>nil,
"rules_html"=>""}}
现在我们的索引已经建立并且其中有了数据,接下来就是最好的部分了。
4)搜索标签
相关拉取请求
在这个搜索示例中,我只会向您展示如何设置查询字符串搜索。然而,搜索正是 Elasticsearch 的真正亮点,因此我强烈建议您查看他们的搜索文档,探索所有可能性。
假设我们要搜索所有名称以“python”开头的标签,并且希望按 进行排序hotness_score
。具体操作如下:
SearchClient.search(
index: "tags_development",
body: {
query: {
query_string: {
query: "name:python*",
analyze_wildcard: true,
allow_leading_wildcard: false
}
},
sort: { hotness_score: "desc" }
}
)
此请求针对索引中的名称字段运行一个基本查询python*
。我们还添加了一个通配符 ,以指示我们需要所有名称以 python开头*
的标签。运行该查询后,您将获得如下结果:
=> {"took"=>251,
"timed_out"=>false,
"_shards"=>{"total"=>1, "successful"=>1, "skipped"=>0, "failed"=>0},
"hits"=>
{"total"=>{"value"=>3, "relation"=>"eq"},
"max_score"=>nil,
"hits"=>
[{"_index"=>"tags_development",
"_type"=>"_doc",
"_id"=>"10",
"_score"=>nil,
"_source"=>{"id"=>10, "name"=>"python", "hotness_score"=>2, "supported"=>true, "short_summary"=>nil, "rules_html"=>nil},
"sort"=>[2]},
{"_index"=>"tags_development",
"_type"=>"_doc",
"_id"=>"40",
"_score"=>nil,
"_source"=>{"id"=>40, "name"=>"PythonBeginners", "hotness_score"=>0, "supported"=>false, "short_summary"=>nil, "rules_html"=>nil},
"sort"=>[0]},
{"_index"=>"tags_development",
"_type"=>"_doc",
"_id"=>"41",
"_score"=>nil,
"_source"=>{"id"=>41, "name"=>"PythonExpert", "hotness_score"=>0, "supported"=>false, "short_summary"=>nil, "rules_html"=>nil},
"sort"=>[0]}]}}
轰!我们刚刚运行了第一个 Elasticsearch 查询!最后一步就是从响应中找出文档匹配项,也就是标签。
results = SearchClient.search(...)
results = search(query_string)
results.dig("hits", "hits").map { |tag_doc| tag_doc.dig("_source") }
end
=> [{"id"=>10, "name"=>"python", "hotness_score"=>2, "supported"=>true, "short_summary"=>nil, "rules_html"=>nil},
{"id"=>40, "name"=>"PythonBeginners", "hotness_score"=>0, "supported"=>false, "short_summary"=>nil, "rules_html"=>nil},
{"id"=>41, "name"=>"PythonExpert", "hotness_score"=>0, "supported"=>false, "short_summary"=>nil, "rules_html"=>nil}]
轮到你了!
现在你已经掌握了所有要素,是时候开始将 Elasticsearch 集成到你自己的 Ruby 或 Rails 应用程序中了。如果你有任何疑问,请随时告诉我。祝你搜索愉快!😃
PS:我最近一直在追《富家穷路》,欢迎大家分享我的动图。
文章来源:https://dev.to/molly/getting-started-with-elasticsearch-and-ruby-30hh