DolphinDB 高可用部署实战:从容灾设计到故障自动转移

摘要:生产环境里的 DolphinDB 一旦承载实时行情、历史数据或流计算任务,单点故障就可能造成业务中断和数据丢失。本文基于 DolphinDB 2.x 的高可用机制,系统讲解高可用部署、容灾与故障自动转移三大核心能力:从高可用架构选型(主备/多副本/集群)到心跳检测与故障判定,从自动/手动故障转移到数据同步与一致性校验,再到容灾演练与最小可用系统搭建。文章包含 5 组可直接复用的 DolphinDB 脚本、3 张 GPT-Image-2 占位图提示词、3 个对比表和 4 个 Mermaid 图,帮助读者建立一套可落地的容灾与故障转移方案。

文章目录

引言:为什么高可用部署不是"可选项"

在金融行情、工业物联网和实时风控等场景里,DolphinDB 通常被部署为数据中枢:它既承接毫秒级的流数据写入,又支撑着海量历史数据的查询与分析。一旦单机宕机,轻则查询超时,重则行情数据断流、风控信号缺失。因此,高可用部署不是运维的加分项,而是生产上线的准入门槛

很多团队对高可用的理解停留在"多开几台机器",但高可用真正的挑战在于如何在故障发生时快速、正确地完成切换,同时保证数据不丢、业务无感。这需要解决三个问题:第一,如何及时发现节点故障;第二,如何在主节点不可用时选出新的主节点;第三,如何确保切换前后的数据一致。本文将围绕这三个问题,给出一条从架构设计到实战演练的完整路径。

需要说明的是,DolphinDB 的高可用能力在不同版本(1.x、2.x、3.x)和不同授权(社区版/企业版/集群版)之间存在差异。本文示例基于 DolphinDB 2.x 企业版集群机制,并标注了社区版的替代思路。实际部署时,请以官方文档和当前版本为准。

一、核心概念拆解

标题里提到了三个核心技术词:高可用部署、容灾、故障转移。在进入实操之前,先把它们拆开讲清楚。

1.1 什么是高可用部署

高可用部署(High Availability Deployment)指的是通过冗余设计、故障检测和自动切换,让系统在面对节点宕机、网络分区、磁盘损坏等故障时,仍然能够持续对外提供服务。它不是让系统永远不出现故障,而是让故障的影响范围和时间都被控制在可接受范围内

在 DolphinDB 中,高可用通常依赖三类机制:

  • 数据冗余:同一份数据在多个节点上保存副本,例如多副本模式下的 3 副本策略。
  • 服务冗余:关键服务(如 controller、data node、compute node)部署多个实例,避免单点。
  • 自动切换:当主节点不可用时,系统或脚本自动将备节点提升为主节点,继续对外服务。

1.2 什么是容灾

容灾(Disaster Recovery)比高可用更进一步。高可用关注"单节点故障时业务不中断",容灾关注"当整个机房、城市或区域发生灾难时,数据能不能恢复、业务能不能重启"。衡量容灾能力的核心指标有两个:

  • RTO(Recovery Time Objective):从故障发生到业务恢复所需的时间。RTO 越短,业务中断时间越短。
  • RPO(Recovery Point Objective):故障发生时允许丢失的数据量。RPO 越短,数据丢失越少。

例如,RTO=5 分钟、RPO=1 分钟意味着:业务最多中断 5 分钟,最多丢失 1 分钟的数据。这两个指标是容灾设计的起点,也是验收的终点。

1.3 什么是故障转移

故障转移(Failover)是高可用和容灾落地的核心动作。它指的是当某个节点被判定为不可用时,系统把该节点承担的角色(如主节点、写入节点、计算节点)迁移到其他可用节点上。

故障转移通常分为两类:

  • 自动故障转移:由监控系统或内置机制触发,无需人工干预。适用于对恢复时间要求高的场景。
  • 手动故障转移:由运维人员触发,通常用于计划内维护、版本升级或自动转移失败后的兜底。

1.4 故障转移中的关键角色

在 DolphinDB 的高可用架构中,通常涉及三类角色:

  • Controller:负责集群元数据管理、节点注册和集群状态维护。
  • Data Node:负责数据存储和查询执行。
  • Compute Node:负责计算任务,可与数据节点分离以提升查询性能。

一个完整的高可用集群通常包含至少 1 个 controller、多个 data node 和可选的 compute node。controller 本身也需要冗余,否则 controller 单点会成为新的瓶颈。

二、高可用架构选型与对比

DolphinDB 的高可用架构可以根据业务规模和对 RTO/RPO 的要求,分为主备模式、多副本模式和集群模式三种。

2.1 主备模式

主备模式(Master-Slave)是最简单的高可用架构。一个主节点负责写入和查询,一个或多个备节点实时同步主节点数据。当主节点故障时,备节点被提升为新的主节点。

这种架构的优点是部署简单、配置清晰,适合中小规模业务。缺点是备节点平时不对外服务,资源利用率较低;另外,如果同步是异步的,故障切换时可能会丢失部分数据。

2.2 多副本模式

多副本模式(Multi-Replica)下,同一份数据在多个 data node 上保存副本。写入操作需要满足写仲裁(write quorum),读取操作可以从任意副本读取。某个节点故障时,其他副本仍然可以继续提供服务。

这种模式的优点是可用性更高、读性能可以横向扩展。缺点是需要更多的存储资源和网络带宽,配置也相对复杂。

2.3 集群模式

