Skip to content

故障排查 ​

这页帮你在本地起不来、连不上、被拒绝时快速定位:每条问题都按 症状 → 根因 → 定位 展开,不只给解法,还告诉你"为什么会这样、该读哪条日志、该看哪个端口"。读完你能独立判断卡在依赖、环境变量、端口还是鉴权上。

你在这里:多半是在跟着从源码本地开发或起容器栈,遇到了启动或连接报错。先按下面的决策流程把问题归类,再跳到对应小节。除非特别说明,命令都在 iot-dc3/ 目录执行。

先把问题归类:排障决策流程 ​

绝大多数"起不来 / 连不上"都能归到五类:依赖未就绪、环境变量没加载、端口被占、依赖服务启动顺序错、以及鉴权链路问题。按下图自上而下排除,比逐个猜测快得多——平台的 API 流量都经网关(8000;app 栈不发布到宿主机,由 web 前端 8080/8443 反代进网关,dev 栈与本地运行可直连),中心服务靠 gRPC facade 互联、驱动与数据中心靠 RabbitMQ 解耦,所以一旦底层依赖(PostgreSQL / RabbitMQ)没起,上层会连环失败。

按序自上而下排除症状入口是是是是是否否否否否起不来 / 连不上API 经网关 8000 · 中心 gRPC · 驱动走 RabbitMQ依赖容器healthy ?环境变量已加载 ?端口未占用 ?启动顺序正确 ?鉴权头三件套齐 ?依赖未就绪Connection refused · Channel shutdown · vhost not foundmake ps STACK=db 等 healthy · 核对 host 端口 35432 / 35672连接指向错误主机根 .env 只服务 Compose,改它对本地 Java 不生效source dc3/env/dev.env.sh · IDE 用 EnvFile 挂 dc3/env/dev.env端口被占用Address already in use · Web server failed to startlsof / ss / netstat / Get-NetTCPConnection 找 PID → kill 或覆盖端口启动顺序错gRPC UNAVAILABLE · 驱动注册失败 · 下游拿不到上游Auth → Manager → Data → Agentic → Gateway → Driver,每起一个等就绪鉴权失败 401 / 403取盐 → 换 token → 带 X-Auth-Tenant / Login / Token网关跳 HMAC: AUTH_HMAC_SECRET —— pre/pro 弱密钥直接 fail-fast日志关键字定位 —— 前四关排掉后,根因几乎都在日志里Connection refused / Channel shutdown —— 依赖或 vhost 连不通Address already in use —— 端口冲突,按 PID 处理Driver register failed on attempt n/30 —— 注册重试中,查 CENTER_MANAGER_HOSTUnknownHostException / pre·pro fail-fast —— 本地源码调试一律用 dev profile顺序不是随意的: 变量没加载会让所有连接指向错误主机,端口占用让进程在绑定阶段就退出,而启动顺序决定 gRPC facade 与驱动注册能否成功 —— 先排前四关,剩下的几乎都能在日志关键字里看到根因症状入口判定根因与处理解析终点排查顺序

这条决策链的顺序不是随意的:变量没加载会让所有连接指向错误主机,端口占用会让进程在绑定阶段就退出,而依赖服务的启动顺序决定了 gRPC facade 和驱动注册能否成功。把前四关排掉之后,剩下的几乎都能在日志关键字里看到根因。

依赖未就绪:PostgreSQL / RabbitMQ 连不上 ​

症状:应用启动日志反复打印 Connection refused、Connection to localhost:35432 refused,或 RabbitMQ 报 Channel shutdown / vhost not found;中心服务起来后又退出。

根因:PostgreSQL 或 RabbitMQ 容器尚未启动、健康检查未通过,或本地源码运行时连接参数指向了错误的主机/端口。关键陷阱是 host 与容器内地址不同:容器内服务之间用 dc3-postgres:5432、dc3-rabbitmq:5672,而宿主机上的本地 Java 进程要走对外发布端口 localhost:35432、localhost:35672。

定位与处理:先确认依赖栈在跑、且发布端口与应用变量一致。

bash
make ps STACK=db        # 看 postgres / rabbitmq 容器是否 healthy
make config STACK=db    # 打印生效的 compose 配置,核对发布端口
make logs STACK=db      # 跟随依赖容器日志(最近 200 行)
bash
docker ps               # 列出运行中的容器与端口映射(podman 同名命令)
docker exec dc3-postgres psql -U dc3 -d dc3 -c "select 1"   # 直连验证库可用

