WSL2 + Docker 环境下 SpringBoot 启动端口被占用的诡异问题排查
在本地开发环境中,我遇到了一个非常"玄学"的问题:SpringBoot 服务启动时提示 48080 端口被占用,但服务销毁后端口又立刻释放 。
更离谱的是,有时候启动失败,再次启动却又完全正常。
经过排查,最终发现问题与 WSL2 网络模式 + Docker 端口映射 + Redis 容器连接有关。本文把完整排查过程记录下来,供遇到类似问题的人参考。
一、问题现象
项目环境:
- 开发系统:Windows 11 + WSL2
- Java:JDK8
- SpringBoot
- Redis:Docker 容器
- RocketMQ / RabbitMQ / Kafka 等中间件
- 运行方式:SpringBoot 本地运行,Redis 在 WSL 的 Docker 容器中
启动 SpringBoot 时,出现异常:
Bind for :::48080 failed: port is already allocated
查看端口占用:
lsof -i:48080
输出:
docker-pr 16508 root TCP *:48080 (LISTEN)
docker-pr 16516 root TCP *:48080 (LISTEN)
但是当 SpringBoot 启动失败后,再查看端口:
lsof -i:48080
端口又消失了。
也就是说:
| 时间 | 状态 |
|---|---|
| 启动瞬间 | 端口被占用 |
| 服务退出 | 端口释放 |
| 再次启动 | 有时正常 |
表现为 "端口占用,但又不是持续占用"。
二、最终解决方案
经过排查后,最终的解决方法是:
不要连接 WSL 内 Docker 容器的 localhost Redis。
原来我的 Redis 是:
WSL2
└── docker
└── redis:6379
SpringBoot 配置:
spring.redis.host=127.0.0.1
spring.redis.port=6379
后来我把 Redis 改成 另一台服务器上的 Docker Redis:
spring.redis.host=192.168.x.x
spring.redis.port=6379
然后:
项目启动完全正常
之后再切换回来,竟然 偶尔也可以正常启动。
这说明问题并不是代码问题,而是 WSL 网络瞬态状态问题。
三、问题原因分析
这个问题本质上是:
WSL2 Mirror 网络 + Docker 端口代理 + Redis 启动时序竞争(race condition)
完整调用链大致如下:
SpringBoot
↓
Redis Client (Lettuce / Netty)
↓
127.0.0.1:6379
↓
WSL2 Mirror Network
↓
docker-proxy
↓
Redis Container
这里面涉及多个网络层:
SpringBoot
WSL 虚拟网络
Docker bridge
docker-proxy
iptables
Redis container
只要其中一个 未 ready,就会出现问题。
四、为什么会出现"端口占用"
SpringBoot 启动流程大致如下:
1 Bean 初始化
2 RedisClient 初始化
3 MQ 初始化
4 WebServer (Tomcat) 启动
如果 Redis 初始化失败:
Redis connect fail
↓
Spring Context Exception
↓
ApplicationContext destroy
↓
Tomcat stop
↓
端口释放
但是日志顺序会变成:
Tomcat started
Port already in use
ApplicationContextException
看起来就像:
48080 被占用了
实际上:
真正失败的是 Redis 连接。
五、为什么第二次启动又好了
这种现象在 WSL + Docker 中非常常见。
可能原因:
1 Redis 容器刚启动
Redis 可能:
6379 已监听
但服务还没 ready
第一次连接失败,第二次成功。
2 docker-proxy 还未建立端口映射
Docker 端口映射:
host:6379
↓
docker-proxy
↓
container:6379
docker-proxy 在某些情况下 需要几百毫秒建立规则。
3 WSL2 Mirror 网络初始化
WSL2 的 Mirror 网络会涉及:
vEthernet
iptables
bridge
第一次访问可能触发规则创建。
六、如何避免这种问题
推荐以下三种方式。
方案1:服务全部 Docker 化(最稳定)
docker compose
├── springboot
├── redis
├── mysql
├── kafka
SpringBoot 连接:
spring.redis.host=redis
直接通过 Docker Network。
方案2:Redis 放 Windows Docker Desktop
结构:
Windows Docker
└── Redis
WSL SpringBoot
SpringBoot:
localhost:6379
稳定性会比 WSL Docker 高。
方案3:增加连接重试
例如 Redis 连接:
retry
wait-for-it
healthcheck
Docker Compose:
depends_on:
redis:
condition: service_healthy
七、总结
这个问题看起来像:
SpringBoot 端口占用
但实际上是:
WSL2 网络
+
Docker 端口代理
+
Redis 服务启动时序
三者叠加导致的 瞬态连接问题。
典型现象就是:
第一次启动失败
第二次启动成功
如果你在 WSL + Docker + SpringBoot 环境中遇到类似问题,优先检查:
- Redis / MySQL / Kafka 是否 ready
- docker-proxy 端口映射
- WSL 网络模式
- localhost 连接路径
很多时候问题并不在代码,而在 开发环境网络架构。