集群模式(Cluster)是 DolphinDB 企业版推荐的生产部署方式。它通过 controller 管理多个 data node 和 compute node,支持数据分片、副本、负载均衡和自动故障转移。集群模式可以跨机架甚至跨机房部署,是实现容灾的基础。

图 1:高可用分层架构视觉图。同城双活层处理日常读写,异地灾备层承担极端灾难恢复,监控与切换控制层负责故障发现与决策。

2.4 三种架构模式对比

架构模式 数据冗余方式 RTO 典型值 RPO 典型值 适用场景 主要缺点
主备模式 备节点异步/同步复制 分钟级 秒级~分钟级 中小规模、预算有限 备节点资源利用率低
多副本模式 3 副本仲裁写入 秒级 零~秒级 读多写少、高可用要求 存储和网络成本高
集群模式 分片+副本+controller 秒级 零~秒级 大规模生产环境 配置和运维复杂度高

选择架构时,核心依据是业务对 RTO/RPO 的容忍度。如果 RPO=0 是硬性要求,那么多副本或集群模式是更稳妥的选择;如果业务可以容忍分钟级中断,主备模式可以显著降低部署成本。
#mermaid-svg-IosbmNpnP89f0Tn4{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-IosbmNpnP89f0Tn4 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-IosbmNpnP89f0Tn4 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-IosbmNpnP89f0Tn4 .error-icon{fill:#552222;}#mermaid-svg-IosbmNpnP89f0Tn4 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-IosbmNpnP89f0Tn4 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-IosbmNpnP89f0Tn4 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-IosbmNpnP89f0Tn4 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-IosbmNpnP89f0Tn4 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-IosbmNpnP89f0Tn4 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-IosbmNpnP89f0Tn4 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-IosbmNpnP89f0Tn4 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-IosbmNpnP89f0Tn4 .marker.cross{stroke:#333333;}#mermaid-svg-IosbmNpnP89f0Tn4 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-IosbmNpnP89f0Tn4 p{margin:0;}#mermaid-svg-IosbmNpnP89f0Tn4 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-IosbmNpnP89f0Tn4 .cluster-label text{fill:#333;}#mermaid-svg-IosbmNpnP89f0Tn4 .cluster-label span{color:#333;}#mermaid-svg-IosbmNpnP89f0Tn4 .cluster-label span p{background-color:transparent;}#mermaid-svg-IosbmNpnP89f0Tn4 .label text,#mermaid-svg-IosbmNpnP89f0Tn4 span{fill:#333;color:#333;}#mermaid-svg-IosbmNpnP89f0Tn4 .node rect,#mermaid-svg-IosbmNpnP89f0Tn4 .node circle,#mermaid-svg-IosbmNpnP89f0Tn4 .node ellipse,#mermaid-svg-IosbmNpnP89f0Tn4 .node polygon,#mermaid-svg-IosbmNpnP89f0Tn4 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-IosbmNpnP89f0Tn4 .rough-node .label text,#mermaid-svg-IosbmNpnP89f0Tn4 .node .label text,#mermaid-svg-IosbmNpnP89f0Tn4 .image-shape .label,#mermaid-svg-IosbmNpnP89f0Tn4 .icon-shape .label{text-anchor:middle;}#mermaid-svg-IosbmNpnP89f0Tn4 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-IosbmNpnP89f0Tn4 .rough-node .label,#mermaid-svg-IosbmNpnP89f0Tn4 .node .label,#mermaid-svg-IosbmNpnP89f0Tn4 .image-shape .label,#mermaid-svg-IosbmNpnP89f0Tn4 .icon-shape .label{text-align:center;}#mermaid-svg-IosbmNpnP89f0Tn4 .node.clickable{cursor:pointer;}#mermaid-svg-IosbmNpnP89f0Tn4 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-IosbmNpnP89f0Tn4 .arrowheadPath{fill:#333333;}#mermaid-svg-IosbmNpnP89f0Tn4 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-IosbmNpnP89f0Tn4 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-IosbmNpnP89f0Tn4 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-IosbmNpnP89f0Tn4 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-IosbmNpnP89f0Tn4 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-IosbmNpnP89f0Tn4 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-IosbmNpnP89f0Tn4 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-IosbmNpnP89f0Tn4 .cluster text{fill:#333;}#mermaid-svg-IosbmNpnP89f0Tn4 .cluster span{color:#333;}#mermaid-svg-IosbmNpnP89f0Tn4 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-IosbmNpnP89f0Tn4 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-IosbmNpnP89f0Tn4 rect.text{fill:none;stroke-width:0;}#mermaid-svg-IosbmNpnP89f0Tn4 .icon-shape,#mermaid-svg-IosbmNpnP89f0Tn4 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-IosbmNpnP89f0Tn4 .icon-shape p,#mermaid-svg-IosbmNpnP89f0Tn4 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-IosbmNpnP89f0Tn4 .icon-shape .label rect,#mermaid-svg-IosbmNpnP89f0Tn4 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-IosbmNpnP89f0Tn4 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-IosbmNpnP89f0Tn4 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-IosbmNpnP89f0Tn4 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 异地灾备层
控制层
同城双活层
客户端层
写入
查询
同步
同步
状态管理
状态管理
状态管理
热备
异步复制
写入客户端
查询客户端
主 DataNode
备 DataNode 1
备 DataNode 2
ComputeNode
Controller 1
Controller 2
异地备节点

图 2:DolphinDB 高可用集群逻辑拓扑。Controller 负责集群状态管理,DataNode 负责数据存储与写入,ComputeNode 负责查询计算,异地灾备节点承担极端场景恢复。

