Elasticsearch搜索引擎(数据建模)

一个优秀的elasticsearch工程师对elastic官网内容和案例模板要非常清楚,因为elasticsearch的api本就复杂规律性不像sql那么简单易用。

数据类型

和java类似

JSON 数据类型 Elasticsearch 数据类型 ES 类型说明 映射示例 注意事项
string text 全文检索字段 "name": { "type": "text" } 默认分词,适合模糊搜索
string keyword 精确值字段 "status": { "type": "keyword" } 不分词,适合聚合/过滤
number (整数) integer/long 整型数值 "age": { "type": "integer" } 根据数值范围选择类型
number (小数) float/double 浮点数值 "price": { "type": "float" } double 精度更高
boolean boolean 布尔值 "is_active": { "type": "boolean" } 仅接受 true/false
null 忽略/保留 空值处理 无需显式声明 可通过 "null_value": "NULL" 替换空值
array 同元素类型 多值字段 "tags": [ "apple", "banana" ] 数组元素必须为同一类型
object object JSON 对象 "user": { "name": "Alice", "age": 30 } 自动映射为嵌套结构
nested object nested 独立索引的对象数组 "orders": { "type": "nested" } 需显式声明,保持数组对象独立性
date (字符串) date 日期类型 "created_at": { "type": "date", "format": "yyyy-MM-dd" } 需指定格式,如 epoch_millis/strict_date_optional_time
geo_point geo_point 经纬度坐标 "location": { "type": "geo_point" } 支持 "lat,lon"{ "lat": 40.73, "lon": -74.1 } 格式
ip ip IP地址 "client_ip": { "type": "ip" } 支持IPv4/IPv6

更多参考官方文档

indexMapping

  • 类似数据库中的表结构定义,主要作用如下:
    • 定义 Index 下的字段名(Field Name)
    • 定义字段的类型,比如数值型、字符串型、布尔型等
    • 定义倒排索引相关的配置,比如是否索引、记录 position 等

GET /test_index/_mapping

查看

1
2
3
4
5
6
7
8
9
10
11
12
13
14
{
"test_index": {
"mappings": {
"properties": {
"age": {
"type": "integer"
},
"username": {
"type": "keyword"
}
}
}
}
}

PUT my_index

修改/新增结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
"mappings": {
"properties": {
"title": {
"type": "text"
},
"name": {
"type": "keyword"
},
"age": {
"type": "integer"
}
}
}
}

注意事项

  • Mapping 中的字段类型一旦设定后,禁止直接修改,原因如下:
    • Lucene 实现的倒排索引生成后不允许修改
    • 解决方案为:重新建立新的索引,然后做 reindex 重新导入的操作

elastic提供自动根据入参json自动创建对应的文档索引(用户也可以关闭)

dynamic

  • 通过 dynamic 参数来控制字段的新增
    • true(默认) 允许自动新增字段
    • false 不允许自动新增字段,但是文档可以正常写入,但无法 对字段进行查询等操作
    • strict 文档不能写入,报错

index

  • 控制当前字段是否索引,默认为 true,即记录索引,false 不 记录,即不可搜索

分词器

分词器是 es 中专门处理分词的组件,英文为 Analyzer,它的组成

  1. Character Filters: 针对原始文本进行处理,比如去除 html 特殊标记符
  2. Tokenizer: 将原始文本按照一定规则切分为单词
  3. Token Filters: 针对 tokenizer 处理的单词就行再加工,比如转小写、删除或新增等处理
graph LR
A[Character Filters]-->B[Tokenizer]
B-->C[Token Filters]

通过analyzer可以对分词器进行测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
//POST _analyze
//分词hello world
{
"analyzer": "standard",
"text":"hello world!"
}
//POST test_index/_analyze
//对字段内容分词
{
"field": "username",
"text":"hello world!"
}
//POST _analyze
//测试自定义分词器
{
"tokenizer": "standard",
"filter": [ "lowercase" ],
"text":"Hello World!"
}

预定义分词器

  • Standard :特性为: 按词切分,支持多语言 ,小写处理
  • Simple:按照非字母切分 ,小写处理
  • Whitespace:按照空格切分
  • Stop:Stop Word 指语气助词等修饰性的词语,比如 the、an、的、这等等
  • Keyword :不分词,直接将输入作为一个单词输出
  • Pattern:通过正则表达式自定义分割符 ,默认是 \W+,即非字词的符号作为分隔符

自定义分词器

通过自定义分词流程中的Character Filters、Tokenizer 和 Token Filter 实现

Character Filters

  • Character Filters :在 Tokenizer 之前对原始文本进行处理,比如增加、删除或替换字符等 • 自带的如下:
    • HTML Strip 去除 html 标签和转换 html 实体
    • Mapping 进行字符替换操作
    • Pattern Replace 进行正则匹配替换
  • 会影响后续 tokenizer 解析的 position 和 offset 信息

