保姆級(jí)微服務(wù)部署教程
大家好,我是魚皮。
項(xiàng)目上線是每位學(xué)編程同學(xué)必須掌握的基本技能。之前我已經(jīng)給大家分享過很多種上線單體項(xiàng)目的方法了,今天再出一期微服務(wù)項(xiàng)目的部署教程,用一種最簡(jiǎn)單的方法,帶大家輕松部署微服務(wù)項(xiàng)目。
開始之前,先做個(gè)小調(diào)研啊,大家更喜歡看 “真實(shí)踩坑版” 的教程還是 “壓縮純凈版” 的教程呢?
本期教程我還是保持自己一貫的風(fēng)格,依然是保姆級(jí)教程,包含了一些踩坑過程和解決方案,大家跟著做就完事兒~
項(xiàng)目介紹
這次的教程以我?guī)Т蠹胰讨辈ナ职咽珠_發(fā)的、基于 Spring Cloud + Docker 代碼沙箱的 在線判題系統(tǒng) 為例,進(jìn)行演示。
這個(gè)項(xiàng)目的核心功能是讓用戶能夠在線選題和做題,然后由系統(tǒng)自動(dòng)判題:

項(xiàng)目用到的核心依賴包括:MySQL 數(shù)據(jù)庫、Redis 緩存、RabbitMQ 消息隊(duì)列、Nacos 注冊(cè)中心
涉及的核心服務(wù)包括:用戶服務(wù)、題目服務(wù)、判題服務(wù)(代碼沙箱)、Gateway 網(wǎng)關(guān)服務(wù)

傳統(tǒng)部署
對(duì)于這樣一個(gè)項(xiàng)目,如果我們還用傳統(tǒng)單機(jī)項(xiàng)目的部署方式,一個(gè)個(gè)打 jar 包、用 Java 命令來啟動(dòng),會(huì)有哪些問題呢?
要一個(gè)個(gè)安裝依賴,比如 MySQL 數(shù)據(jù)庫、Redis、消息隊(duì)列、Nacos,非常麻煩!
要一個(gè)個(gè)打 jar 包、一個(gè)個(gè)手動(dòng)運(yùn)行 jar 包,非常麻煩!
不方便集中觀察所有服務(wù)的運(yùn)行狀態(tài)和資源占用情況
所以,為了解決這些問題,我們會(huì)選用一種更高效的微服務(wù)部署方式 —— Docker Compose。
Docker Compose 介紹
在介紹 Docker Compose 前,先簡(jiǎn)單介紹下 Docker。
Docker 是一種容器技術(shù),允許開發(fā)者將應(yīng)用程序和所有依賴項(xiàng)(如代碼、庫、配置等)制作為 鏡像
??梢园宴R像簡(jiǎn)單理解為軟件安裝包,可以在不同的計(jì)算機(jī)上通過它快速安裝和啟動(dòng)應(yīng)用程序(容器),這些程序獨(dú)立隔離地運(yùn)行,不受外部環(huán)境的影響。

如果要部署微服務(wù)項(xiàng)目,可能要啟動(dòng)多個(gè) Docker 容器,比如 MySQL 容器、用戶服務(wù)容器等。這時(shí)就需要 Docker Compose 了。它是一個(gè)容器編排助手,用于集中管理多個(gè) Docker 容器的啟動(dòng)和協(xié)同工作。可以在一個(gè)配置文件中集中定義所有容器以及它們的關(guān)系。然后,可以使用一行命令啟動(dòng)所有容器,而不需要手動(dòng)運(yùn)行多個(gè)命令。

需要注意的是,Docker Compose 通常適用于把所有微服務(wù)部署在同一臺(tái)服務(wù)器的場(chǎng)景,在真實(shí)的企業(yè)級(jí)項(xiàng)目中,往往會(huì)使用 K8S 等更專業(yè)的容器編排和自動(dòng)化部署工具,更方便地在多個(gè)服務(wù)器上部署容器。
部署流程
了解了 Docker 和 Docker Compose 的作用后,我們來快速了解下部署流程,分為 2 大階段 —— 本地部署和服務(wù)器部署。
一、本地部署
梳理服務(wù)部署表格
Maven 子父模塊打包
Dockerfile 編寫
編寫環(huán)境依賴配置
編寫服務(wù)配置
調(diào)整程序配置
測(cè)試訪問
二、服務(wù)端部署
準(zhǔn)備服務(wù)器
Docker Compose 安裝
同步文件
獲取 jar 包
服務(wù)啟動(dòng)
測(cè)試訪問
一、本地部署
第一階段是本地部署,也可以叫做部署準(zhǔn)備。
強(qiáng)烈建議大家,比起直接操作線上服務(wù)器,最好是先在本地把所有的流程跑通,風(fēng)險(xiǎn)更低、效率更高。
這里我使用的是 Mac 操作系統(tǒng),已經(jīng)安裝了 Docker Desktop 軟件,管理 Docker 容器會(huì)更方便一些。