三、故障检测:心跳、判定与通知

高可用系统的第一道防线是及时发现故障。DolphinDB 通过心跳检测、健康检查和规则判定来识别异常节点。

3.1 心跳检测机制

心跳检测是最基础的故障发现手段。监控系统定期向每个节点发送探测请求(例如执行 1+1 或查询节点状态),如果在指定超时时间内没有收到响应,就认为该节点可能故障。

心跳检测需要考虑三个参数:

  • 检测间隔:两次心跳之间的时间。间隔越短,发现故障越快,但网络开销也越大。
  • 超时时间:等待响应的最大时间。通常设置为检测间隔的 3 倍左右。
  • 重试次数:连续失败多少次才判定为故障。这是为了避免偶发网络抖动导致误判。

3.2 故障判定规则

单次心跳失败不能立即判定节点故障,因为网络抖动、GC 暂停或瞬时高负载都可能导致响应延迟。通常采用"连续 N 次失败"的规则来降低误判率。同时,还可以结合资源指标进行综合判定:

  • CPU 持续高于 95%:节点可能处于过载状态。
  • 内存使用接近上限:节点可能发生 OOM 风险。
  • 磁盘 IO 长时间阻塞:节点可能无法完成数据写入。

3.3 故障通知

当节点被判定为故障后,需要立即通知相关方。通知渠道可以包括邮件、短信、企业微信、钉钉或 PagerDuty。通知内容应包含节点 ID、故障类型、发现时间和当前状态,便于运维人员快速定位。

dolphindb 复制代码
// ========== 故障检测:心跳、判定与通知 ==========
// 心跳配置:间隔、超时、重试次数
def heartbeatConfiguration() {
    return dict(STRING, ANY, [
        ["interval", 5000],
        ["timeout", 15000],
        ["retry_count", 3],
        ["retry_interval", 1000]
    ])
}

// 对指定节点执行心跳探测,重试指定次数
def heartbeatCheck(nodeId) {
    config = heartbeatConfiguration()
    for (i in 1..config["retry_count"] + 1) {
        try {
            conn = xdb(nodeId)
            conn.run("1+1")
            return dict(STRING, ANY, [
                ["node", nodeId], ["status", "alive"], ["response_time", now()]
            ])
        } catch(ex) {
            sleep(config["retry_interval"])
        }
    }
    return dict(STRING, ANY, [
        ["node", nodeId], ["status", "dead"], ["last_check", now()]
    ])
}

// 综合判定:连续失败次数 + 资源健康状态
def detectNodeFailure(nodeId) {
    recent = select * from heartbeat_log
             where node_id = nodeId and timestamp > now() - 60000
             order by timestamp desc
    failCount = sum(iif(recent.status == "dead", 1, 0))
    if (failCount >= 3) {
        return dict(STRING, ANY, [
            ["node", nodeId], ["status", "failed"],
            ["fail_count", failCount], ["action", "failover"]
        ])
    }
    return dict(STRING, ANY, [
        ["node", nodeId], ["status", "healthy"], ["fail_count", failCount]
    ])
}

代码解析heartbeatConfiguration() 返回探测参数,建议根据网络环境调整重试次数;heartbeatCheck() 在失败时重试 3 次,避免偶发抖动导致误判;detectNodeFailure() 结合最近 60 秒的心跳记录进行统计判定,只有连续失败 3 次才触发故障转移。生产环境中,还可以将 CPU、内存、磁盘指标纳入判定逻辑。
#mermaid-svg-qysOf7ryPMtsu6eI{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-qysOf7ryPMtsu6eI .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-qysOf7ryPMtsu6eI .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-qysOf7ryPMtsu6eI .error-icon{fill:#552222;}#mermaid-svg-qysOf7ryPMtsu6eI .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-qysOf7ryPMtsu6eI .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-qysOf7ryPMtsu6eI .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-qysOf7ryPMtsu6eI .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-qysOf7ryPMtsu6eI .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-qysOf7ryPMtsu6eI .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-qysOf7ryPMtsu6eI .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-qysOf7ryPMtsu6eI .marker{fill:#333333;stroke:#333333;}#mermaid-svg-qysOf7ryPMtsu6eI .marker.cross{stroke:#333333;}#mermaid-svg-qysOf7ryPMtsu6eI svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-qysOf7ryPMtsu6eI p{margin:0;}#mermaid-svg-qysOf7ryPMtsu6eI .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-qysOf7ryPMtsu6eI .cluster-label text{fill:#333;}#mermaid-svg-qysOf7ryPMtsu6eI .cluster-label span{color:#333;}#mermaid-svg-qysOf7ryPMtsu6eI .cluster-label span p{background-color:transparent;}#mermaid-svg-qysOf7ryPMtsu6eI .label text,#mermaid-svg-qysOf7ryPMtsu6eI span{fill:#333;color:#333;}#mermaid-svg-qysOf7ryPMtsu6eI .node rect,#mermaid-svg-qysOf7ryPMtsu6eI .node circle,#mermaid-svg-qysOf7ryPMtsu6eI .node ellipse,#mermaid-svg-qysOf7ryPMtsu6eI .node polygon,#mermaid-svg-qysOf7ryPMtsu6eI .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-qysOf7ryPMtsu6eI .rough-node .label text,#mermaid-svg-qysOf7ryPMtsu6eI .node .label text,#mermaid-svg-qysOf7ryPMtsu6eI .image-shape .label,#mermaid-svg-qysOf7ryPMtsu6eI .icon-shape .label{text-anchor:middle;}#mermaid-svg-qysOf7ryPMtsu6eI .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-qysOf7ryPMtsu6eI .rough-node .label,#mermaid-svg-qysOf7ryPMtsu6eI .node .label,#mermaid-svg-qysOf7ryPMtsu6eI .image-shape .label,#mermaid-svg-qysOf7ryPMtsu6eI .icon-shape .label{text-align:center;}#mermaid-svg-qysOf7ryPMtsu6eI .node.clickable{cursor:pointer;}#mermaid-svg-qysOf7ryPMtsu6eI .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-qysOf7ryPMtsu6eI .arrowheadPath{fill:#333333;}#mermaid-svg-qysOf7ryPMtsu6eI .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-qysOf7ryPMtsu6eI .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-qysOf7ryPMtsu6eI .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-qysOf7ryPMtsu6eI .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-qysOf7ryPMtsu6eI .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-qysOf7ryPMtsu6eI .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-qysOf7ryPMtsu6eI .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-qysOf7ryPMtsu6eI .cluster text{fill:#333;}#mermaid-svg-qysOf7ryPMtsu6eI .cluster span{color:#333;}#mermaid-svg-qysOf7ryPMtsu6eI div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-qysOf7ryPMtsu6eI .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-qysOf7ryPMtsu6eI rect.text{fill:none;stroke-width:0;}#mermaid-svg-qysOf7ryPMtsu6eI .icon-shape,#mermaid-svg-qysOf7ryPMtsu6eI .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-qysOf7ryPMtsu6eI .icon-shape p,#mermaid-svg-qysOf7ryPMtsu6eI .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-qysOf7ryPMtsu6eI .icon-shape .label rect,#mermaid-svg-qysOf7ryPMtsu6eI .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-qysOf7ryPMtsu6eI .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-qysOf7ryPMtsu6eI .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-qysOf7ryPMtsu6eI :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 每 5 秒






