低配服务器-宝塔面板性能优化步骤

此篇文章主要是面向WP站点

宝塔面板性能优化步骤

1. 开启虚拟内存

 “Linux工具箱,第一步直接用它。

        操作: 打开 Linux工具箱 -> 找到 Swap/虚拟内存

        设置: 填入 2048 ( 2GB) 4096 (4GB),点击确定。

        作用: 这是保命底线。当你在后台高频保存复杂页面时,如果 4G 物理内存瞬间吃满,Swap 可以防止数据库和 PHP 崩溃。

Image 1

2. 解除 MySQL 5.7 的内存限制

MySQL 是重型展示站最容易卡脖子的地方,我们要给它足够的内存去缓存数据。

        操作: 点击 MySQL 5.7 -> 设置 -> 性能调整

        设置:

       找到 优化方案,下拉菜单直接选择 2-4GB 这个档位。

       重点检查 innodb_buffer_pool_size 这个参数,它应该被自动调高到了 512M 1024M(如果没有,手动改成 512M)。

        作用: 让绝大多数的产品查询直接在内存中完成,不再慢吞吞地去读写硬盘。

Image 2

3. PHP优化

PHP 负责把前端的请求动态编译出来

        第一步(装扩展): 点击 PHP 8.2 -> 设置 -> 安装扩展。务必安装 opcache redis 这两个扩展(不要漏掉 redis,否则装的 Redis 软件就不起作用)。

        第二步(调并发): 点击 性能调整

       运行模式:动态模式 (Dynamic)

       max_children40(这是给整台服务器设定的最高安全水位,保证即使 5 个站同时活跃,也不会抽干 4G 内存)

       start_servers5

       min_spare_servers5

       max_spare_servers10

        第三步(扩内存): 点击 配置修改。把 memory_limit 从默认的 128M 修改为 256M

Image 10

Image 2

Image 11

4. 优化 Nginx 配置(选做,但强烈建议)

        操作: 点击 Nginx 1.26.3 -> 设置 -> 性能调整

        设置: 检查 worker_processes,通常可以设置为 2(对应你的 2 CPU),或者保持 auto。同时确保 Gzip 压缩 是开启状态(压缩级别 4 左右即可,不用太高以免吃 CPU)。

Image 8

5. 网站内部设置

服务器底层架构调优完毕后,在每一个部署好的 WordPress 站点内部,必须完成这两个动作,彻底形成闭环:

1.      接通 Redis 内存数据库: 装好Redis后,在每个站点的后台安装 Redis Object Cache 插件并一键开启。让海量的重复查询直接走内存,大幅降低 MySQL 的运算压力。

2.      建立全页静态缓存: 给每个站点安装 WP Rocket(或其他强大的缓存插件),把前台海外访客看产品的流量 100% 挡在 PHP 和数据库之外。

Image 12

Image 6

6. Redis 的多站点必要标识

服务器上要放 3-5 个站点,并且每个站点都要开启 Redis Object Cache必须要修改 wp-config.php,给每个站点分配不同的 Redis 数据库 ID 或前缀。

        如果不改的后果 默认情况下,所有站点都会把缓存写进 Redis 0 号数据库。结果就是——A 网站的前台可能会莫名其妙显示出 B 网站的产品,整个数据库全乱套。

        解决办法: 在每一个站点的 wp-config.php 文件中,加入以下两行代码(每个站点的编号必须唯一): 站点 A:

        PHP

Plain Text
define( 'WP_REDIS_DATABASE', 0 );
define( 'WP_CACHE_KEY_SALT', 'siteA_' );

        站点 B:

        PHP

Plain Text
define( 'WP_REDIS_DATABASE', 1 );
define( 'WP_CACHE_KEY_SALT', 'siteB_' );

        以此类推,这样 Redis 就会把每个网站的数据严格物理隔离,查询速度极快且互不干扰。

 

原来的WPconfig.php里可能会有自动生成的
注释掉放上自己的就行

7. WPRoccket严格控制 Heartbeat

1.      Behavior in backend (WordPress 后台)

        关掉会怎样? 你的 WordPress 仪表盘将不再实时更新。比如 WooCommerce 的实时销售额统计、插件的实时通知提示将停止自动刷新,必须等你手动按 F5 刷新网页才会更新。

        对服务器的影响: 如果你开着后台挂机,关闭它可以省下大量无意义的 CPU 消耗。

        建议:选择【Disable completely (完全禁用)。平时谁也不会一直盯着仪表盘看实时数据,直接禁用,把宝贵的并发进程省下来。

 

