
思考:
传统行业 为了保证产品质量
依靠大量测试人员和领导嘱咐和监督
分布式系统 异常复杂,他们如何做系统测试呢?模拟故障
本文 DeepSeek 3FS 测试用例举例说明
我也也没看太明白 就是FAULT_INJECTION_SET(10, 5);宏函数搞定
维度 | Ceph 方式 | 3FS 方式 |
|---|---|---|
配置方式 | 外部配置文件、运行时API | 代码宏、编译时确定 |
粒度控制 | 细粒度(每个命名点独立) | 粗粒度(全局计数控制) |
使用场景 | 生产环境故障演练、测试 | 开发测试、CI/CD |
动态性 | 支持运行时动态调整 | 需要重新编译 |
复杂度 | 高(分布式同步、持久化) | 低(内存状态、线程局部) |
故障类型 | 多样(失败、延迟、损坏等) | 主要模拟操作失败 |
•
Ceph 风格:用“命名的故障注入点”(例如 "bluestore_write_fail"),注入点以名字注册,测试/运维可以通过名字打开/关闭特定注入点,目标是精确定位某一个逻辑分支的失败。
•
3FS 的故障注入框架基于 概率触发 + 作用域管理 的设计,通过 folly::RequestContext 实现跨协程的配置传递。核心思想是:在代码中埋入检查点,按照设定的概率和次数限制触发故障。
1
声明式 API: 通过 FAULT_INJECTION_SET(概率, 次数) 声明故障注入范围,具体故障类型由业务代码决定
2
RAII 自动管理: 利用 C++ 的 RAII 模式,离开作用域自动恢复之前的配置
3
协程友好: 通过 RequestContext 机制,配置在协程调度时自动传递,支持复杂的异步调用链
4
线程安全: 使用 std::atomic_int32_t 保证计数器的原子性



https://github.com/deepseek-ai/3FS/blob/394583db/tests/meta/store/ops/TestRename.cc
TYPED_TEST(TestRename, Basic)
{
folly::coro::blockingWait([&]() -> CoTask<void> {
auto cluster = this->createMockCluster();
auto &meta = cluster.meta().getOperator();
FAULT_INJECTION_SET(10, 5);
// create a, rename a -> b, stat a -> InodeId::root(), stat b -> Inode
CO_ASSERT_OK(co_await meta.create({SUPER_USER, "a", {}, O_RDONLY, p644}));
CO_ASSERT_OK(co_await meta.rename({SUPER_USER, "a", "b"}));
auto a = co_await meta.stat({SUPER_USER, "a", AtFlags(AT_SYMLINK_FOLLOW)});
CO_ASSERT_ERROR(a, MetaCode::kNotFound);
auto b = co_await meta.stat({SUPER_USER, "b", AtFlags(AT_SYMLINK_FOLLOW)});
CO_ASSERT_OK(b);
})
(1) folly::coro::blockingWait是 Facebook Folly 协程库中的核心同步原语,
主要用于
•
阻塞当前线程直至协程执行完成。
•
其设计目标是为异步代码提供同步接口
MetaTestBase的测试类都可以使用这个模式1
createMockCluster() - 创建一个模拟的3FS集群环境 MetaTestBase.h:426-429 。这个方法会初始化:
•
KV存储引擎(FoundationDB或内存KV)
•
模拟的管理服务(mgmtd)客户端
•
模拟的元数据服务器(MockMeta) MetaTestBase.h:391-395
2
cluster.meta().getOperator() - 获取MetaOperator引用 MockMeta.h:61 ,这是执行文件系统元数据操作的核心接口。通过这个接口可以调用:
•
create() - 创建文件 TestStat.cc:45-46
•
mkdirs() - 创建目录 TestStat.cc:32
•
stat() - 查询文件/目录状态 TestStat.cc:34-35
•
remove() - 删除文件/目录 TestRemove.cc:93
FAULT_INJECTION_SET(10, 5) 是一个用于故障注入测试的宏,它会在当前代码作用域内设置故障注入参数: FaultInjection.h:16
•
第一个参数 (10): 表示故障注入的概率为 10% FaultInjection.h:13
•
第二个参数 (5): 表示最多注入 5 次故障 FaultInjection.h:14
#define FAULT_INJECTION_SET(prob, times)
hf3fs::FaultInjection::ScopeGuard FB_ANONYMOUS_VARIABLE(guard)(prob, times)
ScopeGuard 类使用了 C++ 的 RAII(Resource Acquisition Is Initialization)
故障注入配置存储在 folly::RequestContext 中,
这是一个线程局部的上下文对象,可以在协程调度时自动传递。
folly::RequestContext 是 Folly 库提供的一个线程局部存储容器,用于在异步操作链中传递上下文信息。
它的核心特性是:*当协程被调度到不同线程执行时,RequestContext 会自动跟随协程一起传递
A. 线程局部存储 (TLS)
RequestContext 首先利用了“线程局部存储 (Thread Local Storage, TLS)”。
•
解释: 在任何给定时刻,一个线程只能访问它自己当前领用的托盘。如果你在函数 A 中把 "订单号 123" 放进托盘,那么在同一个线程的函数 B 中,你取出来的也一定是 "订单号 123",不会混淆。
B. 异步传递(魔术时刻)
这是 RequestContext 最神奇的地方,也是它超越普通 TLS 的地方。
•
普通 TLS 的问题: 如果你的服务员 A 忙不过来,把剩下的工作交接给了另一个服务员 B(即异步操作从一个线程切换到另一个线程执行),普通 TLS 不会自动把托盘一起交接过去。服务员 B 拿的是一个空托盘。
•
folly::RequestContext 的解决方案: Folly 库的异步机制(如协程)在设计时就包含了这个“魔术”。当你进行线程切换时,Folly 会自动把旧线程(服务员 A)的 RequestContext “打包”,并“激活”到新线程(服务员 B)上。
RequestContext 的自动传递
无论协程是否切换线程,FAULT_INJECTION_SET 的配置都会自动跟随:
这是因为 Folly 的协程实现会在
scheduleContinuation() 和 executeContinuation() 中自动保存和恢复
RequestContext:
FAULT_INJECTION_SET 是一个通用的故障注入框架,
它本身不指定具体的故障类型(网络、服务或其)
具体注入什么故障取决于使用它的代码位置:
虽然 Rename.cc 本身不直接调用 FAULT_INJECTION(),但在实际运行中,故障注入会影响:
1
事务读取操作:在加载 Inode 或 DirEntry 时可能失败
2
事务写入操作:在更新或删除条目时可能失败
3
事务提交:最终提交时可能返回冲突错误
这些都是通过底层 KV 引擎(MemKV 或 FDB)的事务机制自然产生的,而不是显式抛出的错误。
意味:异步任务如何通过协程来实现的,这样有什么好处
https://deepwiki.com/search/-3fs_8d0785bd-e03b-43c7-8270-cbe219f288c4?mode=fast