基于本文回答

播面 播面

文图音视,全方位拆解八股文
0
评论

Elasticsearch 要存储海量数据,应该如何设计路由(Routing)策略?

知识点图片

在 Elasticsearch (ES) 中存储海量数据时,路由(Routing)策略的设计直接决定了查询性能、写入吞吐量以及集群的稳定性

默认情况下,ES 使用文档 _id 进行哈希路由,这能保证数据均匀分布,但会导致查询时的“广播模式”(Scatter-Gather),即查询需要扫描所有分片。对于海量数据,这通常是不可接受的。

以下是针对海量数据场景的路由设计策略指南,分为核心原则、常见策略、以及应对数据倾斜的方案。


一、 核心原理

ES 确定文档所在分片的公式为:
shard=hash(routing)%number_of_primary_shardsshard = hash(routing) \% number\_of\_primary\_shards

  • 默认情况routing = _id
  • 自定义路由routing = user_id / city / tenant_id 等业务字段。

二、 路由策略设计方案

1. 默认路由(不推荐用于海量数据的精确查询)

  • 机制:使用自动生成的 _id 或业务主键作为路由。
  • 优点:数据在分片间分布非常均匀,写入性能好,无热点分片问题。
  • 缺点查询性能差。每次查询都需要访问所有分片,然后聚合结果。随着数据量和分片数增加,Coordinator 节点的压力剧增,延迟变高。
  • 适用场景:数据量中等,或者查询模式非常随机(无法通过特定键聚合)。

2. 自定义路由(Custom Routing)—— 推荐方案

  • 机制:指定业务强相关的字段(如 user_id, order_id, region)作为路由键。
    bash
    PUT /my_index/_doc/1?routing=user_123
    { ... }
  • 优点查询性能极高。查询时带上 routing 参数,ES 只需要去这 1 个分片查找,避免了广播风暴。
    bash
    GET /my_index/_search?routing=user_123
    { ... }
  • 缺点数据倾斜(Data Skew)。如果某个 user_123 的数据量远超其他用户,会导致该分片过大,成为性能瓶颈(木桶效应)。
  • 适用场景:多租户系统(SaaS)、用户维度的订单/日志查询。

3. 路由分区(Routing Partition Size)—— 折中方案

  • 机制:ES 允许将同一个 routing key 的数据路由到一组分片中,而不是一个分片。
  • 配置:设置 index.routing_partition_size(例如设置为 3)。
  • 效果user_123 的数据会分布在 3 个分片上,而不是 1 个。
  • 优点:缓解了单分片过大的数据倾斜问题,同时查询时只需要查这 3 个分片,比查所有分片快,比查 1 个分片慢。
  • 适用场景:单个路由键的数据量较大,单个分片存不下,但又希望获得一定的查询剪枝优化。

三、 海量数据下的架构模式

针对海量数据,通常需要结合 时间维度业务维度 混合设计。

模式 A:基于时间的滚动索引 (Time-Based + ILM)

  • 场景:日志、监控指标、流水记录。
  • 策略
    • 不使用自定义路由,或者仅在特定场景使用。
    • 利用 Data StreamsIndex Lifecycle Management (ILM)
    • 数据按天/周/月生成新索引。
  • 查询优化:查询时带上时间范围,ES 会自动跳过不在时间范围内的索引(利用 min/max 字段优化)。

模式 B:自定义路由 + 别名隔离 (Routing + Alias)

  • 场景:SaaS 平台,每个租户数据量差异巨大。
  • 策略
    • 小租户:共用一个大索引,使用 routing=tenant_id
    • 大租户:单独建立索引(独立索引),不使用路由或使用默认路由。
    • 应用层封装:应用层维护一个映射表(Tenant -> Index),查询时根据租户 ID 决定查哪个索引。

模式 C:冷热分离 + 路由

  • 场景:海量历史数据。
  • 策略
    • 热数据(近期):使用自定义路由,保证写入和高频查询性能。
    • 冷数据(历史):通过 ILM 迁移到冷节点,甚至在 Force Merge 后取消路由限制(如果查询模式发生变化),或者继续保持路由以节省 IO。

四、 如何解决“数据倾斜”问题?

这是自定义路由最大的风险。如果你的业务中存在“超级大V”用户,必须处理。

  1. 业务层拆分(加盐)

    • 如果 user_id=888 数据量太大,应用层写入时人为将其拆分为 888_0, 888_1, 888_2
    • 路由时使用 routing=888_0 等。
    • 查询时使用 routing=888_0,888_1,888_2(ES 支持指定多个 routing)。
  2. 独立索引(Isolated Index)

    • 检测到某个 Routing Key 数据量超过阈值(如 50GB),将其迁移到独立索引。
    • 普通用户继续在共享索引中使用 Routing。
  3. 使用 routing_partition_size

    • 如前所述,让数据分散到多个分片,而非一个。注意:开启此功能后,_id 的唯一性仅在分片内保证,而非索引内保证(通常不是问题)。

五、 最佳实践清单

  1. 强制带 Routing 查询:如果设计了路由,应用代码必须在查询时带上 routing 参数,否则查询结果可能不完整(如果开启了 _routing 必填配置)或性能极差。
  2. 设置 _routing.required: true:在 Mapping 中强制要求写入和查询必须带路由,防止开发人员误操作导致数据分散或全表扫描。
  3. 基数评估:选择的 Routing Key 基数(Cardinality)要够大。不要使用 gender(性别)这种只有几个值的字段做路由,会导致分片极度不均。
  4. 分片大小控制:配合路由策略,单个分片建议控制在 30GB - 50GB 之间。
  5. ID 生成策略:如果使用了自定义路由,尽量不要让 ES 自动生成 _id,最好由业务生成并保证全局唯一,因为 ES 在不同分片上的 ID 查重开销不同。

总结建议

  • 如果是日志/流水数据:优先使用 Data Streams + ILM,路由策略次之(除非有明确的按用户/设备ID追踪需求)。
  • 如果是业务实体数据(订单、用户内容)
    • 数据分布均匀:使用 自定义路由(user_id
    • 存在超级大户:使用 routing_partition_size应用层分索引
00:00
00:00