php ioc容器,PHP 在Swoole中使用双IoC容器实现无污染的依赖注入

在Swoole等常驻内存的PHP扩展中,传统的单例模式可能导致请求间的污染。文章介绍了Laravel的IoC容器在多请求环境下的挑战,以及如何通过'双容器策略'解决请求隔离问题。该策略涉及进程级容器和请求级容器,确保每个请求都有独立的实例空间,防止内存泄漏。同时,文中提到了其他解决策略如克隆、清洗和重新注册,但它们各有不足。
摘要由CSDN通过智能技术生成

容器(container)技术(可以理解为全局的工厂方法), 已经是现代项目的标配. 基于容器, 可以进一步实现控制反转, 依赖注入. Laravel 的巨大成功就是构建在它非常强大的IoC容器 illuminate/container 基础上的. 而 PSR-11 定义了标准的 container , 让更多的 PHP 项目依赖容器实现依赖解耦, 面向接口编程.

另一方面, PHP 天生一个进程响应一次请求的模型, 已经不能完全适应开发的需要. 于是 Swoole, reactPHP, roadrunner 也越来越流行. 它们共同的特点是一个 php worker 进程在生命周期内要响应多个请求, 甚至同一时间同时运行多个请求 (协程).

在这些引擎上使用传统只考虑单请求的容器技术, 就容易发生单例相互污染, 内存泄露等问题 (姑且称之为”IoC容器的请求隔离问题” ). 于是出现了各种策略以解决之.

多轮对话机器人框架 CommuneChatbot 使用 swoole 做通信引擎, 同时非常广泛地使用了容器和依赖注入. 在本项目中使用了 “双容器策略” 来解决 “请求隔离问题” .

所谓”双容器策略”, 总结如下:

同时运行 “进程级容器” 与 “请求级容器”

“进程级容器” :

传统的IoC 容器, 例如 Illuminate/container

“请求级容器” :

所有工厂方法注册到容器的静态属性上

在 worker 进程初始化阶段 注册服务

每个请求到来后, 实例化一个请求容器.

请求中生成的单例, 挂载到容器的动态属性上.

持有”进程级容器”, 当绑定不存在时, 到”进程级容器” 上查找之.

请求结束时进行必要清理, 防止内存泄露

解决方案的代码在 https://github.com/thirdgerb/container 创建了一个 composer 包 commune/container

容器的”请求隔离”问题

关于容器, 控制反转与依赖注入

为防止部分读者不了这些概念, 简单说明一下.

所谓容器, 相当于一个全局的工厂. 可以在这里 “注册” 各种服务的工厂方法, 再使用容器统一地获取. 例如

1 $container = newContainer();2

3 //绑定一个单例

4 $container->singleton(5

//绑定对象的ID, 通常是 interface, 以实现面向接口编程.

6

UserInterface::class,

7

//生成实例的工厂方法.

8

function() {9

return new class implementsUserInterface{};10 }11 );12

13 //从容器中获取实例

14 $user = $container->get(UserInterfacle::class);15

16 $user instanceof UserInterface; //true

当一个类的实例在容器中生成, 或者一个方法被容器调用时, 就可以方便地实现依赖注入.

简单来说, 容器通过反射机制可获取目标方法的依赖 ( laravel 用反射来获取 typehint 类型约束, 而 Swoft项目似乎与spring 相似, 是从注释上获取的).

然后容器查找是否已注册了 依赖 (dependency) 的实现 (resolver), 如果已注册, 就从容器中生成该依赖, 再注入给目标方法.

具有依赖注入能力的容器, 我们称之为 IoC (控制反转) 容器. 关于IoC 容器的好处不是本文重点, 先跳过去了.

IoC 容器的请求隔离问题

容器最典型的应用场景之一, 就是持有单例. 但在 swoole 等引擎上, 一个 worker 进程要响应多个请求, 单例的数据就容易相互污染.

例如我们把 session 的数据放在 一个 SessionInterface 中, 每个逻辑调用时都用容器来取:

$sessionInstance = container()->make(SessionInterface::class);

由于单例在容器内只生成一次, 那第二次请求时, 容器会给出第一次请求的session单例, 从而逻辑就乱套了.

所以容器要运行在 swoole 等引擎上, 必须做到请求与请求相隔离.

常见的解决策略

由于 Laravel 等使用了IoC 容器的项目能带来极好的工程体验, 而Swoole 能带来极大的性能提升, 于是有许多试图结合两者的项目, 都面临了 “请求隔离问题”.

我个人看到过的解决策略有以下三种, 都能一定程度解决问题, 但也有美中不足之处.

克隆策略:

方案: 每次请求, 克隆一个新的 container

问题:

要递归地 clone 属性, 才能避免浅拷贝导致的污染

无法区分进程共享的单例, 和请求隔离的单例.

清洗策略:

方案: 每次请求结束时, 主动清洗掉已注册的单例

问题:

定义类时就要考虑清洗逻辑, 可能要实现interface, 耦合较重

swoole 发展到协程后, 同时可能相应多个请求, 清晰策略失效了.

重新注册:

方案: 每个请求到来时, 实例化一个新容器, 重新注册所有服务

问题:

注册服务其实开销很大, 尤其是需要大量读文件的初始化(比如翻译组件)

无法区分进程共享的单例, 和请求隔离的单例.

利用不了 swoole 的优势, 比起多进程模型只少了 composer autoloader 的加载.

CommuneChatbot 遇到的请求隔离问题

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值