确认容器 healthy 后,本地源码运行必须先加载环境变量,让连接指向 Compose 发布到 localhost 的依赖:

bash
source dc3/env/dev.env.sh

RabbitMQ 单独排查时,核对这几个变量与容器实际一致(默认值见 环境变量详解):RABBITMQ_HOST、 RABBITMQ_PORT(本地 35672,容器内 5672)、RABBITMQ_USERNAME、RABBITMQ_PASSWORD、RABBITMQ_VIRTUAL_HOST(默认 dc3 )。vhost 不一致会直接表现为连接建立后立刻 Channel shutdown。

为什么先等健康检查

中心服务在启动早期就要建立数据库连接池与 RabbitMQ 通道。若依赖还在初始化(PostgreSQL 首次启动会跑 7 个 initdb 脚本建表与种子数据),过早启动的上层服务会因连接失败而退出。等 make ps STACK=db 显示 healthy 再起上层,能省掉一轮无谓的重启。

环境变量没加载:连接指向错误主机 ​

症状:依赖容器明明在跑,本地源码却连不上,或连到了非预期的主机/端口;改了根目录 .env 却对本地 Java 进程"不生效"。

根因:根目录 .env 只服务 Docker Compose,不会自动注入到本地 Java 进程。本地用 IDE 或命令行直接跑 jar 时,必须显式加载 dc3/env/dev.env(.sh),它把连接指向 Compose 发布在 localhost 上的依赖端口。三个文件分工不同:

文件给谁用作用
根目录 .envDocker Compose镜像仓库、标签、发布端口;不注入本地 Java
dc3/env/dev.envIDE(EnvFile 插件)本地 Java 运行,无 export
dc3/env/dev.env.shShell本地 Java 运行,带 export,用 source 加载

处理:命令行/脚本启动前先 source dc3/env/dev.env.sh;IDE 里用 EnvFile 插件挂 dc3/env/dev.env。完整变量目录、host 与容器内地址的对应关系,见 环境变量详解。

环境变量被反向注入:容器栈崩溃重启(Connection refused: localhost:35432) ​

症状:make up-dev 起容器栈时,dc3-center-* 一启动就崩溃重启,日志里反复出现 Connection refused: localhost/127.0.0.1:35432(或 5672/1883 等宿主侧端口),最终 make up-dev 报 dependency failed to start: container dc3-center-auth is unhealthy。

根因:上一节的反向问题——dc3/env/dev.env 的 localhost:35432 是给本地 Java 进程的,但 Makefile 会自动 include $(RUNTIME_ENV_FILE) 并整体 export,这些值再经 docker compose 的 ${VAR:-default} 插值灌进容器; 容器内的服务就去连 localhost 的宿主侧端口(容器 localhost 里没有依赖),连接被拒、健康检查不过。.env.example 里没有同名变量覆盖它,所以 dev.env 的值必然生效。source dev.env.sh 之后再跑 make up-dev 也会触发同样的污染。

处理:起容器栈时显式抑制——make up-dev RUNTIME_ENV_FILE=(空值即不 include dev.env);或在干净 shell 里跑 (不 source dev.env.sh)。反过来,跑本地 Java 进程才需要第二步的 source。两者互斥,别混用。

端口被占用:进程绑定阶段就退出 ​

症状:启动失败,日志含 Address already in use / Web server failed to start. Port 8400 was already in use,提示 8000、8300、8400、8500、8600、9300、9400、9500 等端口已占用。

根因:同一端口被上一轮没退干净的进程、或别的程序占用。这些端口分别对应网关 HTTP(8000)、四个中心 HTTP(Auth 8300 / Manager 8400 / Data 8500 / Agentic 8600)和三个 gRPC 端口(Auth 9300 / Manager 9400 / Data 9500)。

定位(跨操作系统):先查是谁占了端口,拿到 PID 再决定是结束它还是换端口。

bash
lsof -i :8400 -sTCP:LISTEN    # 列出监听 8400 的进程与 PID
kill <PID>                    # 确认是残留进程后再结束
bash
ss -ltnp 'sport = :8400'      # 显示监听 8400 的进程(含 PID)
bash
netstat -ltnp | grep ':8400'  # 旧系统用 netstat 同样能查到 PID
powershell
Get-NetTCPConnection -LocalPort 8400 -State Listen | Select-Object OwningProcess
Stop-Process -Id <PID>        # 核实后结束占用进程

