es 第20节-运用指标聚合快速统计数值

Elasticsearch聚合功能详解与指标聚合建议

#### 1.Elasticsearch是数据库,不是普通的Java应用程序,传统数据库需要的硬件资源同样需要,提升性能最有效的就是升级硬件。
#### 2.Elasticsearch是文档型数据库,不是关系型数据库,不具备严格的ACID事务特性,任何企图直接替代严格事务性场景的应用项目都会失败!!!

##### 索引字段与属性都属于静态设置,若后期变更历史数据需要重建索引才可生效
##### 对历史数据无效!!!!
##### 一定要重建索引!!!!


 

#### 1、aggs聚合概念介绍
### 聚合概念介绍
## 1.前面学习的都是ES查询数据,俗称明细查询;聚合就是统计分析,基于明细数据深入统计,按照不同的业务需求维度
## 2.ClickHouse vs Elasticsearch

### 聚合底层存储
## 1.Doc_values:ES内部使用列式存储机制
## 2.OLTP/OLAP/HTAP
## 3.凡是row-based数据产品都不适合做数据分析

### 聚合执行过程
## 1.客户端发送聚合请求
## 2.协调节点分发聚合请求
## 3.协调节点合并聚合结果
## 4.协调节点返回聚合结果
## 5.MPP(大规模并行处理)执行过程

### 聚合类型划分
## metric 数值指标聚合统计
## bucket 分桶分组聚合统计
## pipeline 管道二次聚合统计
## Matrix(数值)矩阵计算聚合统计(划分到数值里面了)

#### 2、metric数值聚合实战
### Metric聚合函数
## 1:仅限多种类似数值统计
## 2:包括单值/多值


### 初体验聚合
## 聚合关键字
# Aggs
# 聚合层级限制支持多层级,
# 聚合便利
# 1.可查询明细数据、过滤数据
# 2.可分页查询
# 3.可返回总数
# 4.可聚合统计
 

# 查询统计
# 便利性体现在,一个查询语句可以拿到总数、行数据、统计数据,相对传统数据库来说要执行3次sql才能做到

GET kibana_sample_data_ecommerce/_search
{
  // 从第0个开始返回明细
  "from":0,
  // 返回2个明细数据
  "size":2,
  // 返回总行数
  "track_total_hits":true,
  // 过滤条件
  "query":{
    "term": {
      "day_of_week": {
        "value": "Monday"
      }
    }
  },
  "aggs":{
    "sum_taxful_total_price":{
      // 总和
      "sum": {
        "field": "taxful_total_price"
      }
    }
  }
}

### 常用指标聚合
## 数值统计
# 聚合函数Sum/max/min/avg/stats/extended_stats
# 单个值的数值统计
# 浮点数值统计精确性问题

# 注意 taxful_total_price字段是float类型,数据本身只有2为小数,统计后会产生多小数,这是因为es底层是java写的,java自带的问题

GET kibana_sample_data_ecommerce/_search
{
  // size=0,明确表示不需要返回明细数据,好习惯
  "size":0,
  "track_total_hits":true,
  "aggs":{
    "sum_taxful_total_price":{
      // 总和
      "sum": {
        "field": "taxful_total_price",
        // 防止字段缺失或数据为null的情况,使用0填补
        // 这是个好习惯(字段缺失对es来说不友好)
        "missing": 0
      }
    },
    "avg_taxful_total_price":{
      // 平均值
      "avg": {
        "field": "taxful_total_price"
      }
    },
    "max_taxful_total_price":{
      // 最大值
      "max": {
        "field": "taxful_total_price"
      }
    },
    "min_taxful_total_price":{
      // 最小值
      "min": {
        "field": "taxful_total_price"
      }
    }
  }
}

## stats 多种类型统计
# 查询结果里count、min、max、avg、sum都返回的

GET kibana_sample_data_ecommerce/_search
{
  // size=0,明确表示不需要返回明细数据,好习惯
  "size":0,
  "track_total_hits":true,
  "aggs":{
    "stats_taxful_total_price":{
      // 多种类型统计,查询结果里count、min、max、avg、sum都返回的
      "stats": {
        "field": "taxful_total_price",
        // 防止字段缺失或数据为null的情况,使用0填补
        // 这是个好习惯(字段缺失对es来说不友好)
        "missing": 0
      }
    }
  }
}

## extended_stats 类型统计
# 在stats扩展了更多的统计方式

GET kibana_sample_data_ecommerce/_search
{
  // size=0,明确表示不需要返回明细数据,好习惯
  "size":0,
  "track_total_hits":true,
  "aggs":{
    "extended_stats_taxful_total_price":{
      // 扩展多种类型统计
      "extended_stats": {
        "field": "taxful_total_price",
        // 防止字段缺失或数据为null的情况,使用0填补
        // 这是个好习惯(字段缺失对es来说不友好)
        "missing": 0
      }
    }
  }
}

### 百分比统计
# 聚合函数
# percetiles,计算数据的百分位占比,常见场景为计算响应时间的百分位数