2.      Behavior in post editor (文章/页面编辑器)

        关掉会怎样? 这会带来致命后果Heartbeat 在这里的核心作用是自动保存 (Autosave)”文章锁定(防止两个管理员同时编辑覆盖)。如果完全关闭,当你在排版一个复杂的页面时,浏览器突然崩溃或断网,你刚才几个小时的努力就会全部白费,因为系统没有自动保存。

        对服务器的影响: 编辑页面时高频的自动保存确实很吃 CPU

        建议:保持【Reduce activity (减少活动)。也就是你截图里现在的状态。WP Rocket 会把自动保存的频率从默认的每 15 秒强制拉长到每 2 分钟一次。既保住了你的工作心血,又大幅降低了对 PHP 的压力。

 

3.      Behavior in frontend (网站前台 - 访客看到的页面)

        关掉会怎样? 如果你的网站前台安装了实时聊天插件、或者某些极其依赖 AJAX 实时刷新购物车的 WooCommerce 迷你购物车(Mini Cart),完全关闭可能会导致这些功能失效。

        对服务器的影响: 如果前台有几百个访客,每个人每分钟都在向服务器发一次心跳请求,你的 24G 瞬间就会被击穿(出现 502 错误)。

        建议:选择【Disable completely (完全禁用)。既然你的前台页面已经有了 WP Rocket 的静态缓存,访客根本不需要和服务器保持这种毫无意义的心跳连接。(注:如果禁用后发现 WooCommerce 加入购物车有卡顿,再把它退回到截图里的【Reduce activity】即可)。

Image 9

8. 邮件超时引发的 PHP-FPM 雪崩

云厂商为了防止服务器被黑客用来发垃圾邮件,默认都是死死封锁 25 端口的。

站点触发了发邮件的动作,WordPress 底层的 PHPMailer 就会傻傻地去请求 25 端口。

因为端口被墙,请求发不出去,它就会一直在那里等超时

40 PHP 并发进程,如果同时有 40 个人触发了邮件动作,这 40 个进程就会瞬间全部处于挂起等待状态,直接导致 CPU 飙升到 100%,整个网站彻底卡死报 502 Bad Gateway

1.      PHP 底层禁用

直接从 PHP 的配置文件里把 mail 函数阉割掉,让 PHP 彻底丧失发邮件的能力。

1.      打开宝塔面板,找到左侧的 软件商店 -> 点击 PHP-8.2 设置

2.      在左侧菜单找到 【禁用函数】

3.      在输入框里输入 mail,点击 添加

4.      点击左侧的 【服务】,点击 重启 PHP

        效果: 只要有代码试图调用 PHP 底层的发件系统,会被瞬间拦截并报错,绝不会出现等待超时卡死 CPU 的情况。


2.      WordPress 业务层拦截

仅仅禁用 PHP 函数,WordPress 有时候还是会走一遍耗时的代码逻辑然后再报错。作为开发者,最干净的做法是在 WordPress 刚要张嘴发邮件的时候,直接把堵回去。

你可以把下面这行代码,加到你当前主题的 functions.php 文件里(或者通过 Code Snippets 插件添加):

Plain Text
// 彻底拦截 WordPress 发送任何邮件,耗时 0 毫秒
add_filter( 'pre_wp_mail', '__return_false' );

        效果: 这是 WordPress 官方提供的一个前置钩子(Hook)。只要有任何插件(包括 WooCommerce)试图发邮件,走到这里直接返回 false 结束流程。

注意:如果邮件功能被彻底关掉,登录点击忘记密码是收不到重置链接的,账号就彻底成了死档,必须有个发邮件的解决方案。(详细详见SMTP教程)

 

 

相关文章

用 Astro 做工业官网

