端口被占了,服务起不来:用 ss 和 lsof 把占用进程揪出来
刚把服务部署到一台 ECS 上,启动时日志里跳出一行 Address already in use,进程直接退出了。这种情况我遇到过好几次,第一次还以为是配置写错了,反复改了几遍配置文件都没用。其实问题很简单:要用的端口已经被别的进程占了。先别急着改端口,也别急着重启机器,把占用者找出来再动手,十分钟就能收尾。
一、先确认端口到底被谁占着
最顺手的工具是 ss,现在大多数发行版都自带,比 netstat 快,也不用额外安装:
ss -tlnp | grep 8080
-t 是 TCP,-l 只看监听状态,-n 不解析域名和服务名,-p 显示占用进程。输出大概是这个样子:
LISTEN 0 511 0.0.0.0:8080 0.0.0.0:* users:(("java",pid=12345,fd=88))
关键信息就三处:状态是 LISTEN,说明确实有进程在监听这个端口;0.0.0.0:8080 说明监听的是所有网卡;括号里就是占用进程的名字和 PID。拿到 PID 之后,先别急着杀,执行 ps -p 12345 -o pid,ppid,cmd,etime 看一眼这是什么进程、什么时候启动的、启动命令是什么。很多时候一看启动时间和工作目录,就能想起来是自己上次调试时留下的旧进程。
如果 ss 的输出里只有端口、没有进程名,大概率是权限不够,前面加 sudo 再跑一次。另一个常用工具是 lsof:
lsof -i :8080
lsof -iTCP:8080 -sTCP:LISTEN
这两个命令看到的结果是一样的,ss 更快,lsof 在查某个进程打开了哪些端口时更顺手,两个都记住,哪个顺手用哪个。
二、先分清三种“占着”,处理方法完全不同
同样是端口被占,背后的原因不一样,处理方法也不一样,动手之前先分清楚。
第一种是真被别的服务占了。另一台应用、同事起的测试实例、或者自己上次启动后没退干净的进程,都可能先一步监听了这个端口。这是最常见的,也是最容易处理的。
第二种是残留进程。服务明明已经停了,但主进程退了、子进程还在,或者 systemctl stop 超时后进程还活着。这时光看端口查不到完整的故事,要用 ps -ef | grep java(换成你自己的进程名)把同名进程都列出来,对照启动时间判断哪个是残留的。
第三种是 TIME_WAIT,最容易误判。压测或者短连接跑完之后,执行 ss -tan | grep TIME_WAIT | wc -l 可能会看到几百上千条。这种状态是 TCP 正常关闭后的等待,端口过几十秒会自动释放,不用 kill,也 kill 不掉。只有状态是 LISTEN 的才是真正占用,把 TIME_WAIT 当成占用者去杀进程,是白忙活。
三、怎么处理:先停旧进程,改端口是兜底
确认了占用进程的身份之后,按这个顺序处理。
先优雅地停掉它:kill 12345,等几秒再执行 ss -tlnp | grep 8080 确认端口释放。如果进程还不走,确认它不是数据库主进程、不是生产关键进程之后,再用 kill -9 12345 强制结束。kill -9 不会给进程清理现场的机会,只用在确认无害的进程上。
如果占用者是不能停的服务,比如同一台机器上另一个业务的正式实例,那就别硬抢端口,改自己服务的端口才是正道。改完配置之后别忘了同步改三处:反向代理(比如 Nginx 里对应的 proxy_pass)、健康检查的端口、以及安全组和防火墙的放行规则,漏掉任何一处,服务就算起来了外面也访问不到。
治本的办法是把端口管理做进日常。启动脚本里先加一行端口预检,端口被占就直接报错退出,别等到服务跑一半才发现;不同环境用不同的端口规划,比如测试环境用 8081、预发布用 8082,写进团队的部署文档里;systemd 管理的服务配好 Restart 策略,避免残留进程长期挂着。
四、一个固定的排查顺序
以后再遇到 Address already in use,按这个顺序走就够了:看报错确认端口号,ss -tlnp 查监听状态,lsof 或 ps 确认占用进程的身份和启动时间,判断是误启动、残留进程还是正常 TIME_WAIT,能停就停、不能停就改端口,重新启动后用 ss 复核端口,最后 curl 本地验证服务真的通了。
端口冲突本身不难,难的是不确认身份就动手。先查再动,把进程的来龙去脉看清楚,这类故障基本都能在十分钟内收尾,也不会因为误杀进程惹出更大的麻烦。
- 点赞
- 收藏
- 关注作者
评论(0)