尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

从“打开就卡“到“秒开“:一次 PHP GD 动态海报生成的性能优化实战

从“打开就卡“到“秒开“:一次 PHP GD 动态海报生成的性能优化实战 一、背景在做一个合伙人分销系统时有一个很常见的功能推广海报下载——用户点一下下载推广海报按钮页面会生成一张带专属二维码、带推荐人手机号水印的海报图方便用户长按保存转发。上线后收到反馈手机打开这个页面会先卡顿一段时间然后图片才唰地一下出现。本文记录一下这次排查和优化的完整过程思路对做类似动态合成图片/海报功能的同学应该有参考价值。二、排查先搞清楚图是怎么画出来的拿到问题第一反应是是不是前端用了 canvas / html2canvas 之类的方案主线程被阻塞了于是先撸了一遍代码结论出乎意料这张海报完全是服务端用 PHP 的 GD 库实时合成的跟前端 canvas 没有任何关系。流程大致是这样用户点击下载推广海报 │ ▼ window.location.href 整页跳转到 download_partner_qrcode.php │ ▼ 返回一个壳子页面里面塞了一个 img srcdownload_partner_qrcode.php?download1 │ ▼ 浏览器请求这个 img src真正的合成逻辑在这次请求里同步执行 1. imagecreatefromjpeg() 解码 1535×2628 的 JPG 海报模板 2. file_get_contents() 同步请求第三方二维码生成服务 3. imagecopyresampled() 把二维码贴到模板上 4. imagettftext() 逐字绘制推荐人xxx文字还要做仿斜体效果 5. imagejpeg() 用 quality95 重新编码整张图流式输出问题一下就清晰了卡顿的锅主要在这三个地方1. 完全没有缓存每次访问都重新生成一遍响应头里赫然写着header(Cache-Control: no-cache, no-store, must-revalidate);header(Pragma: no-cache);header(Expires: 0);也就是说哪怕同一个用户的推广码、手机号从来没变过每刷新一次页面服务器都要老老实实把上面 5 个步骤重新跑一遍。CPU 图像处理开销 网络请求开销全部白白浪费。2. 同步依赖第三方接口还给了 30 秒超时$contextstream_context_create([http[timeout30,...]]);$qrcodeDatafile_get_contents($qrcodeUrl,false,$context);二维码是实时调用第三方接口http://barcode.tczss.com/qrcode.php生成的这是一个同步阻塞调用卡在这里整个响应就出不来。一旦第三方服务慢一点、抖一下用户能等足足 30 秒——这就是卡顿最直接的元凶。而且没有任何本地兜底方案。3. 前端没有任何加载态整页跳转 img裸标签图片没加载出来之前用户看到的就是一片空白跟卡死了没什么区别体验雪上加霜。三、优化方案问题定位清楚之后方案其实很直接没有伤筋动骨地重写架构三板斧1. 加一层磁盘缓存收益最大的一步推广码、手机号这些内容基本是稳定不变的完全没必要每次都重新合成。加一个基于文件的缓存层$cacheDir__DIR__./../cache/posters/;$cacheKeymd5(partner_.$agentId._.$agent[promo_code]._.$maskedMobile);$cacheFile$cacheDir.$cacheKey..jpg;// 命中缓存直接秒回跳过整个 GD 合成流程if(is_file($cacheFile)){header(Content-Type: image/jpeg);header(Cache-Control: public, max-age86400);readfile($cacheFile);exit;}// ... 未命中才走原来的 GD 合成逻辑 ...// 合成完成后写入缓存同时输出给浏览器imagejpeg($template,$cacheFile,95);imagedestroy($template);readfile($cacheFile);缓存 key 用agent_id promo_code 脱敏手机号做 md5既保证不同合伙人互不冲突又保证一旦手机号这类内容变化会自动生成新文件、不会用错缓存。效果第一次访问该走的流程一步不少但从第二次开始直接读文件秒回GD 解码、第三方网络请求、逐字画图全部省掉。2. 把超时时间从 30 秒砍到 6 秒timeout6,// 原来是 30正常情况下二维码接口秒级响应30 秒的超时设置只是让最坏情况变得无比漫长。缩短超时之后即使第三方服务真出问题用户等待的上限也可控很多——而且加了缓存之后这个慢请求本来就只会在首次生成时触发一次影响面进一步收窄。3. 前端补一个骨架屏加载态用最原生的方式不引入任何 JS 库divclassimage-preview-wrapperdivclassimage-placeholderidimagePlaceholderdivclassimage-loading-text海报生成中…/div/divimgsrcdownload_partner_qrcode.php?download1alt合伙人推广码onloadthis.classList.add(loaded);document.getElementById(imagePlaceholder).style.displaynone;onerrordocument.getElementById(imagePlaceholder).innerHTMLdiv class\image-loading-text\生成失败请返回重试/div;/div配合一个带流光动画的 CSS 骨架屏padding-top按图片真实宽高比撑开容器避免布局跳动图片加载完自动淡入显示失败也有明确的文案提示而不是一个裸露的浏览器默认破图标。.image-placeholder{width:100%;padding-top:171.2%;/* 按模板图 1535:2628 的宽高比撑开占位 */background:#f2f2f2;position:relative;overflow:hidden;}.image-placeholder::after{content:;position:absolute;top:0;left:-60%;width:60%;height:100%;background:linear-gradient(90deg,transparent,rgba(255,255,255,.6),transparent);animation:placeholder-shine 1.4s infinite;}keyframesplaceholder-shine{0%{left:-60%;}100%{left:120%;}}四、优化效果优化前优化后首次访问完整走一遍 GD 合成 第三方网络请求一样缓存冷启动无法避免二次及以后访问依然完整重跑一遍直接读本地缓存文件跳过全部图像处理和网络请求第三方接口异常最长可能卡 30 秒最长卡 6 秒且只影响首次生成加载体验白屏 → 突然出图骨架屏动画 → 平滑出图 / 明确的失败提示五、小结这次优化没有引入任何新的技术栈或复杂架构核心思路就是**“能不算就不算实在要等也让用户知道在等什么”**缓存永远是性价比最高的优化手段——尤其是这种输入基本不变、每次却都重新计算的场景加一层文件缓存往往比优化算法本身收益大得多同步阻塞的第三方依赖一定要设置合理超时别让最坏情况没有上限哪怕后端优化还没生效前端一个恰当的加载态也能显著改善用户的主观体感——用户能接受等待但接受不了卡死。如果你的项目里也有类似动态生成图片/海报/报表的功能不妨先问自己一句这个结果是不是每次都在重复计算同一个答案
返回列表