首页
关于
Search
1
给你10个市场数据调研报告的免费下载网站!以后竞品数据就从这里找!
182 阅读
2
php接口优化 使用curl_multi_init批量请求
144 阅读
3
《从菜鸟到大师之路 ElasticSearch 篇》
107 阅读
4
2024年备考系统架构设计师
104 阅读
5
PHP 文件I/O
92 阅读
php
thinkphp
laravel
工具
开源
mysql
数据结构
总结
思维逻辑
令人感动的创富故事
读书笔记
前端
vue
js
css
书籍
开源之旅
架构
消息队列
docker
教程
代码片段
redis
服务器
nginx
linux
科普
java
c
ElasticSearch
测试
php进阶
php基础
登录
Search
标签搜索
php函数
php语法
性能优化
安全
错误和异常处理
问题
vue
Composer
Session
缓存
框架
Swoole
api
并发
异步
正则表达式
php-fpm
mysql 索引
开发规范
协程
dafenqi
累计撰写
786
篇文章
累计收到
29
条评论
首页
栏目
php
thinkphp
laravel
工具
开源
mysql
数据结构
总结
思维逻辑
令人感动的创富故事
读书笔记
前端
vue
js
css
书籍
开源之旅
架构
消息队列
docker
教程
代码片段
副业
redis
服务器
nginx
linux
科普
java
c
ElasticSearch
测试
php进阶
php基础
页面
关于
搜索到
100
篇与
的结果
2023-08-11
PHP依赖注入原理与用法分析
PHP依赖注入原理与用法分析这篇文章主要介绍了PHP依赖注入原理与用法,简单讲述了依赖注入的概念、原理并结合实例形式分析了php实现与使用依赖注入的相关操作技巧,需要的朋友可以参考下java本文实例讲述了PHP依赖注入原理与用法。分享给大家供大家参考,具体如下:引言依然是来自到喜啦的一道面试题,你知道什么是依赖注入吗?依赖注入(DI)的概念虽然听起来很深奥,但是如果你用过一些新兴的php框架的话,对于DI一定不陌生,因为它们多多少少都用到了依赖注入来处理类与类之间的依赖关系。php中传递依赖关系的三种方案其实要理解DI,首先要明白在php中如何传递依赖关系。第一种方案,也是最不可取的方案,就是在A类中直接用new关键词来创建一个B类,如下代码所示:12345678<?phpclass A{ public function __construct() {$b = new B();}}为什么这种方案不可取呢?因为这样的话,A与B就耦合在了一起,也就是说A类无法脱离B类工作。第二种方案就是在A类的方法中传入需要的B类,如下代码所示:1234567<?phpclass A{ public function __construct(B $b) { }}这种方法比第一种方案有了改进,A类不必与B类捆绑在一起,只要传入的类满足A类的需求,也可以是C类,也可以是D类等等。但是这种方案的弊端在于如果A类依赖的类较多,参数列表会很长,容易发生混乱。第三种方案是使用set方法传入,如下代码所示:12345678<?phpclass A{ public function setB(B $b) {$this->b = $b;}}这种方案同样存在和第二种方案一样的弊端,当依赖的类增多时,我们需要些很多很多的set方法。这时我们在想如果有一个专门的类(或者说一个容器)可以帮我们管理这些依赖关系就好了。一个简单的依赖注入的例子如下代码来自twittee:123456<?phpclass Container { private $s=array(); function __set($k, $c) { $this->s[$k]=$c; } function __get($k) { return $this->s[$k]($this); }}有了container类之后我们可以怎样管理A与B之间的依赖关系呢,用代码说话吧:123456789101112131415<?phpclass A{ private $container; public function __construct(Container $container) {$this->container = $container;} public function doSomeThing() {//do something which needs class B $b = $this->container->getB(); //to do}}再将B类注入到容器类中:12$c = new Container();$c->setB(new B());还可以传入一个匿名函数,这样B类就不会在传入时就立即实例化,而是在真正调用时才完成实例化的工作:1234$c = new Container();$c->setB(function (){ return new B();});这里举的只是一个很简单的例子,在实际中,容器类要考虑的有很多,比如延迟加载等等。
2023年08月11日
9 阅读
0 评论
0 点赞
2023-08-11
如何为开源PHP软件包做贡献
如何为开源PHP软件包做贡献介绍鉴于即将举行的Hacktoberfest,我想为初学者分享一些技巧,这些初学者可能想专门为PHP 软件包做出第一笔贡献。从我自己的经验来看,与“常规”(Laravel)应用程序相比,在一个程序包上工作可能看起来很艰巨。这篇文章旨在为初学者贡献一些开源PHP软件包的指导。步骤1.在GitHub上Fork软件包例如,假设我们要处理laravel-medialibrary软件包。首先,将包Fork到GitHub上,因为我们(可能)无权将分支推送到主存储库。分支将充当我们软件包的“工作副本”,并在中心位置push包含您的工作的分支。步骤2.克隆你的Fork将包fork克隆到本地计算机。我个人有一个单独的文件夹,用于“应用程序”和“程序包”。将包从你的fork克隆到本地计算机。就我个人而言,我有一个单独的文件夹为"applications"和"packages"。cd packagesgit clone git@github.com:Jhnbrn90/laravel-medialibrary.git .步骤3.在(Laravel)项目中Require包在应用程序中Require克隆的软件包,以测试所需的功能或错误修复。例如,此应用程序可以是Laravel的全新安装,但并非必须如此。就个人而言,我总是创建一个名为“ hacktober”的新Laravel应用程序。在这个应用程序中,通过在composer.json文件中定义一个所谓的自定义repository,你可以从本地require包,而不是通过Packagist。将“ url”替换为软件包所在的目录。{ "scripts": { ... },"repositories": [{ "type": "path", "url": "../../packages/laravel-medialibrary" }]}由于Composer使用指定的repositories作为后备,因此您需要在 composer.json 中更新包的名称。否则,它将只使用Packagist的Spatie的最新版本的laravel-medialibrary包。您可以在composer.json中按以下方式重命名程序包:{ "name": "JhnBrn90/laravel-medialibrary", ....}重要提示: 切勿提交此更改!在Laravel应用程序中Require该软件包:composer require JhnBrn90/laravel-medialibrary这将创建到本地软件包的符号链接,而不是从Packagist安装软件包。第4步:提交工作您在软件包中所做的所有更改现在都将直接反映在用于测试软件包的应用程序中。按照项目贡献准则中的说明,为该功能或错误修复程序创建一个新的分支,通常在CONTRIBUTING.md文件中进行描述。git checkout -b feature/some-feature在尝试通过相关代码段分隔提交的同时,在此分支上提交您的工作。用GitHub的桌面工具,可以很容易地将更改分别提交。步骤5.Push分支如果您对工作的当前状态感到满意,则可以将分支push到自己的存储库分支中。git push --set-upstream origin some-feature推送您的分支后,GitHub很可能会提供一个URL以直接创建新的PR。或者,您可以通过GitHub的Web界面创建PR。步骤6.创建一个新的PR现在您已经有了一个包含工作的分支,您可以根据贡献准则(最常见的是master或develop)向分支创建一个新的拉取请求(pull request)。使用 Pull Requests选项卡中的“New pull request”按钮,并确保启用“compare across forks”。这允许您从fork上的分支创建到基本存储库的 PR。从头开始创建新的PR写一个好的描述理想情况下,PR应该易于理解,并且意图明确:你为什么要做/改变一些东西如何运作别人如何测试PR有时,需要先进行PHPUnit或端到端测试,然后才能接受PR。如果您发现这一挑战,您可以随时询问维护人员,是否有人可以稍后将其添加到您的PR中。良好描述的示例如下:解决问题#13做了什么(为什么)确认对话框已添加到“重置操作”按钮,以防止意外重置。它是如何工作的确认modal的视图已添加回调已添加到onClick操作中,这将打开一个新的确认modal此外,该回调接受参数X和Y能够...,请参见下面的代码示例:public function showConfirmationDialog($x, $y){ // Add a code example to clarify the PR} 测试方法单击“重置”,确认modal并断言计数器已重置单击“重置”,取消modal并断言计数器未重置Todo这些是我不确定的事情添加测试:断言单击“取消”不会重置计数器断言单击“确认”会重置计数器摘要我希望建议的工作流程可以帮助初学者对PHP软件包充满信心。如果您想了解有关创建Laravel特定软件包的详细信息,请确保签出以下资源:LaravelPackage.comLaravel包装培训PHP软件包开发课程进阶技巧配置upstream虽然是可选的,但我建议您也将我们从中分叉软件包的存储库配置为“上游”存储库。这使我们可以在以后阶段从此存储库中获取更改,例如,与此同时,当PR合并到主数据库中时。While optional, I would advise to also configure the repository where we forked the package from as an "upstream" repository. This allows us to pull in changes from this repository at a later stage, for example when a PR was merged into master in the meantime.我们可以使用以下git remote add命令添加此远程仓库:git remote add upstream git@github.com:spatie/laravel-medialibrary.git运行时git remote -v,您现在应该看到两个单独的远程仓库:“ origin”指的是您自己的存储库,“upstream”指的是原始存储库。使用最新更改更新(rebase)您的PR当您处理问题或功能时,可能会在创建您的PR之前将其合并。在这种情况下,最好master在上游存储库中引入最新更改。While you work on an issue or feature, it might happen that another PR was merged before you created yours. In that case, it might be a good idea to pull in the latest changes on master in the upstream repository.在您当前正在开发的分支中并入master(或develop)中的更改,如下所示:Incorporate changes in master (or develop) in the branch you're currently developing on as follows:确保工作分支上的所有更改都已提交并且工作目录是干净的Pull in the latest version of master from the upstream repository:git checkout mastergit pull upstream masterRebase your feature branch on the latest version of master:git checkout feature/some-featuregit rebase mastervia https://johnbraun.blog/posts/contributing-to-a-PHP-package推荐:在线生成软件包样板https://laravelpackageboilerplate.com/#/
2023年08月11日
23 阅读
0 评论
0 点赞
2023-08-11
如何选择laravel的身份认证系统
如何选择laravel的身份认证系统最近经常有人问如何使用laravel的身份认证功能,因为laravel的身份认证方式比较多,laravel的作者最近专门写了一文章来解释说明,以下为译文:laravel认证生态系统概述Laravel提供了一些与身份验证相关的软件包。在继续之前,我们将回顾Laravel中的常规身份验证生态系统,并讨论每个软件包的预期目的。首先,请考虑身份验证的工作原理。使用网络浏览器时,用户将通过登录表单提供其用户名和密码。如果这些凭据正确,则应用程序将在用户的会话中存储有关经过身份验证的用户的信息。发布给浏览器的cookie包含会话ID,以便对应用程序的后续请求可以将用户与正确的会话相关联。接收到会话cookie后,应用程序将基于会话ID检索会话数据,注意身份验证信息已存储在会话中,并将用户视为“已身份验证”。当远程服务需要进行身份验证才能访问API时,通常不使用cookie,因为没有Web浏览器。而是,远程服务根据每个请求将API令牌发送到API。应用程序可以对照有效API令牌表验证传入令牌,并“认证”与该API令牌相关联的用户正在执行的请求。Laravel的内置浏览器身份验证服务Laravel包含内置的身份验证和会话服务,通常可以通过Auth和SessionFacades访问它们。这些功能为从Web浏览器发起的请求提供基于cookie的身份验证。它们提供了允许您验证用户凭据并验证用户身份的方法。另外,这些服务将自动在用户的会话中存储正确的数据并发出正确的会话cookie。本文档中包含有关如何使用这些服务的讨论。Jetstream / Fortify如本文档中所述,您可以手动与这些身份验证服务交互以构建应用程序自己的身份验证层。但是,为了帮助您更快地入门,我们发布了免费软件包,这些软件包为整个身份验证层提供了强大而现代的脚手架。这些软件包是Laravel Jetstream和Laravel Fortify。Laravel Fortify是Laravel的无头(headless)身份验证后端,它实现了本文档中的许多功能,包括基于cookie的身份验证以及其他功能,例如( two-factor)双因素身份验证和电子邮件验证。Laravel Jetstream是一个UI,它使用由Tailwind CSS,Laravel Livewire和/或Inertia.js提供支持的美观、现代的UI来使用Fortify的身份验证服务并将其公开。Laravel Jetstream除了提供基于浏览器的cookie身份验证之外,还包括与Laravel Sanctum的内置集成以提供API令牌身份验证。Laravel的API身份验证产品将在下面讨论。Laravel的API身份验证服务Laravel提供了两个可选的软件包来帮助您管理API令牌和认证使用API令牌发出的请求:Passport和Sanctum。请注意,这些库和基于Laravel的内置cookie的身份验证库不是互斥的。这些库主要关注API令牌身份验证,而内置身份验证服务则关注基于cookie的浏览器身份验证。许多应用程序将同时使用Laravel的基于内置cookie的身份验证服务和Laravel的API身份验证程序包之一。PassportPassport是OAuth2身份验证提供程序,提供各种OAuth2“授权类型”,使您可以颁发各种类型的令牌。通常,这是用于API身份验证的强大而复杂的程序包。但是,大多数应用程序都不需要OAuth2规范提供的复杂功能,这会使用户和开发人员感到困惑。另外,从历史上看,开发人员对于如何使用Passport等OAuth2身份验证提供程序对SPA应用程序或移动应用程序进行身份验证感到困惑。Sanctum为了应对OAuth2的复杂性和开发人员的困惑,我们着手构建一个更简单,更简化的身份验证程序包,该程序包既可以处理来自Web浏览器的第一方Web请求,也可以处理通过令牌的API请求。Laravel Sanctum的发布实现了这一目标,对于那些除了API之外还将提供第一方Web UI或将由单页面应用程序提供支持的应用程序,Laravel Sanctum的发布应被视为首选和推荐的身份验证程序包。与后端Laravel应用程序或提供移动客户端的应用程序分开存在。Laravel Sanctum是Web / API混合身份验证程序包,可以管理应用程序的整个身份验证过程。这是可能的,因为当基于Sanctum的应用程序收到请求时,Sanctum将首先确定该请求是否包括引用已验证会话的会话cookie。Sanctum通过调用我们前面讨论的Laravel的内置身份验证服务来实现此目的。如果未通过会话cookie对请求进行身份验证,Sanctum将检查请求中的API令牌。如果存在API令牌,Sanctum将使用该令牌对请求进行身份验证。要了解有关此过程的更多信息,请查阅Sanctum的“工作原理”文档。Laravel Sanctum是我们选择包含在Laravel Jetstream身份验脚手架中的API程序包,因为我们认为它最适合大多数Web应用程序身份验证需求。总结与选择总之,如果将使用浏览器访问您的应用程序,则您的应用程序将使用Laravel的内置身份验证服务。接下来,如果您的应用程序提供了API,则您将在Passport或Sanctum之间进行选择,以为您的应用程序提供API令牌认证。通常,应该尽可能使用Sanctum,因为它是API身份验证,SPA身份验证和移动身份验证的简单,完整的解决方案,包括对“范围(scopes)”或“功能(abilities)”的支持。当您的应用程序绝对地需要OAuth2规范提供的所有功能时,可以选择Passport。而且,如果您想快速入门,我们很高兴向您推荐Laravel Jetstream,这是启动新的Laravel应用程序的快速方法,该应用程序已经使用了我们首选的Laravel内置身份验证服务和Laravel Sanctum身份验证。用以下一张图示例如何选择:参考 http://laravel.p2hp.com/docs/8.x/authentication#ecosystem-overview
2023年08月11日
14 阅读
0 评论
0 点赞
2023-08-11
一文详解PHP的内存泄露
一文详解PHP的内存泄露FPM 的黑魔法首先,传统的跑在 FPM 下的 PHP 代码是没有“内存泄漏”一说的,所谓的内存泄漏就是忘记释放内存,导致进程占用的物理内存(附1)持续增长,得益于 PHP 的短生命周期,PHP 内核有一个关键函数叫做php_request_shutdown此函数会在请求结束后,把请求期间申请的所有内存都释放掉,这从根本上杜绝了内存泄漏,极大的提高了 PHPer 的开发效率,同时也会导致性能的下降,例如单例对象,没必要每次请求都重新申请释放这个单例对象的内存。(这也是Swoole等cli方案的优势之一,因为 cli 请求结束不会清理内存)。Cli 下的内存泄漏相信 PHPer 都遇见过这个报错Fatal error: Allowed memory size of 134217728 bytes exhausted (tried to allocate 12288 bytes),是由于向 PHP 申请的内存达到了上限导致的,在 FPM 下一定是因为这次 web 请求有大内存块申请,例如 Sql 查询返回一个超大结果集,但在 Cli 下报这个错大概率是因为你的 PHP 代码出现了内存泄漏。常见的泄漏姿势有:向类的静态属性中追加数据,例如://不停的调用foo() 内存就会一直涨function foo(){ ClassA::$pro[] = "the big string";}向 $GLOBAL 全局变量中追加数据,例如://不停的调用foo() 内存就会一直涨function foo(){ $GLOBAL['arr'][] = "the big string";}向函数的静态变量中追加数据,例如://不停的调用foo() 内存就会一直涨function foo(){ static $arr = []; $arr[] = "the big string";}我们需要检测工具有的同学可能会说很简单嘛,把追加的变量在请求结束后unset()掉就可以了。但真实场景远没有你想的那么简单:例一:function foo(){$obj = new ClassA(); //foo函数结束后将自动释放 $obj对象 $obj->pro[] = str_repeat("big string", 1024);}while (1) {foo(); sleep(1);}上述代码 Cli 运行起来会泄漏吗?肉眼来看肯定不会泄漏,因为foo()函数结束后$obj是栈上的对象自动释放掉了,但答案是可能泄漏也可能没泄漏,这取决于ClassA的定义:class classA{public $pro; public function __construct() { $this->pro = &$GLOBALS['arr']; //pro是其他变量的引用 }}如果ClassA的定义是上面的样子,那么这个例子就是泄漏的!!例二:class Test{public $pro = null; function run() { $var = "Im global var now";//此处 $var 是长生命周期。 $http = new \Swoole\Http\Server("0.0.0.0", 9501, SWOOLE_BASE); $http->on("request", function($req, $resp) { //此处没有给类的静态属性赋值,没有给全局变量赋值, //也没有给函数的静态变量赋值,但是这里是泄漏的,因为 $this 变成长生命周期了。 $this->pro[] = str_repeat("big string", 1024); $resp->end("hello world"); }); $http->start(); echo "run done\n"; //输出不了 //这个函数永远不会结束,局部变量也变成了"全局变量" } }(new Test())->run();new Test()的本意虽然是创建一个临时的对象,但是run()方法触发了server->start()方法,代码将不向下执行,run()函数结束不了,run()函数的局部变量$var和临时对象本身都可以视为全局变量了,给其追加数据都是泄漏的!!例三:由于php_request_shutdown的存在,很多 PHP 扩展其实是有内存泄漏的(emalloc 后没有 efree),但是在 FPM 下是可以正常运行的,而这些扩展放到 Cli 下就会有内存泄漏问题,如果没有工具,Cli 下遇到扩展的泄漏问题,那也只能 gg 了-.-!还有就是当我们调用第三方的类库的函数,要传一个参数,这个参数是全局变量,我不知道这个第三方库会不会给这个参数追加数据,一旦追加数据就会产生泄漏,同理别人给我的函数传的参数我也不敢赋值,第三方函数的返回值有没有全局变量我也不知道。综上我们需要一个检测工具,相对于其他语言 PHP 在这个领域是空白的,可以说没有这个工具整个 Cli 生态就无法真正的发展起来,因为复杂的项目都会遇到泄漏问题。Swoole Tracker可以检测泄漏问题,但它是一款商业产品,现在我们决定重构这个工具,把内存泄漏检测的功能(下文简称Leak工具)完全免费给 PHP 社区使用,完善 PHP 生态,回馈社区,下面我将概述它的具体用法和工作原理。Swoole Tracker 用法Leak工具的实现原理是直接拦截系统底层的 emalloc,erealloc,以及 efree 调用,记录一个巨大的指针表,emalloc/erealloc 的时候添加,efree 的时候删除表中的记录,如果请求结束,指针表中仍然有值就证明产生了内存泄漏,不仅能发现 PHP 代码的泄漏,扩展层甚至 PHP 语言层面的泄漏都能发现,从根本上杜绝泄漏问题。使用方式很简单:前往官网下载最新的 tracker(3.0+) 扩展。php.ini 加入以下配置:extension=swoole_tracker.so;总开关apm.enable=1;Leak检测开关apm.enable_malloc_hook=1在 Cli 模式下主业务逻辑一定是可以抽象成循环体函数的,例如Swoole的OnReceive函数,workerman 的OnMessage函数,以及上文例一中的foo()函数, 在循环体主函数(下文简称主函数)最开始加上trackerHookMalloc()调用即可:function foo(){trackerHookMalloc(); //标记主函数,开始hook malloc $obj = new ClassA(); $obj->pro[] = str_repeat("big string", 1024);}while (1) {foo(); sleep(1);}每次调用主函数结束后(第一次调用不会被记录),都会生成一个泄漏的信息到/tmp/trackerleak日志里面。查看泄漏结果在 Cli 命令行调用trackerAnalyzeLeak()函数即可分析泄漏日志,生成泄漏报告,可以直接php -r "trackerAnalyzeLeak();"即可。下面是泄漏报告的格式:没有内存泄漏的情况:[16916 (Loop 5)] ✅ Nice!! No Leak Were Detected In This Loop其中16916表示进程 id,Loop 5表示第 5 次调用主函数生成的泄漏信息有确定的内存泄漏:[24265 (Loop 8)] /Users/guoxinhua/tests/mem_leak/http_server.php:125 => [12928][24265 (Loop 8)] /Users/guoxinhua/tests/mem_leak/http_server.php:129 => [12928][24265 (Loop 8)] ❌ This Loop TotalLeak: [25216]表示第 8 次调用http_server.php的 125 行和 129 行,分别泄漏了 12928 字节内存,总共泄漏了 25216 字节内存。通过调用trackerCleanLeak()可以清除泄漏日志,重新开始。技术特性(技术难点)支持持续增长检测:想象一个场景,第一次请求运行主函数的时候申请 10 字节内存,然后请求结束前释放掉,然后第二次请求申请了 100 字节,请求结束再释放掉,虽然每次都能正确的释放内存但是每次又都申请更多的内存,最终导致内存爆掉,Leak工具支持这种检测,如果某一行代码有N次(默认 5 次)这种行为就会报"可疑的内存泄漏",格式如下:The Possible Leak As Malloc Size Keep Growth:/Users/guoxinhua/tests/mem_leak/hook_malloc_incri.php:39 => Growth Times : [8]; Growth Size : [2304]表示 39 行有 8 次 malloc size 的增长,总共增长了 2304 字节。支持跨 loop 分析://Swoole Http Server的OnRequest回调$http->on("request", function($request, $response) {trackerHookMalloc(); if(isset(classA::$leak['tmp'])){ unset(classA::$leak['tmp']);//每一次loop都释放上一次loop申请的内存}classA::$leak['tmp'] = str_repeat("big string", 1024);//申请内存 并在本次loop结束后不释放 $response->end("hello world");});按照正常的检测泄漏的理论,上述代码每次都会检测出泄漏,因为每次都给classA::$leak['tmp']赋值并在 Loop 结束也没有释放,但实际业务代码经常这样写,并且此代码也是不会产生泄漏的,因为本次 Loop 的泄漏会在下次释放掉,Leak工具会跨相邻 2 个Loop 进行分析,自动对冲上面这种情况的泄漏信息,如果是跨多个 Loop 的释放,会以如下格式输出:[28316 (Loop 2)] /Users/guoxinhua/tests/mem_leak/hook_efree_pre_loop.php:37 => [-12288]Free Pre (Loop 0) : /Users/guoxinhua/tests/mem_leak/hook_efree_pre_loop.php:42 => [12288][28316 (Loop 2)] /Users/guoxinhua/tests/mem_leak/hook_efree_pre_loop.php:42 => [12288][28316 (Loop 2)] ✅ Nice!! No Leak Were Detected In This Loop上述信息表示 Loop 2 释放了 Loop 0 的 12288 字节内存,然后 Loop 2 又申请了 12288 字节内存,总体来说本次 Loop 跑下来没有内存泄漏。支持循环引用情况:首先简单的介绍一下循环引用问题:function foo(){$o = new classA(); $o->pro[] = $o; //foo结束后 $o无法释放,因为自己引用了自己,即循环引用}while (1) {foo(); sleep(1);}因为循环引用,上面的代码每次运行foo()内存都会增长,但是这个代码确实没有内存泄漏的,因为增长到一定程度 PHP 会开启同步垃圾回收,把这种循环引用的内存都释放掉。但是这给Leak工具带来了麻烦,因为$o的变量是延迟释放的,foo()结束后会报泄漏,而这种写法又确实不是泄漏。Swoole Tracker的Leak工具会自动识别上面的情况,会马上释放循环引用的内存,不会造成误报。如果你发现你的进程内存一直涨,开启了 Tracker 的泄漏检测,通过memory_get_usage(false);打印发现内存不涨了,那么证明你的应用存在循环引用,并且本来就没有内存泄漏问题。支持子协程统计:function loop(){ trackerHookMalloc(); classA::$leak[] = str_repeat("big string", 1024);//申请内存go(function() { echo co::getcid() . "child\n"; go(function() { echo co::getcid()."child2\n"; classA::$leak = [];//释放内存 }); });}Co\run(function(){while (1) { loop(); sleep(1); }});上述代码申请的内存会在第二个子协程里面释放,Leak工具会自动识别协程环境,会在所有子协程都结束后才统计汇总,所以上述代码不会有误报情况。支持 defer,context:$http->on("request", function($request, $response) {trackerHookMalloc(); $context = Co::getContext(); $context['data'] = str_repeat("big string", 1024);//context会在协程结束自动释放 classA::$leak[] = str_repeat("big string1", 1024); defer(function() { classA::$leak = [];//注册defer释放内存 }); $response->end("hello world");});Leak工具会自动识别协程环境,如果存在 defer 和 context,会在 defer 执行结束和 context 释放之后再统计汇总,所以上述代码不会有误报情况,当然如果上面没有注册 defer 也会正确的报告泄漏信息。支持旁路函数干扰排除:例如一个进程由主函数响应请求(OnRequest 等),然后还有个定时器在运行(旁路函数),我们希望检测的是主循环函数的泄漏情况,而当主循环函数执行到一半的时候定时器函数执行了,并申请了内存,然后又切回到主循环函数,此时会误报,Leak工具会支持识别出旁路函数然后不收集旁路函数的 malloc 数据。除了上述这些,Leak工具还支持internd string抓取等等,在此不再展开。注意前几次 Loop 的泄漏信息不用管,因为大部分项目都有一些初始化的缓存是不释放的。检测期间尽量不要有并发。由于开启泄漏检测后性能会非常差,不要在 php.ini 中开启apm.enable_malloc_hook = 1压测。和 Swoole Tracker2.x 的检查泄漏原理不一样,不能一起用。一个进程只能有一个地方调用trackerHookMalloc()函数。Swoole4.5.3由于底层 api 有问题,Leak工具无法正常工作,请升级到最新版Swoole或者降级Swoole版本。附件:免费公开课--如何正确查看进程内存占用
2023年08月11日
13 阅读
0 评论
0 点赞
2023-08-11
PHP实现简单RPC
PHP实现简单RPC1.什么是rpcRPC全称为Remote Procedure Call,翻译过来为“远程过程调用”。目前,主流的平台中都支持各种远程调用技术,以满足分布式系统架构中不同的系统之间的远程通信和相互调用。远程调用的应用场景极其广泛,实现的方式也各式各样。2.从通信协议的层面基于HTTP协议的(例如基于文本的SOAP(XML)、Rest(JSON),基于二进制Hessian(Binary))基于TCP协议的(通常会借助Mina、Netty等高性能网络框架)3.从不同的开发语言和平台层面单种语言或平台特定支持的通信技术(例如Java平台的RMI、.NET平台Remoting)支持跨平台通信的技术(例如HTTP Rest、Thrift等)4.从调用过程来看同步通信调用(同步RPC)异步通信调用(MQ、异步RPC)5.常见的几种通信方式远程数据共享(例如:共享远程文件,共享数据库等实现不同系统通信)消息队列RPC(远程过程调用)6.php实现简单的rpc目录结构image.pngrpc服务端<?php/**User: yuzhaoCreateTime: 2018/11/15 下午11:46Description: Rpc服务端 */class RpcServer {/** * User: yuzhao * CreateTime: 2018/11/15 下午11:51 * @var array * Description: 此类的基本配置 */ private $params = [ 'host' => '', // ip地址,列出来的目的是为了友好看出来此变量中存储的信息 'port' => '', // 端口 'path' => '' // 服务目录 ]; /** * User: yuzhao * CreateTime: 2018/11/16 上午12:14 * @var array * Description: 本类常用配置 */ private $config = [ 'real_path' => '', 'max_size' => 2048 // 最大接收数据大小 ]; /** * User: yuzhao * CreateTime: 2018/11/15 下午11:50 * @var nul * Description: */ private $server = null; /** * Rpc constructor. */ public function __construct($params) { $this->check(); $this->init($params); } /** * User: yuzhao * CreateTime: 2018/11/16 上午12:0 * Description: 必要验证 */ private function check() { $this->serverPath(); } /** * User: yuzhao * CreateTime: 2018/11/15 下午11:48 * Description: 初始化必要参数 */ private function init($params) { // 将传递过来的参数初始化 $this->params = $params; // 创建tcpsocket服务 $this->createServer(); } /** * User: yuzhao * CreateTime: 2018/11/16 上午12:0 * Description: 创建tcpsocket服务 */ private function createServer() { $this->server = stream_socket_server("tcp://{$this->params['host']}:{$this->params['port']}", $errno,$errstr); if (!$this->server) exit([ $errno,$errstr ]); } /** * User: yuzhao * CreateTime: 2018/11/15 下午11:57 * Description: rpc服务目录 */ public function serverPath() { $path = $this->params['path']; $realPath = realpath(__DIR__ . $path); if ($realPath === false ||!file_exists($realPath)) { exit("{$path} error!"); } $this->config['real_path'] = $realPath; } /** * User: yuzhao * CreateTime: 2018/11/15 下午11:51 * Description: 返回当前对象 */ public static function instance($params) { return new RpcServer($params); } /** * User: yuzhao * CreateTime: 2018/11/16 上午12:06 * Description: 运行 */ public function run() { while (true) { $client = stream_socket_accept($this->server); if ($client) { echo "有新连接\n"; $buf = fread($client, $this->config['max_size']); print_r('接收到的原始数据:'.$buf."\n"); // 自定义协议目的是拿到类方法和参数(可改成自己定义的) $this->parseProtocol($buf,$class, $method,$params); // 执行方法 $this->execMethod($client, $class, $method, $params); //关闭客户端 fclose($client); echo "关闭了连接\n"; } } } /** * User: yuzhao * CreateTime: 2018/11/16 上午12:19 * @param $class * @param $method * @param $params * Description: 执行方法 */ private function execMethod($client, $class, $method, $params) { if($class && $method) { // 首字母转为大写 $class = ucfirst($class); $file = $this->params['path'] . '/' . $class . '.php'; //判断文件是否存在,如果有,则引入文件 if(file_exists($file)) { require_once $file; //实例化类,并调用客户端指定的方法 $obj = new $class(); //如果有参数,则传入指定参数 if(!$params) { $data = $obj->$method(); } else { $data = $obj->$method($params); } // 打包数据 $this->packProtocol($data); //把运行后的结果返回给客户端 fwrite($client, $data); } } else { fwrite($client, 'class or method error'); } } /** * User: yuzhao * CreateTime: 2018/11/16 上午12:10 * Description: 解析协议 */ private function parseProtocol($buf, &$class, &$method, &$params) { $buf = json_decode($buf, true); $class = $buf['class']; $method = $buf['method']; $params = $buf['params']; } /** * User: yuzhao * CreateTime: 2018/11/16 上午12:30 * @param $data * Description: 打包协议 */ private function packProtocol(&$data) { $data = json_encode($data, JSON_UNESCAPED_UNICODE); } }RpcServer::instance(['host' => '127.0.0.1', 'port' => 8888, 'path' => './api'])->run();rpc 客户端<?php/**User: yuzhaoCreateTime: 2018/11/16 上午12:2Description: Rpc客户端 */class RpcClient {/** * User: yuzhao * CreateTime: 2018/11/16 上午12:21 * @var array * Description: 调用的地址 */ private $urlInfo = array(); /** * RpcClient constructor. */ public function __construct($url) { $this->urlInfo = parse_url($url); } /** * User: yuzhao * CreateTime: 2018/11/16 上午12:2 * Description: 返回当前对象 */ public static function instance($url) { return new RpcClient($url); } public function __call($name, $arguments) { // TODO: Implement __call() method. //创建一个客户端 $client = stream_socket_client("tcp://{$this->urlInfo['host']}:{$this->urlInfo['port']}", $errno, $errstr); if (!$client) { exit("{$errno} : {$errstr} \n"); } $data = [ 'class' => basename($this->urlInfo['path']), 'method' => $name, 'params' => $arguments ]; //向服务端发送我们自定义的协议数据 fwrite($client, json_encode($data)); //读取服务端传来的数据 $data = fread($client, 2048); //关闭客户端 fclose($client); return $data; }}$cli = new RpcClient('http://127.0.0.1:8888/test');echo $cli->tuzisir1()."\n";echo $cli->tuzisir2(array('name' => 'tuzisir', 'age' => 23));提供服务的文件<?php/**User: yuzhaoCreateTime: 2018/11/16 上午12:28Description: */class Test {public function tuzisir1() { return '我是无参方法'; } public function tuzisir2($params) { return $params; }}效果image.png7.RPC的注意事项性能:影响RPC性能的主要在几个方面:1.序列化/反序列化的框架2.网络协议,网络模型,线程模型等安全RPC安全的主要在于服务接口的鉴权和访问控制支持。跨平台跨不同的操作系统,不同的编程语言和平台。
2023年08月11日
17 阅读
0 评论
0 点赞
1
...
10
11
12
...
20