ecFlow笔记:优化节点状态监控工具

目录

去年底,使用 ecFlow C++ API 开发了 ecFlow 节点状态监控工具 ecflow-watchman,并于今年 1 月替换原有基于 GO 开发的监控工具。 详情请阅读之前的文章《ecflow学习笔记:节点状态监控工具V2》。

但经过这几个月的运行,发现线程会在某些情况下退出,导致无法持续监控所有的 ecFlow 服务。 经排查发现,线程退出都在将状态数据保存到 Redis 数据库的地方。

本文介绍如何避免 Redis 数据保存异常时退出,并介绍 ecflow-watchman 中对于 ecflow C++ API的异常处理。

后台服务的异常处理

后台运行的服务一定要保证可用性,在任何情况下都能持续运行下去。

节点状态监控工具会定时调用 ecFlow API 获取节点状态,向其他应用提供业务系统的准实时运行状态。 如果当前循环获取并保存状态出错,可以等到下一次循环再更新数据,基本不会影响整个工具的可用性。

C++ 程序错误处理一般有两种:

  • 抛出异常
  • 返回错误码

分别对应保存状态和获取状态。 下面首先介绍保存 Redis 数据异常的处理,再介绍 ecFlow API 出错的处理。

redis-plus-plus 异常处理

ecflow-watchman 使用 sewenew/redis-plus-plus 将系统状态保存到 Redis 数据库。

redis++ 库在出错时会抛出异常。比如在下面的代码中,将连接耗时限制为 1 秒钟。 存储操作执行超过 1 秒,会抛出 TimeoutError 异常。

ConnectionOptions connection_options;
// ...skip...
connection_options.socket_timeout = 1s;

client_ = std::make_unique<Redis>(connection_options);
client_->set(key, value);

ecflow-watchman 在保存数据时会捕获 redis++ 库抛出的所有异常(定义在 namespace sw::redis 中)。

其中:

  • TimeoutError 表示操作超时
  • ClosedError 表示连接已关闭,redis++ 库会在下一次执行操作时自动尝试重新创建连接
  • IoError 表示 IO 故障
  • Error 表示其他故障
  • std::exception 用于捕获所有异常

下面代码在捕获到异常时会打印提示消息,并正常返回,不会再次抛出异常。

void RedisStorer::save(const std::string &key, const std::string &value) {
    try {
        client_->set(key, value);
    }catch (const TimeoutError &err) {
        spdlog::error("[redis] TimeoutError for {}: {}", key, err.what());
    } catch (const ClosedError &err) {
        spdlog::error("[redis] ClosedError for {}: {}", key,  err.what());
    } catch (const IoError &err) {
        spdlog::error("[redis] IoError for {}: {}", key,  err.what());
    } catch (const Error &err) {
        spdlog::error("[redis] Error for {}: {}", key, err.what());
    } catch (const std::exception &err) {
        spdlog::error("[redis] std::exception for {}: {}", key, err.what());
    }
}

增加异常捕获后,已运行超过半个月时间,尚未观察到线程异常结束的现象。

ecFlow C++ API 异常处理

从 ecFlow 获取状态时也同样可以设置连接时限。 使用 set_connect_timeout 设置超时时间,单位为秒。

ClientInvoker invoker_;
invoker_.set_connect_timeout(5);

使用 invoker_ 从 ecFlow 服务获取节点状态,如果超时,默认情况下会抛出异常。 不过 ClientInvoker 提供 set_throw_on_error 函数可以设置出错情况下不抛出异常。

下面检测 sync_local 的返回值,判断是否超时,并在超时的情况下获取错误信息。

invoker_.set_throw_on_error(false);

auto sync_result = invoker_.sync_local();
if (sync_result != 0) {
    error_message_ = invoker_.errorMsg();
}

在主循环中,只有成功获取到节点状态信息的情况下,才会更新 Redis 数据库。

因为 NWPC 业务系统部分 ecFlow 服务的节点太多,连接超时的现象时有发生。 ecflow-watchman 获取数据的间隔设置为 60 秒,可以满足现有系统的需求。

参考

nwpc-oper/ecflow-client-cpp

ecflow学习笔记:节点状态监控工具V2