我用 Astro 做了 LIANXIN 的工业机械官网,里面有产品目录、工厂展示,也接了 Directus 新闻和 AI 产品顾问。做这种网站,很多选择都落在同一个问题上:哪些内容应该直接送到用户面前,哪些功能等用到的时候再启动。 机器图片、参数表、介绍文字适合提前生成;新闻要能随时更新;轮播、咨询和视频又各有自己的交互。把这些边界拆清楚,后面的实现会顺很多。 官网现用的工厂主题海报。来源:LIANXIN 官网。 页面先到,交互按需启动 Astro 比较合我胃口的一点,是它允许我从 HTML 开始组织页面。产品介绍和图片这些内容先渲染出来,需要状态和事件的地方再接 React。装了 React 集成,也不用把整页都做成客户端应用。 本站导航用 client:load,页面加载时就激活;产品轮播用 client:visible,进入视口再加载和水合。两种方式都可以先输出 HTML,区别在于何时让组件在浏览器里运行。具体触发时机可以看 Astro 的客户端指令文档。 FAQ 更进一步,用了项目自己注册的 client:interaction。鼠标移入、触摸按下或键盘聚焦时开始加载。这里有个小坑:用户可能在加载结束前就点了按钮,所以指令会拦住这次点击,等组件就绪再补发。普通链接照常跳转。省 JavaScript 可以,别把第一次点击也省了。 新闻更新走另一条路。列表和详情按请求读取 Directus,首页的最新新闻则交给 server:defer。首页先返回主体和占位内容,新闻区块随后单独请求 HTML。这样 CMS 响应慢一些,也不会拖住整个首页。这部分需要服务端,我用 Node adapter 的 standalone 模式运行站点。 Astro 官方原理图,说明静态页面与动态区块如何分别取得内容。来源:Astro 5.0,&copy; Astro;此图并非本站部署架构图。 产品页和 AI 共用一份资料 产品有不同系列,每个型号又有图片、参数和应用说明。如果这些东西散在页面里,改一项参数就得挨个找。我把产品整理成 TypeScript 数据,再让页面读取它。型号详情用同一份模板,通过 getStaticPaths() 生成路由。 这是详情页里的路径生成部分,省略了导入和页面模板。 export function getStaticPaths() { return products.map((product) => ({ params: { category: product.category, slug: product.slug, }, props: { product, category: getCategory(product.category), }, })); } 参数表读这份数据,页面里的 Product 结构化数据也读它。AI 产品知识同样从这里生成,避免官网改了规格,顾问还在背旧版本。数据模型里保留型号、分类、图片和规格这些明确字段,页面只负责把它们展示出来。 AI 后端是独立的 Cloudflare Worker。它根据问题里的型号、别名和当前页面检索资料,最多选六份交给 DeepSeek,再检查回答附带的来源 ID 是否属于这批资料。浏览器拿到的是回答和对应的站内链接,模型密钥留在 Worker。 这种检索方式规模小、规则直接,适合目前这批产品。它能约束资料来源,不能保证模型每句话都对,具体选型和报价仍要人工确认。另外,共用源文件不等于自动同步上线。产品资料改动后,主站和顾问 Worker 都需要重新构建部署,这个步骤不能漏。 图片讲构图,视频讲时机 工业设备本来就长,外面再套几层大边框,能看清的机器只会越来越小。产品图这里用 object-fit: contain,保留完整外形;标题、卖点和按钮放到旁边,让图片有足够的展示面积。轮播控制留在左右两侧,翻图时不用往下找。 IBM 80E 的展示图,机身轮廓完整保留。来源:LIANXIN 对应型号页。 首页海报则准备了横、竖两版,用 <picture> 按 max-aspect-ratio: 1/1 切换。选图依据是视口形状,竖屏平板也能得到竖版构图。首张海报优先加载,后面的图延迟加载。文案目前也在海报里,换来的排版控制更直接,代价是改文字和做多语言都要重新出图。 视频的处理更干脆。工厂介绍先展示封面,视频地址放进 data-src,点击播放时才赋给 src。访问者只是看看页面,就不用顺手下载一段几分钟的视频。 播放函数中的关键动作如下,省略了加载提示、超时和错误处理。 if (!video.hasAttribute("src")) { video.src = video.dataset.src!; } void video.play(); 这一块用原生 <video> 和一个自定义元素管理播放状态,就够用了。网络慢时显示加载提示,失败后给出重试按钮和原视频链接;离开组件时中止事件监听并暂停视频。用户至少能知道点下去之后发生了什么,也有办法继续操作。

2026/9/7·技术分享
用 Astro 做工业官网

用 Android 做疲劳检测