心跳探测器
节点是否响应
记录 alive
重试 3 次
是否全部失败
标记节点 dead
连续失败 >= 3
继续观察
触发故障通知
进入故障转移流程

图 3:故障检测流程图。节点无响应后先重试,再统计连续失败次数,满足阈值才触发转移,避免误判。

四、故障转移:自动、手动与恢复

故障检测之后,下一步是执行故障转移。自动转移讲究速度和准确性,手动转移讲究可控性,故障恢复则讲究数据完整性。

4.1 自动故障转移

自动故障转移通常由 controller 或独立的监控节点触发。基本流程是:

  1. 标记故障节点为不可用。
  2. 从可用节点中选出优先级最高的节点作为新主节点。
  3. 将新主节点提升为 master。
  4. 更新集群配置,通知客户端连接新主节点。
  5. 记录故障转移日志。

自动转移的关键是选主策略。常见的策略包括:优先级最高、数据最新、负载最低。DolphinDB 集群模式下,controller 会根据节点状态自动完成选主;主备模式下,通常需要自定义选主逻辑。

4.2 手动故障转移

手动故障转移常用于计划内维护。例如,需要对主节点进行操作系统升级或 DolphinDB 版本更新时,可以主动将主节点切换到备节点,完成维护后再切回。手动转移的流程比自动转移多了两步:

  1. 先停止当前主节点的写入。
  2. 等待数据同步完成后再提升新主节点。

这样可以最大限度地保证切换过程中没有数据丢失。

4.3 故障恢复

故障节点修复后,不能直接重新加入集群,而是需要先验证数据一致性,必要时进行全量或增量同步。恢复流程包括:

  1. 检查节点是否可达。
  2. 将节点数据版本与当前主节点对比。
  3. 如果版本不一致,执行增量或全量同步。
  4. 将节点重新加入集群。
  5. 标记节点为健康。
dolphindb 复制代码
// ========== 故障转移执行与恢复 ==========
def failoverConfiguration() {
    return dict(STRING, ANY, [
        ["enabled", true], ["auto_failover", true],
        ["failover_timeout", 30000], ["recovery_timeout", 300000],
        ["notify_on_failover", true]
    ])
}

def executeFailover(failedNode) {
    print("开始故障转移: " + failedNode)
    markNodeFailed(failedNode)
    newMaster = selectNewMaster(failedNode)
    if (isNull(newMaster)) {
        return dict(STRING, ANY, [["status", "failed"], ["message", "无可用节点"]])
    }
    promoteToMaster(newMaster)
    updateClusterConfig(newMaster)
    notifyClients(newMaster)
    logFailover(failedNode, newMaster)
    return dict(STRING, ANY, [
        ["status", "success"], ["failed_node", failedNode],
        ["new_master", newMaster], ["timestamp", now()]
    ])
}

def selectNewMaster(failedNode) {
    available = select * from getClusterNodes()
              where status = "alive" and id != failedNode
    if (available.rows() == 0) return NULL
    return (select top 1 * from available order by priority desc).id[0]
}

def recoverNode(nodeId) {
    if (heartbeatCheck(nodeId)["status"] != "alive") {
        return dict(STRING, ANY, [["status", "failed"], ["message", "节点仍不可用"]])
    }
    syncData(nodeId)
    if (!verifyDataConsistency(nodeId)) fullSync(nodeId)
    joinCluster(nodeId)
    markNodeRecovered(nodeId)
    return dict(STRING, ANY, [["status", "success"], ["node", nodeId], ["timestamp", now()]])
}