對(duì)于本地沒有 Docker 環(huán)境的同學(xué),這一階段仔細(xì)看一遍有個(gè)印象就足夠了??梢灾苯幽梦艺{(diào)試好的配置文件在服務(wù)器上部署,而不用自己調(diào)試。
怎么樣,夠貼心吧!可以叫我 “保姆魚皮”,簡(jiǎn)稱 “保姆皮”。

1.1、梳理服務(wù)部署表格
在部署微服務(wù)項(xiàng)目前,首先要規(guī)劃好要部署哪些服務(wù)、以及各服務(wù)的關(guān)鍵信息,比如服務(wù)名稱、版本號(hào)、占用端口號(hào)、關(guān)鍵配置等。
對(duì)于我的在線判題項(xiàng)目,梳理好的服務(wù)表格如下:
服務(wù)名稱英文名端口號(hào)版本號(hào)服務(wù)類別數(shù)據(jù)庫mysql3306v8環(huán)境依賴緩存redis6379v6環(huán)境依賴消息隊(duì)列rabbitmq5672, 15672v3.12.6環(huán)境依賴注冊(cè)中心nacos8848v2.2.0環(huán)境依賴網(wǎng)關(guān)服務(wù)gateway8101java 8業(yè)務(wù)服務(wù)用戶服務(wù)yuoj-backend-user-service8102java 8業(yè)務(wù)服務(wù)題目服務(wù)yuoj-backend-question-service8103java 8業(yè)務(wù)服務(wù)判題服務(wù)yuoj-backend-judge-service8104java 8業(yè)務(wù)服務(wù)
為什么這里我要?jiǎng)澐址?wù)類別為 “環(huán)境依賴” 和 “業(yè)務(wù)服務(wù)” 呢?
因?yàn)樵趩?dòng)服務(wù)時(shí),必須要先啟動(dòng)環(huán)境依賴,才能啟動(dòng)業(yè)務(wù)服務(wù),否則就會(huì)報(bào)類似 “無法連接數(shù)據(jù)庫” 之類的錯(cuò)誤。
1.2、Maven 子父模塊打包
對(duì)于微服務(wù)項(xiàng)目,我們通常是使用 Maven 的子父模塊功能進(jìn)行管理的。需要部署項(xiàng)目時(shí),不用針對(duì)每個(gè)子服務(wù)單獨(dú)執(zhí)行 mvn package
命令進(jìn)行打包,而是可以一鍵打包所有服務(wù)。
想要實(shí)現(xiàn)這個(gè)功能,需要給子父模塊的依賴文件(pom.xml)進(jìn)行一些配置,主要包括:
1)父模塊配置
在父模塊的 pom.xml 文件中引入 spring-boot-maven-plugin
即可,注意一定不要配置 configuration 和 repackage!
示例代碼如下:
<plugin>
????<groupId>org.springframework.boot</groupId>
????<artifactId>spring-boot-maven-plugin</artifactId>
????<version>${spring-boot.version}</version>
</plugin>
2)子模塊配置
修改所有需要啟動(dòng) Spring Boot 的服務(wù)(用戶服務(wù)、題目服務(wù)、判題服務(wù)、網(wǎng)關(guān)服務(wù))的子模塊 pom.xml 文件。
主要是增加 executions 配置,使用 spring-boot-maven-plugin 的 repackage 命令來構(gòu)建子模塊,從而自動(dòng)在構(gòu)建時(shí)將公共模塊的依賴打入 jar 包。
示例代碼如下:
<plugin>
????<groupId>org.springframework.boot</groupId>
????<artifactId>spring-boot-maven-plugin</artifactId>
????<executions>
????????<execution>
????????????<id>repackage</id>
????????????<goals>
????????????????<goal>repackage</goal>
????????????</goals>
????????</execution>
????</executions>
</plugin>
1.3、Dockerfile 編寫
Dockerfile 是定義 Docker 容器鏡像構(gòu)建過程的文件,包括容器鏡像使用的基礎(chǔ)環(huán)境、容器內(nèi)的依賴和文件、容器的配置、啟動(dòng)命令等。
有了 Dockerfile,我們就能很輕松地制作出自己的容器鏡像。
雖然 Dockerfile 的寫法并不復(fù)雜,但我還是建議大家盡量不要自己寫,直接去網(wǎng)上找個(gè)差不多的項(xiàng)目,復(fù)制粘貼別人的 Dockerfile 就足夠了!
這里魚皮給大家提供 2 種常用的 Spring Boot 項(xiàng)目的 Dockerfile。
1)復(fù)制 jar 包版
思路:在本地打好 jar 包后,復(fù)制 jar 包到容器中運(yùn)行
示例代碼如下:
#?基礎(chǔ)鏡像
FROM?openjdk:8-jdk-alpine
#?指定工作目錄
WORKDIR?/app
#?將?jar?包添加到工作目錄,比如?target/yuoj-backend-user-service-0.0.1-SNAPSHOT.jar
ADD?{本地?jar?包路徑}?.?
#?暴露端口
EXPOSE?{服務(wù)端口號(hào)}
#?啟動(dòng)命令
ENTRYPOINT?["java","-jar","/app/{jar?包名稱}","--spring.profiles.active=prod"]
2)Maven 打包版
思路:復(fù)制本地代碼到容器中,在容器中使用 Maven 打包再運(yùn)行
示例代碼如下:
#?基礎(chǔ)鏡像
FROM?maven:3.8.1-jdk-8-slim?as?builder
#?指定工作目錄
WORKDIR?/app
#?添加源碼文件
COPY?pom.xml?.
COPY?src?./src
#?構(gòu)建?jar?包,跳過測(cè)試
RUN?mvn?package?-DskipTests
#?啟動(dòng)命令
ENTRYPOINT?["java","-jar","/app/target/{jar?包名稱}","--spring.profiles.active=prod"]
此處由于我們的微服務(wù)項(xiàng)目可以一鍵打好所有子服務(wù)的 jar 包,就沒必要每個(gè)服務(wù)單獨(dú)在容器中打包了,所以選擇第一種方式的 Dockerfile。
我們需要給每個(gè) Spring Boot 服務(wù)(用戶服務(wù)、題目服務(wù)、判題服務(wù)、網(wǎng)關(guān)服務(wù))都編寫一個(gè) Dockerfile,放到每個(gè)子服務(wù)的根目錄下。