处理:若端口被合法程序占用、不便结束,就改用环境变量或根目录 .env 覆盖端口,让 DC3 服务避开冲突。常用覆盖变量:

  • DC3_GATEWAY_PORT、DC3_AUTH_PORT、DC3_MANAGER_PORT、DC3_DATA_PORT、DC3_AGENTIC_PORT(Compose 发布端口)
  • SERVER_PORT、GRPC_SERVER_PORT(单服务本地运行时覆盖该进程的 HTTP / gRPC 端口)

本地同时跑多个服务时

SERVER_PORT / GRPC_SERVER_PORT 是单进程级覆盖。只在本地单独起某个服务、需要避让默认端口时设置;多服务并行时要分别赋不同值,否则它们会互相抢同一个端口。

启动顺序错:依赖服务尚未就绪 ​

症状:驱动注册失败、gRPC 调用报 UNAVAILABLE,或中心服务起来后因拿不到下游而异常。

根因:中心服务之间通过 gRPC facade 协作,驱动启动时要向管理中心注册并依赖 RabbitMQ。若上游还没就绪就起下游,连接会失败。正确的启动次序是 Auth → Manager → Data → Agentic → Gateway → Driver(驱动依赖 Manager 与 RabbitMQ 就绪,可与网关并行启动)。

定位与处理:

  1. 按 Auth → Manager → Data → Agentic → Gateway → Driver 顺序启动,每起一个等它就绪再起下一个;驱动只依赖 Manager 与 RabbitMQ,可与网关并行。
  2. 本地源码运行确认已 source dc3/env/dev.env.sh。
  3. 查看管理中心与驱动日志,确认 gRPC 目标地址(CENTER_MANAGER_HOST 等,默认 localhost)可达。
  4. 确认 dc3.driver.code 唯一且稳定——编码重复会让注册被拒。

驱动编码不可随意改

dc3.driver.code 是驱动的稳定路由标识,数据中心据此把命令路由回对应驱动实例。一旦上线就不要改动;改了会导致已绑定该驱动的设备命令无法送达。

设备在线但没数据:值链路逐跳排查 ​

症状:设备显示在线,位号值却不更新;或者部分位号有值、部分没有。

根因:值要走完"驱动读 → 发到总线 → 数据中心消费 → 落库 → API 可查"五跳,任何一跳断了都是这个表现。按跳定位:

  1. 驱动读到没有:驱动日志找读任务的执行记录(读任务按位号采集周期跑);协议类驱动同时看有没有超时/异常。没读到,查设备连通性和位号配置(读写方向、地址、enable 状态)。
  2. 发出去没有:值发布到值交换机(dc3.e.value)。dev 环境带 dev_<用户名>. 前缀——发布端和消费端前缀不一致时,消息发进一套没人消费的拓扑,这是最隐蔽的一类断链,见消息总线拓扑。
  3. 队列积压没有:RabbitMQ 管理页(15672)看 dc3.q.value.point 的 ready 数。堆积说明数据中心消费跟不上,调批量/并发参数(POINT_BATCH_*,见环境变量)。
  4. 落库没有:数据中心日志查摄取错误;死信队列 dc3.q.point_value_dead 有货说明消息被拒或重试耗尽(毒消息),处理方式见数据平面的死信一节。
  5. 查得到没有:dc3_point_latest 里有没有这个位号的行(表结构见数据库结构参考)。有行但界面不显示,再查租户过滤与位号 enable。

鉴权失败:401 / 403 与 HMAC ​

症状:通过网关访问受保护接口返回 401(未认证)或 403(无权限)。

根因:请求没有携带有效 token,或租户/登录名/token 三件套不齐。平台登录是两步:先取盐、再连同盐提交明文密码换 token(盐不参与密码哈希,校验在服务端完成);后续每个受保护请求要带上 X-Auth-Tenant、X-Auth-Login、X-Auth-Token 三个请求头。

定位与处理:先走登录拿 token,再带头访问。下面是黄金路径的真实接口(示例值需替换为你环境的实际值):

bash
# 1) 取盐(公开端点,建议 5 分钟内使用)
curl -X POST http://localhost:8000/api/v3/auth/token/salt \
  -H 'Content-Type: application/json' \
  -d '{"tenant":"default","name":"dc3"}'

# 2) 连同盐提交明文密码换 token(12 小时有效;校验在服务端完成)
curl -X POST http://localhost:8000/api/v3/auth/token/generate \
  -H 'Content-Type: application/json' \
  -d '{"tenant":"default","name":"dc3","salt":"<上一步返回的盐>","password":"<明文密码>"}'

