1. 先说清楚 transfer 到底是干什么的有段时间我特别怕半夜听见手机震动。不是怕报警是怕监控系统自己先挂了那感觉就像是自己家装的摄像头结果贼来了它先断电了。做运维的人应该都懂这种憋屈大盘上所有机器一片绿没有任何告警但你就是觉得哪里不对劲一查才发现数据链路早就断了过去两个小时全是哑数据。我经历过不止一次也正是因为这类事故才认认真真把 open-falcon 里 transfer 这块的多重备份研究了个透。先给还不太熟的读者定位一下。open-falcon 是一套老牌的开源监控系统在 Prometheus 还没像今天这么普及的时候很多公司的监控体系都是靠它撑起来的。如今虽然讨论热度不如当年但在大量存量机房和内部自建监控平台里它依然在跑而且跑得挺好。至于 transfer 这个组件简单说就是整个监控数据链路的中转站agent 采集上来的数据先打到 transfertransfer 再根据一致性哈希把数据分发给后端的 judge告警判断和 graph历史数据存储。你可以把它理解成快递分拣中心所有包裹都得在这儿过一遍然后按地址往不同的仓库发。所以 transfer 一旦出问题哪怕只是单实例宿主机宕机结果就是整条链路的数据流被切断。更麻烦的是open-falcon 这种老架构里的 graph 存储节点如果数据写丢了基本没有很好的修复手段历史曲线就是会缺一段。监控数据不像业务数据丢了就是丢了你想补都不知道去哪儿补。所以我说的transfer 多重备份并不是单纯的给 transfer 进程做一份备份而是从 agent 上报入口到 transfer 分发节点再到 graph 持久化存储整条链路做多层次的冗余和兜底。这篇博文就把我在实际环境里踩过坑之后的完整方案、代码配置和恢复流程一次说清楚。适合什么人来参考第一种是还在用 open-falcon、但没做过任何高可用改造的团队这篇可以当改造手册用。第二种是已经在用其他监控系统、但想看分布式监控链路怎么设计备用路径的读者这篇里的很多思路是通用的迁移到别的时序系统照样成立。第三种就是想搞明白多重备份到底是在备哪一段的运维新人看完至少能听懂老同事在讲什么。2. 方案选型用哪几重备份来兜住 transfer 这条链路2.1 先说结论三层备份分别防什么多重备份这四个字听起来简单真正拆开之后其实分三个层面缺一个都不算完整。第一层是 transfer 实例本身的冗余。transfer 是一个可以多实例部署的无状态组件但默认情况下每台机器上只有一个 transfer 进程agent 上报时也只配置了一个 transfer 地址。如果这台机器挂了agent 虽然会在重试几次后报错但这一段时间内的监控数据就丢了。所以第一重备份就是把 transfer 从单点变多点同时解决 agent 端的上报入口冗余。第二层是 transfer 分发后端的存储冗余也就是 graph 节点的多副本。open-falcon 原版架构中每个监控指标根据一致性哈希只会被写入一个 graph 实例也就是说每个数据点只有一份存储。graph 节点磁盘坏了那部分历史数据就没有了。要真正做到多重备份就得让每个 partition 的数据同时写入多个 graph 节点查询时从任何一个副本读都行。第三层是静态备份也就是把 graph 落盘的 rrd 数据文件定期打包归档同时把重要的历史指标通过接口导出到外部存储。这一层不解决实时高可用但解决集群整体崩了之后的灾难恢复。比如机房断电、存储阵列全挂的时候前面两层备份再漂亮数据本身已经物理损坏了这时候第三层备份就是最后的救命稻草。这三层的关系可以这样理解第一层保证送信的人不会全消失第二层保证信送到之后不会被独一份的仓库搞丢第三层保证即使仓库烧了外面还有一份历史存档。实际部署中每一层的成本和复杂度是递增的但每一层都有它的必要性。2.2 多实例方案对比域名负载、LVS、nginx 透传先聊第一层的选型。transfer 多实例部署很简单难的是 agent 端怎么在上报的时候做故障转移。我最早是在 agent 配置里直接写死两个 transfer 地址。open-falcon 原版 agent 是支持配置多个 transfer 地址的上报时会逐个尝试第一个连不上就换第二个。这种方式很简单、见效最快但有个问题agent 的故障切换是串行探测的如果第一个 transfer 只是进程卡死而不是端口不通agent 会在超时后才切换期间会产生一小段数据丢失窗口。另外多个 agent 对 transfer 的选择没有负载均衡概念所有流量会优先打向第一个地址容易造成一台 transfer 压力很大而另一台闲置。后来我换成了域名 负载均衡的方案。做法是在 DNS 里给 transfer 集群绑定一个域名agent 配置里就写这个域名由负载均衡器负责把流量分发到后端的多个 transfer 实例。负载均衡这层可以选择 F5、LVS也可以直接用两台 nginx 做个 keepalived 高可用。我个人比较推荐 nginx 的 stream 模块做 TCP 透传配置简单、社区资料多、版本要求也不高一台普通服务器能扛住很大规模的 TCP 连接。具体负载均衡层的对比我也整理过所有方案里直接写 N 个 IP 地址是最原始的只适合临时测试。正式环境建议域名 nginx 或者域名 LVS区别在于 LVS 性能更好、但部署更重nginx 灵活、四层配置简单、排障也方便对于大多数公司的监控系统规模来说 nginx 完全够用。引用一个我实际经历过的数据某个环境的监控 agent 数量在 2000 台左右每秒产生约 8 万个数据点用一台 4 核 8G 的 nginx 做 TCP 透传CPU 占用不到 20%几乎没有瓶颈。2.3 存储冗余方案graph 多副本和查询容错第二层存储冗余就不是改配置文件这么简单了。原版 open-falcon 的 graph 组件数据分布靠的是 transfer 端的一致性哈希。每个监控指标(key)会计算出一个哈希值然后映射到某个 partition再对应到具体的 graph 实例。默认配置下每个分区只对应一个 graph 实例所以是没有副本概念的。要做出双副本常见做法是在 transfer 的分发逻辑上做改造让同一个 partition 的数据同时写入两个 graph 列表。社区里有不少团队做到了这一点思路基本一致transfer 配置里增加两套 graph 分区映射或者在同一套映射上配置多个节点地址。例如原来的配置是这样graph: { cluster: { graph-01: 192.168.1.10:6071, graph-02: 192.168.1.11:6071, graph-03: 192.168.1.12:6071 }, replicas: 1 }改成多副本之后把replicas调整为 2transfer 在做一致性哈希时会选出两个相邻的节点同时写入。当然这个字段名和语义要以你实际使用的版本或改造分支为准我这里表达的是思路。查询端也需要配套调整。原版 query 组件会根据哈希找到唯一的 graph 节点去查多副本环境里查询端应该能够在一个节点查询失败时自动切换到另一个副本。这个逻辑很多团队是在 query 组件上做了二次开发或者在更上层的 open-falcon 前端 API 里做了重试。我在实际改造中采用的是后一种方式前端 API 查询时组装好所有 graph 节点列表默认打第一个失败后按备份列表重试重试间隔 200ms最多三次。这种方式改动最小、对 query 组件入侵最少而且效果非常明显。2.4 静态备份方案归档和导出二选一还是都要第三层静态备份分成两种做法。一种是以 graph 磁盘上的 rrd 文件为对象直接打包归档。graph 的 storage 目录下按 partition 和指标分了很多层级目录只要 graph 进程不写入直接把整个目录 tar 一份就行。这种备份方式是文件级的恢复的时候也最快解压回去就能查到历史数据。但代价是备份文件可能很大几百万个指标的情况下一个 graph 节点的 rrd 目录动不动就是几十 GB全量打包一次既耗时又占磁盘。另一种方式是通过 open-falcon 的 HTTP 接口把历史数据拉取下来按指标和时段导出成自己的格式再存到对象存储或者普通文件。这种方式灵活可以只备份核心指标但恢复时需要重新灌数据比较麻烦。我的建议是两者结合磁盘空间允许的节点做全量 rrd 文件周期性归档重要业务的指标再额外走接口导出。归档频率不用太高一天一次即可保留最近 7 天的归档就够了。因为前面两层备份已经把实时可用性解决了静态备份只是为了对付最极端的情况不需要做到分钟级。3. 实操部署一步步搭出 transfer 多重备份环境3.1 环境准备与基础依赖开始动手之前先把环境准备好。我下面这套方案基于 open-falcon 的常见部署拓扑组件包括agent、transfer、graph、query 和前端 API。操作系统是 CentOS 7transfer 和 graph 都通过二进制方式部署各自独立进程。相比编译安装二进制包部署更快也更方便后续做多实例扩展。依赖方面transfer 本身需要能访问后端的 judge 和 graph 服务所以至少要把后端节点的 IP 和端口列表准备好。graph 实例需要 rrdtool 相关的库支持二进制包一般已经包含但如果是从源码编译要提前装好依赖。另外我强烈建议把系统时间和 NTP 对齐open-falcon 的监控数据对时间戳比较敏感如果多台机器时间差太大查数据的时候会困惑很久。 for node_dir in ${GRAPH_DATA_DIR}/partition_*; do partition_name$(basename ${node_dir}) tar czf ${BACKUP_BASE}/${partition_name}_${DATE_STR}.tar.gz -C ${GRAPH_DATA_DIR} ${partition_name} done # 只保留最近 7 个归档 find ${BACKUP_BASE} -name *.tar.gz -mtime 7 -delete这个脚本不需要在每台 graph 上都跑可以在任意一台能够访问所有 graph 数据目录的机器上执行。如果 graph 节点本身磁盘压力大也可以改成在每台 graph 上各自执行归档然后把归档文件远程传输到同一个备份服务器。归档文件落地后用 md5 校验完整性这个细节每漏一次都可能在恢复的时候后悔。恢复演练一定要做。我当时的做法是找一台新机器解压一份归档启动一个临时 graph 实例用 query 组件手动查一个指标的历史数据。如果曲线能对得上说明归档是可用的。这个流程看起来简单但很多团队搭建了备份从不演练真到需要恢复的时候才发现备份文件权限不对、格式不兼容、或者根本没有解压工具。每个月挑一天演练一次半小时就够关键时刻能救命。3.6 效果验证模拟故障看曲线是否断点方案落地后验证环节不能省。我通常做的验证分三步。第一步正常流量验证。在 agent 主动上报开启的情况下确认 transfer 集群的两个实例都有流量流入graph 的两个副本节点都有数据写入。这一步通过观察各节点日志中的数据量变化即可。第二步模拟 transfer 单点故障。直接把 transfer-01 进程 kill 掉观察 agent 到负载均衡的流量是否全部切到 transfer-02曲线是否出现断点。由于 nginx 探测到后端失败的周期大约在 10 秒左右这一段时间内 agent 上报的数据可能会重试失败但 nginx 完成切换后后续数据链路自动恢复。如果你的 agent 配置了本地缓存能力那这段数据会在网络恢复后补上来曲线就能基本连续。第三步模拟 graph 单点故障。关掉一个 graph 节点进程用 query 查询该节点负责的指标应该能自动从副本节点查到数据。同时观察前端的图曲线不应该断裂只是查询响应时间小幅上升。这一步验证的是第二层备份的效果。我在一次演练中发现只做了 transfer 多实例、没做 graph 多副本的时候graph 节点一挂页面曲线直接缺一大块。所以第二步和第三步要分开验证因为它们防的故障类型不一样。完整验证完成后把过程记录成文档后面的人再改这套环境就有据可依了。4. 常见问题与排查心得4.1 数据重复同一个指标被写入多份且不一致多副本环境里最常遇到的怪问题就是数据重复同一个监控指标出现了两条曲线一条正常一条偏差。出现这种情况先别急着怀疑多副本逻辑要先确认是不是多个 transfer 同时收到了同一条数据。排查思路在 agent 端确认上报地址是否经过负载均衡。如果 agent 配置里写的是多个 transfer 直连 IP且没有负载均衡层那么 agent 重试机制可能会把同一条数据同时发给两个 transfer。而两个 transfer 都认为自己是主都会往 graph 去写最终就出现重复数据。解决办法是让 agent 只配置一个入口域名或 VIP由负载均衡层保证流量分发的一致性。如果确实有多个入口的诉求需要在 transfer 端做幂等判重这个就复杂了一般不建议绕这么大弯。4.2 扩容后历史数据消失了某次给 graph 集群增加了一个新节点然后 Xen 上一些指标的历史数据不见了。这不是数据丢了而是扩容改变了一致性哈希的节点映射。一致性哈希的特点就是这样增加节点后部分 key 的映射位置发生变化。如果新节点上并没有这些 key 的历史数据而 transfer 又把新数据写到新节点上那么查询时就可能查不到旧数据。原版 open-falcon 的扩容就存在这个问题需要依赖 graph 的数据迁移工具或者接受新增节点只承载新增数据、旧节点继续查旧数据的方式。在做 graph 多副本改造时扩容一定要提前规划。我的建议是每次扩容之后跑一次指标分布统计脚本对比扩容前后每个 partition 的 key 数量确定哪些 partition 的映射发生了偏移然后针对这些分区做数据迁移或重新同步。实际操作中双副本环境下问题更复杂因为同一 partition 的两个副本可能出现分叉需要把旧数据先补齐到新副本上再切换映射。这块没有银弹只能一步一步核对。4.3 rrd 归档文件损坏时间戳和权限的坑我第一次写归档脚本时遇到过一个诡异问题归档包解压出来之后rrdtool info直接报错说文件损坏。排查了很久才发现问题出在归档脚本是用 root 跑的。graph 进程在运行时会以 monitor 用户写入 rrd 文件root 打包并没有问题但解压恢复到新机器时文件属主变成了 root而 graph 进程没有权限读取这些文件表现就是查不到数据。正确的做法是归档时保留权限信息恢复时把属主改回 monitor。另外rrd 文件中包含绝对路径信息归档时不能改路径结构恢复时必须解压到和原来一致的目录层级否则rrdtool会打不开文件。这也是一个很隐蔽的坑我换了一台路径不一样的机器做恢复测试时才踩到。4.4 transfer 的 RPC 连接数飙升有段时间负载均衡层没有设置连接空闲超时导致 agent 到 transfer 的连接越积累越多transfer 的并发连接数一路冲到 3 万以上最后把节点 CPU 打满。原因是一些老旧的 agent 版本在长时间运行之后不会主动断开连接而 nginx 四层透传默认也不主动断连接就全部堆在服务端。解决方案就是在 nginx stream 配置里加proxy_timeout 60s让空闲连接超过一分钟自动断开。同时调整 transfer 配置里的socket.maxIdle和keepAlive把空闲连接回收的时间控制在合理范围。改完之后连接数降到稳定水平CPU 也恢复正常。这类问题排查起来不算难但光看监控曲线很容易误判成容量不足进而盲目扩容浪费资源还解决不了问题。4.5 备份脚本和主进程的资源竞争归档脚本在 graph 节点上跑的时候如果正好赶上业务高峰tar 打包会很吃磁盘 IO可能导致 graph 写入延迟升高。我在归档脚本里加了 IO 限制ionice -c3 -p $$这样归档进程以空闲优先级运行不会影响正常监控数据写入。另外把归档时间避开整点分钟的采集高峰也是一种简单有效的规避方式。细节虽然小但对生产环境的稳定性影响很大属于那种没遇到过就不会注意的问题。5. 最后的个人体会做监控系统的高可用说到底是在做对故障的预期管理。你要清楚地知道每一种故障模式下系统会表现成什么样然后在故障真正发生之前把对应的冗余手段做上去。transfer 多重备份这套方案成本不高、思路清晰但它最大的价值不是那几台冗余节点而是逼着你把数据从产生到展示这条路走了一遍搞清楚哪些环节是唯一的哪些环节可以被替换哪些环节一旦断了根本补不回来。我自己的体会是做这类改造一定要动手验证不能停留在理论可行的层面。配置改完、脚本写完一定要亲手 kill 掉几次进程试试亲眼看曲线断不断亲眼看日志报什么错亲自动手恢复一次归档数据。踩过的坑多了心里才有底。每次有人问我你这套方案真的稳吗我都能直接说出哪年哪月哪次故障它帮我扛住了这比任何文档都有说服力。另外一个建议是如果你们团队有精力可以顺手把 open-falcon 的这套链路和新的时序数据库做一次对比评估看看是继续在存量系统上加固还是逐步迁移到新方案。这并不冲突做好多重备份本来就是迁移之前的基本功把数据保住、把链路理清楚后面做任何决策都会轻松很多。