代码解析failoverConfiguration() 集中管理转移参数,failover_timeout 用于限制转移总耗时;executeFailover() 按"标记故障→选主→提升→更新配置→通知"的顺序执行,任何一步失败都会返回失败状态;selectNewMaster() 按优先级排序选主,实际生产中可以扩展为考虑数据版本和负载;recoverNode() 在节点恢复后先校验一致性,不一致则全量同步,避免带病节点重新加入集群。
客户端 备节点 监控/Controller 主节点 客户端 备节点 监控/Controller 主节点 #mermaid-svg-rxWCXbeZcGKlVVcV{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-rxWCXbeZcGKlVVcV .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-rxWCXbeZcGKlVVcV .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-rxWCXbeZcGKlVVcV .error-icon{fill:#552222;}#mermaid-svg-rxWCXbeZcGKlVVcV .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-rxWCXbeZcGKlVVcV .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-rxWCXbeZcGKlVVcV .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-rxWCXbeZcGKlVVcV .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-rxWCXbeZcGKlVVcV .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-rxWCXbeZcGKlVVcV .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-rxWCXbeZcGKlVVcV .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-rxWCXbeZcGKlVVcV .marker{fill:#333333;stroke:#333333;}#mermaid-svg-rxWCXbeZcGKlVVcV .marker.cross{stroke:#333333;}#mermaid-svg-rxWCXbeZcGKlVVcV svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-rxWCXbeZcGKlVVcV p{margin:0;}#mermaid-svg-rxWCXbeZcGKlVVcV .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-rxWCXbeZcGKlVVcV text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-rxWCXbeZcGKlVVcV .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-rxWCXbeZcGKlVVcV .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-rxWCXbeZcGKlVVcV .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-rxWCXbeZcGKlVVcV .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-rxWCXbeZcGKlVVcV #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-rxWCXbeZcGKlVVcV .sequenceNumber{fill:white;}#mermaid-svg-rxWCXbeZcGKlVVcV #sequencenumber{fill:#333;}#mermaid-svg-rxWCXbeZcGKlVVcV #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-rxWCXbeZcGKlVVcV .messageText{fill:#333;stroke:none;}#mermaid-svg-rxWCXbeZcGKlVVcV .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-rxWCXbeZcGKlVVcV .labelText,#mermaid-svg-rxWCXbeZcGKlVVcV .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-rxWCXbeZcGKlVVcV .loopText,#mermaid-svg-rxWCXbeZcGKlVVcV .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-rxWCXbeZcGKlVVcV .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-rxWCXbeZcGKlVVcV .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-rxWCXbeZcGKlVVcV .noteText,#mermaid-svg-rxWCXbeZcGKlVVcV .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-rxWCXbeZcGKlVVcV .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-rxWCXbeZcGKlVVcV .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-rxWCXbeZcGKlVVcV .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-rxWCXbeZcGKlVVcV .actorPopupMenu{position:absolute;}#mermaid-svg-rxWCXbeZcGKlVVcV .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-rxWCXbeZcGKlVVcV .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-rxWCXbeZcGKlVVcV .actor-man circle,#mermaid-svg-rxWCXbeZcGKlVVcV line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-rxWCXbeZcGKlVVcV :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 客户端切换至新主节点继续写入 心跳探测 连续 3 次无响应 确认备节点存活 promoteToMaster 提升成功 通知新主节点地址 标记为 failed

图 4:故障转移时序图。监控发现主节点无响应后,先确认备节点存活,再提升新主并通知客户端切换。

五、数据同步与一致性保障

故障转移的可靠性最终取决于数据同步的可靠性。如果主备节点之间的数据延迟过大,故障切换时就可能丢失数据。

5.1 实时同步机制

DolphinDB 支持异步和同步两种复制模式。异步复制性能更好,但切换时可能丢失少量数据;同步复制可以保证数据不丢,但对网络延迟和写入性能要求更高。

同步模式的选择需要在一致性和性能之间做权衡。对于行情写入这类高吞吐场景,通常采用异步复制并设置合理的同步延迟告警阈值;对于账户余额、交易记录这类强一致性场景,则需要同步复制或至少半同步复制。

5.2 同步状态监控

无论采用哪种复制模式,都需要持续监控同步延迟和队列积压。常见的监控指标包括:

  • 同步延迟(lag_ms):主备节点之间数据落后的时间。
  • 队列大小(queue_size):待同步的数据量。
  • 复制速率:单位时间内同步的数据量。

当同步延迟超过阈值时,应触发告警;当队列持续积压时,需要排查网络带宽或备节点性能瓶颈。

5.3 数据一致性校验

故障节点恢复或定期巡检时,需要校验数据一致性。常用的校验方式包括:

  • 版本号对比:比较主节点和备节点的数据版本。
  • 校验和对比:计算数据的校验和(checksum)进行对比。
  • 记录数对比:对比表的记录数是否一致。
dolphindb 复制代码
// ========== 数据同步、监控与一致性校验 ==========
// 同步配置:模式、间隔、队列与批次大小
def realtimeSyncConfiguration() {
    return dict(STRING, ANY, [
        ["mode", "async"],
        ["sync_interval", 1000],
        ["queue_size", 10000],
        ["batch_size", 1000],
        ["timeout", 30000]
    ])
}