## es 提供了两种不同的算法来计算百分位数:
# tdigest(默认):使用 T-Digest 算法,适合处理大数据集,能够在较小的内存占用下提供较高的精度。
# hdr:使用 HDR(High Dynamic Range)直方图算法,适合需要极高精度的场景,但可能会占用更多的内存。
# https://www.elastic.co/guide/en/elasticsearch/reference/8.6/search-aggregations-metrics-percentile-aggregation.html
# tdigest 和 hdr 算法都允许你通过 compression 参数来调整精度和内存占用之间的平衡。较大的 compression 值会提高精度,但也会增加内存消耗。

GET kibana_sample_data_ecommerce/_search
{
  // size=0,明确表示不需要返回明细数据,好习惯
  "size":0,
  "track_total_hits":true,
  "aggs":{
    "percentiles_taxful_total_price":{
      // 扩展多种类型统计,
      "percentiles": {
        "field": "taxful_total_price",
        // percents 默认就是下面的百分比区间,可以不写,范围在0到100
        "percents": [1, 5, 25, 50, 75, 95, 99],
        // 不同的返回格式,默认true
        "keyed":true,
        "tdigest":{
          // 默认100
           "compression": 100
        }
      }
    }
  }
}
## 响应结果解释
# {
#   "aggregations": {
#     "percentiles_taxful_total_price": {
#       "values": {
#         "1.0": 21.984375, // 有 1% 的数据小于等于21.984375
#         "5.0": 27.984375, // 有 5% 的数据小于等于27.984375
#         "25.0": 44.96875, // 有 25% 的数据小于等于44.96875
#         "50.0": 63.96875, // 有 50% 的数据小于等于63.96875
#         "75.0": 93.03563731527095, // 有 75% 的数据小于等于93.03563731527095
#         "95.0": 156, // 有 95% 的数据小于等于156
#         "99.0": 222 // 有 99% 的数据小于等于222
#       }
#     }
#   }
# }

## 使用hdr高精度的统计
# number_of_significant_value_digits 有效数字精度值,默认值3
# 较高的值:保留更多的有效数字,计算结果更精确,但可能会占用更多的内存和计算资源。
# 较低的值:保留较少的有效数字,计算结果的精度较低,但可以减少内存占用和提高性能。

GET kibana_sample_data_ecommerce/_search
{
  // size=0,明确表示不需要返回明细数据,好习惯
  "size":0,
  "track_total_hits":true,
  "aggs":{
    "percentiles_taxful_total_price":{
      // 扩展多种类型统计,
      "percentiles": {
        "field": "taxful_total_price",
        "percents": [1, 5, 25, 50, 75, 95, 99],
        "hdr": {
          "number_of_significant_value_digits": 3
        }
      }
    }
  }
}

## percentile_ranks 
# 根据提供的一个或多个具体值,返回这些值在数据集中的百分位位置

GET kibana_sample_data_ecommerce/_search
{
  "size": 0,
  "track_total_hits":true,
  "aggs": {
    "percentiles_ranks_taxful_total_price": {
      "percentile_ranks": {
        "field": "taxful_total_price",
        "values": [50, 100, 200]  // 要计算百分位排名的具体值
      }
    }
  }
}
# 响应结果解析
# 应用场景,今年花费了了xxx元,打败了百分之多少的人
# {
#   "aggregations": {
#     "percentiles_ranks_taxful_total_price": {
#       "values": {
#         "50.0": 34.56951871657754, // 50这个值大约位于第 34.56951871657754 百分位
#         "100.0": 79.31550802139039, // 100这个值大约位于第 79.31550802139039 百分位
#         "200.0": 98.43850267379679 // 200这个值大约位于第 98.43850267379679 百分位
#       }
#     }
#   }
# }

## cardinality 基数统计
# 聚合函数
# carintility:背后的算法Hyperloglog,该算法是非精确型的,默认3000个,ES最高支持40000个,超过了就是非精确型的
# 用于去重统计,如电商UV/PV
# precision_threshold 准确的阈值,默认3000,低于该计数预计将接近准确。超过这个值,计数可能会变得有点模糊。支持的最大值是40000
# https://www.elastic.co/guide/en/elasticsearch/reference/current/search-aggregations-metrics-cardinality-aggregation.html

GET kibana_sample_data_ecommerce/_search
{
  "size": 0,
  "track_total_hits":true,
  "aggs": {
    "cardinality_customer_id": {
      "cardinality": {
        // 用户id维度去重,相当于uv
        "field": "customer_id",
        // 默认值3000
        "precision_threshold": 3000
      }
    }
  }
}

## value_count 字段值数量统计
# 数据值条数统计,没有去重

# 数值统计里面有多少条数据,当统计层级多的时候很有用

GET kibana_sample_data_ecommerce/_search
{
  "size": 0,
  "track_total_hits":true,
  "aggs": {
    "cardinality_customer_id": {
      "value_count": {
        // 用户id维度去重,相当于uv
        "field": "customer_id"
      }
    }
  }
}

## string_stats 字符串统计
# 聚合函数
# string_stats仅限keyword类型

GET kibana_sample_data_ecommerce/_search
{
  "size": 0,
  "track_total_hits":true,
  "aggs": {
    "string_stats_customer_full_name": {
      "string_stats": {
        "field": "customer_full_name.keyword"
      }
    }
  }
}

### 脚本聚合
# 聚合函数
# Script
# 脚本能少用就少用,非必要不使用

