Elasticsearch 底层原理系列(八):分布式一致性
2020-12-20·5 分钟阅读
前言
分布式系统的一致性是一个复杂而关键的问题。Elasticsearch 作为分布式搜索引擎,需要处理节点故障、网络分区等异常情况,同时保证数据的可用性和一致性。
本章将深入解析 ES 的分布式一致性机制。
技术亮点
| 技术点 | 难度 | 面试价值 | 本文覆盖 |
|---|---|---|---|
| 选举机制 | ⭐⭐⭐⭐ | 高频考点 | ✅ |
| 脑裂问题 | ⭐⭐⭐⭐ | 高频考点 | ✅ |
| 故障检测 | ⭐⭐⭐ | 实战价值 | ✅ |
| 数据一致性 | ⭐⭐⭐⭐⭐ | 进阶考点 | ✅ |
面试考点
- Elasticsearch 如何保证分布式一致性?
- 什么是脑裂?如何避免?
- ES 的故障检测机制是怎样的?
- 节点崩溃后数据如何恢复?
选举机制详解
ES 7.x+ 选举算法
┌─────────────────────────────────────────────────────────────────────────┐
│ ES 选举算法演进 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ES 7.0 之前:Zen Discovery(基于 Bully 算法) │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 问题: │ │
│ │ • 需要手动配置 minimum_master_nodes │ │
│ │ • 网络抖动可能导致频繁选举 │ │
│ │ • 脑裂风险 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ ES 7.0+:改进的选举算法(类似 Raft) │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 改进: │ │
│ │ • 自动计算 minimum_master_nodes = N/2 + 1 │ │
│ │ • 单节点也能正常启动 │ │
│ │ • 减少选举冲突 │ │
│ │ • 更快的收敛速度 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
选举流程
┌─────────────────────────────────────────────────────────────────────────┐
│ 选举流程详解 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 状态机: │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ ┌──────────────┐ │ │
│ │ │ START │ ─── 启动 │ │
│ │ └──────┬───────┘ │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ ┌──────────────┐ │ │
│ │ │ DISCOVERING │ ─── 发现集群 │ │
│ │ └──────┬───────┘ │ │
│ │ │ │ │
│ │ ├─── 发现 Master ────────► FOLLOWER │ │
│ │ │ │ │
│ │ └─── 未发现 Master │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ ┌──────────────┐ │ │
│ │ │ CANDIDATE │ ─── 参与选举 │ │
│ │ └──────┬───────┘ │ │
│ │ │ │ │
│ │ ├─── 获得多数票 ──► MASTER │ │
│ │ │ │ │
│ │ └─── 未获多数 ──► 继续选举 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ 选举条件: │
│ • 没有 Master │
│ • 当前节点是 Master-eligible │
│ • 已发现足够的节点(满足 minimum_master_nodes) │
│ │
└─────────────────────────────────────────────────────────────────────────┘
投票排序规则
┌─────────────────────────────────────────────────────────────────────────┐
│ 投票排序规则 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 节点排序依据: │
│ 1. 集群状态版本(越高越优先) │
│ 2. 节点 ID(字典序) │
│ │
│ 示例: │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ Node A: version=10, id=node_abc │ │
│ │ Node B: version=10, id=node_def │ │
│ │ Node C: version=9, id=node_ghi │ │
│ │ │ │
│ │ 排序结果:Node A > Node B > Node C │ │
│ │ (版本相同,按 ID 排序) │ │
│ │ │ │
│ │ 投票结果:Node A 获得 3 票(全票)当选 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ 排序优先级的设计目的: │
│ • 版本高 = 拥有最新数据 │
│ • 避免旧数据的节点当选 │
│ │
└─────────────────────────────────────────────────────────────────────────┘
脑裂问题
脑裂场景
┌─────────────────────────────────────────────────────────────────────────┐
│ 脑裂场景 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 正常状态: │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ │ │
│ │ │ M1 │ │ M2 │ │ M3 │ │ M4 │ │ M5 │ │ │
│ │ │Master│ │ │ │ │ │ │ │ │ │ │
│ │ └─────┘ └─────┘ └─────┘ └─────┘ └─────┘ │ │
│ │ Node1 Node2 Node3 Node4 Node5 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │ │
│ ▼ 网络分区 │
│ │
│ 脑裂状态(未正确配置时): │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 分区 A: 分区 B: │ │
│ │ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ │ │
│ │ │ M1 │ │ M2 │ │ M3 │ │ M4 │ │ M5 │ │ │
│ │ │Master│ │ │ │ │ │Master│ │ │ ← 两个Master!│ │
│ │ └─────┘ └─────┘ └─────┘ └─────┘ └─────┘ │ │
│ │ │ │
│ │ 问题: │ │
│ │ • 两个分区各自处理请求 │ │
│ │ • 数据分叉,无法合并 │ │
│ │ • 可能导致数据丢失 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
ES 7.x+ 如何避免脑裂
┌─────────────────────────────────────────────────────────────────────────┐
│ ES 7.x+ 脑裂避免机制 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 自动计算 minimum_master_nodes: │
│ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ Master-eligible 节点数 minimum_master_nodes │ │
│ │ ────────────────────────────────────────────── │ │
│ │ 1 1 │ │
│ │ 2 2 │ │
│ │ 3 2 │ │
│ │ 4 3 │ │
│ │ 5 3 │ │
│ │ N floor(N/2) + 1 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ 网络分区后: │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 分区 A(3 节点): 分区 B(2 节点): │ │
│ │ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ ┌─────┐ │ │
│ │ │ M1 │ │ M2 │ │ M3 │ │ M4 │ │ M5 │ │ │
│ │ │Master│ │ │ │ │ │ 无 │ │ │ │ │
│ │ └─────┘ └─────┘ └─────┘ └─────┘ └─────┘ │ │
│ │ 3 ≥ minimum_master_nodes(3) 2 < minimum_master_nodes(3) │ │
│ │ 可以维持 Master 无法选出 Master │ │
│ │ │ │
│ │ 结果:只有分区 A 能继续工作,避免了脑裂 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
最佳实践
# 配置建议
# 1. 奇数个 Master-eligible 节点
# 推荐:3 个(可容忍 1 个故障)
# 5 个(可容忍 2 个故障)
# 2. 专用 Master 节点
node.roles: [ master ]
# 3. 设置合理的超时
discovery.zen.ping_timeout: 5s
discovery.zen.join_timeout: 60s
# 4. 配置初始 Master 候选节点
cluster.initial_master_nodes: ["node-1", "node-2", "node-3"]
# 5. 单播发现
discovery.seed_hosts: ["node-1", "node-2", "node-3"]
故障检测
故障检测机制
┌─────────────────────────────────────────────────────────────────────────┐
│ 故障检测机制 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 两种检测: │
│ │
│ 1. Master 检测节点故障 │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ Master 定期向所有节点发送 Ping │ │
│ │ 如果节点未响应,标记为故障 │ │
│ │ │ │
│ │ 配置: │ │
│ │ • ping_interval: 1s(发送间隔) │ │
│ │ • ping_timeout: 30s(响应超时) │ │
│ │ • ping_retries: 3(重试次数) │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ 2. 节点检测 Master 故障 │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 所有节点定期检查 Master 是否存活 │ │
│ │ 如果 Master 无响应,触发重新选举 │ │
│ │ │ │
│ │ 配置: │ │
│ │ • cluster.fault_detection.leader_check.interval: 1s │ │
│ │ • cluster.fault_detection.leader_check.timeout: 10s │ │
│ │ • cluster.fault_detection.leader_check.retry_count: 3 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
故障恢复流程
┌─────────────────────────────────────────────────────────────────────────┐
│ 故障恢复流程 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 节点故障后的恢复: │
│ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ Step 1: 检测故障 │ │
│ │ ┌───────────────────────────────────────────────────────────┐ │ │
│ │ │ Master 检测到 Node 3 故障 │ │ │
│ │ └───────────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ Step 2: 更新集群状态 │ │
│ │ ┌───────────────────────────────────────────────────────────┐ │ │
│ │ │ • 将 Node 3 标记为离开 │ │ │
│ │ │ • 更新分片路由表 │ │ │
│ │ │ • 副本分片提升为主分片(如需要) │ │ │
│ │ └───────────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ Step 3: 重新分配分片 │ │
│ │ ┌───────────────────────────────────────────────────────────┐ │ │
│ │ │ • 将丢失的主分片从副本提升 │ │ │
│ │ │ • 创建新的副本分片 │ │ │
│ │ │ • 分片数据从其他节点复制 │ │ │
│ │ └───────────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ │ Step 4: 恢复完成 │ │
│ │ ┌───────────────────────────────────────────────────────────┐ │ │
│ │ │ • 集群状态变为 Green │ │ │
│ │ │ • 所有分片已分配 │ │ │
│ │ └───────────────────────────────────────────────────────────┘ │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
数据一致性保证
写入一致性
┌─────────────────────────────────────────────────────────────────────────┐
│ 写入一致性级别 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ consistency 参数: │
│ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ one(默认) │ │
│ │ • 主分片写入成功即可返回 │ │
│ │ • 性能最高,但可能有数据丢失风险 │ │
│ │ │ │
│ │ quorum │ │
│ │ • 需要 majority 分片写入成功 │ │
│ │ • majority = (primary + replicas) / 2 + 1 │ │
│ │ • 平衡性能和可靠性 │ │
│ │ │ │
│ │ all │ │
│ │ • 所有分片(主分片 + 所有副本)写入成功 │ │
│ │ • 可靠性最高,性能最低 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ 示例: │
│ PUT /my_index/_doc/1?consistency=quorum&wait_for_active_shards=2 │
│ { │
│ "title": "Document" │
│ } │
│ │
└─────────────────────────────────────────────────────────────────────────┘
副本同步
┌─────────────────────────────────────────────────────────────────────────┐
│ 副本同步机制 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 写入流程: │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 1. 文档写入主分片 │ │
│ │ 2. 主分片写入 Translog │ │
│ │ 3. 主分片将操作发送到所有副本分片 │ │
│ │ 4. 副本分片写入本地 Translog │ │
│ │ 5. 副本分片确认写入 │ │
│ │ 6. 主分片返回成功响应 │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
│ 同步模型: │
│ │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ Primary Shard │ │
│ │ │ │ │
│ │ │ 写入操作 │ │
│ │ ▼ │ │
│ │ ┌─────────┐ │ │
│ │ │ Translog│ │ │
│ │ └─────────┘ │ │
│ │ │ │ │
│ │ ├──────────────┬──────────────┐ │ │
│ │ ▼ ▼ ▼ │ │
│ │ ┌─────────┐ ┌─────────┐ ┌─────────┐ │ │
│ │ │Replica 1│ │Replica 2│ │Replica 3│ │ │
│ │ │ Translog│ │ Translog│ │ Translog│ │ │
│ │ └─────────┘ └─────────┘ └─────────┘ │ │
│ │ │ │ │ │ │
│ │ └──────────────┴──────────────┘ │ │
│ │ │ │ │
│ │ ▼ │ │
│ │ 确认写入 │ │
│ │ │ │
│ └────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
Sequence ID
┌─────────────────────────────────────────────────────────────────────────┐
│ Sequence ID 机制 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ ES 6.0+ 引入 Sequence ID 解决副本一致性问题 │
│ │
│ ┌─────────────────────────────────────────────────────────────────┐ │
│ │ │ │
│ │ 每个操作分配唯一的递增 ID: │ │
│ │ │ │
│ │ Operation 1: seq_no=0, primary_term=1 │ │
│ │ Operation 2: seq_no=1, primary_term=1 │ │
│ │ Operation 3: seq_no=2, primary_term=1 │ │
│ │ ... │ │
│ │ │ │
│ │ primary_term:主分片任期,每次主分片变更后递增 │ │
│ │ seq_no:操作序号,每个主分片内递增 │ │
│ │ │ │
│ │ 用途: │ │
│ │ • 副本按顺序重放操作 │ │
│ │ • 检测和恢复数据不一致 │ │
│ │ • 优化恢复过程(只同步差异) │ │
│ │ │ │
│ └─────────────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────────────┘
高可用配置最佳实践
生产环境配置
# elasticsearch.yml
# 集群名称(所有节点相同)
cluster.name: production-cluster
# 节点名称(每个节点唯一)
node.name: node-1
# 角色配置
node.roles: [ master, data ]
# 初始 Master 候选节点
cluster.initial_master_nodes: ["node-1", "node-2", "node-3"]
# 发现配置
discovery.seed_hosts: ["node-1", "node-2", "node-3"]
# 网络配置
network.host: 0.0.0.0
http.port: 9200
transport.port: 9300
# 故障检测
discovery.zen.ping_timeout: 5s
discovery.zen.join_timeout: 60s
# 内存锁定
bootstrap.memory_lock: true
# 索引恢复设置
indices.recovery.max_bytes_per_sec: 100mb
cluster.routing.allocation.node_concurrent_recoveries: 4
监控指标
┌─────────────────────────────────────────────────────────────────────────┐
│ 关键监控指标 │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ 集群健康: │
│ GET /_cluster/health │
│ • status: green/yellow/red │
│ • number_of_nodes │
│ • unassigned_shards │
│ │
│ 节点状态: │
│ GET /_cat/nodes?v&h=name,role,heap.percent,ram.percent,load_1m │
│ │
│ 分片状态: │
│ GET /_cat/shards?v&state=UNASSIGNED │
│ │
│ Pending Tasks: │
│ GET /_cluster/pending_tasks │
│ │
└─────────────────────────────────────────────────────────────────────────┘
总结
本章深入解析了 ES 分布式一致性机制:
| 要点 | 说明 |
|---|---|
| 选举机制 | ES 7.x+ 类 Raft 算法,自动计算 quorum |
| 脑裂避免 | minimum_master_nodes = N/2 + 1 |
| 故障检测 | Master 检测节点 + 节点检测 Master |
| 数据一致性 | Translog + Sequence ID 保证 |
参考资料
下一章预告
下一章将探讨 生产实践与性能调优,包括:
- 硬件选型与 JVM 调优
- 索引设计最佳实践
- 性能监控与调优
- 常见问题排查
相关文章
Elasticsearch 底层原理系列(九):生产实践与性能调优
2020-12-31·6 分钟阅读
深入解析 Elasticsearch 生产环境最佳实践,包括硬件选型、JVM 调优、索引设计、性能监控与调优、常见问题排查。
Elasticsearch 底层原理系列(七):分片与路由机制
2020-12-13·5 分钟阅读
深入解析 Elasticsearch 分片路由机制,包括路由算法、自定义路由、分片数量规划、以及分片重平衡策略。
Elasticsearch 底层原理系列(六):集群架构与节点角色
2020-11-29·6 分钟阅读
深入解析 Elasticsearch 集群架构,包括 Master、Data、Coordinating 等节点角色的职责、集群状态管理、以及节点角色配置最佳实践。