// 监控所有备节点的同步延迟和队列大小
def syncStatusMonitor() {
    master = getCurrentMaster()
    slaves = getSlaveNodes()
    status = array(ANY, 0)
    for (slave in slaves) {
        lag = getReplicationLag(master, slave)
        queueSize = getReplicationQueueSize(master, slave)
        status.append!(dict(STRING, ANY, [
            ["master", master], ["slave", slave],
            ["lag_ms", lag], ["queue_size", queueSize],
            ["status", iif(lag < 1000, "normal", "lagging")]
        ]))
    }
    return status
}

// 校验主节点与指定备节点的数据版本和校验和
def verifyDataConsistency(nodeId) {
    master = getCurrentMaster()
    masterVer = getNodeDataVersion(master)
    nodeVer = getNodeDataVersion(nodeId)
    if (masterVer != nodeVer) return false
    masterSum = getNodeDataChecksum(master)
    nodeSum = getNodeDataChecksum(nodeId)
    return masterSum == nodeSum
}

代码解析realtimeSyncConfiguration() 使用异步模式并设置 1 秒同步间隔,适合大多数高吞吐场景;syncStatusMonitor() 返回每个备节点的延迟和队列大小,lag_ms > 1000 时标记为 lagging;verifyDataConsistency() 同时对比数据版本和校验和,避免仅比较版本号时漏过逻辑损坏。校验和函数需要 DolphinDB 企业版支持,社区版可用记录数对比作为替代。
#mermaid-svg-TSVAldnHR9Wwgsv6{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-TSVAldnHR9Wwgsv6 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-TSVAldnHR9Wwgsv6 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-TSVAldnHR9Wwgsv6 .error-icon{fill:#552222;}#mermaid-svg-TSVAldnHR9Wwgsv6 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-TSVAldnHR9Wwgsv6 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-TSVAldnHR9Wwgsv6 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-TSVAldnHR9Wwgsv6 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-TSVAldnHR9Wwgsv6 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-TSVAldnHR9Wwgsv6 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-TSVAldnHR9Wwgsv6 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-TSVAldnHR9Wwgsv6 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-TSVAldnHR9Wwgsv6 .marker.cross{stroke:#333333;}#mermaid-svg-TSVAldnHR9Wwgsv6 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-TSVAldnHR9Wwgsv6 p{margin:0;}#mermaid-svg-TSVAldnHR9Wwgsv6 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-TSVAldnHR9Wwgsv6 .cluster-label text{fill:#333;}#mermaid-svg-TSVAldnHR9Wwgsv6 .cluster-label span{color:#333;}#mermaid-svg-TSVAldnHR9Wwgsv6 .cluster-label span p{background-color:transparent;}#mermaid-svg-TSVAldnHR9Wwgsv6 .label text,#mermaid-svg-TSVAldnHR9Wwgsv6 span{fill:#333;color:#333;}#mermaid-svg-TSVAldnHR9Wwgsv6 .node rect,#mermaid-svg-TSVAldnHR9Wwgsv6 .node circle,#mermaid-svg-TSVAldnHR9Wwgsv6 .node ellipse,#mermaid-svg-TSVAldnHR9Wwgsv6 .node polygon,#mermaid-svg-TSVAldnHR9Wwgsv6 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-TSVAldnHR9Wwgsv6 .rough-node .label text,#mermaid-svg-TSVAldnHR9Wwgsv6 .node .label text,#mermaid-svg-TSVAldnHR9Wwgsv6 .image-shape .label,#mermaid-svg-TSVAldnHR9Wwgsv6 .icon-shape .label{text-anchor:middle;}#mermaid-svg-TSVAldnHR9Wwgsv6 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-TSVAldnHR9Wwgsv6 .rough-node .label,#mermaid-svg-TSVAldnHR9Wwgsv6 .node .label,#mermaid-svg-TSVAldnHR9Wwgsv6 .image-shape .label,#mermaid-svg-TSVAldnHR9Wwgsv6 .icon-shape .label{text-align:center;}#mermaid-svg-TSVAldnHR9Wwgsv6 .node.clickable{cursor:pointer;}#mermaid-svg-TSVAldnHR9Wwgsv6 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-TSVAldnHR9Wwgsv6 .arrowheadPath{fill:#333333;}#mermaid-svg-TSVAldnHR9Wwgsv6 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-TSVAldnHR9Wwgsv6 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-TSVAldnHR9Wwgsv6 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-TSVAldnHR9Wwgsv6 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-TSVAldnHR9Wwgsv6 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-TSVAldnHR9Wwgsv6 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-TSVAldnHR9Wwgsv6 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-TSVAldnHR9Wwgsv6 .cluster text{fill:#333;}#mermaid-svg-TSVAldnHR9Wwgsv6 .cluster span{color:#333;}#mermaid-svg-TSVAldnHR9Wwgsv6 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-TSVAldnHR9Wwgsv6 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-TSVAldnHR9Wwgsv6 rect.text{fill:none;stroke-width:0;}#mermaid-svg-TSVAldnHR9Wwgsv6 .icon-shape,#mermaid-svg-TSVAldnHR9Wwgsv6 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-TSVAldnHR9Wwgsv6 .icon-shape p,#mermaid-svg-TSVAldnHR9Wwgsv6 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-TSVAldnHR9Wwgsv6 .icon-shape .label rect,#mermaid-svg-TSVAldnHR9Wwgsv6 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-TSVAldnHR9Wwgsv6 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-TSVAldnHR9Wwgsv6 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-TSVAldnHR9Wwgsv6 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 异步复制
网络发送
一致
不一致
主节点写入
同步队列
备节点接收
数据校验
应用数据
标记异常
触发全量同步
返回确认

