拓十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

pm2启动Hono服务无响应?排查监听地址与环境变量等关键细节

pm2启动Hono服务无响应?排查监听地址与环境变量等关键细节 “pm2启动hono服务后访问api没有响应”这句话我在Node.js部署相关的社群里已经看到过太多次了。Hono这个框架这两年确实火轻量、快、TS友好一套代码能跑Node也能跑边缘运行时但正因为跨运行时很多人第一次从本地开发切到pm2生产部署时就懵了pm2 list里明明显示onlinecurl却一直卡住直到超时浏览器里接口转圈后台日志又什么都没有最后只能重启服务器或者干脆放弃pm2转手用screen。这篇文章不扯概念直接按我线下排障的顺序走一遍从进程状态确认到监听地址检查从pm2配置细节到Hono代码里的隐蔽坑最后给一张速查表。正在用Hono写API、准备用pm2部署到服务器的朋友建议收藏着看遇到同款问题能少走好几小时弯路。1. 先判断“没响应”到底发生在哪一层1.1 三种典型的“没响应”现象我处理过大量的同类问题发现“访问API没有响应”这个描述其实掩盖了三种完全不同的情况排障方向截然不同一开始分错类会浪费大量时间。第一种是pm2列表里显示online进程活着但用curl访问本机端口直接卡住不返回直到超时。这种情况最迷惑人因为进程状态看起来一切正常实际上服务可能根本没在监听或者事件循环被某段代码堵死了。第二种是pm2显示errored或者restarts次数不停增长进程反复崩溃重启。这种情况相对好排查因为至少有错误日志可看大概率是启动入口有问题、模块加载失败、或者端口被占用。第三种是本地命令行curl能通但从外部浏览器或客户端访问不通。这种情况pm2和Hono基本没责任问题多半出在防火墙、云安全组、监听地址绑定在回环上或者反向代理没配好。你拿到问题先问自己一句到底是哪一种别上来就改代码。我见过有人在Hono业务代码里翻了一晚上最后发现是云服务器安全组没放行端口属于典型的定位方向错误。1.2 第一步pm2进程状态和重启次数排障第一步我习惯先跑三组命令把pm2的真实情况摸清楚pm2 list pm2 logs hono-api --lines 50 pm2 describe hono-api很多人只看pm2 list的第一列状态看到online就放心了但online只能说明进程被fork出来并且没有退出绝不能说明服务已经正常进入监听状态。真正有价值的是describe输出里的restarts字段这个数字会在进程每次意外退出时加一。如果restarts一直在涨说明进程反复崩溃这根本不是“没响应”是“起不来”日志里一定有原因。还有一个容易忽略的点pm2 list会显示uptime如果这个时间很短就说明进程刚重启过。配合logs看具体报错绝大多数启动失败都能当场定位。再补充一个细节pm2启动后端口有没有起来从pm2本身是看不出来的。pm2只负责进程生命周期管理它不检测你的应用是否监听了端口也不检测HTTP服务是否健康。这一点很多人没想明白总以为pm2显示online服务就一定在正常工作这是最典型的认知误区。1.3 第二步从外部链路逐层探测当pm2进程状态看起来正常时我一般会从链路最远端往回逐层探测而不是一上来就翻代码。先在外面或换个终端执行curl -v http://127.0.0.1:3000/ping curl -v http://服务器公网IP:3000/ping如果本机通、公网IP不通那问题就在网络层跟pm2、Hono都没有关系。这时候要查的是云控制台的安全组入站规则、服务器防火墙放行状态。如果本机都不通那问题才回到进程和服务本身再继续往下查监听端口和日志。这层判断是整个排障过程里成本最低、收益最高的一步。我自己踩过的最大一个坑就在这里某次部署后本地怎么测都通但外部客户端始终连不上排查了pm2配置、Hono中间件、Nginx转发最后才发现是安全组只放行了80端口而我把服务跑在了3000端口上。2. 问题十有八九出在pm2配置上2.1 ecosystem.config.js的正确打开方式很多人喜欢pm2 start src/index.js --name hono-api这种一行命令启动方式但生产环境真的别这么干。我强烈建议从第一天就用ecosystem.config.js把配置固化下来可维护性天差地别。下面是经历过线上验证的模板逐项解释module.exports { apps: [ { name: hono-api, script: ./dist/index.js, cwd: /var/www/hono-api, instances: 1, exec_mode: fork, autorestart: true, max_memory_restart: 512M, env: { NODE_ENV: production, PORT: 3000, HOST: 0.0.0.0 }, out_file: /var/log/pm2/hono-out.log, error_file: /var/log/pm2/hono-error.log, merge_logs: true, kill_timeout: 5000 } ] }有几个点必须单独拎出来说。script指向的是./dist/index.js而不是./src/index.ts这是刻意为之。用pm2直接跑TS源文件需要额外配置interpreter比如tsx但这会引入一系列兼容性问题日志堆栈错位、进程异常退出时错误信息不完整、pm2对进程状态的判断也可能失真。所以正确做法是先把TS编译成JSpm2只负责跑构建产物职责单一不容易出事。cwd字段特别容易被忽略。它的含义是进程的工作目录默认是执行pm2命令时所在的目录。如果你的代码用process.cwd()去拼接路径读取.env文件而cwd没配置对就会出现一种非常隐蔽的问题进程起得来但环境变量加载失败数据库连不上接口一直在报错或者pending。exec_mode设为fork、instances设为1这是我处理此类问题时的默认起点。原因往下看cluster模式在部署Hono时经常是祸根。2.2 别急着用cluster模式pm2的cluster模式看起来很美一行配置就能多进程负载均衡。但在Hono场景下它是我最先怀疑的变量之一。cluster模式的核心是多个worker进程共享同一个端口靠SO_REUSEPORT机制实现。这在多数情况没问题但一旦你的Hono实例内部存在单例资源比如内存缓存、WebSocket连接池、定时器、或者某个不想被多进程共享的第三方客户端每个worker各持一份行为就开始变得诡异。更麻烦的是某些服务器环境下SO_REUSEPORT的兼容性问题会让新启动的worker明明没报错却bind不上端口表现出来就是“服务在跑但请求全挂”。我的建议非常明确起步阶段一律fork模式、单实例。先确保服务稳定跑起来然后再根据真实压力去评估要不要上cluster。多实例解决的是并发能力和CPU多核利用不是部署完备性。你连单实例的稳定性都没验证就上多实例出问题时连日志都要从三四个进程里翻排查难度直接翻倍。如果前期非要试cluster至少把这几项配置检查一遍instance数量建议和CPU核数一致、代码里不要有依赖进程内状态的逻辑、启动后必须逐个worker测试请求是否正常。2.3 环境变量和.env的隐性坑pm2启动环境和本地开发环境在环境变量上可能完全是两个世界。本地跑的是shell里export的变量或者启动时.env被工具链加载但pm2启动的进程默认不会加载你项目根目录下的.env文件除非你用了pm2 start时带--env production并从ecosystem配置里读env或者代码里显式调用dotenv去加载。如果你写的是const port Number(process.env.PORT) || 3000本地跑没问题因为.env里有PORT3000。但pm2启动的进程如果没加载.env又没有在ecosystem.config.js的env字段里设置PORT就会退回默认值。端口实际监听3000你做其他配置时以为是3001那自然怎么访问都不对。解决办法是在ecosystem.config.js的env字段里显式声明关键变量或者保证代码入口第一行加载dotenvimport dotenv/config但这里注意dotenv默认加载的是process.cwd()下的.env如果你pm2的cwd配置错误即便代码里写了dotenv.config()也可能读到错文件。所以cwd和env要一起看不能只修一个。2.4 启动之后千万别忘了save有一个很经验主义的细节pm2不是配置一次永远生效的。你在服务器上执行pm2 start之后如果后来重启过机器pm2的进程列表并不会自动恢复。生产环境标准的部署流程应该是pm2 start ecosystem.config.js pm2 save pm2 startuppm2 startup会在系统里注册一个开机启动脚本把pm2本身拉起pm2 save则把当前进程列表固化下来开机后自动恢复。少了任何一步服务器一重启服务就丢这时候你ssh进去看到pm2 list是空的接口自然全挂但这个锅不应该算在Hono头上。3. hono服务本身容易踩的监听与代码细节3.1 监听地址决定别人能不能连上这一节可能是整篇文章里含金量最高的一节。Hono本身是一个框架真正让它跑在Node环境里的通常是用hono/node-server这个包。很多人在开发环境写完代码直接npm run dev一切正常因为开发脚本里监听地址可能根本没有显式设置或者设置了localhost。问题就在这localhost在大多数Node环境下会解析成127.0.0.1也就是只看回环接口。服务起来后只有本机能访问外部请求进不来。我见过有人把Hono服务部署到服务器pm2显示onlinecurl本机也通但外部就是访问不了最后发现监听地址写的127.0.0.1。正确的做法是显式绑定所有可用接口import { serve } from hono/node-server import { Hono } from hono const app new Hono() app.get(/ping, (c) c.json({ status: alive })) serve({ fetch: app.fetch, port: Number(process.env.PORT || 3000), hostname: process.env.HOST || 0.0.0.0 })用process.env.HOST兜底到0.0.0.0是个好习惯这样在pm2的ecosystem配置里可以通过env字段控制监听地址本地开发和线上部署用同一份代码不需要改代码切分支。还有一个验证技巧服务启动后执行ss -tlnp | grep node看监听地址到底是0.0.0.0:3000还是127.0.0.1:3000一眼就能判断外部能不能访问。3.2 事件循环被阻塞导致的假死有一种非常隐蔽的无响应进程完全正常端口有监听请求能到但就是不返回。这时候你要往代码执行层面想——事件循环被阻塞了。Node.js是单线程事件循环模型。如果你的某个Hono路由处理函数里有一段耗时的同步操作比如大文件读取、复杂的正则匹配、或者某个循环里跑了同步加密这个请求执行期间整个进程的CPU会被占满后续所有请求全部排队等待表现为“接口没响应”。这种情况pm2帮不上任何忙它不是监控工具也不会自动重启卡死的进程。你只会看到pm2 list一切正常但请求全挂。排查方法执行pm2 monit看CPU占用或者直接用top -p pid看进程CPU。如果CPU长期逼近100%而请求无响应基本就是事件循环被同步任务堵死。解决思路很简单但需要重构把重计算交给异步方式比如改用流式处理、worker_threads、或者拆成异步任务队列。在生产环境同步阻塞事件循环是不可接受的。3.3 中间件、CORS和路由注册导致的假无响应第三类隐蔽问题出在Hono应用本身典型症状是curl直连通但浏览器里API调用失败或无响应。浏览器跨域请求和普通curl最大的区别是跨域时会先发一个OPTIONS预检请求。如果你的Hono应用没有配置CORS中间件OPTIONS请求会被框架拦截或者直接挂起浏览器拿不到预期响应就会判定请求失败。Hono官方提供了cors中间件几行代码就能解决import { cors } from hono/cors app.use(/api/*, cors({ origin: *, allowHeaders: [Content-Type, Authorization], allowMethods: [POST, GET, OPTIONS, PUT, DELETE] }))另外还要注意中间件的注册顺序。Hono里中间件按注册顺序执行如果app.use()写在了某条具体路由的app.get()之后这个中间件对该路由是不生效的。这不会导致请求无响应但会导致你的认证中间件、日志中间件逻辑不执行后续排查时很容易误判。我建议在Hono应用里至少加一个全局错误处理中间件避免未捕获异常导致请求挂起app.onError((err, c) { console.error(${new Date().toISOString()} ${err.message}) return c.json({ error: internal_error }, 500) })这也是很多人忽略的一点Hono默认的错误处理虽然能返回500但如果你在serve环节自己封装了fetch函数并且吞掉了异常请求就会pending住。错误处理中间件是确保“必有响应”的最后一道保险。4. 日志、命令与三板斧排障法4.1 日志你其实没看全pm2默认会捕获进程的stdout和stderr并分别写到日志里。很多人出了问题时只会pm2 logs看几行然后没看到东西就说“没日志”其实是用错了命令。建议这样操作pm2 flush # 先清空旧日志 pm2 restart hono-api # 干净地重启一次 pm2 logs hono-api --lines 200 --raw先清空日志再复现问题这样日志文件里记录的就是你这次操作产生的内容不会被几百行旧日志淹没。--raw参数会输出原始日志格式不再带pm2的时间戳和进程前缀方便直接复制去搜索报错。如果你在ecosystem.config.js里配置了out_file和error_file也可以直接tail -f /var/log/pm2/hono-error.log标准输出和错误输出分流后查找异常堆栈的效率会高很多。4.2 一条命令确认监听地址定位“没响应”问题时ss命令比pm2自带的任何输出都有用ss -tlnp | grep node输出里如果看到127.0.0.1:3000外部访问绝对不通看到0.0.0.0:3000或者[::]:3000才是绑定所有接口。顺带说一句0.0.0.0和*在输出里有时候显示为:::3000这是IPv6的通配形式也代表所有接口是正常的别误判。如果ss输出里完全没有node相关监听说明进程根本没有成功监听端口这时候就该去看启动日志问题通常在入口脚本、模块加载或者端口占用。4.3 排障三板斧curl、ss、logs我把这套流程总结成三板斧每个新项目部署完都按这个顺序过一遍第一板斧curl本机健康检查接口curl -v http://127.0.0.1:3000/ping确认服务本身存活这一步超时就先看日志看监听别往后走。第二板斧ss确认监听地址是否对外ss -tlnp | grep node只看0.0.0.0:端口或者:::端口看到127.0.0.1直接去改hostname。第三板斧从外部链路自测curl -v http://公网IP:3000/ping如果本机通、公网不通去看防火墙和安全组。这套三板斧最多十分钟能跑完能过滤掉80%的“没响应”问题。剩下那20%再去结合日志、CORS、中间件这些细节排查。5. 常见问题速查表与避坑清单5.1 五分钟定位的问题速查表现象大概率原因快速验证方法解决方案pm2 online但本机curl超时服务没监听成功/事件循环阻塞ss -tlnp查看端口配合日志定位启动异常本机通但外部不通监听127.0.0.1或安全组未放行ss -tlnp看监听地址hostname改0.0.0.0pm2状态errored或restarts增长入口报错/依赖缺失pm2 logs --lines 200本地先跑通再部署curl通但浏览器不通CORS未配置浏览器网络面板看OPTIONS加cors中间件报错ERR_CONNECTION_REFUSED端口未监听或防火墙拦截ss -tlnp 防火墙状态放行端口/修正监听SyntaxError/ERR_MODULE_NOT_FOUNDESM模块配置不一致看日志堆栈检查package.json的type字段请求长时间pending不返回代码里有同步阻塞/异常被吞pm2 monit看CPU重构为异步加onError重启服务器后服务消失pm2未save和startuppm2 list为空pm2 save pm2 startup5.2 避坑清单生产环境别用pm2 start裸启动用ecosystem.config.js把配置固化。script指向编译后的dist文件别依赖pm2去解析TS源码。监听地址必须显式设置为0.0.0.0不要写localhost或127.0.0.1。fork模式单实例起步cluster模式是优化步骤不是部署必需品。部署完必须pm2 save否则重启服务器等于服务全没。.env文件不会自动加载用ecosystem的env字段或代码里显式dotenv。日志文件路径的目录必须提前创建好否则pm2可能报错。遇到诡异问题先检查防火墙和安全组技术栈经常是背锅的。这套排障流程我用了很久从简单的“端口没监听”到极其隐蔽的“事件循环阻塞”基本都能覆盖。写下来才发现绝大多数“pm2启动hono后没响应”的问题根因都不在pm2或者Hono本身而是部署细节没有对齐。把监听地址、环境变量、安全组这几件基础功课做扎实这类问题几乎可以绝迹。
返回列表