[{"content":"现象与结论 Windows 服务器（IIS + FastCGI + PHP 5.6）上的一个多年未升级的 POSCMS 站点，IIS 站点一启动 php-cgi.exe 进程就不断增多，伴随偶发数据库连接失败。\nPHP 5.6 本身不会 fork，多个 php-cgi.exe 是 FastCGI 为并发请求分配进程槽位的正常机制。真正的问题是：每个请求都可能被拖住最长 60 秒释放不掉，而站点被扫描机器人 7×24 轰炸，请求不断堆积，IIS 只能不停拉新进程接客。把请求拖死的元凶是 PHP 文件 Session 的垃圾回收（GC）。\n排查过程 先搜 pcntl_fork/exec/fsockopen 等调用，确认程序没有主动起进程，也排除了 POSCMS 自带的任务队列（SYS_CRON_QUEUE 为关闭）和附件同步的自我 HTTP 调用（未启用），顺手扫了一遍无 webshell。\n决定性证据来自 cache/errorlog/ 的运行日志：从两年前持续到排查当天，几乎每天都是同一种致命错误，最重的日子一天 200+ 条：\nERROR --\u0026gt; Maximum execution time of 60 seconds exceeded C:\\wwwroot\\\u0026lt;站点目录\u0026gt;\\diy\\system\\libraries\\Session\\drivers\\Session_files_driver.php 380 ----\u0026gt; http://\u0026lt;域名\u0026gt;/index.php/xin/2023-11-05 14:37:57/ben/amp/a/xxxxxxx.ppt 报错行号 375/380/387 全落在 Session_files_driver.php 的 gc() 里——遍历整个 session 目录逐文件 filemtime+unlink 的循环。请求卡在\u0026quot;清扫 session 目录\u0026quot;上整整 60 秒，被入口文件的 set_time_limit(60) 强杀。一天 200 次即 200 分钟被记录在案的卡死，且凌晨也在持续（机器人不睡觉）。\nsession 目录为什么这么大，三个条件叠加：\nPOSCMS 基础控制器对每个请求无条件 $this-\u0026gt;load-\u0026gt;library('session')； 文件驱动写入 cache/session/； 机器人不带 cookie，CI3 对新访客必定写一个 session 文件（文件名 = cookie 名 + 会话 ID），只能等 GC 删。 目录膨胀到数万文件后，PHP 默认约 1% 的请求触发 GC 全目录扫描，Windows/NTFS 上逐文件操作极慢（再叠杀软更甚）。这里有个 Windows 特有的坑：max_execution_time 在 Windows 下算墙钟时间（含 IO 等待），所以这种 IO 型卡死在 Linux 上往往不触发超时，在 Windows 上必然 60 秒被杀。\n请求侧还有一个细节：日志里被卡死的全是 /index.php/xxx.ppt 这类扫描 URL，而 POSCMS 的伪静态解析把 index.php 开头的 URI 一律置空——这类请求全部被当首页处理，每个垃圾 URL 都完整跑一遍 CMS + MySQL + session，程序没有廉价的 404 出口。\n连锁反应随之而来：并发 php-cgi 滚起来后 MySQL 连接被拒（日志成串的\u0026quot;由于目标计算机积极拒绝\u0026quot;），其他请求又在等连接超时，进程进一步堆住。\n机器人 7×24 轰炸（无 cookie） → 每请求完整跑 CMS + 生成一个 session 文件 → 目录膨胀到数万文件 → 1% 请求触发 GC 全目录扫描，60 秒被强杀 → 每个 GC 请求钉住一个 php-cgi 一分钟，新请求还在涌入 → IIS 不停拉新进程，进程数暴涨，MySQL 被拖垮 修复方案 方案一（治本，改三处代码）：无 session cookie 的请求不启动 session 机器人永远不携带 session cookie，真人在第一次 POST/登录/验证码后就会带上。改前需把代码里所有 session 使用点过一遍，本例只需动三处。下表行号为本站代码的原始行号，套到其他 POSCMS 站时若版本有偏移，按\u0026quot;定位方法\u0026quot;列搜索即可：\n# 文件 位置 定位方法 ① diy/poscms/core/Common.php 第 126 行 搜索 library('session')（全文件唯一一处） ② diy/poscms/model/Attachment.php 第 18 行（__construct() 内） 搜索 userdata('siteid') ③ diy/poscms/model/Member.php 第 1113 行之后（member_uid() 内） 搜索 function member_uid，找到 if (!$uid) { return NULL; } 的闭合 } ① Common.php 第 126 行（基础控制器构造函数内，define('SYS_ADMIN_PAGESIZE', ...) 下方）：把 $this-\u0026gt;load-\u0026gt;library('session'); 这一行替换为：\nif (IS_POST || IS_ADMIN || IS_MEMBER || isset($_COOKIE[config_item(\u0026#39;sess_cookie_name\u0026#39;)]) || strtolower((string)$this-\u0026gt;input-\u0026gt;get(\u0026#39;m\u0026#39;)) == \u0026#39;captcha\u0026#39; // 验证码图片 || strtolower((string)$this-\u0026gt;input-\u0026gt;get(\u0026#39;m\u0026#39;)) == \u0026#39;aslogin\u0026#39; // 后台免登录握手 || $this-\u0026gt;input-\u0026gt;get(\u0026#39;auth_code\u0026#39;)) { // 移动端api授权 $this-\u0026gt;load-\u0026gt;library(\u0026#39;session\u0026#39;); } 注意 IS_MEMBER 在 Init/Rewrite 阶段恒有定义（非会员为 FALSE），判断要用常量值而非 defined('IS_MEMBER')（后者恒真）。\n② Attachment.php 第 18 行（附件模型构造函数，全站唯一无条件下读 session 的点，而附件模型每个内容页都会实例化）：把\n$this-\u0026gt;siteid = (int)$this-\u0026gt;session-\u0026gt;userdata(\u0026#39;siteid\u0026#39;); 替换为（紧随其后的 SITE_ID 兜底行保持不变）：\n$this-\u0026gt;siteid = $this-\u0026gt;load-\u0026gt;is_loaded(\u0026#39;session\u0026#39;) ? (int)$this-\u0026gt;session-\u0026gt;userdata(\u0026#39;siteid\u0026#39;) : 0; ③ Member.php 第 1113 行之后（member_uid() 方法内）：处理\u0026quot;记住登录\u0026quot;用户 session 过期（2 小时）后回访的边界情况。在该方法里 if (!$uid) { return NULL; } 的闭合 } 与 if (!$this-\u0026gt;session-\u0026gt;userdata('uid')) 之间插入一行：\n$this-\u0026gt;load-\u0026gt;is_loaded(\u0026#39;session\u0026#39;) || $this-\u0026gt;load-\u0026gt;library(\u0026#39;session\u0026#39;); 其余调用点天然安全：无 cookie 机器人走不到 session 读取就提前返回；评论防灌水等都是 POST；OAuth 回调在会员域被 IS_MEMBER 覆盖。上线后用无痕窗口回归：前台页面不再新增 sess 文件，登录/验证码/评论/后台正常。\n方案二（治流量）：IIS 层把机器人挡在 PHP 之前 利用\u0026quot;/index.php/* 被当首页处理、对站点毫无价值\u0026quot;这一事实，URL Rewrite 整段 404（正常链接是 /index.php?c=xx 格式，不受影响），加在站点 web.config 的 \u0026lt;rewrite\u0026gt; 节点内：\n\u0026lt;rule name=\u0026#34;blockPathInfoJunk\u0026#34; stopProcessing=\u0026#34;true\u0026#34;\u0026gt; \u0026lt;match url=\u0026#34;^index\\.php/.+\u0026#34; ignoreCase=\u0026#34;true\u0026#34; /\u0026gt; \u0026lt;action type=\u0026#34;CustomResponse\u0026#34; statusCode=\u0026#34;404\u0026#34; statusReason=\u0026#34;Not Found\u0026#34; /\u0026gt; \u0026lt;/rule\u0026gt; 再开 IIS 自带的动态 IP 限制，把高频扫描源直接封掉。先执行两条前置命令（装角色服务 + 解锁站点级配置，不解锁的话 web.config 会报 500.19）：\nInstall-WindowsFeature Web-IP-Security %windir%\\system32\\inetsrv\\appcmd unlock config -section:system.webServer/security/dynamicIpSecurity 然后在同一个 web.config 的 \u0026lt;system.webServer\u0026gt; 内、与 \u0026lt;rewrite\u0026gt; 并列加上：\n\u0026lt;security\u0026gt; \u0026lt;!-- 单IP并发超过10个请求、或1秒内超过30个请求即拒绝 --\u0026gt; \u0026lt;dynamicIpSecurity denyByConcurrentRequests=\u0026#34;10\u0026#34; denyByRequestRate=\u0026#34;30\u0026#34; requestRateInInterval=\u0026#34;1000\u0026#34; denyAction=\u0026#34;Forbidden\u0026#34; enableProxyMode=\u0026#34;false\u0026#34;\u0026gt; \u0026lt;/dynamicIpSecurity\u0026gt; \u0026lt;/security\u0026gt; 套了 CDN/反代的站必须把 enableProxyMode 改为 true 并放宽阈值，否则所有访客都记在代理 IP 一个头上，会把代理整个封掉。\n宝塔 Windows 面板用户：面板只是壳，底层仍是 IIS，最终都落到站点根目录 web.config——URL 重写用\u0026quot;网站 → 设置 → URL重写\u0026quot;或文件管理直接编辑（先备份）；动态 IP 限制按上面命令操作；session 定时清理用面板的 计划任务跑 forfiles 删一天前的旧文件（注意 CI3 文件前缀是 cookie 名如 poscms_ci_session*，不是 sess_*）。\n配套参数（php.ini） session.gc_probability = 0 禁用请求时 GC（靠计划任务清理），这是消灭 60 秒卡死最见效的一条。另外建议入口文件里 memory_limit 从 1024M 降到 256M、set_time_limit 从 60 降到 30。\n验证清单 /index.php/anything 返回 404；站点日志出现 403.501/403.502（IP 限制生效）；php-cgi.exe 数量回落；cache/session/ 不再增长；错误日志里 Session 超时基本消失；前台/登录/后台回归正常。\n复盘要点 多 php-cgi 进程 ≠ 程序在起进程。进程数暴涨几乎总是\u0026quot;请求变慢 + 流量不退\u0026quot;，要找的是什么把请求拖住了； \u0026ldquo;每请求无条件 session + 文件驱动\u0026quot;在 Windows 上有放大效应：墙钟超时 + NTFS 小文件遍历慢 + 杀软，GC 慢到荒谬且每次钉死一个 FastCGI 进程一分钟； 排查这类问题，错误日志里致命错误的文件名 + 行号比通读代码快得多； 无 cookie 访客跳过 session 是普适模式：机器人在入口就被识别，真人首次需要 session 时才创建； 挡扫描流量优先在 Web 服务器层静态拒绝，让恶意请求的成本从\u0026quot;完整跑一遍 PHP + MySQL\u0026quot;降到一次内核态 404。 ","date":"2026-10-03T14:00:00+08:00","permalink":"/posts/technology/poscms-cgi/","title":"POSCMS 在 IIS + PHP 5.6 下 php-cgi 进程持续暴涨的排查与处理"},{"content":"任务 老张的服务器（宝塔面板，Aliyun ECS）数据盘从 20G 扩到 100G，让面板识别新容量。\n现状（操作前） 系统盘 /dev/vda1：ext4 100G（已满，不动） 数据盘 /dev/vdb1：ext4 20G，挂在 /www，已用 17G（92%） vdb 整盘已扩到 100G，但分区表 vdb1 还是 20G，文件系统也只认 20G 操作步骤（实际成功路径） growpart /dev/vdb 1 → 扩分区表 resize2fs /dev/vdb1 → 扩文件系统 df -hT /www → 验证 跳过项 没有走 echo 1 \u0026gt; /sys/class/scsi_disk/.../rescan 重扫——因为 lsblk 已经看到 vdb 是 100G，内核已认到新容量，分区表没跟上而已。 virtio 通道的 ECS 走 /sys/block/vdb/device/rescan，不是 scsi_disk 路径。 注意事项 宝塔面板不用动，磁盘监控和文件管理自动刷新。 growpart + resize2fs 都是热操作，不用卸载 /www，ext4 在线扩安全。 操作前在阿里云控制台做一次快照更稳。 ","date":"2026-08-25T20:00:00+08:00","permalink":"/posts/technology/server-disk-expand/","title":"服务器数据盘扩容：growpart + resize2fs 热操作"},{"content":"七月里和儿子起了一场冲突，情绪落定之后，写了这首《窗台的试卷》。\n发行版歌词 阳台堆满了试卷\n奈何我见他不见\n滴答滴答\n盛夏被切成了碎片\n任务计划都在线\n他一心要把平板见\n我好心把他来劝\n忧伤 不满 立刻现\n他哭着 嚎嚎嚎\n我怒火 烧烧烧\n他跟我 哇哇叫\n我气的 脑仁跳\n情绪决堤\n收不了\n收不了\n椅子掀翻试卷抛\n散落一地各自逃\n房门一关两不相见才是最好\n这个夏天我的心儿碎了碎了\n躺在沙发思绪飘\n担心未来他不好\n可我事业未寻到\n正值中年最难熬\n罢了罢了\n认了认了\n该来的躲不掉\n该好的希望他更好\n灵感描述 以钢琴作为核心主伴奏乐器，表达出一位父亲和儿子之间的激烈情绪冲突和父亲的咆哮行为，到黯然离开房间，到惆怅的思绪和自己和解。\nAI 写歌提示词 以厚重带颗粒感的原声钢琴作为核心主伴奏乐器，开篇用急促密集的低音区重键敲击，搭配短促尖锐的高音跳音，烘托父子之间激烈的情绪冲突，穿插粗粝的低音和弦重击模拟充满张力的咆哮感；随后节奏逐步放缓，琴键触键力度变轻，低音区旋律带着断续的颤音，对应转身离开房间的落寞；后续转为舒缓平缓的分解和弦流动，高音区铺展柔和长音，传递怅然思绪，最终以轻柔的低音单音收尾，完成与自我和解的松弛氛围。整体BPM为70-90，动态范围跨度大，从强冲击感逐步过渡到温和松弛，混响轻微带室内空间感。 发行平台 汽水音乐\n","date":"2026-07-24T12:00:00+08:00","permalink":"/posts/daily/a-song-for-my-son/","title":"窗台的试卷——写给儿子的一首歌"},{"content":"新网站上线，想了很多可能性，最后决定这第一篇不成文的文章，先写写自己吧。\n一、稀里糊涂地入行 高中毕业那会儿，考得不咋地。爹妈问我怎么打算？地道的农村人，既不聪明又没见过什么世面，哪知道往哪走。只是记得那会儿家里电视上，新东方烹饪、美容美发的广告铺天盖地。于是，他们从电视广告里给我提供了一些选项：汽修、电脑、美发、烹饪。他们比较倾向汽修，那会儿私家车越来越多，觉得前景不是问题。但我毫不犹豫地选择了电脑——单纯觉得会打电脑的人比较厉害，而且以后工作环境应该会比较干净（主观认为我是个重度的强迫症和洁癖患者）。就这样走进了合肥电脑学院的大门，一学就是2年。于是，我的大专学历和电脑技能（学的皮毛）就到手了。也算是一只脚踏入了软件行业。\n二、财富自由的东方 毕业要去实习，学校也给了几个城市坐标：上海、杭州、合肥（留在本地），还有其他几个城市，时间太久了记不清了。跟着朋友们就一起去了杭州。这里为什么说\u0026quot;财富自由的东方\u0026quot;呢？是因为也有点相信玄学，说我往这个方向容易发财。我秉着“要信早信”的理念，先做个知行合一再说。\n三、千里相会的贵人 在骄阳似火的夏天到了杭州。学校安排的实习单位，现场让做一张电子商务的海报，虽然也学了点，但让一个开发的人做设计的活，那 PS 根本玩不转。后来还去应聘电脑维修学员，老板生意做得看起来不错，给我开出 800 元/月的薪资。一个不要我，一个我不要。眼看没希望了，接到一个应聘公司 HR 叫我过去面试。我提前 1 小时坐公交车晃到他们公司，公司 400 平，一切看起来都非常新。大概聊了 10 分钟左右吧，就让我第二天去上班——可我连老板或是部门负责人都没见到。但不重要了，当天晚上奖励自己一顿，也算是为找了半个月的工作画上个句号。后来老板也确实对我挺好的，是我第一个贵人。\n四、铺满荆棘的软路 在公司安顿下来之后，技术部实际上就我一个人。后来陆续来了新的成员，硬是把三脚猫功夫的我推上部门负责人宝座。可原本内向型的我，面对自身技术烂、带不好团队、应付不了客户等场面，一度非常崩溃。也就这样深一脚浅一脚地走在软件开发的路上。后来和一些朋友聊天才发现，大多数人都没从事所学的专业。我们常说专业要对口，但好像只有我还坚守在这个专业上，不知道是对是错，但我当时好像没有什么更好的选择。在这条软件路上，我遇到第二位贵人。\n五、双城生活之创业 在入行 7 年之后，跟着我人生中第二位贵人，开启了创业之路，也同时开启了为期 6 年的双城生活。我们从 5 人团队做到 30 多人团队，再做到 10 人团队。从企业应用定制做到三维数据可视化，再做到政府数字化系统软件建设。这 6 年里跌宕起伏，行业发展、时代变迁、客户群体变化等等，也算是见了大风大浪了。创业真的是九死一生，也深刻地感受到：这个世界唯一不变的就是一切都在不停地变化。身处这个变化中，你只能让自己以更快的速度去适应。\n六、深思熟虑的回归 随着年龄增长，职业瓶颈也渐渐凸显。另外，长期不着家，家庭成员角色的缺失，让我陷入一种极度没有归属感的环境里。家顾不上，孩子管不了，爱人不能陪，收入缩水且每周往返让我疲惫不堪。在纠结 2 个月后，我还是选择了辞职回归家庭。也想过回来不知道做什么，也可能很长时间没有收入，但现实是——到那一步的时候，我觉得我不能在犹豫中浪费时间，我该回去了。\n写在最后 如今，我回来已经一年了。虽然有时候会觉得，处于当前这样的经济时期，应该慎重考虑辞职一事。但人生之路的选择，谁又能保证每一次都是对的。这一年里，对自己这些年的表现也深刻地做了反省和总结。有可圈可点的，也有蠢到让人想哭的。不过，都翻篇了。\n现在在昆山，陪陪家人，做做饭，看看书，学习 AI 技能。偶尔做一些老客户的项目，最近也在做一些自己的小软件。收入不多，但胜在内心平静。\n如果你也在昆山，需要网站建设、小程序开发，软件定制服务或者就是想聊聊软件定制那些事，可以找我。\n15 年的经验不敢说多厉害，但至少知道哪里容易踩坑。\n这次，也希望我自己的网站和应用能落地生根，让那条曾经铺满荆棘的“软件之路”变得软软的，以“懒洋洋”的姿态，欣赏这世界。\n","date":"2026-07-18T12:00:00+08:00","permalink":"/posts/essays/my-it-journey/","title":"稀里糊涂入行，兜兜转转回归——一个老程序员的自白"}]