以用戶服務(wù)為例,示例代碼如下:
#?基礎(chǔ)鏡像
FROM?openjdk:8-jdk-alpine
??
#?指定工作目錄
WORKDIR?/app
??
#?將?jar?包添加到工作目錄,比如?target/yuoj-backend-user-service-0.0.1-SNAPSHOT.jar
ADD?target/yuoj-backend-user-service-0.0.1-SNAPSHOT.jar?.
??
#?暴露端口
EXPOSE?8102
??
#?啟動(dòng)命令
ENTRYPOINT?["java","-jar","/app/yuoj-backend-user-service-0.0.1-SNAPSHOT.jar","--spring.profiles.active=prod"]
建議先在本地利用 IDEA 開發(fā)工具調(diào)通鏡像構(gòu)建流程,確保每個(gè) Dockerfile 都是可以成功制作鏡像的:

查看容器的啟動(dòng)日志,發(fā)現(xiàn)能夠啟動(dòng)服務(wù)、看到 Spring 圖標(biāo)即可:

1.4、編寫環(huán)境依賴配置
接下來,我們就要編寫 Docker Compose 的配置文件了,可以根據(jù)配置文件快速啟動(dòng)多個(gè)服務(wù)。
之前我們已經(jīng)梳理了服務(wù)部署表格,將服務(wù)劃分為了 “環(huán)境依賴” 和 “業(yè)務(wù)服務(wù)”。
由于業(yè)務(wù)服務(wù)依賴 MySQL 等環(huán)境依賴,所以需要拆分 2 套 Docker Compose 的配置文件,分別為 docker-compose.env.yml
環(huán)境配置和 docker-compose.service.yml
業(yè)務(wù)服務(wù)配置,保證先成功啟動(dòng)依賴,再啟動(dòng)服務(wù)。
學(xué)過 Docker Compose 的同學(xué)可能聽說過 depends_on 配置,也能決定服務(wù)的啟動(dòng)順序。但是千萬注意,depends_on 并不會(huì)等待服務(wù)完全就緒,只是確保它們?cè)趩?dòng)時(shí)的順序,并不穩(wěn)定。
如何編寫 Docker Compose 文件呢?
和 Dockerfile 一樣,直接去網(wǎng)上找現(xiàn)成的 Docker Compose file,復(fù)制粘貼過來略做修改就能使用了~
再配合以下 2 個(gè)網(wǎng)站,完全無需記憶 Docker Compose 的寫法!
Docker Compose file 官方文檔:https://docs.docker.com/compose/compose-file/
搜索現(xiàn)成的 Docker 鏡像:https://hub.docker.com/
當(dāng)然,現(xiàn)在 AI 時(shí)代了,還有更簡(jiǎn)單的方式!
直接把我們整理好的服務(wù)部署需要喂給 GPT,讓 AI 幫我們生成配置即可~
示例 prompt:
現(xiàn)在我需要用?docker?compose?來部署?mysql?8(3306?端口)username=root,password=123456
redis?6(無密碼,6379端口)、rabbitmq?v.3.12.6(?5672?端口???password:?guest,username:?guest)、nacos?2.2.0(8848端口);還有?4?個(gè)本地的?springboot?服務(wù)(名稱分別為:yuoj-backend-user-service?8102端口、yuoj-backend-question-service?8103端口、yuoj-backend-judge-service?8104端口、yuoj-backend-gateway?8101?端口),每個(gè)服務(wù)本地目錄都有一個(gè)?Dockerfile,請(qǐng)幫我自動(dòng)生成?docker?compose?的?yml?配置文件,要求這些服務(wù)網(wǎng)絡(luò)能夠連通
效果還是非常不錯(cuò)的,只要你描述地足夠清楚,生成的配置完全可用!

