首页
关于
Search
1
给你10个市场数据调研报告的免费下载网站!以后竞品数据就从这里找!
184 阅读
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
篇文章
累计收到
32
条评论
首页
栏目
php
thinkphp
laravel
工具
开源
mysql
数据结构
总结
思维逻辑
令人感动的创富故事
读书笔记
前端
vue
js
css
书籍
开源之旅
架构
消息队列
docker
教程
代码片段
副业
redis
服务器
nginx
linux
科普
java
c
ElasticSearch
测试
php进阶
php基础
页面
关于
搜索到
560
篇与
的结果
2023-08-09
5天学会一种 web 开发框架
5天学会一种 web 开发框架eb framework层出不穷,特别是ruby/python,各有10+个,php/java也是一大堆 根据我自己的经验写了一个to do list,按照这个清单,一条一条的学习,事半功倍,很快就能掌握 一共25条,即便很磨蹭,2小时也能搞定一条,25*2=50。只需要50小时就能掌握任意一种web框架各类web框架大同小异:现代web开发框架的6大元素,把握主线,就不会迷路建议把本文打印到一张A4纸,搞定一条打个勾web框架学习列表如何定义 url route如何组织 request handler 函数写一个最简单的request handler 函数如何从get/post请求中取出参数如何定义全局url 拦截函数如何获取/修改/存储 cookie,session数据如何修改/输出 http header 数据如何部部署app 程序服务器部署可以参考读python web 程序的9种部署方式如何配置开发环境如何配置静态文件访问如何访问数据库是否支持ORM支持orm如何维护表结构的变更如何定义/组织/初始化 数据表如何对接orm系统和现有的表结构掌握最基本的add/delete/按字段查询/count/slice/order by如何直接使用sql 访问数据库不支持orm (这样的web框架,不用也罢)如何使用模板系统如何组织/访问 模板文件的目录结构如何在模板中嵌入代码模板是否支持继承结构模板之间如何include如何自定义模板函数如何通过http get/post 获取远程数据如何parse json如何parse xml如何输出为 json如何处理状态码:404和50x如何处理文件上传可选的学习项目发送emaillog图片处理误区表单验证辅助函数,很多框架的表单验证部分实现的特别复杂,初学者完全不需要,手写代码处理就够用ORM中的hasone,manytomany,onetomany关系,概念很复杂,其实只是多写/少写一个查询字段的关系,学习成本太高,初学者完全不需要理会,直接跳过
2023年08月09日
16 阅读
0 评论
0 点赞
2023-08-09
PHP 应用性能分析 - Davey Shafik(全)
PHP 应用性能分析 - Davey Shafik(全)PHP 性能分析第一篇: Xhprof & Xhgui 介绍原文 http://news.oneapm.com/php-xhprof-xhgui/主题 XHProf 性能分析【前言】这是国外知名博主 Davey Shafik所撰写的 PHP 应用性能分析系列的第一篇,阅读第二篇可深入了解 xhgui,第三篇则关注于性能调优实践。什么是性能分析?性能分析是衡量应用程序在代码级别的相对性能。性能分析将捕捉的事件包括:CPU的使用,内存的使用,函数的调用时长和次数,以及调用图。性能分析的行为也会影响应用性能。影响的程度取决于基准测试。基准测试在外部执行,用于衡量应用真实性能。所谓真实性能,即终端用户所体验的应用表现。什么时候应该进行性能分析?在考虑是否进行性能分析时,你首先要想:应用是否存在性能问题?如果有,你要进一步考虑:这个问题有多大?如果你不这样做,将会陷入一个陷阱——过早优化,这可能会浪费你的时间。为了评断应用是否存在性能问题,你应该确定性能目标。例如,100个并发用户的响应时间小于1s。然后,你需要进行基准测试,看是否达到这个目标。一个常见的错误是,在开发环境进行基准测试。事实上,你必须在生产环境进行基准测试。(实际生产环境或模拟的生产环境,后者很容易在 SaaS 实现(见:OneAPM 性能在线分析)。用于基准测试的产品很多,包括 ab,siege 和 JMeter。我个人比较喜欢JMeter的功能集,但 ab 和 siege 更加易用。一旦你确定应用存在性能问题,就需要分析其性能,实施改进,然后再一次进行基准测试,查看问题是否解决。每一次变更之后,你都该进行基准测试查看效果。如果你做了很多变更,却发现应用性能有所下降,你就无法确定具体是哪一次变更导致了这个问题。下图是我定义的性能生命周期:性能下降的一般原因导致性能下降的一般原因中,有些相当出人意料。即便是像 PHP 这样的高级语言,代码的好坏也很少是问题的根源。在当今的硬件配置条件下,CPU 很少是性能限制的原因。常见的原因反而是:数据存储PostgreSQLMySQLOracleMSSQLMongoDBRiakCassandraMemcacheCouchDBRedis外部资源APIs文件系统网络接口外部流程糟糕的代码选择哪一种性能分析器?在 PHP 世界里,有两个截然不同的的性能分析器——主动和被动。主动 VS 被动性能分析主动分析器在开发过程中使用,由开发人员启用。主动分析器收集的信息比被动分析器多,对性能的影响更大。通常,主动分析器不能用在生产环境中。Xdebug 就是一种主动分析器。因为无法在生产环境中使用主动分析器,Facebook 推出了一个被动分析器——XHprof。XHprof 是为了在生产环境中使用而打造的。它对性能的影响最小,同时收集足够的信息用于诊断性能问题。XHprof 和 OneAPM 都是被动分析器。通常,Xdebug 收集的额外信息对于一般的性能问题分析并不必要。这意味着,被动分析器是用于不间断性能分析的更佳选择,即使是在开发环境中。Xhprof + XhguiXhprof 由 Facebook 开发的,包含一个基本的用户界面用于查看性能数据。此外,Paul Reinheimer 开发了 Xhgui 和一个增强的用户界面(UI)用于查看、比较和分析性能数据。安装安装 XHPROFXhprof 可通过 PECL 安装,步骤如下:$pecl install xhprof-beta该 pecl 命令将尝试自动更新你的 php.ini 设置。pecl 尝试更新的文件可以使用以下命令找到:$ pecl config-get php_ini它会在指定的文件(如果有的话)顶部增加新的配置行。你可能想把他们移到一个更合适的位置。一旦你编译了该扩展程序,您必须启用它。为此,您需要在 PHP INI 文件添加以下代码:[xhprof]extension=xhprof.so之后,结合 Xhgui 就能轻松地执行性能分析与检查。安装 XHGUI安装 Xhgui,必须直接从 git 获取。该项目可以在 github 上找到,地址为https://github.com/perftools/xhguiXhgui 要求:PHP 5.3+ext/mongocomposerMongoDB(若只需要收集数据,则可选可不选;若需要数据分析,则为必选)首先,克隆项目到任意位置。在基于 Debian 的 Linux 系统(例如 Ubuntu 等等),可能是 /var/www。在 Mac OS X 系统,可能是 /Library/WebServer/Documents。$cd /var/www$ git clone https://github.com/perftools/xhgui.git$ cd xhgui$ php install.php最后一个命令是运行 composer 以安装依赖并检查 xhgui 缓存目录的权限。如果失败,你可以手动运行 composer install。下一步,你可能需要创建配置文件。这一步很容易实现,可以使用在 /path/to/xhgui/config/config.default.php 下的默认配置文件。如果你在本地运行 mongodb ,没有身份验证,则可能不需要这样做。因为它将回退为默认值。而在多服务器环境中,你会需要一个所有服务器都能进行存储的远程 mongodb 服务器,并进行恰当的配置。为提高 MongoDB 的性能,你可以运行以下指令以添加索引:$ mongouse xhprofdb.results.ensureIndex( {'meta.SERVER.REQUEST_TIME': -1} )db.results.ensureIndex( {'profile.main().wt': -1} )db.results.ensureIndex( {'profile.main().mu': -1} )db.results.ensureIndex( {'profile.main().cpu': -1} )db.results.ensureIndex( {'meta.url':1} )其他配置如果你不想在生产环境中安装 mongo ,或无法让 Web 服务器访问 mongo 服务器,您可以将性能分析数据保存在磁盘中,再导入到本地MongoDB 供以后分析。为此,请在 config.php 中进行以下修改:<?php'save.handler' = 'file','save.handler.filename' => '/path/to/xhgui/xhprof-' .uniqid("", true). '.dat',?>改变文件中的 save.handler,然后取消批注 save.handler.filename ,为其赋一个恰当的值。注意:默认每天只保存一个分析文件。一旦分析数据的准备就绪,你就可以使用 xhgui 附带的脚本导入之:$ php /path/to/xhgui/external/import.php /path/to/file.dat在此之后的步骤都相同。运行 XhguiXhgui 是以 PHP 为基础的 Web 应用程序,你可以以 /path/to/xhgui/webroot为根文件,设置一个标准的虚拟主机。或者,你可以简单地使用 PHP 5.4+ cli-server 例如:$ cd /path/to/xhgui$ php -S 0:8080 -t webroot/这将使 XHGui 在所有网络接口都可通过 8080 端口进行通信。运行性能分析器运行分析器时,你需要在待分析的所有页面包含 external/header.php 脚本。为此,你可以在 PHP ini 文件设置 auto_prepend_file 。你既可以直接在公共 INI 文件进行设置,也可以限制到单一的虚拟主机。对于 Apache 服务器,添加以下代码:php_admin_value auto_prepend_file "/path/to/xhgui/external/header.php"对于 Nginx 服务器,在服务器配置中添加以下代码:fastcgi_param PHP_VALUE "auto_prepend_file=/path/to/xhgui/external/header.php";如果您使用 PHP 5.4 + cli-server(PHP - S),则 必须 通过命令行标记进行设置:$ php -S 0:8080 -dauto_prepend_file=/path/to/xhgui/external/header.php默认情况下,分析器运行时只分析(大约) 1% 的请求。这是由以下 external/header.php 代码控制的:<?php if (rand(0, 100) !== 42) { return;}?>如果你想分析每一个请求(例如,在开发阶段),你可以将这段代码注释掉。如果你想让分析 10% 的请求,你可以做如下改动:<?phpif (rand(0, 10) !== 4) {return;}?>这允许你对一小部分用户请求进行分析,而不过多影响单个用户或太多用户。如果你想在性能分析时进行手动控制,你可以这样做:<?phpif (!isset($_REQUEST['A9v3XUsnKX3aEiNsUDZzV']) && !isset($_COOKIE['A9v3XUsnKX3aEiNsUDZzV'])) {return;} else {// Remove trace of the special variable from REQUEST_URI$_SERVER['REQUEST_URI'] = str_replace(array('?A9v3XUsnKX3aEiNsUDZzV', '&A9v3XUsnKX3aEiNsUDZzV'), '', $_SERVER['REQUEST_URI']);setcookie('A9v3XUsnKX3aEiNsUDZzV', 1);}if (isset($_REQUEST['no-A9v3XUsnKX3aEiNsUDZzV'])) {setcookie('A9v3XUsnKX3aEiNsUDZzV', 0, time() - 86400);return;}?>这段代码会检查一个随机命名的 GET/POST/COOKIE 变量(在此例中为:A9v3XUsnKX3aEiNsUDZzV),同时创建一个同名的 Cookie ,用于分析该请求的整个过程,例如:表单提交后的重定向,Ajax 请求等等。此外,它允许一个名为 no-A9v3XUsnKX3aEiNsUDZzV 的 GET/POST 变量来删除 cookie ,停止分析。当然,我们欢迎大家尝试使用 OneAPM 来为您的PHP 和Java 应用做免费的性能分析。OneAPM 独有的探针能够深入到所有 PHP 和 Java 应用内部完成应用性能管理和监控,包括代码级别性能问题的可见性、性能瓶颈的快速识别与追溯、真实用户体验监控、服务器监控和端到端的应用性能管理。 OneAPM 可以追溯到性能表现差的 SQL 语句 Traces 记录、性能表现差的第三方 API、Web 服务、Cache 等等。在下一篇文章中,我们将深入研究 Xhgui ,以及用于展示、比较 xhprof 数据的用户界面( 本文系应用性能管理领军企业OneAPM 工程师编译整理 ) 。PHP 性能分析第二篇: Xhgui In-Depth原文 http://news.oneapm.com/profiling-php-part-2-xhgui-in-depth/主题 PHP 性能分析【前言】这是国外知名博主 Davey Shafik 撰写的 PHP 应用性能分析系列的第二篇,第一篇介绍Xhprof/Xhgui,第三篇则关注于性能调优实践。在第一篇中,我们初步介绍了 xhprof,以及如何安装和运行分析器。在本文,我们将介绍 Xhgui——用于审查并比较 xhprof 数据的用户界面(UI)。使用 XhguiXhgui 提供了许多协助性能评估的功能,既适用于单次运行,也能满足聚合环境——让你精确至具体问题、发现趋势。术语为了提高 Xhgui 的使用效率,你需要熟悉许多术语:1.调用次数函数调用的次数2.[包含] 实际执行时间 (wt)函数实际执行时间3.[包含] CPU 使用/CPU 用时 (cpu)运行该函数 CPU 所用时间4.[包含] 内存使用 (mu)目前该函数使用的内存量5.[包含] 内存使用量峰值 (pmu)函数使用的内存高峰6.专一实际执行时间 (ewt)7.专一 CPU 时间 (ecpu)8.专一内存使用量 (emu)9.专一内存使用量峰值 (epmu)术语2至5都是包含型的测量指标(尽管不总是明确指出),这些指标会计算函数及其子函数的调用。术语6至9是专一型的测量指标——它们只计算函数本身的资源调用。所有的测量数值都是调用该函数后的累计值。(例如,如果一个函数调用两次,第一次用时900毫秒,第二次,因为缓存的缘故,只耗时40毫秒,最终显示的时间就是940毫秒)。准备开始一旦在 HTTP 服务器上运行 Xhgui ,你首先会看到:在顶部,你会看到一个菜单,它包含:Recent — 近期大部分运行 (分页)Longest wall time — 根据实际执行时间从最慢的运行开始排序Most CPU — 从占用 CPU 时间最多的运行开始排序Most Memory — 从占用内存最多的运行开始排序Custom View — 执行 mongo DB 自定义查询Watch Functions — 应该出现在审查页面顶部的标记函数Waterfall — 从实验性视图查看并发请求的相互影响在本教程中,我选择分析用 Wordpress 搭建的网站性能。互联网上多于18%的网站都是基于 Wordpress 搭建的,这意味着,即便是对 Wordpress 很小的性能改进, 亦能产生巨大影响。查看一次运行的性能数据分析了几个页面的性能(或导入了文件)之后,你会看到它们罗列在 Xhgui :查看一次运行的性能数据,只需点击日期。通过单击适当的表头,你可以根据实际执行时间 (wt) , CPU 时间 (CPU) 、内存使用量 (mu) 或 内存使用量峰值 (pmu) 查看这些运行情况。从而轻易找出最慢的页面。单个性能页面展示了相当多的信息。在左侧可以看到运行的总体情况,以及运行时的环境数据,包括 GET (或 POST) 数据和服务器数据:在右侧,展示了 watch function 列表:该表详细列出了函数名称,调用次数 ,专一实际执行时间 (ewt), 专一内存使用量(emu)、和专一内存使用峰值(epmu)。此外,你可能会注意到页面顶部的两个按钮,“View Callgraph(查看调用图)” 和 “Compare this run(对比此次运行)”。接下来,我们看到两个图。图一展示了专一实际执行时间最长的六个函数,该时间是用在函数本身的时间(不包含任何子函数调用所占的时间)。图二展示内存使用量最大的六个函数。这些图通常能将你指向性能瓶颈。函数的细节在下方列出。如果将鼠标滑过图中的圆柱,这些信息也将出现在提示框中。最后,我们看到性能分析器收集到的大宗信息——函数列表:该表包含一个浮动的标题栏(即便鼠标向下滚动,该栏目也会保持在屏幕顶端),包含函数名,调用次数,和前面提到的专一和包含的测量值。默认情况下,该表按专一实际执行时间排序,时间最长者排在首位。通常你不会想改变这一次序,因为这让你快速找出运行最慢的函数,除非你想看内存使用量。当你想查看一个函数的运行情况时,点击该函数,会跳转到其详细页面。该页面首先会递归展示函数本身的细节。接下来, “Parent Functions(父函数)” 部分列出所有直接调用该函数的函数。最后,“Child Functions(子函数)”列出该函数直接调用的其他函数。父函数按照专一实际运行时间,列出标准列表数据。你需要确定:是函数本身运行缓慢,还是调用它的次数太多导致累积的实际执行时间太长。通过检查该函数的调用计数,然后回顾其父函数列表。如果你觉得函数调用次数没问题,你就要看看子函数运行情况。此处才是函数运行消耗时间的部分。子函数只显示包含测量值;这是因为你想很快找到耗时最长的代码路径。你可以点击每个子函数,下钻到相同的细节视图,并进行相同的分析。比较性能数据Xhgui 最好的特性在于比较两个不同的运行。这使你:修改系统 (如启用 opcache , mysql 查询缓存) 并比较结果修改代码(代码或 SQL 优化)并比较结果将异常的运行与“正常”运行比较比较两个运行时,你必须首先选择一个基础运行。点击其日期就能看到该运行的详细信息页。接下来,单击右上角的“Compare this run” 按钮:接着会跳转到同一 URL 下的运行列表,你可以选择一个进行比较:点击你想进行比较的运行的 "Compare" 按钮,将跳转到比较页面。比较视图只显示两个运行之间的差异。在页面顶部显示比较中的两个运行,以及一些辅助修改排序的按钮。接下来是概览:尽管这个表的所有信息都有用,但特别值得注意的两个差别是 "函数调用次数" 和 "专一实际运行时间" 。函数调用次数的差别暗示着两次运行的重要差异:不同的代码路径或缓存。第一个差别可能是有意的优化导致的,但若这并非你的目的,比较这两个运行很可能不会有太大的价值。另一方面,缓存是有益且有效的提高性能的方式。这种比较很容易验证缓存是否发生。包含实际执行时间的百分比差展示了性能调优的实际成果。理想情况下,我们将看到一个较小的百分比——这是第二运行时间比上第一次运行时间的占比。在截图中,第二次运行只花了第一次运行79%的时间,这意味着性能提升了21%。最后,我们看到功能细节:请记住,该视图只展示差别。差别通过绿色的负数和红色的正数表示。(负数表明调用次数更少,实际执行时间更短,CPU 耗时更短或内存消耗更少)如果没有差异,则显示为灰色的0。与其他表一样,您可以在任意列进行排序,默认的顺序方式是函数的调用次序。在这里你可以验证,你做的改变是否确有效果,是否为预期效果。你也可以在性能下降时使用该视图追踪原因。性能提高的一个好例子是:基于一个条件只调用一个函数——例如,您可能不需要过滤数据,如果之前已经做了。当你做出这种改变时,你会预期过滤函数的调用次数减少,从而性能提升。这两件事都可以在此处得到验证,以及其他意想不到的原因——你的条件比过滤本身需要更长的时间?如果真是如此,这将对性能产生负面影响。在此处,我们可以看到, NOOP Translations::translate 和 apply filter 的调用次数都减少了,但是 apply_filter 的专一内存使用量增加了133560个字节!发现趋势对我来说,Xhgui 最强大的功能是查看趋势。因为 xhprof 是被动分析器,可以在所有环境中启用 (dev、qa、阶段性、生产),可以持续地对流量取样分析。审查给定 URL 的所有数据,只需在运行列表点击它:这将跳转到该 URL 运行页面。该页面显示两个重要图表。第一个显示实际运行时间和 CPU 时间,第二个显示内存使用情况和峰值内存使用。这些图表列表中运行的数据,包括每次运行的 URL,时间,实际运行时间、CPU时间、内存使用和峰值。这些图是查看趋势和异常值的关键所在。但是该如何处理这些信息呢?对于数据异常者,首先你可以将鼠标悬浮在它上面验明正身,接着,你可以看一下它的单次运行。或用其他正常运行与其比较,从而发现不同。对于趋势,最好的选择是审查趋势开始的时间——你在此时添加缓存了吗?随着缓存变得更加完整,整体趋势应该向下。或者你的缓存失效,你将看到一个上升趋势,此时缓存正在重建。默认情况下,这些图表显示最近100次运行,你可以点击下一页去查看更久远时间的运行。另外,你可以点击搜索按钮来定制显示的界面:单击该按钮将显示搜索表单:你可以搜索具体日期之间的运行。也可以查看最近30分钟、1小时、2小时、12小时、24小时、1周、2周或30天内的运行——更小的时间间隔适合评估性能调优的结果。最后,你可以使用 PHPs DateTimeIntervalInterval 规范格式 指定自定义时间区间——例如,最近2天可使用 P2D,最近15分钟可使用 PT15M。Watch FunctionsWatch functions 允许你通过正则表达式识别特定的函数,或函数组,并显示在单个运行页面(见前文)。因为可以使用正则表达式,我们可以轻易地查看一个模块或扩展中的功能。For example, to watch all MySQL activity, simply add one of the following:例如,查看所有 MySQL 活动,只需添加如下列表的任意一项:mysql_(.*)for ext/mysqlmysqli(.*)for ext/mysqlipdo(.*)for PDO (适用于所有PDO-based数据库交互)如果你使用诸如 Propel 的 ORM,你可能使用 (. )Query::(. ) 追踪所有 Query 类。调用图(Callgraphs)Xhprof 的最后一部分是调用图 ,该图展示运行的代码执行路径。点击单一运行页面顶部的“View Callgraph”按钮即可查看调用图。在调用图中,拖拽结点可以更好地查看数据。鼠标悬浮在每个点击上,会显示其包含实际执行时间,同时允许你进入该函数的详情页。更直观地查看图,请点击: 体验免费使用OneAPM在线PHP应用性能分析SaaS服务!下一章节在第三部分也即最后一部分,我们会使用 xhprof 数据来优化代码。我们也会简单地介绍其他优化代码的工具。 (本文系应用性能管理领军企业OneAPM 工程师编译整理)PHP 性能分析第三篇: 性能调优实战原文 http://www.ituring.com.cn/article/205844主题 PHP 性能分析 数据库注意:本文是我们的 PHP 性能分析系列的第三篇,点此阅读 PHP 性能分析第一篇: XHProf & XHGui 介绍 ,或 PHP 性能分析第二篇: 深入研究 XHGui 。在本系列的 第一篇 中,我们介绍了 XHProf 。而在 第二篇 中,我们深入研究了 XHGui UI, 现在最后一篇,让我们把 XHProf /XHGui 的知识用到工作中!性能调优不用运行的代码才是绝好的代码。其他只是好的代码。所以,性能调优时,最好的选择是首先确保运行尽可能少的代码。OpCode 缓存首先,最快且最简单的选择是启用 OpCode 缓存。OpCode 缓存的更多信息可以在这里 找到。在上图,我们看到启用 Zend OpCache 后发生的情况。最后一行是我们的基准,也即没有启用缓存的情况。在中间行,我们看到较小的性能提升,以及内存使用量的大幅减少。小的性能提升(很可能)来自 Zend OpCache 优化,而非 OpCode 缓存。第一行是优化和 OpCode 缓存后结果,我们看到很大的性能提升。现在,我们看看 APC 之前和之后的变化。如上图所示,跟 Zend OpCache 相比,随着缓存的建立,我们看到初始(中间行)请求的性能下降,在消耗时长与内存使用量方面的表现都明显下降。接着,随之 opcode 缓存的建立,我们看到类似的性能提升。内容缓存第二件我们能做的事是缓存内容——这对 WordPress 而言小菜一碟。它提供了许多安装简便的插件来实现内容缓存,包括 WP Super Cache。WP Super Cache 会创建网站的静态版本。该版本会在出现诸如评论事件时依照网站设置自动过期。(例如,在非常高负载情况下,您可能会想禁止任何原因造成的缓存过期)。内容缓存只能在几乎没有写操作时有效运行,写操作会使缓存失效,而读操作不会。你也应该缓存应用从第三方 API 处收到的内容,从而减少由于 API 可用性导致的延迟与依赖。 WordPress 有两个缓存插件,可以大大提高网站的性能: W3 Total Cache 和 WP Super Cache 。这两个插件都会创建网站的静态 HTML 副本,而不是每次收到请求时再生成页面,从而压缩响应时间。如果你正在开发自己的应用程序,大多数框架都有缓存模块:Zend Framework 2: Zend\CacheSymfony 2: Multiple optionsLaravel 4: Laravel CacheThinkPHP 3.2.3: ThinkPHP Cache查询缓存另一个缓存选项是查询缓存。针对 MySQL,有一个通用的查询缓存帮助极大。对于其他数据库,将查询结果集缓存在 Memcached 或者 cassandra 这样的内存缓存,也非常有效。跟内容缓存一样,查询缓存在包含大量读取操作的场景是最有效的。由于少量的数据改动就会使大块的缓存区无效,尤其不能在这种情况下依赖 MySQL 查询缓存来提高性能。查询缓存或许在生成内容缓存时对性能有提升。如下图所示,当我们开启查询缓存后,实际运行时间减少了 40% ,尽管内存使用量没有明显改变。现有三种类型的缓存选项,由 query_cache_type 控制设置。设置值为 0 或 OFF 将禁用缓存设置值为 1 或 ON 将缓存除了以 SELECT SQL_NO_CACHE 开头之外的所有选择设置值为 2 或 DEMAND 只会缓存以 SELECT SQL_CACHE 开头的选择此外,你应该将 query_cache_size 设置为非零值。将它设置为零将禁用缓存,不管 query_cache_type 是否设置。想得到设置缓存的帮助,与许多其他性能相关的设置,请查看 mysql-tuning-primer脚本。MySQL 查询缓存的主要问题是,它是全局的。对缓存结果集构成的表格的任何更改都将导致缓存失效。在写入操作频繁的应用程序中,这将使缓存几乎无效。然而,你还有许多其他选择,可以根据你的需求和数据集建立更多的智能缓存,例如 Memcached , riak , cassandra 或 redis查询优化如前所述 ,数据库查询常常是程序执行缓慢的原因,查询优化往往能比代码优化带来更多切身的好处。查询优化有助于生成内容缓存时提高性能,而且,在无法缓存这种最坏的情况下也有益处。除了分析, MySQL 还有一个帮助识别慢查询的选择——慢查询日志。慢查询日志会记录所有耗时超过指定时间的查询,以及不使用索引的查询(后者为可选项)。您可以在 my.cnf 中使用以下配置启用日志。[mysqld]log_slow_queries =/var/log/mysql/mysql-slow.log long_query_time =1log-queries-not-using-indexes任何查询如果慢于 long_query_time (以秒为单位),该查询就会记录到日志文件log_slow_queries 中。默认值是10秒,最低1秒。此外, log-queries-not-using-indexes 选项可以将任何不使用索引的查询捕获到日志中。之后我们可以用与 MySQL 捆绑在一起的 mysqldumpslow 命令检查日志。在 WordPress 安装时使用这些选项 ,主页加载完成并运行后得到如下数据:$ mysqldumpslow -g "wp_" /var/log/mysql/mysql-slow.logReading mysql slow query log from /var/log/mysql/mysql-slow.logCount: 1 Time=0.00s (0s) Lock=0.00s (0s) Rows=358.0(358), user[user]@[host] SELECT option\_name, option\_value FROM wp_options WHERE autoload ='S'Count: 1 Time=0.00s (0s) Lock=0.00s (0s) Rows=41.0(41), user[user]@[host] SELECT user\_id, meta_key, meta_value FROM wp_usermeta WHERE user_id IN (N)首先,注意所有字符串值都以 S 表示,数字则以 N 表示。你可以添加 -a 标志来显示这些值。 接下来,请注意,这两个查询均耗时 0.00 s,这意味着他们的耗时在 1 秒的阈值以下,且没有使用索引。在 MySQL 控制台 使用 EXPLAIN,可以检查性能下降的原因:mysql> EXPLAIN SELECT option_name, option_value FROM wp_options WHERE autoload = 'S'\G*************************** 1. row ***************************id: 1select_type: SIMPLEtable: wp_optionstype: ALLpossible_keys: NULLkey: NULLkey_len: NULLref: NULLrows: 433Extra: Using where此处,我们看到 possible_keys 是 NULL,从而确认未使用索引。EXPLAIN是对优化 MySQL 查询非常强大的工具,更多信息可以在 这里 找到。PostgreSQL 同样也包括一个 EXPLAIN (该 EXPLAIN 与 MySQL 的差别很大),而 MongoDB 有 $explain 元 操作符 。代码优化通常只有当你不再受到 PHP 本身限制(通过使用 OpCode 缓存),缓存了尽可能多的内容,优化了查询之后,才可以开始调整代码。代码和查询优化带来足够的性能提升才能创建其他缓存;代码在最糟糕的环境(没有缓存)下性能越高,应用就越稳定,重建缓存的速度也就越快。让我们看看如何(潜在地)优化我们的 WordPress 安装。首先,让我们看看最慢的函数:令我惊讶的是,列表中的第一项 不是 MySQL (事实上 mysql_query() 是第四),而是 apply_filter() 函数。WordPress 代码库的特点是,通过基于事件的过滤系统执行多种数据转换,执行次序按照数据经内核、插件添加或回调的顺序。apply_filter()函数是这些回调应用的地方。首先,你可能会注意到,函数被调用 4194 次。如果我们点击查看更多细节,就可以按照“调用次数”降序排列“父函数”,从而发现 translate() 调用了 apply_filter() 函数 778 次。这很有趣,因为实际上我不使用任何翻译。我(并怀疑大多数用户)在使用 WordPress 软件时都设置为本土语言:英语。因此,让我们点击查看细节,进一步查看该 translate() 函数在做什么。在这里,我们看到两间有趣的事。首先,在父函数中,有一个被调用了773次:__()。查看该函数的源代码后,我们发现它是 translate() 的包装器。<?php/*** Retrieves the translation of $text. If there is no translation, or* the domain isn't loaded, the original text is returned.** @see translate() An alias of translate()* @since 2.1.0** @param string $text Text to translate* @param string $domain Optional. Domain to retrieve the translated text* @return string Translated text*/function __( $text, $domain = 'default' ) {return translate( $text, $domain );}?>根据经验法则,函数调用代价昂贵,应该尽量避免。现在我们总是调用 __() 而不是translate() ,我们应该把别名改为 translate() 来保持向后兼容性,而 __() 则不再调用非必要的函数。然而,实际上,这种改变不会带来多大的差异,只是微观的优化罢了——但它的确提高了代码可读性,简化了调用图。继续前进,让我们看看子函数:现在,深入该函数,我们看到有 3 个 函数或方法被调用,每个 778 次:get_translations_for_domain()NOOP_Translations::translate()apply_filters()按照包容性实际运行时间降序排列,我们看到 apply_filter() 是目前为止耗时最长的调用。查看代码:<?phpfunction translate( $text, $domain = 'default' ) {$translations = get_translations_for_domain( $domain );return apply_filters( 'gettext', $translations->translate( $text ), $text, $domain );}?>这段代码的作用是检索一个翻译对象,然后将 $translations->translate() 的结果传给apply_filter() 。我们发现 $translations 是 NOOP_Translations 类的一个实例。仅根据名称( NOOP ),再经代码中的注释证实,我们发现翻译器实际上没有任何动作!<?php/*** Provides the same interface as Translations, but doesn't do anything*/class NOOP_Translations {?>因此,也许我们完全可以避免这种代码!通过在代码上进行小规模调试,我们看到当前使用的是默认的域,我们可以修改代码以忽略翻译器:<?phpfunction translate( $text, $domain = 'default' ) {if ($domain == 'default') { return apply_filters( 'gettext', $text, $text, $domain );} $translations = get_translations_for_domain( $domain ); return apply_filters( 'gettext', $translations->translate( $text ), $text, $domain );}?>接下来,我们再次分析,确保要运行 至少两次 ——确保所有缓存都建立,才是公平的对比!这次运行的确更快!但是,快多少?为什么?使用 XHGui 的比较运行这一特性就能找到答案。回到我们最初的运行,点击右上角的 “比较此处运行” 按钮,并从列表中选择新的运行。我们发现,函数调用的次数减少了3% ,包容性实际运行时间减少 9% ,包容性CPU时间减少12%!之后,可以按调用次数降序排列细节页,这证实(如同我们的预期)get_translations_for_domain() 和 NOOP_Translations::translate() 函数的调用次数减少。同样,可以确认没有预料之外的变化发生。30 分钟的工作带来9 - 12% 的性能提升,这非常可喜。这就意味着真实世界的性能收益,即便是在应用了 opcache 之后。现在我们可以对其函数重复这个过程,直到找不到更多优化点。注意:此更改已提交到 WordPress.org 并已获更新。你可以在 WordPress Bug Tracker 跟踪讨论,查看实践过程。此更新计划包含在 WordPress 4.1 版本中。其他工具除了出色的 XHProf/XHGui,还有一些很好的工具。New Relic & OneAPMNew Relic 与 OneAPM 均提供前后端性能分析;洞察后台堆栈讯息,包括 SQL 查询与代码分析,前端 DOM 与 CSS 呈现,以及 Javascript 语句。 OneAPM 更多功能请移步 ( OneAPM 在线DEMO ) uprofileruprofiler 是目前还未发布的 Facebook XHProf 分支,该分支计划删除 Facebook 所需的 CLA。目前,两者具备相同的特性,只有一些部分重命名了。XHProf.ioXHProf.io 是 XHProf 的另一种用户界面。XHProf.io 在配置文件存储使用 MySQL ,用户友好性方面不及 XHGui。Xdebug在 XHProf 出现之前, Xdebug 早已存在——Xdebug 是一种主动的性能分析器,这意味着它不应该用于生产环境,但可以深入了解代码。然而,它必须与另一个工具配合使用以读取分析器的输出 , 比如 KCachegrind。但是 KCachegrind 很难安装在非 linux 机器上。另一个选择是 Webgrind 。Webgrind 无法提供 KCachegrind 的那些特性,但它是一个 PHP Web 应用程序,在任何环境都易于安装。若搭配 KCachegrind ,你可以轻易探索并发现性能问题。(事实上,这是我最喜欢的剖析工具!)结语分析和性能调优是非常复杂的工程。有了对的工具,并理解如何善用这些工具,我们可以很大程度地提高代码质量——即使是对我们不熟悉的代码库。花时间去探索和学习这些工具是绝对值得的。注意:本文是我们的 PHP 性能分析系列的第三篇,阅读 PHP 性能分析第一篇: XHProf & XHGui 介绍 ,和 PHP 性能分析第二篇: 深入研究 XHGui 。( 本文系 应用性能管理 领军企业 OneAPM 工程师编译整理 )
2023年08月09日
10 阅读
0 评论
0 点赞
2023-08-09
PHP 底层的运行机制和工作原理
PHP 底层的运行机制和工作原理(一)PHP 底层的运行机制和工作原理(二)
2023年08月09日
16 阅读
0 评论
0 点赞
2023-08-09
PHP编程中的锁
PHP编程中的锁最近看了《理解Linux进程》这本开源书,链接。该书描述了linux中的进程概念,对锁和进程间通信(IPC)有一些总结。不过该书的描述语言是golang, 平时用的比较少,就想对应概念找找php中的接口。文件锁全名叫 advisory file lock, 书中有提及。 这类锁比较常见,例如 mysql, php-fpm 启动之后都会有一个pid文件记录了进程id,这个文件就是文件锁。这个锁可以防止重复运行一个进程,例如在使用crontab时,限定每一分钟执行一个任务,但这个进程运行时间可能超过一分钟,如果不用进程锁解决冲突的话两个进程一起执行就会有问题。使用PID文件锁还有一个好处,方便进程向自己发停止或者重启信号。例如重启php-fpm的命令为kill -USR2 cat /usr/local/php/var/run/php-fpm.pid发送USR2信号给pid文件记录的进程,信号属于进程通信,会另开一个篇幅。php的接口为flock,文档比较详细。先看一下定义,bool flock ( resource $handle , int $operation [, int &$wouldblock ] ).$handle是文件系统指针,是典型地由 fopen() 创建的 resource(资源)。这就意味着使用flock必须打开一个文件。$operation 是操作类型。&$wouldblock 如果锁是阻塞的,那么这个变量会设为1.需要注意的是,这个函数默认是阻塞的,如果想非阻塞可以在 operation 加一个 bitmask LOCK_NB. 接下来测试一下。$pid_file = "/tmp/process.pid";$pid = posix_getpid();$fp = fopen($pid_file, 'w+');if(flock($fp, LOCK_EX | LOCK_NB)){echo "got the lock \n"; ftruncate($fp, 0); // truncate file fwrite($fp, $pid); fflush($fp); // flush output before releasing the lock sleep(300); // long running process flock($fp, LOCK_UN); // 释放锁定} else {echo "Cannot get pid lock. The process is already up \n";}fclose($fp);保存为 process.php,运行php process.php &, 此时再次运行php process.php,就可以看到错误提示。flock也有共享锁,LOCK_SH.互斥锁和读写锁sync模块中的MutexMutex是一个组合词,mutual exclusion。用pecl安装一下sync模块, pecl install sync。 文档中的SyncMutex只有两个方法,lock 和 unlock, 我们就直接上代码测试吧。没有用IDE写,所以cs异常丑陋,请无视。$mutex = new SyncMutex("UniqueName");for($i=0; $i<2; $i++){$pid = pcntl_fork(); if($pid <0){ die("fork failed"); }elseif ($pid>0){ echo "parent process \n"; }else{ echo "child process {$i} is born. \n"; obtainLock($mutex, $i); }}while (pcntl_waitpid(0, $status) != -1) {$status = pcntl_wexitstatus($status); echo "Child $status completed\n"; }function obtainLock ($mutex, $i){echo "process {$i} is getting the mutex \n"; $res = $mutex->lock(200); sleep(1); if (!$res){ echo "process {$i} unable to lock mutex. \n"; }else{ echo "process {$i} successfully got the mutex \n"; $mutex->unlock(); } exit();}保存为mutex.php, run php mutex.php, output isparent process parent process child process 1 is born. process 1 is getting the mutex child process 0 is born. process 0 is getting the mutex process 1 successfully got the mutex Child 0 completedprocess 0 unable to lock mutex. Child 0 completed这里子进程0和1不一定谁在前面。但是总有一个得不到锁。这里SyncMutex::lock(int $millisecond)的参数是 millisecond, 代表阻塞的时长, -1 为无限阻塞。sync模块中的读写锁SyncReaderWriter的方法类似,readlock, readunlock, writelock, writeunlock,成对出现即可,没有写测试代码,应该和Mutex的代码一致,把锁替换一下就可以。sync模块中的Event感觉和golang中的Cond比较像,wait()阻塞,fire()唤醒Event阻塞的一个进程。有一篇好文介绍了Cond, 可以看出Cond就是锁的一种固定用法。SyncEvent也一样。php文档中的例子显示,fire()方法貌似可以用在web应用中。上测试代码for($i=0; $i<3; $i++){$pid = pcntl_fork(); if($pid <0){ die("fork failed"); }elseif ($pid>0){ //echo "parent process \n"; }else{ echo "child process {$i} is born. \n"; switch ($i) { case 0: wait(); break; case 1: wait(); break; case 2: sleep(1); fire(); break; } }}while (pcntl_waitpid(0, $status) != -1) {$status = pcntl_wexitstatus($status); echo "Child $status completed\n"; }function wait(){$event = new SyncEvent("UniqueName"); echo "before waiting. \n"; $event->wait(); echo "after waiting. \n"; exit();}function fire(){$event = new SyncEvent("UniqueName"); $event->fire(); exit();}这里故意少写一个fire(), 所以程序会阻塞,证明了 fire() 一次只唤醒一个进程。pthreads模块貌似也看到了Mutex, Cond, Pool. 没来得及看,看完再补充。信号量sync模块中的信号量SyncSemaphore文档中显示,它和Mutex的不同之处,在于Semaphore一次可以被多个进程(或线程)得到,而Mutex一次只能被一个得到。所以在SyncSemaphore的构造函数中,有一个参数指定信号量可以被多少进程得到。public SyncSemaphore::__construct ([ string $name [, integer $initialval [, bool $autounlock ]]] ) 就是这个$initialval (initial value)$lock = new SyncSemaphore("UniqueName", 2);for($i=0; $i<2; $i++){$pid = pcntl_fork(); if($pid <0){ die("fork failed"); }elseif ($pid>0){ echo "parent process \n"; }else{ echo "child process {$i} is born. \n"; obtainLock($lock, $i); }}while (pcntl_waitpid(0, $status) != -1) {$status = pcntl_wexitstatus($status); echo "Child $status completed\n"; }function obtainLock ($lock, $i){echo "process {$i} is getting the lock \n"; $res = $lock->lock(200); sleep(1); if (!$res){ echo "process {$i} unable to lock lock. \n"; }else{ echo "process {$i} successfully got the lock \n"; $lock->unlock(); } exit();}这时候两个进程都能得到锁。sysvsem模块中的信号量sem_get 创建信号量sem_remove 删除信号量(一般不用)sem_acquire 请求得到信号量sem_release 释放信号量。和 sem_acquire 成对使用。$key = ftok('/tmp', 'c');$sem = sem_get($key);for($i=0; $i<2; $i++){$pid = pcntl_fork(); if($pid <0){ die("fork failed"); }elseif ($pid>0){ //echo "parent process \n"; }else{ echo "child process {$i} is born. \n"; obtainLock($sem, $i); }}while (pcntl_waitpid(0, $status) != -1) {$status = pcntl_wexitstatus($status); echo "Child $status completed\n"; }sem_remove($sem); // finally remove the semfunction obtainLock ($sem, $i){echo "process {$i} is getting the sem \n"; $res = sem_acquire($sem, true); sleep(1); if (!$res){ echo "process {$i} unable to get sem. \n"; }else{ echo "process {$i} successfully got the sem \n"; sem_release($sem); } exit();}这里有一个问题,sem_acquire()第二个参数$nowait默认为false,阻塞。我设为了true,如果得到锁失败,那么后面的sem_release会报警告 PHP Warning: sem_release(): SysV semaphore 4 (key 0x63000081) is not currently acquired in /home/jason/sysvsem.php on line 33, 所以这里的release操作必须放在得到锁的情况下执行,前面的几个例子中没有这个问题,没得到锁执行release也不会报错。当然最好还是成对出现,确保得到锁的情况下再release。此外,ftok这个方法的参数有必要说明下,第一个 必须是existing, accessable的文件, 一般使用项目中的文件,第二个是单字符字符串。返回一个int。输出为parent process parent process child process 1 is born. process 1 is getting the mutex child process 0 is born. process 0 is getting the mutex process 1 successfully got the mutex Child 0 completedprocess 0 unable to lock mutex. Child 0 completed最后,如果文中有错误的地方,希望大神指出,帮助一下菜鸟进步,谢谢各位。
2023年08月09日
19 阅读
0 评论
0 点赞
2023-08-09
PHP 收藏集
收藏集PHP 收藏集(一)csdnPHP 收藏集(二)Awesome PHP其他文章ThinkPHPhttps://blog.p2hp.com/archives/11222 ThinkPHPV8.0发布——AI助力开发体验一些laravel 框架学习资源http://www.cnblogs.com/yjf512/p/4061892.html 使用laravel一分钟搭建CURD后台页面https://www.laravist.com/blog/post/programming-with-laravel-5-model-controller-view-basic-workflow Laravel教程 五:MVC的基本流程https://www.laravist.com/blog/post/programming-with-laravel-5-laravel-forms-input Laravel教程 六:表单 Formshttps://www.laravist.com/blog/post/programming-with-laravel-5-database-and-eloquent-model Laravel教程 四:数据库和Eloquenthttps://my.oschina.net/u/1186749/blog/643850 php artisan常用方法http://www.verronknowles.com/laravel-migrations-with-moloquent-and-mongodb-with-mysql-still-present/http://www.opentechguides.com/tutorials/laravel-mongodb/10/mongodb-migration.htmlhttp://blog.sina.com.cn/s/blog_a77576280102x60a.htmlhttp://www.jb51.net/article/54736.htmhttp://blog.csdn.net/iroycn/article/details/47036719http://www.jb51.net/article/60989.htmhttps://blog.p2hp.com/archives/7442https://blog.p2hp.com/archives/7392 加速你的laravel框架运行, 教你如何减少服务提供者的启动.https://blog.p2hp.com/archives/7523https://blog.p2hp.com/archives/7551 使用Laravel Packer创建laravel包脚手架https://blog.p2hp.com/archives/7853 Laravel Model 利用 Macroable 为数据模型添加宏能力https://blog.p2hp.com/archives/7829 Laravel Telescope 完美的应用调试工具https://blog.p2hp.com/archives/8110 Laravel框架的Pipeline解读。它是一个非常好用的组件,能够使代码的结构非常清晰。 Laravel的中间件机制便是基于它来实现的。通过Pipeline,可以轻松实现APO编程。https://blog.p2hp.com/archives/7883 终极Laravel应用性能检查表https://blog.p2hp.com/archives/8122 Laravel框架中使用 Repository 模式Lhttps://blog.p2hp.com/archives/8120 aravel核心解读–服务提供器(ServiceProvider)https://blog.p2hp.com/archives/8114 优化laravel数据库查询的 18 个提示https://blog.p2hp.com/archives/8821 Laravel 的 ORM 缓存包https://blog.p2hp.com/archives/8677 18 个 Laravel 8 数据库查询优化建议https://blog.p2hp.com/archives/10241 Laravel 10 现已发布!新特性一览了解 Laravel 的 Macroable 特性https://blog.p2hp.com/archives/11067https://blog.p2hp.com/archives/11065框架关于如何正确使用PHP框架及如何选择框架之我见微信开发关于微信网页授权获取用户基本信息的切入问题 微信公众平台—-带参数二维码生成和扫描事件 微信开发上传图文消息内的图片(只返回图片URL),报错41005 【微信开发】网页授权多域名解决方案服务器php在Nginx环境下进行刷新缓存立即输出,实现常驻进程轮询 php nginx 实时输出 https://blog.p2hp.com/archives/5808 解决PHP的一个长期存在的通过Opcache泄漏敏感数据的问题。 https://blog.p2hp.com/archives/5789 为PHP-FPM和nginx设置多个进程池而安全地运行多个网站https://blog.p2hp.com/archives/5778 nginx不同站点的php-fpm的PHP_ADMIN_VALUE值会覆盖其它站点值的问题!https://blog.p2hp.com/archives/5814 共享APC或OPcache:为什么多个PHP-FPM主机更好https://blog.p2hp.com/archives/7768 nginx + php做服务,在高并发的时候会出现一些错误 connect() to unix:/var/run/php-fpm.sock failed (11: Resource temporarily unavailable) 。mongodb用PHP把 图片,文件上传到 mongodb gridfs 中中间件为什么要关心PHP中间件 关于中间件的一切 漫谈php框架之中间件 在PHP中实现前置/后置中间件其他周末有空,我们来聊聊几块钱的PHP 原 PHP 在不调用构造函数的情况下创建对象 https://learnku.com/php/t/24576https://blog.p2hp.com/archives/6655 PHP加密文件解密过程详解https://blog.p2hp.com/archives/6648 PHP获取类私有属性的几种方式https://blog.p2hp.com/archives/7061【PHP】获取浏览器HTTP请求header信息、获取服务器HTTP响应header信息https://blog.p2hp.com/archives/7364 php设置跨子域名可读的cookiePHP 生成器入门 https://blog.p2hp.com/archives/7243模仿laravel,使用反射来实现自动依赖注入 https://www.jianshu.com/p/9176b12eb843https://stitcher.io/blog/php-in-2020PHP8.0 JIT介绍,及如何在PHP 8中设置JIT https://blog.p2hp.com/archives/7577如何处理 PHP 中file_get_contents 函数的警告?https://blog.p2hp.com/archives/7846Zend Engine中的函数内联-使用完全限定函数名称提高PHP程序性能 https://blog.p2hp.com/archives/7967 Zend PHP8.1发布,新特性一览 https://blog.p2hp.com/archives/8224Laravel 8更改密码功能实现 https://blog.p2hp.com/archives/8210如何优化symfony(PHP)的性能 https://blog.p2hp.com/archives/8196How Livewire works (a deep dive) https://blog.p2hp.com/archives/8190移除无用的Composer加载文件,以提升性能 https://blog.p2hp.com/archives/8178现代 PHP 数据加密/解密与Sodium扩展 https://blog.p2hp.com/archives/8174PHP–激动人心的时代即将到来,让我们来看看现代的 PHP https://blog.p2hp.com/archives/8310Modern PHP Without a Framework–现代PHP不使用框架 https://blog.p2hp.com/archives/9205PHP 8.2 新特性 https://blog.p2hp.com/archives/9144PHP mysqli 查询数据库的几个方法 https://blog.p2hp.com/archives/90632023 年的 PHP https://blog.p2hp.com/archives/10124如何延长遗留 PHP 应用程序的生命周期 https://blog.p2hp.com/archives/10122Nginx 黑魔法:使用 NGX-PHP 模块低成本实现高性能应用 https://blog.p2hp.com/archives/10540https://blog.p2hp.com/archives/10282https://blog.p2hp.com/archives/10279https://blog.p2hp.com/archives/10268spatie / invade 使用私有属性和方法的 PHP 函数 https://blog.p2hp.com/archives/11069知识点web全栈大福袋 https://www.52fun.com/13860.htmlhttps://www.itresource.org/2023/08/03/web3.0热门领域nft项目实战完结/
2023年08月09日
12 阅读
0 评论
0 点赞
1
...
74
75
76
...
112