玩转 Elasticsearch 之入门使用
Elasticsearch 是基于 Lucene 的全文检索引擎,本质也是存储和检索数据。 ES 中的很多概念与 MySQL 类似,我们可以按照关系型数据库的经验去理解。
核心概念
关系型数据库( 比如 MySQL ) | 非关系型数据库( Elasticsearch ) |
---|---|
数据库 Database | 索引 Index |
表 Table | 索引 Index 类型( 原为 Type ) |
数据行 Row | 文档 Document |
数据列 Column | 字段 Field |
约束 Schema | 映射 Mapping |
Elasticsearch Reference [7.12] » Mapping » Field data types
-
索引(
index
)类似的数据放在一个索引,非类似的数据放不同索引, 一个索引也可以理解成一个关系型数据库。
-
类型(
type
)代表
document
属于index
中的哪个类别(type
),也有一种说法,type
就像是数据库的表,比如 dept 表, user 表。注意 ES 每个大版本之间区别很大:
- ES 5.x 中一个 index 可以有多种 type 。
- ES 6.x 中一个 index 只能有一种 type 。
- ES 7.x 以后要逐渐移除 type 这个概念。
-
映射(
mapping
)mapping
定义了每个字段的类型等信息。相当于关系型数据库中的表结构。常用数据类型:
text
、keyword
、number
、array
、range
、boolean
、date
、geo_point
、ip
、nested
、object
Elasticsearch API 介绍
Elasticsearch 提供了 Rest 风格的 API ,即 http 请求接口,而且也提供了各种语言的客户端 API 。
Elasticsearch 没有自带图形化界面,我们可以通过安装 Elasticsearch 的图形化插件,完成图形化界面的效果,完成索引数据的查看,比如可视化插件 Kibana 。
安装配置 Kibana
什么是 Kibana
Kibana 是一个基于 Node.js 的 Elasticsearch 索引库数据统计工具,可以利用 Elasticsearch 的聚合功能,生成各种图表,如柱形图,线状图,饼图等。
而且还提供了操作 Elasticsearch 索引数据的控制台,并且提供了一定的 API 提示,非常有利于我们学习 Elasticsearch 的语法。
Kibana dev tools 快捷键:
ctrl+enter
:提交请求ctrl+i
:自动缩进
安装过程
略
Elasticsearch 集成 IK 分词器
集成 IK 分词器
IKAnalyzer 是一个开源的,基于 Java 语言开发的轻量级的中文分词工具包。从 2006 年 12 月推出 1.0 版开始, IKAnalyzer 已经推出 了 3 个大版本。最初,它是以开源项目 Lucene 为应用主体的,结合词典分词和文法分析算法的中文分词组件。新版本的 IKAnalyzer3.0 则发展为 面向 Java 的公用分词组件,独立于 Lucene 项目,同时提供了对 Lucene 的默认优化实现。
IK 分词器 3.0 的特性如下:
- 采用了特有的 “正向迭代最细粒度切分算法“,具有 60万字/秒 的高速处理能力。
- 采用了多子处理器分析模式,支持:英文字母( IP 地址、Email、URL )、数字(日期,常用中文数量词,罗马数字,科学计数法),中文词汇(姓名、地名处理)等分词处理。
- 支持个人词条的优化的词典存储,更小的内存占用。
- 支持用户词典扩展定义。
- 针对 Lucene 全文检索优化的查询分析器
IKQueryParser
;采用歧义分析算法优化查询关键字的搜索排列组合,能极大的提高 Lucene 检索的命中率。
IK 分词器有两种分词模式: ik_max_word
和 ik_smart
模式:
ik_max_word
(常用):会将文本做最细粒度的拆分ik_smart
:会做最粗粒度的拆分
扩展、停用词典
扩展词:就是不想让哪些词被分开,让他们分成一个词。
停用词:有些词在文本中出现的频率非常高。但对本文的语义产生不了多大的影响。例如英文的 a
、 an
、 the
、 of
等。或中文的 的
、了
、呢
等。这样的词称为停用词。停用词经常被过滤掉,不会被进行索引。在检索的过程中,如果用户的查询词中含有停用词,系统会自动过滤掉。停用词可以加快索引的速度,减少索引库文件的大小。
同义词典使用
语言博大精深,有很多相同意思的词,我们称之为同义词,比如“番茄”和“西红柿”,“馒头”和“馍”等。在搜索的时候,我们输入的可能是“番茄”,但是应该把含有“西红柿”的数据一起查询出来,这种情况叫做 同义词查询 。
注意:扩展词和停用词是在索引的时候使用,而同义词是检索时候使用。
配置 IK 同义词
Elasticsearch 自带一个名为 synonym
的同义词 filter 。为了能让 IK 和 synonym 同时工作,我们需要定义新的 analyzer ,用 IK 做 tokenizer , synonym 做 filter 。听上去很复杂,实际上要做的只是加一段配置。
-
创建
/config/analysis-ik/synonym.txt
文件,输入一些同义词并存为 utf-8 格式。例如lagou,拉勾 china,中国
-
创建索引时,使用同义词配置,示例模板如下
PUT /索引名称 { "settings": { "analysis": { "filter": { "word_sync": { "type": "synonym", "synonyms_path": "analysis-ik/synonym.txt" } }, "analyzer": { "ik_sync_max_word": { "filter": [ "word_sync" ], "type": "custom", "tokenizer": "ik_max_word" }, "ik_sync_smart": { "filter": [ "word_sync" ], "type": "custom", "tokenizer": "ik_smart" } } } }, "mappings": { "properties": { "字段名": { "type": "字段类型", "analyzer": "ik_sync_smart", "search_analyzer": "ik_sync_smart" } } } }
以上配置定义了
ik_sync_max_word
和ik_sync_smart
这两个新的 analyzer ,对应 IK 的ik_max_word
和ik_smart
两种分词策略。ik_sync_max_word
和ik_sync_smart
都会使用 synonym filter 实现同义词转换 -
到此,索引创建模板中同义词配置完成,搜索时指定分词为
ik_sync_max_word
或ik_sync_smart
。 -
测试案例
PUT /lagou-es-synonym { "settings": { "analysis": { "filter": { "word_sync": { "type": "synonym", "synonyms_path": "analysis-ik/synonym.txt" } }, "analyzer": { "ik_sync_max_word": { "filter": [ "word_sync" ], "type": "custom", "tokenizer": "ik_max_word" }, "ik_sync_smart": { "filter": [ "word_sync" ], "type": "custom", "tokenizer": "ik_smart" } } } }, "mappings": { "properties": { "name": { "type": "text", "analyzer": "ik_sync_max_word", "search_analyzer": "ik_sync_max_word" } } } }
插入数据
POST /lagou-es-synonym/_doc/1 { "name": "拉勾是中国专业的互联网招聘平台" }
使用同义词
lagou
或者china
进行搜索POST /lagou-es-synonym/_doc/_search { "query": { "match": { "name": "lagou" } } }
索引操作(创建、查看、删除)
创建索引库
Elasticsearch 采用 Rest 风格 API ,因此其 API 就是一次 http 请求,你可以用任何工具发起 http 请求
PUT /索引名称
{
"settings": {
"属性名": "属性值"
}
}
PUT /lagou-company-index
settings
:就是索引库设置,其中可以定义索引库的各种属性 比如分片数 副本数等,目前我们可以不设置,都走默认
判断索引是否存在
HEAD /索引名称
HEAD /lagou-company-index
查看索引
查看单个或多个索引:
GET /索引名称
GET /索引名称1,索引名称2,索引名称3,...
GET /lagou-company-index
GET /lagou-company-index,lagou-employee-index
查看所有索引:
GET _all
GET /_cat/indices?v
health
字段说明:
- 绿色(
green
):索引的所有分片都正常分配。 - 黄色(
yellow
):至少有一个副本没有得到正确的分配。 - 红色(
red
):至少有一个主分片没有得到正确的分配。
打开索引
POST /索引名称/_open
POST /lagou-company-index/_open
关闭索引
关闭的索引是不可以读写的
POST /索引名称/_close
POST /lagou-company-index/_close
删除索引库
DELETE /索引名称1,索引名称2,索引名称3...
DELETE /lagou-company-index
映射操作
索引创建之后,等于有了关系型数据库中的 database 。 Elasticsearch7.x 取消了索引 type
类型的设置,不允许指定类型,默认为 _doc
,但字段仍然是有的,我们需要设置 字段的约束信息,叫做 字段映射( mapping
)
字段的约束包括但不限于:
- 字段的数据类型
- 是否要存储
- 是否要索引
- 分词器
创建映射字段
Elasticsearch Reference [7.3] » Mapping » Mapping parameters
PUT /索引库名/_mapping
{
"properties": {
"字段名": {
"type": "类型",
"index": true,
"store": true,
"analyzer": "分词器"
}
}
}
PUT /lagou-company-index
PUT /lagou-company-index/_mapping/
{
"properties": {
"name": {
"type": "text",
"analyzer": "ik_max_word"
},
"job": {
"type": "text",
"analyzer": "ik_max_word"
},
"logo": {
"type": "keyword",
"index": "false"
},
"payment": {
"type": "float"
}
}
}
字段名可以任意填写,下面指定许多属性,例如:
type
:类型,可以是text
、long
、short
、date
、integer
、object
等index
:是否索引,默认为true
store
:是否存储,默认为false
analyzer
:指定分词器
映射属性详解
type
Elasticsearch Reference [7.3] » Mapping » Field datatypes
Elasticsearch 中支持的数据类型非常丰富:
-
string
类型,又分两种:text
:可分词,不可参与聚合keyword
:不可分词,数据会作为完整字段进行匹配,可以参与聚合
-
Numerical
:数值类型,分两类- 基本数据类型:
long
、interger
、short
、byte
、double
、float
、half_float
- 浮点数的高精度类型:
scaled_float
- 需要指定一个精度因子,比如
10
或100
。Elasticsearch 会把真实值乘以这个因子后存储,取出时再原。
- 需要指定一个精度因子,比如
- 基本数据类型:
-
Date
:日期类型Elasticsearch 可以对日期格式化为字符串存储,但是建议我们存储为毫秒值,存储为
long
,节省
空间。 -
Array
:数组类型进行匹配时,任意一个元素满足,都认为满足
排序时,如果升序则用数组中的最小值来排序,如果降序则用数组中的最大值来排序
-
Object
:对象{ "name": "Jack", "age": 21, "girl": { "name": "Rose", "age": 21 } }
如果存储到索引库的是对象类型,例如上面的
girl
,会把girl
变成两个字段:girl.name
和girl.age
index
index
影响字段的索引情况。
true
:字段会被索引,则可以用来进行搜索。默认值就是true
false
:字段不会被索引,不能用来搜索
index
的默认值就是 true
,也就是说你不进行任何配置,所有字段都会被索引。
但是有些字段是我们不希望被索引的,比如企业的 logo 图片地址,就需要手动设置 index
为 false
。
store
是否将数据进行独立存储。
原始的文本会存储在 _source
里面,默认情况下其他提取出来的字段都不是独立存储的,是从 _source
里面提取出来的。当然你也可以独立的存储某个字段,只要设置 store:true
即可,获取独立存储的字段要比从 _source
中解析快得多,但是也会占用更多的空间,所以要根据实际业务需求来设置,默认为 false
。
analyzer
analyzer
:指定分词器
一般我们处理中文会选择 ik 分词器:ik_max_word
,ik_smart
查看映射关系
GET /索引名称/_mapping
GET /lagou-company-index/_mapping
查看所有索引映射关系:
GET _mapping
GET _all/_mapping
修改索引映射关系
注意:修改映射只可以增加字段,如果要做其它更改只能删除索引,重新建立映射
PUT /索引库名/_mapping
{
"properties": {
"字段名": {
"type": "类型",
"index": true,
"store": true,
"analyzer": "分词器"
}
}
}
PUT /lagou-company-index/_mapping
{
"properties": {
"logo2": {
"type": "keyword",
"index": false
}
}
}
一次性创建索引和映射
刚才的案例中我们是把创建索引库和映射分开来做,其实也可以在创建索引库的同时,直接制定索引库中的索引,基本语法:
put /索引库名称
{
"settings": {
"索引库属性名": "索引库属性值"
},
"mappings": {
"properties": {
"字段名": {
"映射属性名": "映射属性值"
}
}
}
}
PUT /lagou-employee-index
{
"settings": {},
"mappings": {
"properties": {
"name": {
"type": "text",
"analyzer": "ik_max_word"
}
}
}
}
文档增删改查及局部更新
文档,即索引库中的数据,会根据规则创建索引,将来用于搜索。可以类比做数据库中的一行数据。
新增文档
新增文档时,涉及到 id 的创建方式,可以手动指定或者自动生成。
// 新增文档(手动指定id),第一次执行为 创建,之后执行为 更新
POST /索引名称/_doc/{id}
POST /lagou-company-index/_doc/1
{
"name": "百度",
"job": "小度用户运营经理",
"payment": "30000",
"logo": "http://www.lgstatic.com/thubnail_120x120/i/image/M00/21/3E/CgpFT1kVdzeAJNbUAABJB7x9sm8374.png"
}
// 新增文档(自动生成id)
POST /索引名称/_doc
{
"field": "value"
}
POST /lagou-company-index/_doc
{
"name": "2百度",
"job": "2小度用户运营经理",
"payment": "30000",
"logo": "http://www.lgstatic.com/thubnail_120x120/i/image/M00/21/3E/CgpFT1kVdzeAJNbUAABJB7x9sm8374.png"
}
查看单个文档
GET /索引名称/_doc/{id}
GET /lagou-company-index/_doc/1
返回结果,文档元数据解读:
元数据项 | 含义 |
---|---|
_index |
document 所属 index |
_type |
document 所属 type , Elasticsearch7.x 默认 type 为 _doc |
_id |
代表 document 的唯一标识,与 index 和 type 一起,可以唯一标识和定位一个 document |
_version |
document 的版本号, Elasticsearch 利用 _version (版本号)的方式来确保应用中相互冲突的变更不会导致数据丢失。需要修改数据时,需要指定想要修改文档的 version 号,如果该版本不是当前版本号,请求将会失败 |
_seq_no |
严格递增的顺序号,每个文档一个, Shard 级别严格递增,保证后写入的 doc 的 seq_no 大于先写入的 Doc 的 seq_no |
_primary_term |
任何类型的写操作,包括 index 、 create 、 update 和 Delete ,都会生成一个 _seq_no 。 |
found |
true/false,是否查找到文档 |
_source |
存储原始文档 |
查看所有文档
POST /索引名称/_search
{
"query": {
"match_all": {}
}
}
POST /lagou-company-index/_search
{
"query": {
"match_all": {}
}
}
_source 定制返回结果
某些业务场景下,我们不需要搜索引擎返回 _source
中的所有字段,可以使用 _source
进行定制,如下,多个字段之间使用逗号分隔
GET /lagou-company-index/_doc/1?_source=name,job
更新文档(全部更新)
这里的【全部更新】指的是 doc
把刚才新增的请求方式改为 PUT( POST 也可以 ) ,就是修改了,不过修改必须指定 id
- id 对应文档存在,则修改
- id 对应文档不存在,则新增
PUT /lagou-company-index/_doc/6
{
"name": "5百度",
"job": "小度用户运营经理",
"payment": "30000",
"logo": "http://www.lgstatic.com/thubnail_120x120/i/image/M00/21/3E/CgpFT1kVdzeAJNbUAABJB7x9sm8374.png"
}
更新文档(局部更新)
Elasticsearch 可以使用 PUT 或者 POST 对文档进行更新(全部更新),如果指定 ID 的文档已经存在,则执行更新操作。
注意: Elasticsearch 执行更新操作的时候, Elasticsearch 首先将旧的文档标记为删除状态,然后添加新的文档,旧的文档不会立即消失,但是你也无法访问, Elasticsearch 会在你继续添加更多数据的时候在后台清理已经标记为删除状态的文档。
全部更新,是直接把之前的老数据,标记为删除状态,然后,再添加一条更新的(使用 PUT 或者 POST )
局域更新,只是修改某个字段(使用 POST )
POST /索引名/_update/{id}
{
"doc": {
"field": "value"
}
}
POST /lagou-company-index/_update/1
{
"doc": {
"field": "job1"
}
}
删除文档
// 根据 id 进行删除
DELETE /索引名/_doc/{id}
DELETE /lagou-company-index/_doc/1
// 根据查询条件进行删除
{
"query": {
"match": {
"字段名": "搜索关键字"
}
}
}
POST /lagou-company-index/_delete_by_query
{
"query": {
"match": {
"job": "小度用户运营经理"
}
}
}
// 删除所有文档
POST /索引名/_delete_by_query
{
"query": {
"match_all": {}
}
}
POST /lagou-company-index/_delete_by_query
{
"query": {
"match_all": {}
}
}
文档的全量替换、强制创建
-
全量替换
- 语法与创建文档是一样的,如果文档 id 不存在,那么就是创建;如果文档 id 已经存在,那么就是全量替换操作,替换文档的 JSON 串内容;
- 文档是不可变的,如果要修改文档的内容,第一种方式就是全量替换,直接对文档重新建立索引,替换里面所有的内容, Elasticsearch 会将老的文档标记为
deleted
,然后新增我们给定的一个文档,当我们创建越来越多的文档的时候, Elasticsearch 会在适当的时机在后台自动删除标记为deleted
的文档
-
强制创建
PUT /index/_doc/{id}?op_type=create {}, PUT /index/_doc/{id}/_create {}