# 3) 带三件套访问受保护接口(X-Auth-Token 头是 JSON:{"salt","token"},裸 token 会 401)
curl -X POST http://localhost:8000/api/v3/data/point_value/latest \
  -H 'X-Auth-Tenant: default' \
  -H 'X-Auth-Login: dc3' \
  -H 'X-Auth-Token: {"salt":"<第 1 步返回的盐>","token":"<第 2 步返回的 token>"}' \
  -H 'Content-Type: application/json' \
  -d '{"deviceId": 1024, "pointId": 2048, "offset": 0, "limit": 10}'

若 401 集中出现在网关到后端这一跳(而非用户 token 问题),多半与 HMAC 签名有关。网关会用 AUTH_HMAC_SECRET 对注入的 X-Auth-Principal 做 HMAC-SHA256 签名,后端校验签名后才信任 principal。Swagger UI 的认证方式见 API 文档。

生产/预发环境 HMAC 会 fail-fast

当 Spring profile(或 spring.env)命中 pre 或 pro 时,若 AUTH_HMAC_SECRET 为空、或仍是默认弱密钥 io.github.pnoker.dc3,服务会在启动时直接抛 IllegalStateException 拒绝启动。这是有意为之的安全闸门:上 pre/pro 前必须把 AUTH_HMAC_SECRET 与 DC3_SECURITY_KEY 换成强随机值,且不得记录或硬编码。

pre/pro profile 本地起不来 ​

症状:用 pre / pro profile 在本地启动时连接报错(UnknownHostException / Connection refused),或服务直接 fail-fast 退出。

根因:pre / pro 面向容器栈部署,连接参数默认指向容器主机名而非 localhost:数据源 POSTGRES_HOST 默认 dc3-postgres、RABBITMQ_HOST 默认 dc3-rabbitmq、gRPC 通道用 static://${CENTER_AUTH_HOST:dc3-center-auth}:9300 这类容器内地址。本地没有这些主机名解析,连接自然失败。叠加 HMAC 安全闸门:pre / pro 下 AUTH_HMAC_SECRET 为空或仍是默认弱密钥时服务会 fail-fast 拒绝启动(见上一节)。

处理:本地源码调试一律用 dev profile——它把连接指向 Compose 发布到 localhost 的依赖端口。只有在真正验证容器部署形态时才用 pre / pro,并确保容器主机名可解析、HMAC / 安全密钥按生产要求配置就位。

构建与镜像类问题 ​

下面几类不属于运行时连接,而是构建/打包阶段,单独归一处。

Maven 构建很慢——根因是并行度或堆内存没生效。仓库已配默认参数:.mvn/maven.config 含 -T 1C、.mvn/jvm.config 含 -Xms512m -Xmx1024m。仍慢可适当加大堆内存或减少本机后台 CPU 占用。

Java 版本错误——出现 unsupported class file major version 或 Maven Enforcer 报错,根因是项目要求 JDK 21。用下面两条确认 Maven 实际使用的 Java 也是 21(两者可能不一致):

bash
java -version
mvn -version

Docker 镜像构建失败——根因多为镜像内 Maven 打包失败或依赖未提前构建。先在宿主机确认 Maven 通过,再构建镜像:

bash
make package
make build STACK=db

镜像源不符合预期——根因是镜像仓库选择。用 REGISTRY 切换:global 走默认仓库(Docker Hub pnoker),cn 走中国大陆镜像(Aliyun)。

bash
make up STACK=db REGISTRY=global   # 默认仓库
make up STACK=db REGISTRY=cn       # 大陆镜像

想更快调试 ​

把 Auth、Manager、Data 的能力放进单个 JVM,用 dc3-center-single 起一个进程,省去多服务间的启动协调:

bash
source dc3/env/dev.env.sh
java -jar dc3-center/dc3-center-single/target/dc3-center-single.jar

单进程仅供本地调试

单 JVM 模式方便本地快速验证,不代表生产部署形态——生产仍是网关 + 四中心 + 驱动的分布式拓扑。

延伸阅读 ​

  • 从源码本地开发 — 本地起依赖、加载环境变量、跑通第一个驱动的完整步骤
  • 环境变量详解 — host 与容器内地址对应、端口与连接变量的完整目录
  • 服务与拓扑 — 网关、四中心与驱动如何编排,理解启动顺序背后的依赖关系
  • API 文档 — Swagger UI 与鉴权头的使用方式

基于 AGPL-3.0 协议发布