ElasticsearchNotes

第 18 章:别名与零停机变更

zjc 于 2026-01-18 发布

这是《Elasticsearch 零基础实战指南》的独立章节版。本章从概念、实操和生产排查三个视角展开,代码块保留了原书可直接运行的版本。 Elasticsearch 中已有字段的类型和分词器不能直接修改。要调整 Mapping,通常需要创建新索引、迁移数据、验证一致性,再通过别名切换。

本章讲解别名、Reindex、Mapping 变更、双写、灰度切换、回滚和数据校验。

18.1 为什么需要别名

不推荐应用直接写死物理索引:

products_v1

推荐使用别名:

products_read -> products_v1
products_write -> products_v1

别名价值:

  1. 应用与物理索引解耦;
  2. 支持 Mapping 变更;
  3. 支持分片数调整;
  4. 支持灰度和回滚;
  5. 一个读别名可以查询多个索引;
  6. 写别名必须指向唯一写索引。

18.2 创建别名

创建索引时添加:

PUT /products_v1
{
  "aliases": {
    "products_read": {},
    "products_write": { "is_write_index": true }
  }
}

后添加:

POST /_aliases
{
  "actions": [
    {
      "add": {
        "index": "products_v1",
        "alias": "products_read"
      }
    },
    {
      "add": {
        "index": "products_v1",
        "alias": "products_write",
        "is_write_index": true
      }
    }
  ]
}

查看:

GET /_cat/aliases/products*?v

18.3 过滤别名

POST /_aliases
{
  "actions": [
    {
      "add": {
        "index": "products_v1",
        "alias": "products_on_sale",
        "filter": {
          "term": { "status": "ON_SALE" }
        }
      }
    }
  ]
}

查询 products_on_sale 时自动附加状态过滤。过滤别名适合逻辑视图,但权限控制仍应使用集群安全能力,而不是只依赖别名。

18.4 Reindex

创建新索引:

PUT /products_v2
{
  "settings": {
    "number_of_shards": 6,
    "number_of_replicas": 0
  },
  "mappings": {
    "properties": {
      "title": {
        "type": "text",
        "analyzer": "ik_max_word"
      },
      "brand": { "type": "keyword" },
      "price": { "type": "scaled_float", "scaling_factor": 100 }
    }
  }
}

迁移数据:

POST /_reindex?wait_for_completion=false
{
  "source": {
    "index": "products_v1",
    "size": 1000
  },
  "dest": {
    "index": "products_v2"
  }
}

返回任务 ID:

{
  "task": "oTUltX4IQMOUUVeiohTt8A:12345"
}

查看任务:

GET /_tasks/oTUltX4IQMOUUVeiohTt8A:12345

取消任务:

POST /_tasks/oTUltX4IQMOUUVeiohTt8A:12345/_cancel

18.5 Reindex 条件与脚本

只迁移部分数据:

POST /_reindex
{
  "source": {
    "index": "products_v1",
    "query": {
      "range": {
        "updated_at": { "gte": "2026-01-01" }
      }
    }
  },
  "dest": {
    "index": "products_v2"
  }
}

字段转换:

POST /_reindex
{
  "source": { "index": "orders_v1" },
  "dest": { "index": "orders_v2" },
  "script": {
    "source": """
      ctx._source.amount = ctx._source.amount_cents / 100.0;
      ctx._source.remove('amount_cents');
    """
  }
}

18.6 零停机变更流程

1. 保留旧索引 products_v1 和写别名
2. 创建新索引 products_v2
3. 启动 Reindex 迁移存量
4. 开启双写或 CDC 同步增量
5. 对比 v1/v2 数量和抽样数据
6. 用读别名灰度切流量
7. 观察查询结果和性能
8. 切写别名到 v2
9. 保留 v1 一段时间用于回滚
10. 下线 v1

切换读别名:

POST /_aliases
{
  "actions": [
    { "remove": { "index": "products_v1", "alias": "products_read" } },
    { "add": { "index": "products_v2", "alias": "products_read" } }
  ]
}

切换写别名:

POST /_aliases
{
  "actions": [
    { "remove": { "index": "products_v1", "alias": "products_write" } },
    { "add": { "index": "products_v2", "alias": "products_write", "is_write_index": true } }
  ]
}

18.7 双写与 CDC

双写

应用写 MySQL
-> 同步写 products_v1
-> 同步写 products_v2

优点是延迟低;缺点是侵入业务、两份数据可能不一致、失败补偿复杂。

CDC

MySQL binlog -> Kafka -> Consumer
  -> products_v1
  -> products_v2

优点是与应用解耦、可回放、失败可重试。切换完成后只需保留 v2 消费者。

推荐策略

大多数系统推荐:

事实源 MySQL + CDC/同步服务写当前索引
Reindex 迁移存量
短窗口双写或增量追赶
别名切换

18.8 数据校验

数量:

GET /products_v1/_count
GET /products_v2/_count

按状态统计:

GET /products_v1/_search
{
  "size": 0,
  "aggs": {
    "status": { "terms": { "field": "status" } }
  }
}

抽样比较:

1. 随机取 1000 个 ID
2. 分别读取 v1/v2 文档
3. 比较关键字段和版本
4. 记录差异并修复

搜索对比:

1. 收集 100 个线上查询
2. 在 v1/v2 分别执行
3. 比较命中数、Top20、耗时
4. 人工评估差异是否合理

18.9 灰度与回滚

灰度方式:

  1. 内部账号先切 v2;
  2. 按用户百分比切;
  3. 按机房或服务实例切;
  4. 对比新旧结果;
  5. 监控无结果率、点击率、延迟和错误率。

回滚:

POST /_aliases
{
  "actions": [
    { "remove": { "index": "products_v2", "alias": "products_read" } },
    { "add": { "index": "products_v1", "alias": "products_read" } }
  ]
}

如果写别名已经切到 v2,回滚前要评估切换窗口内 v2 独有数据的处理方式。

18.10 分片数调整

Reindex 适合任意调整主分片数:

products_v1: 3 shards
products_v2: 12 shards

Split API 只能增加分片数,且原分片数需满足拆分倍数要求:

POST /products_v1/_split/products_v2
{
  "settings": {
    "index.number_of_shards": 6
  }
}

Shrink API 减少分片数:

POST /products_v1/_shrink/products_v2
{
  "settings": {
    "index.number_of_shards": 1
  }
}

两者要求索引只读并满足分片分布条件。通用 Mapping 变更仍优先 Reindex。

18.11 变更管理清单

变更前:

  1. 明确目标:Mapping、分词器、分片数、索引设置;
  2. 评估磁盘容量;
  3. 准备新索引模板;
  4. 确认同步链路;
  5. 备份关键索引;
  6. 制定回滚方案;
  7. 通知业务方。

变更中:

  1. 低峰执行 Reindex;
  2. 控制任务并发;
  3. 观察磁盘、CPU、IO;
  4. 增量追赶;
  5. 校验数量和质量;
  6. 灰度切读。

变更后:

  1. 保留旧索引;
  2. 监控搜索指标;
  3. 确认写链路正常;
  4. 更新文档和监控面板;
  5. 到期删除旧索引。

18.12 本章小结

18.13 思考题

  1. 为什么读写别名要分开?
  2. Reindex 期间如何处理持续更新的增量数据?
  3. 切换写别名前需要做哪些校验?
  4. 双写和 CDC 各有什么优缺点?
  5. 如果新索引查询结果明显变差,如何回滚和定位?