由于這篇文章是教程嘛,我就帶大家通過調(diào)試的方式一步步完成 Docker Compose 文件,最后會(huì)把完整的 Docker Compose 文件給大家分享出來,大家直接用就行了~
我們要分別在 Docker Compose 中定義 4 大基礎(chǔ)依賴,包括 MySQL、Redis、RabbitMQ 和 Nacos。
1)MySQL
我們不僅要?jiǎng)?chuàng)建一個(gè) MySQL 服務(wù),還要在創(chuàng)建服務(wù)后自動(dòng)創(chuàng)建我們需要的庫表結(jié)構(gòu)。
所以需要先準(zhǔn)備數(shù)據(jù)庫 SQL 腳本文件,里面包含了建庫、建表語句,我們把它放在微服務(wù)項(xiàng)目根目錄的 mysql-init
文件夾中:
魚皮帶大家做的每個(gè)項(xiàng)目都提供了現(xiàn)成的建表語句,這是一個(gè)非常好的開發(fā)習(xí)慣,便于其他人快速啟動(dòng)你的項(xiàng)目。

由于要在本地啟動(dòng) MySQL,還需要定義一個(gè)文件夾 .mysql-data
來存放 MySQL 的持久化數(shù)據(jù),防止容器重啟后數(shù)據(jù)丟失。
做好這兩點(diǎn)后,就可以編寫 docker-compose.env.yml
文件了,先只寫一個(gè) MySQL 服務(wù),示例代碼如下:
關(guān)鍵配置的含義我都寫到注釋里了
version:?'3'
services:
??mysql:
????image:?mysql:8?#?使用的鏡像
????container_name:?yuoj-mysql?#?啟動(dòng)的實(shí)例名稱
????environment:
??????MYSQL_ROOT_PASSWORD:?123456?#?root?用戶密碼
????ports:
??????-?"3306:3306"?#?端口映射
????volumes:
??????-?./.mysql-data:/var/lib/mysql?#?將數(shù)據(jù)目錄掛載到本地目錄以進(jìn)行持久化
??????-?./mysql-init:/docker-entrypoint-initdb.d?#?自動(dòng)執(zhí)行啟動(dòng)腳本
????restart:?always?#?崩潰后自動(dòng)重啟
????networks:
??????-?mynetwork?#?指定網(wǎng)絡(luò)
networks:
??mynetwork:?#?自定義網(wǎng)絡(luò),實(shí)現(xiàn)網(wǎng)絡(luò)互通和隔離
寫好配置文件后,可以直接在 IDEA 里執(zhí)行 Docker Compose 文件,調(diào)試 MySQL 的運(yùn)行:

運(yùn)行成功后,我們可以在本地成功連接數(shù)據(jù)庫:

2)Redis
Redis 服務(wù)的定義和啟動(dòng)操作和 MySQL 服務(wù)幾乎一致,Redis 的 Docker Compose 配置示例代碼如下:
version:?'3'
services:
??redis:
????image:?redis:6
????container_name:?yuoj-redis
????ports:
??????-?"6379:6379"
????networks:
??????-?mynetwork
????volumes:
??????-?./.redis-data:/data?#?持久化
networks:
??mynetwork:
然后在本地執(zhí)行 Docker Compose 文件,啟動(dòng) Redis 服務(wù),并且嘗試進(jìn)入 Terminal 來調(diào)試 Redis:

3)RabbitMQ
同 MySQL 和 Redis,RabbitMQ 的 Docker Compose 配置示例代碼如下:
version:?'3'
services:
??rabbitmq:
????image:?rabbitmq:3.12.6-management?#?支持管理面板的消息隊(duì)列
????container_name:?yuoj-rabbitmq
????environment:
??????RABBITMQ_DEFAULT_USER:?guest
??????RABBITMQ_DEFAULT_PASS:?guest
????ports:
??????-?"5672:5672"
??????-?"15672:15672"?#?RabbitMQ?Dashboard?端口
????volumes:
??????-?./.rabbitmq-data:/var/lib/rabbitmq?#?持久化
????networks:
??????-?mynetwork
networks:
??mynetwork:
本地執(zhí)行 Docker Compose 文件,啟動(dòng) RabbitMQ 服務(wù),然后可以訪問 localhost:15672
查看到管理面板,就表示啟動(dòng)成功了~
賬號(hào)密碼都是 guest