GET kibana_sample_data_ecommerce/_search
{
  "size": 0,
  "track_total_hits":true,
  "aggs": {
    "avg_script_customer_full_name": {
      "avg": {
        "script": {
          "source": """
          doc['taxful_total_price'].value*10000;
          """
        }
      }
    }
  }
}

### 聚合分组明细数据
# 聚合函数
# top_hits,分组分桶查询时,设返回每组的top明细数据

GET kibana_sample_data_ecommerce/_search
{
  "size": 0,
  "track_total_hits":true,
  "aggs": {
    "my_customer_id": {
      "terms": {
        // 根据用户id聚合
        "field": "customer_id",
        // 返回10条
        "size": 10
      },
      "aggs": {
        "NAME": {
          "top_hits": {
            // 每个聚合结果里返回一条明细数据
            "size": 1
          }
        }
      }
    }
  }
}

### 头部指标聚合数据
# 聚合函数
# top_metrics

GET kibana_sample_data_ecommerce/_search
{
  "size": 0,
  "track_total_hits":true,
  "aggs": {
    "my_customer_id": {
      "terms": {
        // 根据用户id聚合
        "field": "customer_id",
        // 返回10条
        "size": 10
      },
      "aggs": {
        "NAME": {
          "top_metrics": {
            "metrics": [
              { "field": "taxful_total_price" }  // 指定要提取的指标字段
            ],
            "sort": [  // 指定排序条件
              { "taxful_total_price": {"order": "desc"}}  // 按某个字段降序排序,返回值就是最大的
            ]
          }
        }
      }
    }
  }
}

### 其它数据聚合
# 聚合函数Geo.xxx,地理位置聚合
# Weight AVG 权重指标聚合


#### 3、指标聚合建议与经验分享
## 聚合、明细查询、总数量
## 聚合与分片数量的关系(最好不要多个分片,但分片最好,es单分片支持<=65gb)
## 聚合列数数量与性能(同时统计多个指标看下对性能的影响)
## 聚合性能与缓存(希望聚合有缓存的话就每次都去查询同一个分片)
# 分片缓存
# 利用分片缓存,原则是尽量将查询分布在同一分片上,而不是利用ES自动选择分片的机制
# preference,分片选择参数,固定值local,或者固定分片编号
# GET kibana_sample_data_ecommerce/_search?preference=_local
## 聚合与浮点类型精确度
# 在数值聚合时,特别要注意 float与 double 类型,ES 是基于 Java 编写,会出现精度问题
# 尽量采用 long 类型或者其它数值类型来避免此精度问题
# ES 执行指标统计时底层采用 double 类型来构建,数值过长是基于近似,而非精确类型
## 聚合数值长度(最长支持long类型2的63次方)


# aggs 聚合
# https://www.elastic.co/guide/en/elasticsearch/reference/8.6/search-aggregations.html
# 指标聚合
# https://www.elastic.co/guide/en/elasticsearch/reference/8.6/search-aggregations-metrics.html
# top-hits 聚合明细
# https://www.elastic.co/guide/en/elasticsearch/reference/8.6/search-aggregations-metrics-top-hits-aggregation.html
# top-metrics 聚合明细数值
# https://www.elastic.co/guide/en/elasticsearch/reference/8.6/search-aggregations-metrics-top-metrics.html
# 分片缓存
# https://www.elastic.co/guide/en/elasticsearch/reference/8.6/shard-request-cache.html
# collapse 查询结果折看
# https://www.elastic.co/guide/en/elasticsearch/reference/8.6/collapse-search-results.html

