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 秒,可以满足现有系统的需求。