4)Nacos
和其他服務(wù)一樣,Nacos 也需要編寫 Docker Compose 配置。
但是在選擇 Nacos 鏡像時(shí)必須要注意,建議選擇支持 linux/arm64 架構(gòu)的鏡像版本,比如 v2.2.0-slim
,否則后面可能會(huì)無法運(yùn)行:

Nacos 示例配置文件如下:
version:?'3'
services:
??nacos:
????image:?nacos/nacos-server:v2.2.0-slim
????container_name:?yuoj-nacos
????ports:
??????-?"8848:8848"
????volumes:
??????-?./.nacos-data:/home/nacos/data
????networks:
??????-?mynetwork
????environment:
??????-?MODE=standalone?#?單節(jié)點(diǎn)模式啟動(dòng)
??????-?PREFER_HOST_MODE=hostname?#?支持?hostname
??????-?TZ=Asia/Shanghai?#?控制時(shí)區(qū)
networks:
??mynetwork:
然后在本地執(zhí)行 Docker Compose 啟動(dòng) Nacos,訪問 localhost:8848/nacos
能夠看到管理頁面,就表示運(yùn)行成功了~
管理頁面的賬號(hào)和密碼默認(rèn)都是 nacos

完整 Docker Compose 文件
分別調(diào)試完上述服務(wù)后,我們把所有的配置拼在一起,就得到了完整的文件,文件名為 docker-compose.env.yml
。
完整代碼如下:
version:?'3'
services:
??mysql:
????image:?mysql:8?#?使用的鏡像
????container_name:?yuoj-mysql?#?啟動(dòng)的實(shí)例名稱
????environment:
??????MYSQL_ROOT_PASSWORD:?123456?#?root?用戶密碼
????ports:
??????-?"3306:3306"?#?端口映射
????volumes:
??????-?./.mysql-data:/var/lib/mysql?#?將數(shù)據(jù)目錄掛載到本地目錄以進(jìn)行持久化
??????-?./mysql-init:/docker-entrypoint-initdb.d?#?啟動(dòng)腳本
????restart:?always?#?崩潰后自動(dòng)重啟
????networks:
??????-?mynetwork?#?指定網(wǎng)絡(luò)
??redis:
????image:?redis:6
????container_name:?yuoj-redis
????ports:
??????-?"6379:6379"
????networks:
??????-?mynetwork
????volumes:
??????-?./.redis-data:/data?#?持久化
??rabbitmq:
????image:?rabbitmq:3.12.6-management?#?支持管理面板的消息隊(duì)列
????container_name:?yuoj-rabbitmq
????environment:
??????RABBITMQ_DEFAULT_USER:?guest
??????RABBITMQ_DEFAULT_PASS:?guest
????ports:
??????-?"5672:5672"
??????-?"15672:15672"?#?RabbitMQ?Dashboard?端口
????volumes:
??????-?./.rabbitmq-data:/var/lib/rabbitmq?#?持久化
????networks:
??????-?mynetwork
??nacos:
????image:?nacos/nacos-server:v2.2.0-slim
????container_name:?yuoj-nacos
????ports:
??????-?"8848:8848"
????volumes:
??????-?./.nacos-data:/home/nacos/data
????networks:
??????-?mynetwork
????environment:
??????-?MODE=standalone?#?單節(jié)點(diǎn)模式啟動(dòng)
??????-?PREFER_HOST_MODE=hostname?#?支持?hostname
??????-?TZ=Asia/Shanghai?#?控制時(shí)區(qū)
networks:
??mynetwork:
1.5、編寫業(yè)務(wù)服務(wù)配置
用同樣的方式,我們可以編寫業(yè)務(wù)服務(wù)的 Docker Compose 文件,文件名稱 docker-compose.service.yml
。
示例代碼如下,其中需要格外關(guān)注的配置是 build 和 depends_on:
version:?'3'
services:
??yuoj-backend-gateway:
????container_name:?yuoj-backend-gateway
????build:?#?服務(wù)的?Docker?構(gòu)建文件位置
??????context:?./yuoj-backend-gateway
??????dockerfile:?Dockerfile
????ports:
??????-?"8101:8101"
????networks:
??????-?mynetwork
??
??yuoj-backend-user-service:
????container_name:?yuoj-backend-user-service
????build:
??????context:?./yuoj-backend-user-service
??????dockerfile:?Dockerfile
????ports:
??????-?"8102:8102"
????networks:
??????-?mynetwork
????depends_on:?#?本服務(wù)依賴的服務(wù),控制啟動(dòng)先后順序
??????-?yuoj-backend-gateway
??yuoj-backend-question-service:
????container_name:?yuoj-backend-question-service
????build:
??????context:?./yuoj-backend-question-service
??????dockerfile:?Dockerfile
????ports:
??????-?"8103:8103"
????networks:
??????-?mynetwork
????depends_on:
??????-?yuoj-backend-user-service
??????-?yuoj-backend-gateway
??yuoj-backend-judge-service:
????container_name:?yuoj-backend-judge-service
????build:
??????context:?./yuoj-backend-judge-service
??????dockerfile:?Dockerfile
????ports:
??????-?"8104:8104"
????networks:
??????-?mynetwork
????depends_on:
??????-?yuoj-backend-user-service
??????-?yuoj-backend-question-service
??????-?yuoj-backend-gateway
#?網(wǎng)絡(luò),不定義的話就是默認(rèn)網(wǎng)絡(luò)
networks:
??mynetwork:
1.6、調(diào)整程序配置
編寫好上述配置文件后,本地嘗試運(yùn)行 Docker Compose 業(yè)務(wù)服務(wù),結(jié)果發(fā)現(xiàn):報(bào)錯(cuò)啦!依賴服務(wù)的地址訪問不通!
這是由于之前我們的項(xiàng)目訪問依賴服務(wù)時(shí),全部是使用了固定的 IP 地址(比如 localhost),而容器內(nèi)部的 localhost(或 127.0.0.1)通常指向容器本身,而不是宿主主機(jī)。所以為了在容器內(nèi)訪問其他服務(wù),程序中應(yīng)該使用服務(wù)名稱而不是 localhost。
我們給每個(gè) Spring Boot 服務(wù)都增加一套 prod 上線配置,在配置中更改服務(wù)調(diào)用地址。
用戶服務(wù)、題目服務(wù)和判題服務(wù)的 application-prod.yml
配置修改如下:
#?生產(chǎn)環(huán)境配置文件
spring:
??#?數(shù)據(jù)庫配置
??datasource:
????driver-class-name:?com.mysql.cj.jdbc.Driver
????url:?jdbc:mysql://mysql:3306/yuoj?#?localhost?改為?mysql
????username:?root
????password:?123456
??#?Redis?配置
??redis:
????database:?1
????host:?redis?#?localhost?改為?redis
????port:?6379
????timeout:?5000
??cloud:
????nacos:
??????discovery:
????????server-addr:?nacos:8848?#?localhost?改為?nacos
??rabbitmq:
????host:?rabbitmq?#?localhost?改為?rabbitmq
????port:?5672
????password:?guest
????username:?guest
Gateway 網(wǎng)關(guān)服務(wù)的配置修改如下:
spring:
??cloud:
????nacos:
??????discovery:
????????server-addr:?nacos:8848?#?localhost?改為?nacos
????gateway:
??????routes:
????????-?id:?yuoj-backend-user-service
??????????uri:?lb://yuoj-backend-user-service
??????????predicates:
????????????-?Path=/api/user/**
????????-?id:?yuoj-backend-question-service
??????????uri:?lb://yuoj-backend-question-service
??????????predicates:
????????????-?Path=/api/question/**
????????-?id:?yuoj-backend-judge-service
??????????uri:?lb://yuoj-backend-judge-service
??????????predicates:
????????????-?Path=/api/judge/**
??application:
????name:?yuoj-backend-gateway
??main:
????web-application-type:?reactive
server:
??port:?8101
knife4j:
??gateway:
????enabled:?true
????strategy:?discover
????discover:
??????enabled:?true
??????version:?swagger2
然后執(zhí)行 mvn package
命令重新打包、執(zhí)行 Docker Compose。
結(jié)果發(fā)現(xiàn)大多數(shù)服務(wù)都啟動(dòng)了,但是判題服務(wù)還有報(bào)錯(cuò)。

這是因?yàn)槌绦蛟趧?chuàng)建消息隊(duì)列時(shí)存在硬編碼的變量,指定了 host 為 "localhost",示例代碼如下:
ConnectionFactory?factory?=?new?ConnectionFactory();
factory.setHost("localhost");
Connection?connection?=?factory.newConnection();
Channel?channel?=?connection.createChannel();
String?EXCHANGE_NAME?=?"code_exchange";
channel.exchangeDeclare(EXCHANGE_NAME,?"direct");
舉這個(gè)例子是為了告訴大家,在程序中盡量不要寫固定 IP 或域名,全部改為動(dòng)態(tài)讀取配置文件,便于修改。
示例修改后的代碼如下:
/**
?*?用于創(chuàng)建測(cè)試程序用到的交換機(jī)和隊(duì)列(只用在程序啟動(dòng)前執(zhí)行一次)
?*/
4j
public?class?InitRabbitMqBean?{
???? ("${spring.rabbitmq.host:localhost}")
????private?String?host;
????
????public?void?init()?{
????????try?{
????????????ConnectionFactory?factory?=?new?ConnectionFactory();
????????????factory.setHost(host);
????????????Connection?connection?=?factory.newConnection();
????????????Channel?channel?=?connection.createChannel();
????????????String?EXCHANGE_NAME?=?"code_exchange";
????????????channel.exchangeDeclare(EXCHANGE_NAME,?"direct");
????????????//?創(chuàng)建隊(duì)列,隨機(jī)分配一個(gè)隊(duì)列名稱
????????????String?queueName?=?"code_queue";
????????????channel.queueDeclare(queueName,?true,?false,?false,?null);
????????????channel.queueBind(queueName,?EXCHANGE_NAME,?"my_routingKey");
????????????log.info("消息隊(duì)列啟動(dòng)成功");
????????}?catch?(Exception?e)?{
????????????log.error("消息隊(duì)列啟動(dòng)失敗");
????????}
????}
}
1.7、測(cè)試訪問
修復(fù)上述問題后,所有服務(wù)都可以通過 Docker Compose 文件啟動(dòng)了。
然后我們?cè)L問 localhost:8101/doc.html
網(wǎng)關(guān)地址,能夠看到 Swagger 聚合接口文檔。

