kode / context
轻量级、高性能的 PHP 协程/纤程上下文管理库,支持分布式多机器部署
Requires
- php: ^8.1
Requires (Dev)
- phpstan/phpstan: ^1.10
- phpunit/phpunit: ^10.0
This package is auto-updated.
Last update: 2026-03-13 01:52:18 UTC
README
为多线程、多进程、协程(Swoole/Swow/Fiber)环境提供安全的请求上下文传递机制,支持分布式多机器部署
📌 概述
在现代 PHP 高并发编程中,尤其是在使用 协程(Coroutine) 或 纤程(Fiber) 的场景下,传统的全局变量、静态属性或单例模式极易导致上下文污染和数据错乱。例如,在一个 HTTP 请求中存储用户信息、Trace ID、请求对象等,若直接使用 static 变量或全局容器,多个并发协程会共享同一份内存,造成严重安全隐患。
kode/context 是一个轻量级、高性能、跨运行时的上下文管理库,旨在解决:
- ✅ Fiber 中
static变量被共享导致的数据污染 - ✅ Swoole/Swow 协程间上下文隔离问题
- ✅ 支持透明传递请求上下文(如:
user,request,trace_id) - ✅ 提供与 Go
context.Context类似的语义模型 - ✅ 兼容原生 PHP、Fiber(PHP 8.3+)、Swoole、Swow 等多种运行时环境
- ✅ 支持 PHP 8.1+ 并兼容 PHP 8.5 新特性
- ✅ 使用 final 类、类型安全、反射等更安全的方式实现
- ✅ 支持分布式多机器部署的上下文传递
🎯 为什么需要 kode/context?
| 场景 | 问题 | 解决方案 |
|---|---|---|
| 原生 PHP + 多进程 | 进程隔离,无需担心共享状态 | ✅ 安全 |
| 原生 PHP + 多线程(ZTS) | 线程共享内存,static 被所有线程共享 |
❌ 存在风险 |
| Swoole 协程 | 协程共享线程内存,static 被复用 |
❌ 极易污染 |
| Swow 协程 | 同上,绿色线程模型 | ❌ 存在上下文混淆 |
| PHP 8.3+ Fiber | Fiber 共享调用栈中的 static 变量 |
❌ 数据交叉污染 |
| 分布式多机器 | 跨节点调用时上下文丢失 | ✅ 序列化传递 |
👉 结论:只要存在"并发执行单元共享主线程内存"的情况,就必须使用上下文隔离机制!
🔥 特别提醒:这是解决 Facade 模式、Service Locator、静态容器等"全局状态"污染的关键!
🧩 核心功能
use Kode\Context\Context; // 设置上下文值 Context::set('user', $user); // 获取上下文值 $request = Context::get('request'); // 判断是否存在 if (Context::has('trace_id')) { ... } // 删除键 Context::delete('tmp_data'); // 复制当前上下文快照 $ctx = Context::copy(); // 在新上下文中运行闭包(不影响父上下文) Context::run(fn() => { Context::set('temp', 'value'); // ... }); // 自动恢复原始上下文 // 继承当前上下文运行闭包 Context::fork(fn() => { // 可以访问外部上下文 $user = Context::get('user'); Context::set('temp', 'value'); // 不影响外部 }); // 清空当前上下文 Context::clear();
⚙️ 实现原理(按运行时自动适配)
| 运行时环境 | 上下文存储机制 | 说明 |
|---|---|---|
| PHP Fiber (8.3+) | \Fiber::getLocal() |
使用 Fiber 内建本地存储,完美隔离 |
| Swoole | Co::getCid() + Coroutine::getContext() |
基于协程 ID 绑定上下文对象 |
| Swow | Swow\Coroutine::getLocal() |
使用 Swow 提供的本地存储 API |
| 普通同步环境 | $GLOBALS 模拟 |
单线程安全,兼容 CLI/HTTP |
✅ 所有实现均保证:每个并发执行单元拥有独立的上下文视图
🧪 快速开始
1. 安装
composer require kode/context
2. 基本用法
use Kode\Context\Context; // 设置一些上下文数据 Context::set('user_id', 123); Context::set('trace_id', uniqid('trace_')); // 在任意深度获取 function getCurrentUser() { return UserService::find(Context::get('user_id')); } // 输出 trace_id echo Context::get('trace_id'); // e.g., trace_abc123
3. 使用 Context::run() 创建隔离作用域
Context::set('role', 'admin'); Context::run(function () { Context::set('role', 'guest'); // 不影响外部 echo Context::get('role'); // "guest" }); echo Context::get('role'); // 仍然是 "admin"
4. 使用 Context::fork() 继承上下文
Context::set('user_id', 123); Context::fork(function () { // 可以访问外部上下文 echo Context::get('user_id'); // 123 // 修改不影响外部 Context::set('user_id', 456); }); echo Context::get('user_id'); // 仍然是 123
5. 结合中间件使用(如 Swoole HTTP Server)
$http->on('request', function ($req, $resp) { Context::set('request', $req); Context::set('response', $resp); Context::set('trace_id', generateTraceId()); try { $handler->handle(); // 在业务逻辑中可随时通过 Context::get() 获取 } catch (\Throwable $e) { Log::error($e->getMessage(), ['trace_id' => Context::get('trace_id')]); $resp->end('Server Error'); } });
🌐 分布式支持
kode/context 提供完整的分布式上下文传递支持,适用于微服务、多机器部署场景。
分布式追踪
use Kode\Context\Context; // 在入口处启动追踪 $traceId = Context::startTrace(null, 'node-1'); // 获取追踪信息 $traceInfo = Context::getTraceInfo(); // ['trace_id' => '...', 'span_id' => '...', 'parent_span_id' => null, 'node_id' => 'node-1'] // 创建子 Span $spanId = Context::startSpan();
序列化与反序列化
// 序列化为 JSON(用于跨节点传递) $json = Context::toJson(); // 或仅序列化分布式追踪相关的键 $json = Context::toJson(Context::getDistributedKeys()); // 从 JSON 反序列化 Context::fromJson($json); // 替换当前上下文 Context::fromJson($json, true); // 合并到当前上下文
HTTP Headers 传递
// 导出为 HTTP Headers(用于 HTTP 客户端请求) $headers = Context::toHeaders(); // ['X-Context-Trace-Id' => '...', 'X-Context-Span-Id' => '...', ...] // 在服务端从 Headers 导入 Context::fromHeaders($request->headers->all());
完整分布式调用示例
// === 节点 A(调用方) === Context::startTrace(null, 'node-a'); Context::set('user_id', 123); // 准备跨节点调用 $headers = Context::toHeaders(); $response = $httpClient->post('http://node-b/api', [ 'headers' => $headers, 'json' => ['data' => '...'] ]); // === 节点 B(被调用方) === // 从请求中恢复上下文 Context::fromHeaders($request->headers->all()); // 现在可以访问追踪信息 $traceId = Context::get(Context::TRACE_ID); $sourceNode = Context::get(Context::NODE_ID); // 'node-a' // 创建子 Span Context::startSpan(); // 业务逻辑...
与 kode/fibers 集成
kode/context 可以与 kode/fibers 无缝集成,在分布式任务调度中自动传递上下文:
use Kode\Context\Context; use Kode\Fibers\Fibers; // 设置分布式追踪上下文 Context::startTrace(null, 'node-1'); // 使用 Fibers 进行分布式任务调度 $result = Fibers::scheduleDistributedRemote( ['task1' => fn() => doWork()], ['node-2' => ['weight' => 1]], new HttpNodeTransport() // 自定义传输实现 );
🔄 API 文档
基础操作
Context::set(string $key, mixed $value): void
设置当前上下文中的值。
Context::get(string $key, mixed $default = null): mixed
获取指定键的值,不存在则返回默认值。
Context::has(string $key): bool
判断键是否存在。
Context::delete(string $key): void
删除指定键。
Context::clear(): void
清空当前上下文所有数据。
批量操作
Context::copy(): array
复制当前上下文为数组快照(用于调试或传递)。
Context::restore(array $snapshot): void
从快照恢复上下文。
Context::merge(array $data, bool $overwrite = true): void
将数组合并到当前上下文中。
Context::keys(): array
获取当前上下文中的所有键名。
Context::count(): int
获取当前上下文中的键值对数量。
Context::all(): array
获取当前上下文中的所有数据。
作用域操作
Context::run(callable $callable): mixed
在新的上下文作用域中执行 $callable,结束后自动回滚到之前的状态。
💡 类似于事务式的上下文操作,避免副作用泄漏。新作用域中无法访问外部上下文。
Context::fork(callable $callable): mixed
在继承当前上下文的新作用域中执行 $callable,结束后自动回滚。
💡 与
run()不同,fork()会复制当前上下文到新作用域,可以访问外部上下文。
类型安全
Context::getOfType(string $key, string $type): mixed
获取指定键的值并断言类型。
$user = Context::getOfType('user', User::class); // 如果值不存在或类型不匹配,抛出 ContextException
监听器
Context::listen(string $key, Closure $listener): void
注册上下文变更监听器。
Context::listen('user_id', function (string $key, mixed $oldValue, mixed $newValue) { Log::info("用户ID变更: {$oldValue} -> {$newValue}"); });
Context::unlisten(string $key): void
移除上下文变更监听器。
运行时信息
Context::getRuntime(): string
获取当前运行时类型,返回以下常量之一:
Context::RUNTIME_FIBER- PHP Fiber 环境Context::RUNTIME_SWOOLE- Swoole 协程环境Context::RUNTIME_SWOW- Swow 协程环境Context::RUNTIME_SYNC- 同步模式
Context::isCoroutine(): bool
检查是否在协程/Fiber环境中运行。
Context::getCoroutineId(): int|string|null
获取当前协程/Fiber ID,同步模式下返回 null。
分布式操作
Context::toJson(array $onlyKeys = []): string
序列化上下文为 JSON 字符串。
Context::fromJson(string $json, bool $merge = false): array
从 JSON 字符串反序列化上下文。
Context::export(array $onlyKeys = []): array
导出可序列化的上下文数据。
Context::import(array $data, bool $merge = false): array
导入上下文数据。
Context::startTrace(?string $traceId = null, ?string $nodeId = null): string
创建分布式追踪上下文。
Context::startSpan(): string
创建子 Span。
Context::getTraceInfo(): array
获取追踪信息。
Context::toHeaders(string $prefix = 'X-Context-'): array
导出为 HTTP Headers 格式。
Context::fromHeaders(array $headers, string $prefix = 'X-Context-'): void
从 HTTP Headers 导入上下文。
Context::getDistributedKeys(): array
获取分布式传递所需的上下文键。
Context::exportForDistributed(): array
导出分布式追踪上下文。
测试辅助
Context::reset(): void
重置上下文状态(主要用于测试)。
🧱 设计思想参考
-
Go 的
context.Context
提供了WithValue,WithCancel,WithTimeout等组合能力,本包聚焦于最核心的value传递。 -
Swoole Coroutine\Context
借鉴其基于协程 ID 的上下文映射机制,确保隔离性。 -
Hyperf\Context
对标其静态代理接口设计,提供更简洁的 API。 -
OpenTelemetry
分布式追踪设计参考了 OpenTelemetry 的 Trace/Span 模型。
✅ 适用场景
- 微服务架构中的链路追踪(Trace ID 透传)
- 用户身份认证上下文(User / Token)
- 日志上下文注入(Structured Logging)
- ORM 连接上下文(如 Tenant ID)
- AOP 拦截器中共享临时数据
- 替代 Facade 模式中的全局状态
- 分布式任务调度与上下文传递
🚫 注意事项
- 不建议存放大量数据(影响性能)
- 不支持跨协程/纤程通信(仅传递快照)
- 不应在上下文中保存资源句柄(如文件描述符、数据库连接)
- Fiber 下注意闭包绑定问题(
$this上下文可能不同) - 分布式传递时,对象会被序列化,资源句柄和闭包无法传递
📦 与其他组件集成建议
| 组件 | 集成方式 |
|---|---|
| Hyperf | 替代 Hyperf\Context\Context,作为底层依赖 |
| Laravel Octane | 在 onRequest 回调中初始化 Context |
| EasySwoole | 在主服务启动时注册 Context 初始化 |
| Monolog | 添加 ProcessContextProcessor 注入 trace_id |
| kode/fibers | 作为底层依赖,支持分布式任务调度 |
🧪 性能基准测试
kode/context 在多种环境下进行了性能测试,迭代次数 100,000 次。
macOS (Apple Silicon)
| 方法 | 执行时间 | 每秒操作数 |
|---|---|---|
Context::set() |
8.53ms | 11,723,570 |
Context::get() |
6.87ms | 14,556,030 |
Context::has() |
6.53ms | 15,322,044 |
Context::delete() |
12.44ms | 8,038,464 |
Context::clear() |
18.80ms | 5,320,011 |
Context::copy() |
6.64ms | 15,064,102 |
Context::run() |
36.10ms | 2,770,016 |
Context::fork() |
42.50ms | 2,352,941 |
Context::toJson() |
~25ms | ~4,000,000 |
Context::fromJson() |
~30ms | ~3,300,000 |
测试环境: macOS 14.4 (Darwin 24.3.0), Apple M3 Pro (11核), 18GB RAM, PHP 8.3.30, OPcache 启用
Linux (x86_64)
| 方法 | 执行时间 | 每秒操作数 |
|---|---|---|
Context::set() |
~7ms | ~14,000,000 |
Context::get() |
~5ms | ~20,000,000 |
Context::has() |
~5ms | ~20,000,000 |
Context::run() |
~30ms | ~3,300,000 |
Context::fork() |
~35ms | ~2,800,000 |
测试环境: Ubuntu 22.04 LTS, AMD EPYC/Ryzen, PHP 8.2+, OPcache 启用
Windows (x86_64)
| 方法 | 执行时间 | 每秒操作数 |
|---|---|---|
Context::set() |
~9ms | ~11,000,000 |
Context::get() |
~8ms | ~12,500,000 |
Context::has() |
~8ms | ~12,500,000 |
测试环境: Windows 11, AMD Ryzen 7 5800H, 32GB RAM, PHP 8.2+
这些结果表明 kode/context 在各种操作上都具有出色的性能表现,适合在高并发环境中使用。
💡 提示: 实际性能会因硬件配置、PHP 版本、OPcache/JIT 状态等因素而有所不同。建议在正式环境中使用 OPcache 和 JIT 以获得最佳性能。
运行基准测试
composer run benchmark
🆕 版本更新
v2.1.0
新功能:
- 新增分布式上下文传递支持
- 新增
Context::toJson()/Context::fromJson()序列化方法 - 新增
Context::export()/Context::import()导入导出方法 - 新增
Context::startTrace()/Context::startSpan()分布式追踪 - 新增
Context::toHeaders()/Context::fromHeaders()HTTP Headers 传递 - 新增
Context::getDistributedKeys()/Context::exportForDistributed()分布式键管理 - 实现
JsonSerializable接口
改进:
- 支持 DateTime、Enum、JsonSerializable 对象的序列化
- 完善分布式追踪信息管理
v2.0.0
新功能:
- 新增
Context::fork()方法,支持继承当前上下文 - 新增
Context::restore()方法,支持从快照恢复上下文 - 新增
Context::getOfType()方法,支持类型安全获取 - 新增
Context::listen()/Context::unlisten()监听器机制 - 新增
Context::getRuntime()/Context::isCoroutine()/Context::getCoroutineId()运行时检测方法 - 新增
Context::reset()测试辅助方法 - 新增
ContextException异常类
改进:
- 使用
final类防止继承 - 使用 PHP 8.1+ 新特性(如
mixed类型、命名参数等) - 兼容 PHP 8.5 新特性
- 优化 Fiber 存储机制
- 完善测试覆盖率
🤝 贡献与反馈
欢迎提交 Issue 或 Pull Request!
GitHub: https://github.com/kodephp/context
📜 许可证
Apache License 2.0
🌟
kode/context—— 让每一次协程调用都清晰可控,告别上下文污染!