本篇文章主要介绍 ElasticSearch 中 Mapping、自定义 Analyzer 的使用,同时简单介绍 ES 聚合方面的使用,都含有 Demo 方便大家测试。
Mapping
Mapping 类似数据库中的 schema 定义,用于定义字段名称、类型、相关配置。
字段的数据类型
字段类型 | 使用场景 | 底层实现 | 实例数据 |
---|---|---|---|
text | 全文搜索(如文章内容、长文本)。 | 倒排索引(分词后存储),支持模糊匹配和相关性评分。 | "LanTech 指南 " |
keyword | 精确匹配(如 ID、状态码)、聚合和排序。 | 未分词的原始字符串,基于精确值匹配。 | "user_123" |
数值类型 | 范围查询(如价格、年龄)、数学运算和聚合。 | Lucene 的数值索引优化(如 integer 、long )。 | 42 / 3.14 |
date | 时间序列数据(如日志时间、事件时间戳)。 | 存储为长整型时间戳(毫秒级),支持时区转换。 | "2023-10-05T12:30:00Z" |
boolean | 真/假状态(如开关、是否有效)。 | 布尔索引结构,仅存储 true 或 false 。 | true |
binary | 存储二进制数据(如图片、文件)。 | Base64 编码存储,不支持直接查询。 | "U29tZSBoZWxsbyB3b3JsZA==" |
range | 区间查询(如价格区间、年龄区间)。 | Lucene 的数值/日期范围索引,支持 >= 、<= 等操作。 | {"gte": 10, "lte": 20} |
object | 嵌套 JSON 对象(如用户信息中的地址字段)。 | 内部文档结构,支持嵌套查询。 | {"city": "Beijing", "zip": "100000"} |
nested | 复杂嵌套关系(如订单与多个商品的关联)。 | 独立索引的子文档,需通过 nested 查询访问。 | [{"name": "book", "price": 15}] |
geo_point | 地理位置数据(如经纬度)。 | 存储为坐标对,支持地理距离计算和范围查询。 | {"lat": 40.7128, "lon": -74.0060} |
数组类型 | 存储多个相同类型值(如标签列表、商品分类)。 | 多值字段(无需显式声明),底层以扁平化多值形式存储。 | ["red", "blue", "green"] |
ip | IP 地址存储与查询(如访问日志中的 IP)。 | 存储为 32 位或 128 位整数,支持 CIDR 范围查询。 | "192.168.1.1" |
Dynamic Mapping
在写入文档时如果索引不存在会自动创建索引,该机制使得我们不用手动定义 Mappings,但是通常不用这个,因为容易推算错误,并且 ES 禁止对有数据写入的字段修改定义。
示例
# 插入测试数据
PUT mapping_test/_doc/1
{
"uid" : "123",
"isVip" : false,
"isAdmin": "true",
"age":19,
"heigh":180
}
# 查看字段类型
GET mapping_test/_mapping
# Resp
{
"mapping_test" : {
"mappings" : {
"properties" : {
"age" : {
"type" : "long"
},
"heigh" : {
"type" : "long"
},
"isAdmin" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
},
"isVip" : {
"type" : "boolean"
},
"uid" : {
"type" : "text",
"fields" : {
"keyword" : {
"type" : "keyword",
"ignore_above" : 256
}
}
}
}
}
}
}
如上结果所示,Dynamic Mapping 机制会自动推断类型,同时 text 类型会新增一个 keyword 类型支持精确查找。
控制 Dynamic Mappings
dynamic
字段不同情况下的表现。
true | false | strict | |
---|---|---|---|
文档可索引 | YES | YES | NO |
字段可索引 | YES | NO | NO |
Mapping 被更新 | YES | NO | NO |
Index
index
用于控制字段是否被索引。
示例
"mobile" : {
"type" : "text",
"index": false
}
Index Options
Index Options | 记录内容 | 作用 | 默认类型 |
---|---|---|---|
docs | 仅文档编号(doc id) | 仅记录文档 ID,节省空间,适用于只需文档匹配的场景 | 非 text 类型字段默认 |
freqs | 文档编号(doc id)+ 词频(term frequency) | 记录文档 ID 和词频,支持基于频率的查询优化 | |
positions | 文档编号 + 词频 + 位置(position) | 记录位置信息,支持短语查询(Phrase Query)和邻近查询(Proximity Query) | text 类型字段默认 |
offsets | 文档编号 + 词频 + 位置 + 偏移量(offset) | 记录字符偏移量,支持高亮显示等精细文本处理 |
index_options
用于控制是否存储文档 ID、词频、位置和偏移量等,从而影响搜索效率和功能支持(如短语查询、高亮等)。同时记录的内容越多,占用存储空间越大。
null_value
- 需要对 Null 值实现搜索。
- 只有 Keyword 类型支持设定 null_value。
示例
"mobile" : {
"type" : "keyword",
"null_value": "NULL"
}
自定义 Analyzer
Character Filter
示例
POST _analyze
{
"tokenizer":"keyword",
"char_filter":["html_strip"],
"text": "<b>hello world</b>"
}
能够将 html 的标签去除。
POST _analyze
{
"tokenizer": "standard",
"char_filter": [
{
"type" : "mapping",
"mappings" : [ "- => _"]
}
],
"text": "123-456, I-test! test-990 650-555-1234"
}
能够将 text 中的 -
替换为 _
。
Tokenizer
示例
POST _analyze
{
"tokenizer":"path_hierarchy",
"text":"/user/ymruan/a/b/c/d/e"
}
能够按照目录层级进行切分。
Token Filter
示例
GET _analyze
{
"tokenizer": "whitespace",
"filter": ["lowercase","stop","snowball"],
"text": ["The girls in China are playing this game!"]
}
lowercase
- 仅小写stop
- 停用词过滤snowball
- 词干提取
Index Template
用于自动设定 Mappings 和 Settings,并按照一定的规则自动匹配到新创建的索引中。
- 仅在索引被新创建时才回起作用。
- 可以设置多个模版,设置会 merge 在一起。
- 可以控制 order 的数值控制 merge 的过程。先应用 order 低的,后续高的会覆盖之前的设定。
示例
PUT /_template/template_test
{
"index_patterns" : ["test*"],
"order" : 1,
"settings" : {
"number_of_shards": 1,
"number_of_replicas" : 2
},
"mappings" : {
"numeric_detection": true
}
}
表示当一个新索引以 test
开头时,会自动将索引的分片数设置为 2,同时会自动探测数字类型。
Dynamic Template
动态设定字段类型。例如:
- 将所有字符串类型设定为 keyword。
- is 开头的字段都设置为 boolean。
示例
PUT my_index
{
"mappings": {
"dynamic_templates": [
{
"strings_as_boolean": {
"match_mapping_type": "string",
"match": "is*",
"mapping": {
"type": "boolean"
}
}
},
{
"strings_as_keywords": {
"match_mapping_type": "string",
"mapping": {
"type": "keyword"
}
}
}
]
}
}
表示当字符串类型为 is 开头时设置为 boolean 类型,其余设置为 keyword 类型。
聚合(Aggregation)
- ElasticSearch 除了提供搜索以外,还提供了针对 ES 进行同喜分析的功能。
- 聚合是一个分析总结全套的数据,而不是寻找单个文档。
- 性能高且实时性高(不用 T+1)。
本节示例需要在 Kibana 中添加官方提供的 Sample flight data 样例数据。
Bucket
示例
GET kibana_sample_data_flights/_search
{
"size": 0,
"aggs":{
"flight_dest":{
"terms":{
"field":"DestCountry"
}
}
}
}
# Resp
"aggregations" : {
"flight_dest" : {
"doc_count_error_upper_bound" : 0,
"sum_other_doc_count" : 3187,
"buckets" : [
{
"key" : "IT",
"doc_count" : 2371
},
{
"key" : "US",
"doc_count" : 1987
},
// ...
]
}
}
将国家分成了桶。
Metric
- 基于数据集计算结果。
- 大多数是数学计算,仅输出一个值。
示例
GET kibana_sample_data_flights/_search
{
"size": 0,
"aggs":{
"flight_dest":{
"terms":{
"field":"DestCountry"
},
"aggs":{
"avg_price":{
"avg":{
"field":"AvgTicketPrice"
}
},
"max_price":{
"max":{
"field":"AvgTicketPrice"
}
},
"min_price":{
"min":{
"field":"AvgTicketPrice"
}
}
}
}
}
}
# Resp
// ...
{
"key" : "IT",
"doc_count" : 2371,
"max_price" : {
"value" : 1195.3363037109375
},
"min_price" : {
"value" : 100.57646942138672
},
"avg_price" : {
"value" : 586.9627099618385
}
},
// ...
进行了平均值、最大值、最小值的计算。
嵌套
示例
GET kibana_sample_data_flights/_search
{
"size": 0,
"aggs":{
"flight_dest":{
"terms":{
"field":"DestCountry"
},
"aggs":{
"stats_price":{
"stats":{
"field":"AvgTicketPrice"
}
},
"wather":{
"terms": {
"field": "DestWeather",
"size": 5
}
}
}
}
}
}
# Resp
// ...
{
"key" : "IT",
"doc_count" : 2371,
"wather" : {
"doc_count_error_upper_bound" : 0,
"sum_other_doc_count" : 506,
"buckets" : [
{
"key" : "Clear",
"doc_count" : 428
},
{
"key" : "Sunny",
"doc_count" : 424
},
{
"key" : "Rain",
"doc_count" : 417
},
{
"key" : "Cloudy",
"doc_count" : 414
},
{
"key" : "Heavy Fog",
"doc_count" : 182
}
]
},
"stats_price" : {
"count" : 2371,
"min" : 100.57646942138672,
"max" : 1195.3363037109375,
"avg" : 586.9627099618385,
"sum" : 1391688.585319519
}
},
// ...
在使用国家分桶后再进行票价统计和 5 组最常见天气的分布。
写在最后
这是该系列的第四篇,主要讲解 ElasticSearch Mapping、自定义 Analyzer 和聚合分析的内容,可以自己去到 Kibana 的 Dev Tool 实战操作,未来会持续更新该系列,欢迎关注👏🏻。
同时欢迎关注公众号:LanTech指南。不定时分享职场思考、独立开发日志、大厂方法论和后端经验❤️