依次調(diào)用用戶注冊(cè) => 登錄 => 獲取登錄用戶信息 => 創(chuàng)建題目接口,全部執(zhí)行成功。
至此,第一階段就完成啦。
二、服務(wù)器部署
在第二階段,我們的目標(biāo)就是在真實(shí)的 Linux 服務(wù)器上部署微服務(wù)項(xiàng)目。有了第一階段的準(zhǔn)備,第二階段簡(jiǎn)直可以說是易如反掌!
2.1、準(zhǔn)備服務(wù)器
首先,我們要有一臺(tái) Linux 服務(wù)器。
選擇服務(wù)器前,我們必須要評(píng)估下微服務(wù)項(xiàng)目的內(nèi)存占用,千萬別把服務(wù)器的內(nèi)存買小了!
可以使用 Docker Desktop 直接查看內(nèi)存占用,虛擬機(jī)內(nèi)存大概占用了 8 個(gè) G、容器實(shí)際內(nèi)存占用了 4 個(gè) G:

那我們搞多少內(nèi)存的服務(wù)器呢?
我猜很多同學(xué)會(huì)說 8 G,奈何我天生反骨,明知山有虎偏向虎山行(主要是想省 ??),我就搞一臺(tái) 2 核 4 G 的服務(wù)器吧(發(fā)行版是 CentOS 7.9 Linux),咱們來猜一猜它夠不夠部署這套有 4 個(gè)業(yè)務(wù)服務(wù)的微服務(wù)項(xiàng)目呢?
咱們一起來見證下!
2.2、Docker Compose 安裝
有了服務(wù)器后,直接參考 Docker Compose 官方文檔來安裝。這里我們使用 Docker Compose V2 版本,相比 V1 版本統(tǒng)一了命令,使用更方便:
Docker Compose V2 地址:https://docs.docker.com/compose/migrate/
Docker Compose Linux 安裝:https://docs.docker.com/compose/install/linux/#install-using-the-repository
安裝過程很簡(jiǎn)單,跟著官方文檔來就行了,主要包括以下幾個(gè)步驟:
1)設(shè)定安裝來源:
sudo?yum?install?-y?yum-utils
sudo?yum-config-manager?--add-repo?https://download.docker.com/linux/centos/docker-ce.repo
2)安裝 Docker 和 Docker Compose:
sudo?yum?install?docker-ce?docker-ce-cli?containerd.io?docker-buildx-plugin?docker-compose-plugin
3)啟動(dòng) Docker:
sudo?systemctl?start?docker
4)測(cè)試 Docker:
systemctl?status?docker
sudo?docker?run?hello-world
2.3、同步文件
接下來,我們需要把本地折騰好的微服務(wù)項(xiàng)目源碼上傳到服務(wù)器上,可以選擇用 FTP 或 SSH 連接文件手動(dòng)上傳文件。
我這里使用 JetBrains 開發(fā)工具的遠(yuǎn)程部署功能,可以配置文件自動(dòng)上傳,步驟如下:
1)進(jìn)入遠(yuǎn)程部署配置
2)添加遠(yuǎn)程部署配置:
這里建議大家不要暴露自己的服務(wù)器 IP 啊,當(dāng)你看到本文的時(shí)候,其實(shí)我已經(jīng)把服務(wù)器的 IP 更換掉了哈哈哈哈哈哈哈哈!
3)指定連接的服務(wù)器配置:
4)配置本地文件和服務(wù)器文件路徑映射:
5)開啟自動(dòng)上傳:
6)首次需要手動(dòng)上傳文件。
上傳前記得先刪除無用的文件,然后右鍵項(xiàng)目根目錄,點(diǎn)擊部署上傳代碼:
上傳成功,在服務(wù)器對(duì)應(yīng)路徑(/code/yuoj-backend-microservice)下能看到已上傳的文件列表:
2.4、獲取 jar 包
光把代碼上傳到服務(wù)器還是不夠的,因?yàn)槲覀儤?gòu)建 Docker 鏡像需要 jar 包。
有 2 種方式得到 jar 包:
本地執(zhí)行
mvn package
打好 jar 包,然后再上傳服務(wù)器上裝 Maven,在服務(wù)器上打包
但是因?yàn)?jar 包比較大,頻繁改動(dòng)的話同步速度會(huì)比較慢,所以更建議第二種方式,步驟如下:
1)安裝 Maven:
sudo?yum?install?maven
2)安裝好后,執(zhí)行打包:
sudo?mvn?package
打包成功:
2.5、服務(wù)啟動(dòng)
所有一切準(zhǔn)備就緒,接下來就是使用 Docker Compose 命令分別啟動(dòng)環(huán)境依賴和業(yè)務(wù)服務(wù)啦。
1)啟動(dòng)環(huán)境依賴
先使用 docker compose 一行命令啟動(dòng)環(huán)境依賴:
docker?compose?-f?docker-compose.env.yml?up
注意:
老版本使用 "docker-compose" 替代 "docker compose"
如果沒有權(quán)限,命令前加上 "sudo"
然后喝杯咖啡,等待啟動(dòng)即可~
啟動(dòng)成功后,我們可以通過公網(wǎng) IP 來嘗試訪問服務(wù)。
先進(jìn)入到云服務(wù)商的服務(wù)器配置頁,修改服務(wù)器的防火墻配置,放通以下端口:
然后像訪問本地服務(wù)一樣分別去訪問 MySQL、Redis、RabbitMQ、Nacos 即可,應(yīng)該都是成功的。
由于進(jìn)程在前臺(tái)啟動(dòng)會(huì)影響我們的操作,所以先按 ctrl + c
退出,加上 -d
參數(shù)讓容器在后臺(tái)啟動(dòng):
sudo?docker?compose?-f?docker-compose.env.yml?up?-d
試著查看下 docker 容器的狀態(tài):
sudo?docker?stats
能夠查看到所有容器的資源占用情況:
實(shí)話說,我開始緊張了,不知道 4 G 的內(nèi)存夠不夠撐。。。
2)啟動(dòng)業(yè)務(wù)服務(wù)
確保環(huán)境依賴都啟動(dòng)成功后,接下來啟動(dòng)業(yè)務(wù)服務(wù):
docker?compose?-f?docker-compose.service.yml?up
項(xiàng)目全部啟動(dòng),看得很爽:
正常來說,應(yīng)該會(huì)啟動(dòng)成功;但如果運(yùn)氣背,可能會(huì)有失敗,比如我這的網(wǎng)關(guān)服務(wù)就啟動(dòng)失敗了。
如果某個(gè)服務(wù)啟動(dòng)失敗,可以再次單獨(dú)只啟動(dòng)它,比如網(wǎng)關(guān)服務(wù):
sudo?docker?compose?-f?docker-compose.service.yml?up?yuoj-backend-gateway
2.6、測(cè)試訪問
最后,像驗(yàn)證本地微服務(wù)項(xiàng)目部署一樣,訪問線上網(wǎng)關(guān)的接口文檔( http://你的服務(wù)器 IP:8101/doc.html ),依次調(diào)用用戶注冊(cè) => 登錄 => 獲取登錄用戶信息 => 創(chuàng)建題目,全部成功~
最后使用 docker stats
命令查看 Docker 容器的狀態(tài),發(fā)現(xiàn)總共的內(nèi)存占用大概 3 G,也就是說 4 G 內(nèi)存的服務(wù)器是完全足夠小型微服務(wù)項(xiàng)目的部署了~
至此,微服務(wù)項(xiàng)目部署教程就結(jié)束了。
最后
最后我再問個(gè)問題,如果我只有一臺(tái) 2 G 內(nèi)存的服務(wù)器,能否成功部署這套有 4 個(gè)業(yè)務(wù)服務(wù)的項(xiàng)目呢?如果能的話,又應(yīng)該怎么部署呢?歡迎大家討論。
下圖是個(gè)小提示:

終于寫完了這篇 6000 多字的教程,如果覺得本期內(nèi)容有幫助的話,也歡迎點(diǎn)贊、收藏三連支持,謝謝大家!