由于经验丰富地使用Elasticsearch的一年,我将介绍一些教程
这篇文章是关于2020年12月21日的Cloudworks圣诞日历。
首先
你好,我是今年初加入云港株式会社的mayoxtuna(@mayoxtuna)。
我最近迷上了一款叫做Apex Legends的战地FPS游戏。
《Apex Legends》是一款在《泰坦降临》数百年后的世界中,使用各种被称为“传奇”的角色进行3对3对战的游戏。
最近,它也支持跨平台游戏,因此可以与PS4和XBox的玩家一起游戏。
一起来玩吧~!
不是…而是(快点进入正题)…
…)
当回顾2020年时,我觉得我在Elasticsearch上做得不错,所以想要分享一些积累的经验,给大家做一个Elasticsearch的教程。
对于那些想尝试Elasticsearch或者对它感兴趣的人,或许你只是听过它的名字,想知道它能做些什么?请务必来看看。
Elasticsearch的简介
◯ Elasticsearch是什么?
从公式中引用:
Elasticsearch 在高速可扩展且可索引多样内容,适用于广泛的用例。
– 应用程序搜索
– 网站搜索
– 企业搜索
– 日志记录和分析
– 基础设施指标和容器监控
– 应用程序性能监控(APM)
– 地理空间数据分析和可视化
– 安全分析
– 业务分析
据说。
如果用平易的语言来说,它就是一款快速高效的搜索引擎。
◯ RDB和什么不同?
首先,对于其本身的称呼就存在差异。
使用RESTful API而不是SQL的方式来添加和删除数据,数据以JSON格式进行处理。
最初我对于「呼称不同、不再使用SQL语句而是JSON格式输入」这样的差异有些疑问,虽然基本上RDB和它的构建方式相同,只是速度更快。但是,RDB具有丰富的通用性功能,Elasticsearch也在模仿其功能性改进,让我觉得它有点像RDB。然而,Elasticsearch专注于全文搜索,像Analysis这样的功能在RDB中则没有。此外,在RDB中很少使用多个数据库,但在Elasticsearch中却很常见。此外,如果在RDB中删除数据库在本地环境下是很少发生的,但是在Elasticsearch中可以删除不需要的索引。我认为RDB和Elasticsearch的使用方式虽然相似,但是本质上还是有所差别的。
关于数据的管理方法
在RDB中,我认为一个数据库可以拥有多个表,而在Elasticsearch中,可以根据时间或用户等来创建不同的数据库(索引)。
换句话说,没有太多类似表的概念。虽然可以通过类型(Type)来实现,但从Elasticsearch 7开始,越来越趋向于无类型化,因此我认为这是RDB和Elasticsearch之间的一个重大区别。
在RDB中,一个数据库中包含工作和用户信息,而在Elasticsearch中,通常会将工作和用户信息分开。
在Elasticsearch中,并非将所有的表放在一个数据库中,而是将需要最少的可搜索对象定义为一个数据库的形式。
如果您希望进行复合条件搜索,您可以创建一个复合索引。
此外,在关系型数据库中不会执行数据库删除等操作,但在Elasticsearch中可以删除不需要的索引。根据数据的存储方式,可能需要定期重新构建索引的情况也存在。
◯ 附加内容:对于文本分析的分析等
在RBD中,数据保持不变,但在Elasticsearch中,文本数据会被拆分,转换并保持在所需的形式中。
进行文本分析并将其转化为最佳形式,可以轻松地提取相关的句子。
这里与RDB不同,是非常重要且美味的地方。
分析器由字符过滤器、标记器和令牌过滤器这三个要素组成。
可以使用Kuromoji、icu_normalize等类似的工具。
请允许我忽略这一点。
文本分析 | Elasticsearch 参考文档 [7.10] | 弹性搜索
我建议您查看官方文档了解详细的Elasticsearch规范。
文档也非常详细,发行说明中写明了更改的原因等等,很容易阅读。
准备和确认Elasticsearch的操作
为了避免麻烦的卸载过程,我将使用docker进行安装。
以下是Elasticsearch安装的详细说明,提取了关键部分。
- https://www.elastic.co/guide/en/elasticsearch/reference/7.9/docker.html#docker
这个也在Docker Hub上公开发布了。
- https://hub.docker.com/r/elastic/elasticsearch/
有关Elasticsearch的Docker镜像,有开源版本和非开源版本可供选择。
- https://www.docker.elastic.co/r/elasticsearch/elasticsearch-oss
OSS版本中,默认不包含x-pack。
在本地进行验证时,由于不需要x-pack,我们将使用OSS版。
环境建设
按照公式的方式拉取最新版本的Elasticsearch。
2. 将进行启动确认
3. 尝试点击进入
我們已經確認了啟動。
在终端上使用Ctrl+C来终止进程。
把它转换成Dockerfile格式。
为了安装插件等功能,需要准备一个Dockerfile。
作为安装项目,有可以进行日语分析的kuromoji等工具。
FROM docker.elastic.co/elasticsearch/elasticsearch-oss:7.9.3
RUN elasticsearch-plugin install analysis-kuromoji && \
elasticsearch-plugin install analysis-icu
USER elasticsearch
5. 准备Kibana
由于在处理Elasticsearch时,在终端上使用JSON格式进行交互非常麻烦,因此我们还将准备Kibana。
Kibana提供了在Elasticsearch中索引的数据搜索和可视化功能。
创建docker-compose.yml文件。
由于需要将Kibana与Elasticsearch连接并指定Dockerfile,所以需要准备docker-compose.yml。
version: "3"
services:
elasticsearch-v7.9.3:
container_name: "elasticsearch"
build:
context: .
dockerfile: ./Dockerfile
environment:
- discovery.type=single-node
- bootstrap.memory_lock=true
- logger.deprecation.level=debug
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
ulimits:
memlock:
soft: -1
hard: -1
ports:
- "9200:9200"
volumes:
- elasticsearch-v7.9.3-data:/usr/share/elasticsearch/data
kibana-v7.9.3:
image: docker.elastic.co/kibana/kibana-oss:7.9.3
ports:
- "5601:5601"
restart: always
environment:
- "ELASTICSEARCH_HOSTS=http://elasticsearch:9200"
volumes:
elasticsearch-v7.9.3-data:
driver: local
8. 启动并确认
成功即将浏览到以下页面,请访问 http://localhost:5601。
环境已经搭建完毕,辛苦了。
使用方法简单说明
首先,让我们打开开发工具。
- 点击屏幕右上方的三个图标
- 点击开发工具
您可以通过此界面向Elasticsearch投入、检索和删除数据(使用POST/GET/DELETE方法)。
让我们试试参考一下。请将以下文本粘贴过来。
GET _cat/aliases
通过按下以下部分即可执行。
或者可以通过 ⌘+ENTER 来执行。
当执行完成后,结果将在右侧输出。
让我们亲自体验一下Elasticsearch。
创建一个空的索引
要创建索引,应使用PUT而不是POST。
让我们复制粘贴以下的文本并运行它。
PUT job_offers
只要返回如下这样的内容就表示成功。
{
"acknowledged" : true,
"shards_acknowledged" : true,
"index" : "job_offers"
}
检查指数
基本上,所有的参考系操作都可以使用GET方法完成。
让我们实际确认已创建的索引。
让我们复制粘贴以下的文本并执行一下。
GET _cat/indices
将返回以以下形式的对象。
yellow open job_offers Y99rqJqOSQ6RHnnmOG8jow 1 1 0 0 208b 208b
green open .kibana_1 3WOY-fXOSiGGCyX8qwEo-A 1 0 13 19 46.2kb 46.2kb
既然确认了它看起来是人造的,我们就来看一下这个索引的详细信息吧。
让我们复制并执行以下语句。
GET job_offers
会返回以下类似的格式。
{
"job_offers" : {
"aliases" : { },
"mappings" : { },
"settings" : {
"index" : {
"creation_date" : "1607079649046",
"number_of_shards" : "1",
"number_of_replicas" : "1",
"uuid" : "Y99rqJqOSQ6RHnnmOG8jow",
"version" : {
"created" : "7090399"
},
"provided_name" : "job_offers"
}
}
}
}
因为还没有创建数据库,所以还没有任何数据。
删除已创建的索引
对不起,我刚刚做好了,但让我们尝试删除它。
如果在关系数据库(如RDB)中随意删除数据可能会让人感到恐惧,但在Elasticsearch中,索引的删除等操作通常都比较随意。
因为正如前文所述,由于关系型数据库等方面的持有方式思维完全不同。
让我们复制并执行以下文本。
DELETE job_offers
将返回以下格式的内容。
{
"acknowledged" : true
}
让我们以同样的方式查看索引列表吧。
让我们尝试复制并执行下面的文本。
GET _cat/indices
将返回以下类似的格式。
green open .kibana_1 3WOY-fXOSiGGCyX8qwEo-A 1 0 21 0 23.3kb 23.3kb
和之前不同,现在只剩下一个了。
由於製作與刪除的步驟已經完成,接下來我們將說明實際插入並查看數據的流程。
创建数据
索引可以具有一个或多个类型。
每个文档都被分配了一个唯一的标识符(ID)。
顺便提一下,在Elasticsearch中,我们不需要预先定义类似于表定义的mapping,只需直接输入数据,它会自动解释并保存为数据。
太棒了!
然而,如果在进行POST时指定了不正确的类型并且插入了数据,它将被接受,因此需要注意这一点。
另外,由于会被强制指定类型,可能会与自己所想的不同。
现在应该停止使用这种方法,因为它已被明确指定为不推荐使用的。
因此,我希望本次我们可以通过进行事先定义并插入的方式来推进。
那么,我们立刻开始,这次我们将制作如下所示的内容。
让我们创建一个名为”qiita”的索引,并将标题(title)、描述(description)和用户名(screen_name)放进去。
然后,让我们将ID设置为2434。
(由于用文字表示结构关系会有点复杂,我们将使用JSON来表示。)
{
qiita: {
_doc: {
title: "アドベントカレンダーxx日目のタイトル",
description: "どうも初めまして。xxと申します。",
screen_name: "k-waragai"
}
}
}
让我们尝试复制粘贴以下文本并执行,以表达这样的内容。
首先,创建索引。
让我们尝试复制并执行以下文本。
PUT qiita
2. mapping的定义
2. 映射的定义
让我们复制并运行以下句子。
PUT qiita/_mapping/
{
"properties" : {
"title" : {
"type" : "text"
},
"description" : {
"type" : "text"
},
"screen_name" : {
"type" : "text"
}
}
}
这里有一个补充说明。
在Elasticsearch 6版本之前,可以通过指定类似qiita/article/2434的方式来指定类型名称,但是从Elasticsearch 7版本开始,类型被弃用,并且需要添加include_type_name=true选项来进行指定。顺便说一下,这也是不推荐的,将来会被移除。
因此,如果不指定的话,将会自动附加”_doc”。
因此,将会变成qiita/_doc/2434这样的形式。
以下是对于mapping的一些变更的例子。
- before
{
"mappings": {
"article": {
"properties" : {
"title" : {
"type" : "text"
},
"description" : {
"type" : "text"
},
"screen_name" : {
"type" : "text"
}
}
}
}
}
- after
{
"mappings": {
"_doc": {
"properties" : {
"title" : {
"type" : "text"
},
"description" : {
"type" : "text"
},
"screen_name" : {
"type" : "text"
}
}
}
}
}
虽然多字段也是可能的,但由于这会增加复杂性,所以减少索引的作用,我认为这是一个很好的更新。
现在我们完成了补充。由于已经定义了映射,让我们来确认一下映射是否正确。
3. 确认映射
让我们试着复制粘贴以下的文本并执行。
GET qiita/_mapping
将以以下的形式返回。
{
"qiita" : {
"mappings" : {
"properties" : {
"title" : {
"type" : "text"
},
"description" : {
"type" : "text"
},
"screen_name" : {
"type" : "text"
}
}
}
}
}
你很好地描述了指定为type的文本内容。
请查阅此处有关于type的内容。
- Field data types | Elasticsearch Reference [7.10] | Elastic
4. 插入数据
让我们复制并执行下面的句子。
請注意是使用PUT方法而不是POST方法。
PUT qiita/_doc/2434
{
"title": "アドベントカレンダーxx日目のタイトル",
"description": "どうも初めまして。xxと申します。",
"screen_name": "k-waragai"
}
以下将返回这样的格式。
{
"_index" : "qiita",
"_type" : "_doc",
"_id" : "2434",
"_version" : 1,
"result" : "created",
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"_seq_no" : 0,
"_primary_term" : 1
}
看起来很顺利地插入了。
让Elasticsearch负责插入_id,来试一试。
让我们复制并执行以下文本。
需要注意的是,与之前情况不同,这里使用的是POST而不是PUT方法。
POST qiita/_doc/
{
"title": "Elasticsearchチュートリアル書いてみた",
"description": "チュートリアルです。みんな見てね。",
"screen_name": "mayoxmayo"
}
将返回以下格式的内容。
{
"_index" : "qiita",
"_type" : "_doc",
"_id" : "J32eLXYB4YEI6IRIxT4f",
"_version" : 1,
"result" : "created",
"_shards" : {
"total" : 2,
"successful" : 1,
"failed" : 0
},
"_seq_no" : 1,
"_primary_term" : 1
}
与先前不同,_id字段被赋予了一个随机的英数字值。
由于这个_id是唯一的值,所以无论哪个值都可以,只要不重复即可。
让我们来看一下插入的数据的详细信息。
5. 单独查看数据。
让我们复制并执行以下文本。
GET qiita/_doc/2434
以下提供的形式如下。
{
"_index" : "qiita",
"_type" : "_doc",
"_id" : "2434",
"_version" : 1,
"_seq_no" : 0,
"_primary_term" : 1,
"found" : true,
"_source" : {
"title" : "アドベントカレンダーxx日目のタイトル",
"description" : "どうも初めまして。xxと申します。",
"screen_name" : "k-waragai"
}
}
我们还可以再确认一件事情。
让我们复制并执行以下文本。
※ 此处指定的随机字符串指的是在POST时生成并返回的_id。
GET qiita/_doc/J32eLXYB4YEI6IRIxT4f
以下是返回的形式。
{
"_index" : "qiita",
"_type" : "_doc",
"_id" : "J32eLXYB4YEI6IRIxT4f",
"_version" : 1,
"_seq_no" : 1,
"_primary_term" : 1,
"found" : true,
"_source" : {
"title" : "Elasticsearchチュートリアル書いてみた",
"description" : "チュートリアルです。みんな見てね。",
"screen_name" : "mayoxmayo"
}
}
如果想全部取出的话,请按照以下方式进行操作。
让我们尝试复制并执行以下文本。
GET qiita/_search
将以以下方式的形式进行归还。
{
"took" : 1,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 2,
"relation" : "eq"
},
"max_score" : 1.0,
"hits" : [
{
"_index" : "qiita",
"_type" : "_doc",
"_id" : "2434",
"_score" : 1.0,
"_source" : {
"title" : "アドベントカレンダーxx日目のタイトル",
"description" : "どうも初めまして。xxと申します。",
"screen_name" : "k-waragai"
}
},
{
"_index" : "qiita",
"_type" : "_doc",
"_id" : "J32eLXYB4YEI6IRIxT4f",
"_score" : 1.0,
"_source" : {
"title" : "Elasticsearchチュートリアル書いてみた",
"description" : "チュートリアルです。みんな見てね。",
"screen_name" : "mayoxmayo"
}
}
]
}
}
和以往不同,我使用了一个名为_search的工具进行了利用。
如果没有指定_id并忘记_id的情况下,该如何进行搜索呢?尝试使用常规的搜索方法。
6. 进行搜索
如果你想要查看k-waragai用户的文章,请按以下步骤操作:
复制并执行以下内容。
GET qiita/_search?q=screen_name:k-waragai
将返回以下形式的内容。
{
"took" : 49,
"timed_out" : false,
"_shards" : {
"total" : 1,
"successful" : 1,
"skipped" : 0,
"failed" : 0
},
"hits" : {
"total" : {
"value" : 1,
"relation" : "eq"
},
"max_score" : 1.2199391,
"hits" : [
{
"_index" : "qiita",
"_type" : "_doc",
"_id" : "2434",
"_score" : 1.2199391,
"_source" : {
"title" : "アドベントカレンダーxx日目のタイトル",
"description" : "どうも初めまして。xxと申します。",
"screen_name" : "k-waragai"
}
}
]
}
}
当需要进行关键词搜索时,可以按照以下方式进行指定。
GET qiita/_search?q=初めまして
现在我将详细介绍关于归还的细节。
hits.total.value
検索結果件数hits.total.relation
検索結果方式(eq=等しい)hits.max_score
検索結果の重み付けの最大値hits.hits._score
ヒット時のスコアリング我们来试一试更复杂的搜索吧。
如果在描述中搜索不包含”开始”但包含”教程”的内容,应按以下方式描述。
GET qiita/_search
{
"query": {
"bool": {
"must": [
{"match": {"description": "初め"}}
],
"must_not": [
{"match": {"description": "チュートリアル"}}
]
}
}
}
因为数据量较少,所以没有包含“教程”。
"hits" : [
{
"_index" : "qiita",
"_type" : "_doc",
"_id" : "2434",
"_score" : 1.2730759,
"_source" : {
"title" : "アドベントカレンダーxx日目のタイトル",
"description" : "どうも初めまして。xxと申します。",
"screen_name" : "k-waragai"
}
}
]
如果在Rails上使用的话
现在先简单地创建一个Rails应用程序就可以了。
本次将使用简单的配置,采用Rails5 + MySQL的组合。
我们将对结构进行如下修改。
由于存在多个Dockerfile,我认为创建并分开目录会使其更易读。
准备Rails
准备用于Ruby的Dockerfile
这基本上就是符合官方标准的 Ruby 代码描述。
路径:dockerfiles/ruby/Dockerfile
FROM ruby:2.6.2
RUN apt-get update -qq && \
apt-get install -y build-essential libpq-dev nodejs
RUN mkdir /app
WORKDIR /app
COPY Gemfile /app/Gemfile
COPY Gemfile.lock /app/Gemfile.lock
RUN bundle install
COPY . /app
2. 准备 Gemfile
由于对Rails6还有许多不了解的部分,所以我们将在Rails5上进行。
- https://rubygems.org/gems/rails/versions/5.2.4.4
路径:Gemfile
source 'https://rubygems.org'
gem 'rails', '~> 5.2', '>= 5.2.4.4'
当完成创建后,请生成Gemfile.lock文件。
3. 修改compose文件
我们将修改现有的docker-compose.yml文件。
我正在添加用于mysql的服务定义和用于ruby的服务定义。
我还在Rails应用程序中传递和链接用于Elasticsearch的URL。
version: "3"
services:
elasticsearch-v7.9.3:
container_name: "elasticsearch"
build:
context: .
dockerfile: ./dockerfiles/elasticsearch/Dockerfile
environment:
- discovery.type=single-node
- bootstrap.memory_lock=true
- logger.deprecation.level=debug
- "ES_JAVA_OPTS=-Xms512m -Xmx512m"
ulimits:
memlock:
soft: -1
hard: -1
ports:
- "9200:9200"
volumes:
- elasticsearch-v7.9.3-data:/usr/share/elasticsearch/data
kibana-v7.9.3:
image: docker.elastic.co/kibana/kibana-oss:7.9.3
ports:
- "5601:5601"
restart: always
environment:
- "ELASTICSEARCH_HOSTS=http://elasticsearch:9200"
database:
container_name: mysql
image: mysql:5.7
environment:
MYSQL_ROOT_PASSWORD: password
MYSQL_DATABASE: root
ports:
- "3306:3306"
app:
build:
context: .
dockerfile: ./dockerfiles/ruby/Dockerfile
environment:
ELASTICSEARCH_URL: http://elasticsearch:9200/
MYSQL_HOST: database
command: rails s -p 3000 -b '0.0.0.0'
volumes:
- .:/app
ports:
- "3000:3000"
links:
- database
- elasticsearch-v7.9.3
volumes:
elasticsearch-v7.9.3-data:
driver: local
4. 新建一个 Rails 项目
如果那样做,就会变成熟悉的景象。
5. 配置文件的修改 (Pinyin: de
我将进行数据库.yml的修改操作。
default: &default
adapter: mysql2
encoding: utf8
pool: <%= ENV.fetch("RAILS_MAX_THREADS") { 5 } %>
username: root
password: password
host: database
development:
<<: *default
database: app_development
6. 建设
為了執行bundle install等操作,我們需要進行build。
我觉得把宝石之类的东西作为独立的卷进行持有也可以,但考虑到这次更新的频率并不是非常高,所以我们选择了最简配置。
7. 创建数据库
8. 启动docker-compose。
当你访问 localhost:3000 并出现以下画面时,准备工作已完成。
准备Elasticsearch
1. 对Gemfile进行添加
请将与Elasticsearch相关的Gem插入。请将以下3个Gem添加到Gemfile中。
# Elasticsearch関係
gem 'elasticsearch', '~> 7.10'
gem 'elasticsearch-rails', '~> 7.1', '>= 7.1.1'
gem 'elasticsearch-model', '~> 7.1', '>= 7.1.1'
2. 安装软件包。
请通过Ctrl+C终止已启动的进程,并输入以下命令。
3. 模型的创建
- migrationの変更
class CreateArticles < ActiveRecord::Migration[5.2]
def change
create_table :articles do |t|
t.string :title, null: false, limit: 10, comment: "記事のタイトル"
t.text :description, null: false, comment: "記事の本文"
t.string :screen_name, null: false, comment: "表示名"
t.timestamps
end
end
end
- migrate
4. 数据输入
我会准备大约三项适当的数据。
我自己使用了Seed进行了注入,但也可以从rails console进行注入。
Article.create(
title: "犬の気持ち",
description: "吾輩は犬である。名前はまだない。",
screen_name: "k-waragai"
)
Article.create(
title: "ねこのすべて",
description: "猫はとても気まぐれです。気まぐれロマンティック。構って欲しい時にしか寄ってきません。ぴえん。",
screen_name: "t-suzuki"
)
Article.create(
title: "私VTuberになる",
description: "どうも初めましてVTuberの酢飯マグロです。VTuberを始めて1年経って分かった5つの大事な事を紹介します。",
screen_name: "m-sumeshi"
)
为了使用elasticsearch-rails,需要包含(include)它。
在Article的模型文件中添加Elasticsearch::Model的include。
class Article < ApplicationRecord
include Elasticsearch::Model
end
通过添加这个,model.__elasticsearch__.method就可以使用,导入和搜索等操作会变得更容易。
在Elasticsearch中进行投入和查询
1. 连接至控制台
2. 创建索引 d’index)
通过使用`create_index!`可以创建索引。
在进行详细的映射设置等操作时,最好事先定义好setting和mapping,然后根据定义进行输入。
这次我们将直接使用已经制作好的模型。
我们来实际在 Kibana 中确认一下。
目前已经制作了索引。
3. 数据输入
使用import即可将数据导入。非常简单吧。
让我们实际通过 index/_search 来查看搜索结果。
数据已经被正确录入了啊。
在Rails中,使用model.__elasticsearch__.search来查看这个。
Article.__elasticsearch__.search(version: true, query: { term: { id: 1 } }, size: 1).response.hits.hits.first
以类似的方式构建查询并进行搜索是可能的。
4. 删除
我认为你应该已经明白了,可以使用Article.\_\_elasticsearch\_\_.delete\_index!来实现。
总结
由于Elasticsearch非常易于使用,所以请大家抓住机会去尝试一下。
明天再见。