图 5:数据同步状态机。主节点写入后进入同步队列,备节点接收并校验,一致则应用,不一致则触发全量同步。

同步模式 一致性 写入性能 适用场景 风险
异步复制 最终一致 高吞吐写入、行情数据 切换时可能丢失少量数据
同步复制 强一致 交易记录、账户余额 网络延迟会拖慢写入
半同步复制 折中 中等 大多数生产业务 需要合理设置等待超时

六、容灾演练:计划、执行与报告

高可用系统不能只在架构图上好看,必须在真实故障场景中验证过。容灾演练就是模拟故障、验证切换流程、发现潜在问题的手段。

6.1 演练计划设计

一次完整的容灾演练应包含以下要素:

  • 演练目标:验证自动故障转移、数据同步延迟还是人员响应流程。
  • 目标节点:明确要模拟故障的节点。
  • 预期 RTO/RPO:用于判断演练是否达标。
  • 回滚策略:演练结束后如何恢复初始状态。
  • 通知对象:哪些团队需要知晓演练计划和结果。

6.2 执行演练

执行演练时,建议先用测试环境验证,再在生产环境低峰期执行。演练步骤通常包括:

  1. 记录演练前的集群状态。
  2. 模拟目标节点故障(例如停止服务、断开网络)。
  3. 等待自动故障转移完成。
  4. 验证新主节点是否可写、备节点是否同步。
  5. 计算实际 RTO 和 RPO。
  6. 执行回滚,恢复原始状态。
  7. 生成演练报告。

6.3 生成演练报告

演练报告是容灾能力的证据。报告应包含演练时间、目标节点、预期/实际 RTO/RPO、切换结果、发现的问题和改进建议。

图 6:故障演练控制台界面。界面显示演练进度、目标节点、实际 RTO/RPO 与节点健康状态,便于运维团队实时掌握切换过程。

dolphindb 复制代码
// ========== 容灾演练计划、执行与报告 ==========
// 演练计划:目标节点、预期 RTO/RPO、是否自动回滚
def disasterRecoveryDrillPlan() {
    return dict(STRING, ANY, [
        ["drill_type", "failover"],
        ["target_node", "node2"],
        ["expected_rto", 30000],
        ["expected_rpo", 0],
        ["auto_rollback", true],
        ["notify_stakeholders", true]
    ])
}

// 执行演练:记录状态 -> 模拟故障 -> 等待切换 -> 计算指标 -> 回滚
def executeDrill(plan) {
    startTime = now()
    initialState = captureClusterState()
    simulateFailure(plan["target_node"])
    sleep(plan["expected_rto"])
    failoverResult = verifyFailover()
    actualRto = now() - startTime
    actualRpo = calculateRpo()
    if (plan["auto_rollback"]) rollbackDrill(initialState)
    return dict(STRING, ANY, [
        ["drill_time", startTime],
        ["target_node", plan["target_node"]],
        ["expected_rto", plan["expected_rto"]],
        ["actual_rto", actualRto],
        ["expected_rpo", plan["expected_rpo"]],
        ["actual_rpo", actualRpo],
        ["result", iif(actualRto <= plan["expected_rto"] and actualRpo <= plan["expected_rpo"], "passed", "failed")],
        ["failover_result", failoverResult]
    ])
}

// 生成演练报告,包含 RTO/RPO 对比与结论
def generateDrillReport(drillResult) {
    return """
容灾演练报告
演练时间: """ + format(drillResult["drill_time"], "yyyy-MM-dd HH:mm:ss") + """
目标节点: """ + drillResult["target_node"] + """
RTO: 预期 """ + string(drillResult["expected_rto"]) + """ms / 实际 """ + string(drillResult["actual_rto"]) + """ms
RPO: 预期 """ + string(drillResult["expected_rpo"]) + """ms / 实际 """ + string(drillResult["actual_rpo"]) + """ms
结果: """ + drillResult["result"] + """
"""
}

代码解析disasterRecoveryDrillPlan() 定义演练目标与验收标准;executeDrill() 按标准流程执行,包含自动回滚,避免演练影响生产;generateDrillReport() 输出结构化的演练报告,便于归档和复盘。建议每季度至少执行一次演练,并在演练后更新应急预案。

七、实战:搭建最小可用高可用系统

把前面的模块组合起来,就可以搭建一套最小可用的高可用系统。这个系统的目标是:能够自动发现节点故障、自动完成主备切换、自动恢复节点,并提供状态查询接口。

7.1 最小可用系统的设计

最小可用系统包含四个核心模块:

  1. 初始化模块:配置主备架构、启动心跳监控和同步监控。
  2. 故障检测模块:定期执行心跳检测和故障判定。
  3. 故障转移模块:在检测到主节点故障时执行自动切换。
  4. 状态查询模块:对外提供集群状态、同步状态和配置信息。

7.2 部署建议

  • controller 至少 2 个节点:避免 controller 成为单点。
  • data node 至少 3 个节点:支持 3 副本和多数派仲裁。
  • 监控节点独立部署:不要把监控任务和被监控节点部署在同一台物理机上。
  • 跨机架/跨可用区部署:防止机架级故障导致整个集群不可用。
dolphindb 复制代码
// ========== 最小可用高可用系统初始化与接口封装 ==========
// 初始化:配置架构、启动心跳与同步监控
def initHighAvailabilitySystem() {
    config = masterSlaveArchitecture()
    configureMasterSlave(config)
    continuousHeartbeatMonitor()
    scheduleJob("failure_detection", "故障检测",
                batchFailureDetection, 00:01m, true, 2024.01.01, 2099.12.31, 'D')
    scheduleJob("sync_monitor", "同步监控",
                syncStatusMonitor, 00:01m, true, 2024.01.01, 2099.12.31, 'D')
    print("高可用系统初始化完成")
}