示例

1
2
3
4
5
6
POST _analyze
{
"tokenizer": "keyword",
"char_filter": [ "html_strip" ],
"text": "<p>I&apos;m so <b>happy</b>!</p>"
}

Tokenizer

将原始文本按照一定规则切分为单词(term or token) 自带的如下:

  • standard 按照单词进行分割
  • letter 按照非字符类进行分割
  • whitespace 按照空格进行分割
  • UAX URL Email 按照 standard 分割,但不会分割邮箱和 url
  • NGram 和 Edge NGram 连词分割
  • Path Hierarchy 按照文件路径进行切割

示例

1
2
3
4
5
6
POST _analyze
{
"tokenizer": "path_hierarchy",
"text": "/one/two/three"
}

Token Filters

对于 tokenizer 输出的 单词(term) 进行增加、删除、修改等操作

自带如下

  • lowercase 将所有 term 转换为小写
  • stop 删除 stop words
  • NGram 和 Edge NGram 连词分割
  • Synonym 添加近义词的 term

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
POST _analyze
{
"text": "a Hello,world!",
"tokenizer": "standard",
"filter": [
"stop",
"lowercase",
{
"type": "ngram",
"min_gram": 4,
"max_gram": 4
}
]
}

配置分词器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
PUT test_index
{
"settings": {
"analysis": {
"analyzer": {
"my_custom_analyzer": {
"type": "custom",
"tokenizer": "standard",
"char_filter": [
"html_strip"
],
"filter": [
"lowercase",
"asciifolding"
]
}
}
}
}
}

分词的时机

分词会在如下两个时机使用:

  • 创建或更新文档时(Index Time),会对相应的文档进行分词处理
  • 查询时(Search Time),会对查询语句进行分词

学会查看官方文档:[https://www.elastic.co/guide/en/elasticsearch/reference/8.1/a nalysis-analyzers.html](https://www.elastic.co/guide/en/elasticsearch/reference/8.1/a nalysis-analyzers.html)

多字段

允许对同一个字段采用不同的类型配置,通过fields实现

  • 设定不同的分词器,比如对人名实现拼音搜索,只需要在人 名中新增一个子字段为 pinyin 即可
  • 设定不同的类型,比如设为 text 和 keyword,同时实现全 文检索、排序、聚合的需求

官方文档:[https://www.elastic.co/guide/en/elasticsearch/reference/8.1/m ulti-fields.html](https://www.elastic.co/guide/en/elasticsearch/reference/8.1/m ulti-fields.html)

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
{
"test_index": {
"mappings": {
"doc": {
"properties": {
"username": {
"type": "text",
"fields": {
"pinyin": {
"type": "text",
"analyzer": "pinyin"
}
}
}
}
}
}
}
}
//查询时
GET test_index/_search
{
"query": {
"match": {
"username.pinyin": "hanhan"
}
}
}

Runtime field 类型

https://www.elastic.co/guide/en/elasticsearch/reference/current/runtime-indexed.html

在查询时动态生成的字段,可正常用于查询、排序、聚合分析等;

不占用存储空间,常见的应用场景如下

  • 获取被 disabled 的 _source 中的字段
  • 使用 grok 等从非结构化数据中动态提取相关字段
  • 动态新增字段,无需重写数据(reindex)
  • 覆盖当前的字段和字段类型

runtime field也需要被定义

使用时机

  1. 查询时指定,灵活性高,但增加了查询语句的复杂度
  2. Index Mapping 中提前指定,适用于需求明确后,固化字段,供 所有使用方共用

示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
//索引时指定runtimefield
PUT my-index-000001/
{
"mappings": {
"runtime": {
"day_of_week": {
"type": "keyword"
}
}
}
}
//查询时指定runtimefield
GET my-index-000001/_search
{
"runtime_mappings": {
"day_of_week": {
"type": "keyword",
"script": {
"source": "emit(doc['@timestamp'].value.dayOfWeekEnum.getDisplayName(TextStyle.FULL, Locale.ENGLISH))"
}
}
},
"aggs": {
"day_of_week": {
"terms": {
"field": "day_of_week"
}
}
}
}

注意事项

  • 查询时为每个文档动态计算,耗费额外的资源
  • 设置不合理的计算代码会拖慢整个集群性能,甚至导致节点崩溃
  • 可以实现一些轻量级的运算,避免复杂计算逻辑

Elasticsearch搜索引擎(数据建模)
https://andrewjiao.github.io/2022/08/20/elasticsearch/ElasticSearch数据建模/
作者
Andrew_Jiao
发布于
2022年8月20日
许可协议