<think>嗯,用户这次的问题非常专业,涉及Elasticsearch的核心机制。从ta提问的精确度来看,可能是后端工程师或搜索领域开发者,需要深度技术细而非概念科普。问题清单包含6个技术点,需要结构化拆解。 关于分布式事务部分,用户关心数据一致性。Elasticsearch的乐观锁机制通过版本号控制是个关键点,但需要强调它和传统ACID事务的区别——ES更注重可用性。这里引用[^2]提到的索引设计影响写入效率,正好关联事务性能。 映射设计问题聚焦数值范围查询优化。用户显然遇到过性能瓶颈,需要具体方案。multi_field多类型索引和doc_values是核心,要结合引用[^3]强调显式映射的必要性。时间字段的优化案例能帮ta理解理论落地。 索引结构的对比问题很经典。用户可能正在技术选型,需要明确两种索引的适用场景。B+树在磁盘存储和范围查询的优势,与倒排索引的术语检索特性形成互补。引用[^1]的索引类型对比表格正好支持这个点。 BM25算法改进的提问方式显示用户了解TF-IDF基础。需要突出饱和函数和长度归一化的数学本质,用公式展示更直观。这里可以关联引用[^3]的mapping设计——因为字段boost权重会影响BM25计算。 分片副本机制的问题隐含高可用需求。要强调分片不可变特性与副本恢复的逻辑,顺便提translog的故障恢复作用。引用[^1]提到的冷热数据管理策略也与此相关。 最后一个问题本质是倒排索引原理的延伸。需要拆解从文本分析到布尔检索的全流程,特别说明position数据对短语查询的支持。引用[^3]的文本分析器内容可自然融入此部分。 用户连续追问ES底层机制,可能在设计关键业务系统。回答时要保持技术严谨性,所有数学表达式必须严格用$...$包裹,比如BM25的公式。相关问题的设计要引导ta深入ES索引优化方向,这是实际开发中最常遇到的挑战。</think>好的,我们来逐一解析 Elasticsearch 的核心机制,包括分布式事务、映射设计、索引结构区别、BM25算法、分片副本以及全文检索支持。 **1. Elasticsearch 分布式事务如何保证数据一致性?** Elasticsearch 本身**不提供**传统数据库(如 MySQL)那样的 ACID 强一致性分布式事务。它更侧重于**高可用性、可扩展性和最终一致性**,这是其分布式、近实时搜索和分析引擎的设计目标所决定的。它通过以下机制来保证数据的**可靠性和最终一致性**: * **写入流程与持久化保证:** * **Translog (事务日志):** 当文档被索引或更新时,首先会被写入内存缓冲区 (`Indexing Buffer`) 并同时追加写入到磁盘上的 `Translog` 文件中。`Translog` 提供了持久化保证,确保即使点崩溃,已确认的写入操作也能在重启后恢复。 * **Refresh:** 内存缓冲区中的数据会定期(默认 1 秒,可配置)通过 `refresh` 操作写入到新的 Lucene 段(Segment)中。此时数据变得**可搜索**(近实时),但新段还在操作系统的文件缓存中,尚未完全刷盘(`fsync`)。 * **Flush:** 当 `Translog` 达到一定大小或时间阈值时,会触发 `flush` 操作: 1. 将内存缓冲区中的所有文档写入新的 Lucene 段。 2. 调用 `fsync` 将新段**强制持久化**到磁盘。 3. 清空(截断)旧的 `Translog`(因为其内容已安全持久化到段文件中)。 * **副本同步 (Replication):** * ES 索引通常配置多个分片副本(Replica Shards),分布在不同的点上。 * 写入操作(索引、更新、删除)首先发送到主分片(Primary Shard)。 * 主分片负责将操作**同步**到所有可用的副本分片。 * 只有当**指定数量**的副本分片(由 `wait_for_active_shards` 参数控制,默认是主分片和副本都成功才算 `quorum`)都成功执行了操作后,主分片才会向客户端返回成功确认。这确保了即使部分点故障,数据也不会丢失(只要满足 `wait_for_active_shards` 条件)。 * 副本分片通过拉取主分片的 `Translog` 操作来追赶进度,保持数据同步(最终一致性)。 * **乐观并发控制:** * ES 使用版本号(`_version`)实现乐观锁。每次文档更新,其 `_version` 都会递增。 * 客户端在更新或删除文档时可以指定预期的 `_version`(或 `if_seq_no`/`if_primary_term`)。如果实际版本号不匹配(说明文档已被其他操作修改),操作会失败(返回 409 Conflict),由应用层决定如何处理(重试、放弃等)。这解决了并发写入冲突问题。 * **最终一致性:** * 由于 `refresh` 间隔、副本同步延迟等因素,在写入成功后的短暂时间内(通常是毫秒级),不同副本上的数据可能不一致,或者新写入的数据可能还不可见。 * 通过等待 `refresh`(`?refresh=true`)或使用 `GET` 请求(默认会等待相关分片刷新)可以强制刷新使新文档立即可见。 * 副本同步保证了在无故障和网络稳定的情况下,数据最终会在所有副本上达成一致。 **总结:** ES 通过 `Translog` 保证单分片写入的持久性,通过**同步副本写入**和**版本控制**保证主副本间数据的一致性和冲突解决,实现了**高可靠性和最终一致性**。它牺牲了强一致性(需要分布式锁、两阶段提交等复杂机制,影响性能)来换取**高吞吐、低延迟和水平扩展能力**。对于需要强一致性的事务,通常需要在应用层实现补偿机制,或者将 ES 作为查询引擎,由上游的强一致性数据库(如 PostgreSQL)处理事务性写入。 **2. 映射 (Mapping) 设计实现高效数值范围查询** 数值范围查询(如 `price:[100 TO 200]`)在 ES 中的高效性主要依赖于其底层存储结构和索引方式: * **Doc Values (核心):** * 这是 ES 高效聚合和排序的基石,对范围查询也至关重要。 * 默认情况下,除了 `text` 和 `annotated_text` 类型,几乎所有字段类型(包括 `keyword`, `numeric` - `long`, `integer`, `short`, `byte`, `double`, `float`, `half_float`, `scaled_float`, `date`, `boolean`, `ip`, `geo_point`)都会启用 `doc_values`。 * `doc_values` 是一种**列式存储**结构。它将文档中**某个字段的所有值**按文档 ID (`_doc`) 顺序存储在一起。想象一个表格: * 列:字段名(如 `price`)。 * 行:文档 ID (`_doc`)。 * 单元格:该文档在该字段的值。 * 这种结构使得 ES 可以非常高效地: * **顺序扫描**整列数据(这正是范围查询 `price > 100` 的本质)。 * 进行聚合(如 `min`, `max`, `avg`, `sum`, `histogram`)。 * 对字段排序(`sort`)。 * 存储在磁盘上,但会被 OS 缓存到内存中加速访问。 * **倒排索引的局限性:** * 倒排索引(Inverted Index)是 ES 全文搜索的核心。它将 `Term` 映射到包含该 `Term` 的文档列表 (`Postings List`)。 * 对于精确匹配(`term`, `terms` 查询)和文本搜索极其高效。 * 但对于数值范围查询,倒排索引效率较低: * 数值被索引为独立的 `Term`(例如,`price:100`, `price:101`, ..., `price:200`)。 * 范围查询 `price:[100 TO 200]` 需要查找这 101 个 `Term` 对应的 `Postings List`,然后合并(`OR`)这些列表。当范围很大时,合并大量 `Postings List` 开销显著。 * **优化映射设计:** * **使用合适的数值类型:** 选择能满足需求的最小类型(如 `integer` 而非 `long`, `float` 而非 `double`)。更小的类型占用更少的磁盘和内存空间(`doc_values` 更紧凑),提升扫描速度。 * **避免将数值索引为 `text` 或 `keyword`:** 除非你确实需要对这些数值进行精确匹配(`term`)或聚合(`terms`),否则不要将它们映射为 `text` 或 `keyword`。将它们映射为数值类型才能利用高效的 `doc_values` 进行范围查询和数值聚合。如果同时需要精确匹配和范围查询/聚合,可以使用 `multi-fields`: ```json PUT my_index { "mappings": { "properties": { "product_id": { "type": "keyword", // 用于精确匹配、聚合 "fields": { "numeric": { "type": "integer" // 用于范围查询、数值聚合 } } } } } } // 查询时使用 product_id.numeric 进行范围查询 ``` * **利用 `date` 类型:** 对于时间戳,总是使用 `date` 类型。它本质上是一个 `long` 类型的数值(存储毫秒/微秒/纳秒时间戳),其 `doc_values` 对时间范围查询(`range` on `@timestamp`)极其高效,并且 ES 提供了丰富的日期格式化、计算和聚合功能。 * **考虑 `scaled_float`:** 对于需要高精度的小数(如价格、指标),`scaled_float` 通过指定一个 `scaling_factor`(如 100)将浮点数转换为长整型存储,在保持精度的同时利用整型 `doc_values` 的高效性。例如,价格 `19.99` 存储为 `1999` (`scaling_factor=100`)。查询时需要做相应转换(`gte: 1000` 对应 `10.00`)。 **总结:** ES 高效数值范围查询的核心在于**列式存储的 `doc_values`**。通过将数值字段映射为合适的数值类型(而不是 `text/keyword`),确保 `doc_values` 被启用,并利用 `multi-fields` 解决同时需要精确匹配和范围查询的场景,可以显著提升性能。 **3. 倒排索引与 B+树索引区别** | 特性 | 倒排索引 (Inverted Index - ES/Lucene) | B+树索引 (B+ Tree - MySQL等关系型数据库) | | :----------- | :------------------------------------------------------------- | :----------------------------------------------------------- | | **核心结构** | `Term` -> `Sorted List of Document IDs` (Postings List) <br> + `Term Dictionary` (查找Term) <br> + `Postings Lists` (文档列表) <br> + 可选:`Term Frequency`, `Positions`, `Offsets` | **多叉平衡树** <br> - 叶子点:包含**有序**的键值对(键是索引列值,值是主键值或行数据指针)<br> - 非叶点:索引项(键+指针) | | **主要目的** | **全文搜索**:快速找到包含特定词语/短语的所有文档。<br>**高效处理 `OR` 条件**。 | **精确匹配 & 范围查询**:快速定位特定值或有序范围内的行。<br>**高效处理 `ORDER BY` + `LIMIT`**。 | | **优势查询** | - `term` / `terms` (精确匹配)<br>- `match` (全文搜索)<br>- `match_phrase` (短语搜索)<br>- 布尔组合 (`OR` 高效)<br>- 相关性评分 (TF-IDF, BM25) | - `=` (精确匹配)<br>- `>`/`<`/`BETWEEN` (范围查询)<br>- `ORDER BY ... LIMIT N` (排序+分页)<br>- `MIN()`/`MAX()` (极值查询) | | **数据组织** | **按词(Term)组织**:索引指向包含该词的文档。 | **按值(Value)组织**:索引本身按键值有序存储。 | | **存储方式** | 通常更紧凑(特别是压缩后),但包含大量元数据(位置等)。 | 结构相对固定,叶子点链表便于范围扫描。 | | **写入开销** | **较高**:<br>- 文档需分词(文本)<br>- 需更新多个 `Term` 的 `Postings List`<br>- 段合并开销 | **相对较低**:<br>- 插入/删除需维护树结构平衡<br>- 页分裂/合并 | | **更新效率** | **较低效**:Lucene 段是**不可变**的。更新=标记旧文档删除+添加新文档。删除需合并段才能真正回收空间。 | **相对高效**:可直接在 B+树中找到记录位置进行原地更新(若长度不变)或删除/插入。 | | **范围查询** | **效率较低**:需要遍历多个 `Term` 的 `Postings List` 并合并。数值范围需依赖 `doc_values`。 | **非常高效**:叶子点链表结构,找到起点后顺序扫描即可。 | | **典型应用** | 搜索引擎 (Elasticsearch, Solr),全文检索,日志分析,非结构化/半结构化数据。 | 关系型数据库 (MySQL, PostgreSQL, Oracle),OLTP 系统,需要强一致性和事务支持的结构化数据。 | | **引用[^1]关联** | 对应 "Elasticsearch对应机制" 中的各种倒排索引(_id, 字段, 文本)。 | 对应 "MySQL支持" 中的聚簇索引(主键)、二级索引(普通索引)。 | **核心思想差异:** * **倒排索引:** “哪些文档包含这个词?” - 解决**从词找文档**的问题,是全文检索的基石。 * **B+树索引:** “这个值(或这个范围内的值)在哪几行?” - 解决**按值(或有序范围)快速定位数据行**的问题,是数据库高效查询和排序的基石。 **4. BM25 算法改进 TF-IDF** BM25 (Best Matching 25) 是信息检索领域最先进和广泛使用的相关性评分算法之一,它是在经典的 TF-IDF 基础上发展而来,解决了 TF-IDF 的一些主要缺陷: * **TF-IDF 基础回顾:** * **TF (Term Frequency - 词频):** $`tf(t, d)`$ - 词项 `t` 在文档 `d` 中出现的次数。基本思想:一个词在文档中出现次数越多,该文档与该词的相关性可能越高。但简单计数容易导致长文档占优(词出现次数天然多)。 * **IDF (Inverse Document Frequency - 逆文档频率):** $`idf(t) = \log \frac{N}{n_t + 1}`$ - `N` 是文档总数,`n_t` 是包含词项 `t` 的文档数。基本思想:一个词在所有文档中出现的越少(`n_t` 越小,`idf` 越大),它区分不同文档的能力越强,越重要。 * **TF-IDF Score:** $`score(t, d) = tf(t, d) * idf(t)`$ - 文档 `d` 对查询词 `t` 的得分。查询包含多个词时,通常是各查询词 TF-IDF 得分之和:$`score(q, d) = \sum_{t \in q} tf(t, d) * idf(t)`$。 * **TF-IDF 的主要问题:** 1. **文档长度未归一化:** 长文档更容易积累高 TF 值,即使它们可能只是泛泛而谈(包含很多不重要的高频词)。短文档即使高度相关也可能得分偏低。 2. **TF 增长线性:** TF 值线性增长,但词频对相关性的贡献不是线性的。出现 100 次并不比出现 20 次相关 5 倍。存在饱和效应。 3. **IDF 对常见词惩罚不足:** 标准 IDF 对极其常见的词(如 “the”, “and”)的惩罚可能不够彻底。 * **BM25 的改进:** BM25 引入了几项关键改进,形成了更健壮、效果更好的评分公式: 1. **饱和 TF (Saturation) - 控制词频过度影响:** * 使用一个**非线性函数**替代原始的 `tf`。当词频 `tf` 很高时,其增长对得分的贡献会逐渐减弱(饱和)。 * 公式核心部分:$`\frac{(k_1 + 1) * tf(t, d)}{k_1 * (1 - b + b * \frac{|d|}{avgdl}) + tf(t, d)}`$ * $`tf(t, d)`$: 词 `t` 在文档 `d` 中的词频。 * $`|d|`$: 文档 `d` 的长度(通常是字段的 token 数)。 * $`avgdl`$: 整个索引中所有文档的平均长度。 * $`k_1`$: **控制词频饱和度的调参数**。`k_1` 通常在 [1.2, 2.0] 之间。`k_1 = 0` 时完全忽略 TF;`k_1` 越大,TF 影响越大,饱和越慢。 * $`b`$: **文档长度归一化因子**,取值在 `[0, 1]` 之间。`b = 0` 时完全不做长度归一化;`b = 1` 时做完全归一化;通常取 `0.75`。文档 `d` 的长度 `|d|` 相对于平均长度 `avgdl` 越长,分母中的归一化项 $`(1 - b + b * \frac{|d|}{avgdl})`$ 越大,从而抑制了长文档的得分。 2. **文档长度归一化 (Document Length Normalization):** * 如上面公式所示,BM25 将文档长度 `|d|` 和平均长度 `avgdl` 直接融入到了计算中。 * 通过参数 `b`,可以精细控制文档长度对得分的影响程度。这有效解决了 TF-IDF 中长文档占优的问题,使得短小精悍的相关文档也能获得高分。 3. **IDF 的鲁棒性增强:** * BM25 通常使用一个更鲁棒的 IDF 变体:$`idf(t) = \log(1 + \frac{N - n_t + 0.5}{n_t + 0.5})`$ * 这个公式对包含词 `t` 的文档数 `n_t` 非常小(接近 0)或非常大(接近 `N`)的情况提供了更好的数值稳定性和行为。当 `n_t = N`(每个文档都包含该词),IDF 为负值(通常会被处理为 0),表示该词对区分文档无帮助。 4. **BM25 完整公式:** * 结合饱和 TF 和鲁棒 IDF,BM25 对文档 `d` 和查询 `q` 的得分通常是:$`score(q, d) = \sum_{t \in q} [ idf(t) * \frac{(k_1 + 1) * tf(t, d)}{k_1 * (1 - b + b * \frac{|d|}{avgdl}) + tf(t, d)} ]`$ * ES 的实现还支持查询词权重(boost)等扩展。 **总结:** BM25 通过引入**非线性饱和函数**处理 TF 和**显式文档长度归一化**,显著改进了 TF-IDF,使其相关性评分对文档长度不敏感,更能反映词频的真实贡献(避免过度强调高频词),并且对常见词的处理更鲁棒。参数 `k_1` 和 `b` 提供了调优相关性模型以适应不同语料库特性的灵活性。这是现代搜索引擎默认或推荐使用的相关性算法。 **5. 分片和副本机制实现高可用性** 分片 (Shard) 和副本 (Replica) 是 Elasticsearch 实现**水平扩展 (Scalability)** 和**高可用性 (High Availability, HA)** 的核心机制。 * **分片 (Shard):** * **定义:** 一个分片是一个底层的 **Lucene 索引实例**。一个 Elasticsearch **索引 (Index)** 在逻辑上是一个大的文档集合,但在物理上被分割成多个分片。 * **目的:** * **水平分割数据:** 将海量数据分散存储在多个点上,突破单机存储和性能限制。 * **分布式查询:** 查询可以并行发送到索引的所有相关分片(主分片或副本分片)上执行,大大提升查询吞吐量和速度。 * **主分片 (Primary Shard):** 每个文档属于且只属于一个主分片。主分片是文档索引、更新、删除操作的第一入口点。主分片的数量在索引创建时指定,之后**不能更改**(除非 reindex)。 * **副本分片 (Replica Shard):** * **定义:** 每个主分片可以有零个或多个副本分片。副本是主分片的**完整拷贝**(包含相同的文档数据)。 * **目的:** * **高可用性 (HA):** 这是最核心的目的。如果包含主分片的点发生故障,Elasticsearch 会自动将其中一个**健康的副本分片提升 (Promote) 为新的主分片**,使索引操作(写入)能够继续进行,服务不会中断。没有副本,主分片丢失就意味着该分片上的数据不可用。 * **提高读取吞吐量:** 搜索 (`GET`) 和查询请求(`search`)可以发送到主分片**或**任何副本分片上执行。通过增加副本数量,可以线性提升系统的读取(查询)吞吐量,分担主分片的查询负载。 * **数据冗余:** 副本提供了数据冗余,防止数据丢失。只要不是包含同一个主分片的所有副本(主+副本)的点同时永久丢失,数据就是安全的。 * **高可用性 (HA) 实现过程:** 1. **点故障检测:** ES 集群通过点间的 `Zen Discovery` 协议(或其他发现机制)和心跳检测点故障。如果主点(Master Node)判定某个数据点宕机(默认 30 秒无响应)。 2. **主分片重新选举:** 对于宕机点上承载的**主分片**,集群需要选举一个新的主分片。规则是:从该主分片剩余的**同步中 (in-sync) 的副本分片**(即数据与故障前主分片一致的副本)中选择一个提升为新的主分片。这个选举由主点协调。 3. **副本重新分配:** 集群状态变为 `Yellow`(因为副本数暂时不足)。主点会在集群中其他健康的点上启动新的副本分片创建过程,将数据从新的主分片(或其他同步的副本)复制到新副本,直到满足配置的副本数要求。完成后集群状态恢复 `Green`。 4. **写入恢复:** 新的主分片可以开始接收写入请求。如果客户端在故障切换期间发送了写入请求(未收到成功响应),它可能会收到失败(如 `503`)或冲突(`409`),需要应用层重试。重试的请求会被新的主分片处理。 5. **读取恢复:** 查询请求可以立即发送到新的主分片和剩余的副本分片上执行,服务恢复。 * **关键点:** * **副本数设置:** 为了实现高可用,**副本数至少设置为 1**。这样每个主分片至少有 1 个副本,允许一个点故障而不丢失数据或中断写入(前提是主分片和它的副本不在同一个点上!ES 会自动分配以避免单点故障)。 * **分片分配感知:** 可以使用 `Shard Allocation Awareness` 或 `Forced Awareness` 确保主分片和其副本分布在不同的物理机架、可用区(AZ)甚至区域(Region),防止机架或数据中心故障导致数据完全丢失。 * **脑裂防护:** 通过 `discovery.zen.minimum_master_nodes` (ES 7.x) 或 `cluster.initial_master_nodes` (ES 7.x+) / `cluster.bootstrapping` (ES 8.x+) 设置防止网络分区导致的脑裂,确保只有一个主点被认可。 **总结:** 分片机制实现数据分片和水平扩展;**副本机制是 ES 实现高可用性的基石**。它通过提供数据冗余和自动故障转移(副本提升为主分片),确保了在点故障时,数据仍然可用(读取)且索引操作(写入)能快速恢复。合理设置副本数(至少为 1)和利用分配感知策略是构建高可用 ES 集群的关键。 **6. 倒排索引如何支持全文检索?** 倒排索引是全文搜索引擎(如 Elasticsearch/Lucene)的核心数据结构,它通过以下关键步骤和特性实现对全文检索的高效支持: 1. **文本分析 (Analysis):** 这是全文检索的第一步,也是倒排索引构建的基础。 * **分词 (Tokenization):** 将原始文本(如句子、段落)分解成一个个独立的词元(Token)。例如,“The quick brown fox” -> ["the", "quick", "brown", "fox"]。 * **规范化 (Normalization):** 对分词后的词元进行处理,使其更标准化、更易于匹配: * **转小写 (Lowercasing):** "The" -> "the" (最常见)。 * **去除停用词 (Stop Word Removal):** 移除常见但信息量低的词(如 "a", "an", "the", "in", "on")。 * **词干还原 (Stemming):** 将单词还原为其词根形式(如 "jumping", "jumped", "jumps" -> "jump")。 * **词形归并 (Lemmatization):** 更高级的还原,考虑词性和上下文,得到词典原形(如 "better" -> "good")。 * **同义词处理 (Synonym):** 将特定词或短语映射为标准词(如 "TV" -> "television", "cell phone" -> "mobile phone")。 * **结果:** 原始文本被转换成一个标准化的词元(Token)序列。分析过程由**分析器 (Analyzer)** 定义,包含一个分词器 (`Tokenizer`) 和零或多个词元过滤器 (`Token Filter`)[^3]。 2. **构建倒排索引 (Inverted Index Construction):** 处理后的词元用于构建倒排索引。 * **核心组件:** * **词项字典 (Term Dictionary):** 一个包含所有**唯一**词项(Term,即规范化后的词元)的**有序列表**(通常按字典序排序)。这是查找词项的入口。 * **倒排列表 (Postings List):** 对于词项字典中的**每个词项 `t`**,存储一个**倒排列表 (Postings List)**。该列表记录了哪些文档包含了词项 `t`,以及在该文档中的一些附加信息: * **文档 ID (`DocID`):** 包含词项 `t` 的文档的唯一标识。 * **词频 (Term Frequency - `tf`):** 词项 `t` 在文档 `d` 中出现的次数。用于相关性评分(如 BM25)。 * **位置 (Positions):** 词项 `t` 在文档 `d` 中每次出现的位置偏移(相对于文档开头或前一个词)。这是支持**短语查询** (`"quick fox"`) 和**邻近度查询** (`quick fox ~5`) 的关键。 * **偏移量 (Offsets):** 词项 `t` 在原始文本中字符级别的起始和结束位置(用于高亮显示)。 * **结构关系:** `Term (t)` -> `Postings List for t` = `[ (DocID1, tf, [pos1, pos2, ...]), (DocID2, tf, [pos1, ...]), ... ]` 3. **执行全文检索 (Performing Full-Text Search):** 当用户提交一个查询(如 `search for quick fox`)时: 1. **查询分析:** 查询字符串也会经过(通常与索引时相同的)文本分析过程,生成一组标准化的查询词项 (Query Terms)。例如 `"quick fox"` -> `["quick", "fox"]`。 2. **查找词项:** 在**词项字典**中查找每个查询词项(`quick`, `fox`)。 3. **获取倒排列表:** 对于找到的每个查询词项,检索其对应的**倒排列表 (`Postings List`)**。 4. **执行布尔逻辑:** * **OR 查询 (`should` / `OR` / `||`)**:获取所有查询词项的倒排列表,合并 (`union`) 这些列表中的所有文档 ID。这是最简单的全文匹配(只要包含任意一个词)。 * **AND 查询 (`must` / `AND` / `&&`)**:获取所有查询词项的倒排列表,求交集 (`intersection`),找出同时包含所有查询词项的文档 ID。 * **短语查询 (`match_phrase` / `"..."`)**: * 获取所有查询词项的倒排列表(如 `quick`, `fox`)。 * 对于每个文档,检查 `fox` 的位置列表是否恰好包含一个位置,该位置比 `quick` 在同一个文档中的某个位置大 1(即 `fox` 紧跟在 `quick` 后面)。位置信息是实现短语精确匹配的基础。 * **邻近度查询 (`match_phrase` with `slop` / `"..."~N`)**:类似短语查询,但允许查询词项之间最多有 `N` 个其他词项(位置差 <= N+1)。 5. **相关性评分 (Scoring):** 对于匹配的文档,使用算法(如 BM25)计算其与查询的相关性得分。该算法会利用倒排列表中存储的信息: * 词频 (`tf`):文档中查询词出现的次数。 * 逆文档频率 (`idf`):基于词项在整个索引中出现的频率(可从词项字典的全局统计信息或倒排列表的 `df` - Document Frequency 获得)。 * 文档长度 (`fieldLength`):用于 BM25 的长度归一化(通常也存储在索引中)。 * 查询词权重 (`boost`):可选的提升因子。 6. **结果排序与返回:** 根据计算出的相关性得分对匹配文档进行降序排序,返回排名最高的结果给用户。 **总结:** 倒排索引通过将文档内容(经过文本分析)映射到关键词(词项)上,并记录每个关键词在哪些文档中出现、出现频率和位置,完美解决了“**哪些文档包含这些词?**”这个全文检索的核心问题。其结构(词项字典 + 倒排列表)使得基于词项的查找、布尔组合(OR/AND)、短语匹配(利用位置信息)和相关性计算(利用 tf/idf/长度)都能高效执行,成为支持快速、灵活、相关性驱动的全文搜索的基石[^1][^3]。 [^1]: 数据存储与索引分离,索引类型比较,查询性能特点对比,扩展性差异。 [^2]: ElasticsearchES)中的索引设计,直接影响查询性能、写入效率、资源使用与系统可维护性。一个合理的索引设计要综合考虑数据模型、业务查询模式、写入量、冷热数据管理、容量规划等多个维度。 [^3]: 通过显式映射,Elasticsearch 可以更准确地解析和索引数据,对查询性能优化起到关键作用。如果不提供显式映射,Elasticsearch 将会根据输入数据自动推断并生成隐式映射,但可能无法达到最理想的效果。 **
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值