// 对外提供的统一操作接口
def performFailover(failedNode) {
    return executeFailover(failedNode)
}

def recoverFailedNode(nodeId) {
    return recoverNode(nodeId)
}

def getHASystemStatus() {
    return dict(STRING, ANY, [
        ["cluster_status", getClusterStatus()],
        ["sync_status", syncStatusMonitor()],
        ["failover_config", failoverConfiguration()]
    ])
}

def runDisasterRecoveryDrill() {
    plan = disasterRecoveryDrillPlan()
    result = executeDrill(plan)
    return generateDrillReport(result)
}

// 注册为函数视图,便于远程调用
addFunctionView(initHighAvailabilitySystem)
addFunctionView(performFailover)
addFunctionView(recoverFailedNode)
addFunctionView(getHASystemStatus)
addFunctionView(runDisasterRecoveryDrill)
print("高可用系统接口已注册")

代码解析initHighAvailabilitySystem() 是系统的启动入口,依次完成架构配置、心跳监控、故障检测调度和同步监控调度;performFailover()recoverFailedNode() 提供显式的故障操作接口,便于手动干预;getHASystemStatus() 返回集群状态、同步状态和故障转移配置,是日常巡检的核心接口;runDisasterRecoveryDrill() 将容灾演练封装为可一键执行的函数。通过 addFunctionView 注册后,这些函数可以通过 API 远程调用。

八、边界、风险与版本差异

高可用部署不是银弹。理解它的边界和风险,才能避免在错误场景下过度设计或设计不足。

8.1 常见风险

  • 脑裂(Split-Brain):网络分区时,两边节点都认为自己应该成为主节点。解决方式是依赖 controller 仲裁或外部协调服务(如 ZooKeeper)。
  • 误判故障:心跳阈值设置过严会导致频繁切换,过松会导致故障发现延迟。建议根据历史监控数据调参。
  • 同步延迟导致丢数据:异步复制模式下,如果故障发生在数据尚未同步到备节点时,切换后这部分数据会丢失。

8.2 版本与授权差异

DolphinDB 的高可用能力在不同版本和授权下差异较大:

  • 社区版:通常不支持企业级的集群高可用和自动故障转移,需要通过自定义脚本实现。
  • 企业版:提供完整的高可用集群、多副本、自动故障转移和异地灾备能力。
  • 3.x 版本:在集群管理、配置项名称和函数签名上可能与 2.x 存在差异,迁移时需要逐项核对官方文档。

如果当前使用的是社区版,可以考虑引入外部工具(如 Keepalived + VIP、Pacemaker)或自建监控脚本作为过渡方案。但这些替代方案无法替代企业版的原生高可用能力,业务规模扩大后建议升级到企业版。

需要强调的是,心跳检测、故障判定、数据冗余和故障转移 是分布式系统高可用领域的经典原理,长期有效,不会因为 DolphinDB 版本迭代而失效。真正随版本变化的是具体配置项、函数签名和授权能力。因此,本文给出的思路可以复用到其他时序数据库或分布式系统上,只是具体实现需要结合目标产品的官方文档调整。

图 7:灾备指标仪表盘。仪表盘集中展示 RTO、RPO、系统可用性和同步延迟四项核心指标,帮助运维团队快速判断容灾健康度。

总结:从容灾设计到持续演练的闭环

DolphinDB 的高可用部署本质上是一个"设计→检测→切换→恢复→演练"的闭环。只有把每个环节都落到实处,才能在面对真实故障时不慌不乱。

本文从高可用架构选型开始,对比了主备、多副本和集群三种模式;然后讲解了心跳检测、故障判定和故障通知的实现方式;接着讨论了自动/手动故障转移、节点恢复和数据一致性校验;最后给出了容灾演练的最小可用系统封装。核心结论是:高可用不是一次性配置,而是需要持续验证的运维能力

思考题

  1. 如果你的业务要求 RPO=0,应该选择哪种同步模式?异步复制能否满足?
  2. 自动故障转移中,如何避免因网络抖动导致的误判和频繁切换?
  3. 容灾演练应该多久执行一次?演练报告中最应该关注哪些指标?

参考链接

相关推荐
不会就选b14 分钟前
Linux之线程池(三)
linux·开发语言
l12586514 分钟前
# LangGraph Deep Research Agent 全流程设计:多轮研究、人机协同与真实来源管理
数据库·人工智能·python·算法·自然语言处理·oracle·langchain
棉晗榜18 分钟前
C# HttpClient配置具有tls指纹发起跟浏览器一样的请求解决反爬虫防护
开发语言·爬虫·c#
青少儿编程课堂1 小时前
图形化编程实战:智能交通灯调度台,一个作品讲透循环、条件与广播
c++·python·算法·bfs·信息学竞赛
l1t1 小时前
DeepSeek总结的DuckDB 如何更快地运行递归 CTE
java·开发语言·数据库·mysql·duckdb
HAPPY酷1 小时前
python的对象和方法
开发语言·python
橙露1 小时前
移动端客户端原生开发语言
开发语言
Demon--hx1 小时前
设计不能被继承的类
开发语言·c++
the局外人1 小时前
别再背 Chain、Agent、Memory 了:用一条“智能流水线”学会 LangChain
python·langchain·llm