我做了一个 Android 疲劳提醒应用,用前置摄像头观察闭眼和张嘴,在手机上完成检测。模型接进来以后,真正需要想清楚的是后面的判断。眨一下眼就报警,提醒很快会变成噪声;摄像头看不清了还显示正常,又会让人误解系统的能力。 先把眼睛和嘴巴量出来 项目用的是 ML Kit Face Detection,模型随 APK 打包,安装后不用再等模型下载。它提供人脸轮廓、嘴部关键点和睁眼概率,疲劳规则由应用自己实现。这样检测和判定就能分开,调整持续时间不用动相机代码。 模型负责提取人脸信息,什么时候提醒,由后面的规则决定。插画来自 Google ML Kit 官方文档。 眼睛用 EAR,也就是眼睛的纵横比。从眼轮廓里选六个点,把两段上下眼睑距离相加,再除以两倍眼宽。计算式是 EAR =(上下一组眼睑距离 + 另一组眼睑距离)&divide;(2 &times; 眼宽)。闭眼时高度变小,EAR 通常也会下降。用比例可以减轻人脸远近带来的尺度变化,但侧脸、遮挡和关键点偏移仍然会影响结果。 Google ML Kit 的官方轮廓示例,点图可查看原图。眼睑和嘴唇的点要分清,本文的 MAR 量的是唇外沿。这是官方示例,不是本应用实测截图。图片来源。 嘴巴用 MAR。这个项目取上唇外上沿中心到下唇外下沿中心的距离,除以左右嘴角距离。公式看着简单,取点位置却不能含糊。量唇外沿和量嘴巴内部开口,算出来的数不会一样,网上找到一个阈值直接抄过来,很可能对不上。 眼部还保留了 ML Kit 的睁眼概率,与 EAR 做 OR 判断,任一路满足条件就进入对应计时。概率这一路要求左右眼的值都有效。这样能让两路信号各自参与,但也意味着单路误差就可能触发候选;它们来自同一个检测器,不能当成两套独立证据。嘴部单独判断,眼睛暂时不可用时,有效的 MAR 仍然可以参与检测。 一次越线,还不能算一个动作 单帧只知道这一刻眼睛有多窄、嘴张得有多大。要把眨眼、短暂张嘴和持续动作分开,还得加上时间。当前普通闭眼条件是 EAR &le; 0.22,或两眼概率都有效时任一眼低于 0.35,持续 800 ms 后进入 WARNING。更强的闭眼条件是 0 < EAR < 0.17,或两眼概率都低于 0.35,持续 400 ms 就进入 DANGER,且优先级更高。 哈欠候选用的是双阈值。MAR 超过 0.40 才开始计时,开始后只要不低于 0.35 就继续,累计达到 800 ms 才提醒。假设数值在 0.41、0.39、0.42 之间晃,单一阈值会让计时反复归零。嘴还张着,计时器已经替你闭上了。 代码里的关键分支只有几行。下面节选自判定逻辑,省略了无效值检查和后续计时。 val yawning = mar != null && if (highMarStartMs == null) { mar > config.marYawnEnterThreshold // 0.40 } else { mar >= config.marYawnExitThreshold // 0.35 } 时间取相机帧的时间戳,不取模型回调完成的时刻。否则推理这次慢一点、下次快一点,也会混进动作时长。相邻有效观测间隔超过 1.5 秒时,未完成的计时清零,从当前帧重新开始。中间没有画面,系统就没有依据把两次张嘴拼成一次连续动作。这个间隔也是工程取舍,不能证明采样之间发生了什么。 看不清,就别继续报正常 判定器除了 NORMAL、WARNING 和 DANGER,还留了 DEGRADED。没有人脸、眼嘴信号全部无效,或检测到黑暗画面、相机异常时,就清除未完成计时,显示当前无法可靠检测的原因。NORMAL 也只表示当前有效信号没有达到提醒条件,不能解释成驾驶员一定清醒。 相机侧通过 acquireLatestImage() 取最新分析帧,当前会话已有推理任务时,新帧直接释放,避免越算越落后。停止检测或切到后台时,会话编号递增;旧任务即使晚点返回,也不能覆盖新的状态。重新开始检测后,同样要从新画面重新计时。 声音和振动只由 DANGER 触发,WARNING 只更新界面。危险告警做了约两秒冷却,并用不重放历史事件的 SharedFlow 发送;界面只在前台恢复状态下接收,暂停时停止提示。否则回到应用突然补响一声,提醒的就成了过去的自己。 目前这套实现仍是固定阈值的研究原型,没有个人校准。持续张嘴也可能来自说话,睁眼概率还有正脸角度限制。当前手机端只在模拟器上验证了安装、相机链路和前后台切换,真人识别、覆盖层精度与声音振动还需要目标手机实测。接下来更值得做的是用正常说话、短张嘴、自然哈欠和闭眼录像,在静止环境里检验误报与漏报;这套原型还不能当作经过道路验证的驾驶安全系统。

2026/9/7·技术分享
用 Android 做疲劳检测

MediaPipe 手势识别的几个坑

这次用鲁班猫 3 做了个离线手势识别,系统是 Android 14。摄像头看到手就画出骨架,比个 OK,自动保存一张带骨架的截图。 模型直接用 Google 的 MediaPipe。它已经能找出手部的 21 个关键点,还带七种静态手势分类。自己要处理的是相机、画面坐标,以及识别结果怎么变成实际操作。 模型用现成的 Android 用 Kotlin 写,相机接 CameraX,识别接 MediaPipe Tasks。依赖用这几项。 def cameraxVersion = '1.4.2' implementation "androidx.camera:camera-core:$cameraxVersion" implementation "androidx.camera:camera-camera2:$cameraxVersion" implementation "androidx.camera:camera-lifecycle:$cameraxVersion" implementation 'com.google.mediapipe:tasks-vision:1.0.0' 把官方模型放进 app/src/main/assets,创建识别器时指定这个文件,运行模式设为 LIVE_STREAM,通过回调拿结果。官方 Android 示例已经有完整接法,照着看比从空白工程硬猜快。 模型跟着 APK 一起打包,推理在板子上完成,运行时只需要相机权限。 底图来自 MediaPipe 官方测试图片,这是本项目的板端测试输出。红点是关节点,蓝线标出拇指,其余连线用绿色。 相机别开两路 CameraX 常见的接法是 Preview 显示画面,ImageAnalysis 给模型送帧。但在我这块 RK3576 板子的固件上,两路一起开会卡住。 最后只保留一条 ImageAnalysis,预览和识别共用它。拿到 1920&times;1080 的图像后,处理行步幅,把最长边缩到 640 像素,再统一旋转和镜像,送进模型。 帧策略用 KEEP_ONLY_LATEST。处理不过来就丢旧帧,别让识别排队。当前代码还限制了至少 100 ms 才提交一帧,所以提交速率最高约每秒 10 帧,实际显示速度还得看推理回调。 ImageProxy 用完一定要 close,跳过帧和异常分支也一样。 缓冲区占着不放,相机后面就可能没帧了。 另外,板子上的 CAM0、Android 的 Camera ID 0 和 /dev/video0 要分别核对。这个项目同时检查物理 m00_ 模块、逻辑 ID 0 和当前固件的 FRONT 属性,条件不满足就停。这套映射只针对已经验证过的板子和固件。 骨架为什么会飘 这个问题得从坐标和时间两头查。 坐标上,模型给的是归一化关键点,预览用的却是 centerCrop。图片会先放大,再裁掉超出显示区域的部分。直接拿关键点乘屏幕宽高,当然对不上。 把关键点投到屏幕上,要和预览用同一个缩放比例和裁切偏移。下面的 x、y 是模型返回的归一化坐标,srcW、srcH 是模型输入图像的宽高。 scale = max(viewW / srcW, viewH / srcH) offsetX = (viewW - srcW * scale) / 2 offsetY = (viewH - srcH * scale) / 2 screenX = x * srcW * scale + offsetX screenY = y * srcH * scale + offsetY 这些除法要用浮点数。旋转和镜像也统一在推理前处理,别让预览和模型各算各的方向。 时间上,手已经移走了,上一帧的结果才回来。把旧骨架叠到新画面,多少有点灵魂出窍。 我的处理是从结果回调里取出对应的输入图片,和这次关键点一起更新到界面。画面和骨架必须属于同一帧。 代价是显示会等识别结果,预览流畅度也跟着受限。 还有一个容易漏的判断。分类结果是 None,只代表没认出支持的手势。只要关键点还在,骨架就继续画;没检测到手、结果过期或者切到后台时,再清掉。 OK 截图要防连拍 官方这七种分类没有 OK,我用现有关键点补了一层几何判断。拇指和食指指尖靠近,食指弯曲,另外三根手指伸直,才算匹配。 距离按掌部尺寸归一化,再结合关节角度判断。用固定像素距离的话,手往前伸一点,阈值就不合适了。这里用的是按输入宽高还原后的 2D 坐标,和画面里的骨架保持一致。 然后才是触发控制。视频每帧都可能认出 OK,你一直举着手,就一直存图,相册受不了这个。 判断 当前条件 确认手势 最近最多 5 帧中至少 4 帧强匹配,当前帧也满足,窗口持续至少 300 ms 触发后锁定 保持 OK 只拍一张 允许再拍 离开 OK 至少 600 ms,两次触发至少间隔 1.5 秒 触发条件严一点,保持条件宽一点,减少边界抖动。采样断开超过 400 ms,就清掉旧的观察窗口。这些是当前配置,实际触发时间还受帧率影响。 截图直接合成当前画面和同帧骨架,再用 MediaStore 存成 PNG。只画这两层,状态栏和提示框就不会混进去。编码和写盘放到独立线程,别堵着相机识别。 保存的是当前显示画面的合成图,输入已经缩小过,不能当成传感器原图。 这版配置的还是 Delegate.CPU。RK3576 有 NPU,但应用得接入 RKNN 才用得上。RKNN 的官方流程包括模型转换和运行时接入,前后处理也要重新对齐,光改一个配置没用。

2026/9/7·技术分享
MediaPipe 手势识别的几个坑
听歌