@@ -386,32 +386,6 @@ graph TD
386386
387387** 实现原理** : 我们通过 ` -javaagent ` 的方式,为每个微服务无侵入式地挂载了 SkyWalking 的探针。该探针会自动拦截所有进出的 HTTP 请求、JDBC 调用、MQ 消息等,并生成和传递一个全局唯一的 ` TraceID ` ,最终将整条调用链的数据上报给 SkyWalking OAP 服务器进行分析和存储。
388388
389- ### 亮点八:自动化CI/CD流水线 (GitHub Actions)
390-
391- ### 亮点九:生产级的容器化部署实践
392-
393- 将一个复杂的微服务应用正确地部署到容器中,会遇到一系列经典的网络与文件系统挑战。本项目通过优雅的工程实践,解决了两大核心难题,确保了部署的健壮性和可移植性。
394-
395- - ** 优雅的 Nginx 服务发现与路径重写** :
396- - ** 问题** : 在 Docker 环境中,当后端服务(如网关)重启并获得新 IP 时,Nginx 因 DNS 缓存会连接到旧的、已失效的 IP,导致 ` Connection Refused ` 。同时,前端的 ` /api ` 路径前缀也需要被正确剥离。
397- - ** 解决方案** : 我们采用了 ` upstream ` + ` resolver ` + ` rewrite ` 的黄金组合。通过 ` upstream ` 块定义服务池,并利用 ` resolver ` 指令强制 Nginx 动态解析其中的域名,彻底解决了 DNS 缓存问题。同时,使用 ` rewrite ` 指令清晰地处理路径重写,将职责完美分离,保证了配置的健壮与可读性。
398-
399- - ** 巧妙的 Docker-out-of-Docker 路径转换** :
400- - ** 问题** : ` sandbox-service ` (判题服务)自身在容器中运行,但它需要调用宿主机的 Docker 来创建新的判题容器。此时,如果在 ` docker run ` 命令中直接使用容器内部的文件路径作为挂载卷,会因宿主机 Docker 守护进程不认识该路径而导致 ` Mounts denied ` 错误。
401- - ** 解决方案** : 我们实现了一套巧妙的路径转换机制。首先,在 ` docker-compose.yml ` 中,通过 ` ${PWD} ` 变量将宿主机的当前目录绝对路径注入为环境变量 ` HOST_CODE_PATH ` 。然后,在 ` SandboxService.java ` 代码中,动态地将容器内路径(如 ` /app/static/codes/... ` )替换为对应的宿主机路径(如 ` /path/on/host/static/codes/... ` ),最后才将这个转换后的、宿主机守护进程能够理解的路径,用于 ` docker run ` 的 ` -v ` 参数。这一实践完美解决了跨容器边界的文件系统挂载难题。
402-
403- 为了实现快速、可靠、一致的软件交付,我们基于 GitHub Actions 构建了一套完整的自动化 CI/CD (持续集成/持续部署) 流水线。开发者只需将代码推送到 ` main ` 分支,后续的测试、构建、打包、推送镜像、服务器部署等所有繁琐的流程都将自动完成。
404-
405- - ** 并行构建 (` Matrix Strategy ` )** : 流水线利用 GitHub Actions 的 ` strategy.matrix ` 特性,为每一个微服务(` user-service ` , ` problem-service ` 等)创建一个独立的构建任务。这些任务在云端并行执行,极大地缩短了所有服务的总构建时间。
406-
407- - ** 优化的多阶段构建 (` Dockerfile ` )** : 我们为每个微服务都编写了优化的多阶段 ` Dockerfile ` 。在“构建阶段”,使用包含完整 Maven 环境的镜像来编译代码和下载依赖;在“运行阶段”,仅将编译好的 ` .jar ` 文件复制到一个轻量的 JRE 镜像中。这使得最终生成的生产镜像体积最小化,并能充分利用 Docker 的构建缓存机制,提升后续构建的速度。
408-
409- - ** 职责分离的 Jobs (` build-and-push ` & ` deploy ` )** :
410- 1 . ** ` build-and-push ` Job** : 负责编译所有代码,构建所有微服务的 Docker 镜像,并将其推送到 Docker Hub 镜像仓库进行统一的版本管理。
411- 2 . ** ` deploy ` Job** : 该任务依赖于 ` build-and-push ` 的成功。它会通过 SSH 安全地连接到生产服务器,执行预设的部署脚本,自动拉取最新的镜像并以 ` docker-compose ` 的方式重启所有服务,完成部署的“最后一公里”。
412-
413- 通过这套流水线,我们实现了从“代码提交”到“服务上线”的全流程自动化,确保了每次部署都是可重复、可预测和高度可靠的。
414-
415389#### 实际应用场景与查询指南
416390
417391** 场景** : 用户反馈“提交代码后,页面卡了很久才显示结果”。
@@ -434,6 +408,35 @@ graph TD
434408
4354093. **发现根因**: 从上图可以一目了然地发现,整个请求的耗时主要集中在 `submission-service` 对 `sandbox-service` 的 Feign 调用上。这立刻将我们的排查范围缩小到了 `sandbox-service` 内部,而不是去怀疑数据库或 Redis。这正是链路追踪的巨大价值所在。
436410
411+
412+ ### 亮点八:自动化CI/CD流水线 (GitHub Actions)
413+
414+ 为了实现快速、可靠、一致的软件交付,我们基于 GitHub Actions 构建了一套完整的自动化 CI/CD (持续集成/持续部署) 流水线。开发者只需将代码推送到 `main` 分支,后续的测试、构建、打包、推送镜像、服务器部署等所有繁琐的流程都将自动完成。
415+
416+ - **并行构建 (`Matrix Strategy`)**: 流水线利用 GitHub Actions 的 `strategy.matrix` 特性,为每一个微服务(`user-service`, `problem-service` 等)创建一个独立的构建任务。这些任务在云端并行执行,极大地缩短了所有服务的总构建时间。
417+
418+ - **优化的多阶段构建 (`Dockerfile`)**: 我们为每个微服务都编写了优化的多阶段 `Dockerfile`。在“构建阶段”,使用包含完整 Maven 环境的镜像来编译代码和下载依赖;在“运行阶段”,仅将编译好的 `.jar` 文件复制到一个轻量的 JRE 镜像中。这使得最终生成的生产镜像体积最小化,并能充分利用 Docker 的构建缓存机制,提升后续构建的速度。
419+
420+ - **职责分离的 Jobs (`build-and-push` & `deploy`)**:
421+ 1. **`build-and-push` Job**: 负责编译所有代码,构建所有微服务的 Docker 镜像,并将其推送到 Docker Hub 镜像仓库进行统一的版本管理。
422+ 2. **`deploy` Job**: 该任务依赖于 `build-and-push` 的成功。它会通过 SSH 安全地连接到生产服务器,执行预设的部署脚本,自动拉取最新的镜像并以 `docker-compose` 的方式重启所有服务,完成部署的“最后一公里”。
423+
424+ 通过这套流水线,我们实现了从“代码提交”到“服务上线”的全流程自动化,确保了每次部署都是可重复、可预测和高度可靠的。
425+
426+ ### 亮点九:生产级的容器化部署实践
427+
428+ 将一个复杂的微服务应用正确地部署到容器中,会遇到一系列经典的网络与文件系统挑战。本项目通过优雅的工程实践,解决了两大核心难题,确保了部署的健壮性和可移植性。
429+
430+ - **优雅的 Nginx 服务发现与路径重写**:
431+ - **问题**: 在 Docker 环境中,当后端服务(如网关)重启并获得新 IP 时,Nginx 因 DNS 缓存会连接到旧的、已失效的 IP,导致 `Connection Refused`。同时,前端的 `/api` 路径前缀也需要被正确剥离。
432+ - **解决方案**: 我们采用了 `upstream` + `resolver` + `rewrite` 的黄金组合。通过 `upstream` 块定义服务池,并利用 `resolver` 指令强制 Nginx 动态解析其中的域名,彻底解决了 DNS 缓存问题。同时,使用 `rewrite` 指令清晰地处理路径重写,将职责完美分离,保证了配置的健壮与可读性。
433+
434+ - **轻量级的 Docker-out-of-Docker 判题沙箱**:
435+ - **目标**: `sandbox-service`(判题服务)自身运行在容器中,但它需要具备动态创建和管理其他“一次性”判题容器的能力。
436+ - **核心实现**: 我们采用了业界标准的 “Docker-out-of-Docker” 模式,而非在容器内再嵌套一个完整 Docker 环境的 “Docker-in-Docker” 重模式。其实现包含两个关键点:
437+ 1. **共享通信套接字 (`docker.sock`)**: 我们通过 `volumes` 将宿主机的 Unix Socket 文件 `/var/run/docker.sock` 挂载到 `sandbox-service` 容器内部。这个 `sock` 文件是本地 Docker 客户端与 Docker 守护进程之间进行通信的 IPC 通道。通过共享它,我们巧妙地让容器内的进程能够直接与宿主机上的 Docker 守护进程对话。
438+ 2. **安装 Docker 客户端**: 我们在 `sandbox-service` 的 `Dockerfile` 中,通过 `apt-get` 安装了 `docker.io` 包。这为容器提供了 `docker` 命令行客户端。当服务在代码中执行 `docker run` 命令时,这个客户端就会通过挂载进来的 `docker.sock` 文件,将指令发送给宿主机的守护进程去执行,从而实现了“在容器内编排和管理宿主机上的其他容器”这一强大功能。
439+
437440---
438441
439442## 附录:关键技术与设计决策
0 commit comments