docker部署springboot集群实例,使用nginx做负载均衡
·
概述
此案例为使用docker来部署springboot集群,且用nginx来做负载均衡。springboot项目里面引入了mysql8.0和mybatis-plus和druid等框架。环境:jdk11 + gradle-7.5.1 + DockerVersion:20.10.12+DocerkComposeVersion:1.23.2。
一、准备工作
1.新建一个springboot的项目,使用gradle或者maven来添加依赖都行,我用的gradle。
1)项目的结构

2)gradle-wrapper.properties文件
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://mirrors.cloud.tencent.com/gradle/gradle-7.5.1-bin.zip
networkTimeout=10000
retries=0
retryBackOffMs=500
validateDistributionUrl=true
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
3)build.gradle依赖
plugins {
id 'org.springframework.boot' version '2.3.7.RELEASE'
id 'io.spring.dependency-management' version '1.0.10.RELEASE'
id 'java'
}
group = 'org.example'
version = '0.0.1-SNAPSHOT'
description = 'SpringBootDemo'
apply plugin: 'war'
java {
toolchain {
languageVersion = JavaLanguageVersion.of(11)
}
}
repositories {
maven { url 'https://maven.aliyun.com/repository/public/' }
}
dependencies {
// Spring Boot 核心依赖
implementation 'org.springframework.boot:spring-boot-starter'
implementation 'org.springframework.boot:spring-boot-starter-web'
// MyBatis-Plus Spring Boot Starter
implementation 'com.baomidou:mybatis-plus-boot-starter:3.5.3.1'
// Druid 连接池(Spring Boot Starter 版本)
implementation 'com.alibaba:druid-spring-boot-starter:1.2.16'
// Lombok 依赖配置
compileOnly 'org.projectlombok:lombok:1.18.36'
annotationProcessor 'org.projectlombok:lombok:1.18.36'
// MySQL 驱动
runtimeOnly 'mysql:mysql-connector-java:8.0.29'
// 外部 Tomcat 部署(打包成 WAR 时需要)
providedRuntime 'org.springframework.boot:spring-boot-starter-tomcat'
// 如果测试代码中也需要使用 Lombok,可以添加以下两行(可选)
testCompileOnly 'org.projectlombok:lombok:1.18.36'
testAnnotationProcessor 'org.projectlombok:lombok:1.18.36'
// 测试依赖
testImplementation('org.springframework.boot:spring-boot-starter-test') {
exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
}
}
tasks.named('test') {
useJUnitPlatform()
}
// WAR 包配置
war {
enabled = true
archiveName = 'demo.war'
}
4)application.yaml
# 服务器配置
server:
port: 8080
servlet:
context-path: /
# Spring Boot 核心配置
spring:
application:
name: springboot-cluster-demo
# 数据源配置(使用 Druid 连接池)
datasource:
# 驱动类名(MySQL 8)
driver-class-name: com.mysql.cj.jdbc.Driver
# 数据库连接 URL(通过环境变量覆盖,适配 Docker 部署)
url: ${SPRING_DATASOURCE_URL:jdbc:mysql://localhost:3306/demo?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai&characterEncoding=UTF-8}
username: ${SPRING_DATASOURCE_USERNAME:testuser}
password: ${SPRING_DATASOURCE_PASSWORD:user123}
# Druid 连接池配置
druid:
# ---------- 连接池核心配置 ----------
# 初始化连接数
initial-size: 5
# 最小空闲连接数
min-idle: 5
# 最大活跃连接数
max-active: 20
# 获取连接超时时间(毫秒)
max-wait: 60000
# 连接保持空闲而不被回收的最小时间(毫秒)
min-evictable-idle-time-millis: 300000
# 连接保持空闲而不被回收的最大时间(毫秒)
max-evictable-idle-time-millis: 600000
# 连接池中连接空闲多久后,进行空闲连接检测(毫秒)
time-between-eviction-runs-millis: 60000
# ---------- 连接有效性检测 ----------
# 检测连接是否有效的 SQL(MySQL)
validation-query: SELECT 1
# 是否在获取连接前检测连接有效性
test-on-borrow: false
# 是否在归还连接时检测连接有效性
test-on-return: false
# 是否在连接空闲时检测连接有效性
test-while-idle: true
# ---------- 连接泄漏监控 ----------
# 是否开启连接泄漏监控
remove-abandoned: true
# 连接超过多少秒被认为泄漏(秒)
remove-abandoned-timeout: 180
# 是否打印连接泄漏日志
log-abandoned: true
# ---------- 统计与监控(Druid 控制台) ----------
# 开启 Druid 监控页面
stat-view-servlet:
# 是否启用
enabled: true
# 访问路径
url-pattern: /druid/*
# 登录用户名
login-username: admin
# 登录密码
login-password: admin123
# 是否允许重置数据
reset-enable: false
# ---------- Web 监控配置 ----------
web-stat-filter:
enabled: true
# 拦截路径
url-pattern: /*
# 排除静态资源
exclusions: '*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*'
# 开启 Session 监控
session-stat-enable: true
# Session 统计最大数量
session-stat-max-count: 1000
# ---------- 过滤器配置 ----------
filter:
# 统计过滤器
stat:
enabled: true
# 慢查询记录(毫秒)
slow-sql-millis: 1000
# 是否记录慢查询日志
log-slow-sql: true
# 是否合并 SQL
merge-sql: true
# 日志过滤器(记录 SQL 执行日志)
slf4j:
enabled: true
# 日志级别
statement-log-enabled: true
statement-executable-sql-log-enable: true
# Jackson 配置
jackson:
time-zone: Asia/Shanghai
date-format: yyyy-MM-dd HH:mm:ss
serialization:
write-dates-as-timestamps: false
# MyBatis-Plus 配置(替换 mybatis 配置)
mybatis-plus:
# Mapper XML 文件位置
mapper-locations: classpath:mapper/*.xml
# 实体类包路径(用于别名)
type-aliases-package: org.example.bean
# 全局配置
global-config:
db-config:
# 主键生成策略:自增
id-type: auto
# 表名前缀(可选)
# table-prefix: t_
# 逻辑删除字段(可选)
# logic-delete-field: deleted
# 逻辑已删除值
# logic-delete-value: 1
# 逻辑未删除值
# logic-not-delete-value: 0
# 配置信息
configuration:
# 开启驼峰命名自动映射
map-underscore-to-camel-case: false
# 开启日志输出
log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl
# 开启缓存
cache-enabled: true
# 懒加载
lazy-loading-enabled: true
# 超时时间
default-statement-timeout: 30
# 日志配置
logging:
file:
path: /app/logs
name: /app/logs/application.log
level:
root: INFO
org.example.springbootdemo: DEBUG
org.example.springbootdemo.mapper: DEBUG
org.mybatis: DEBUG
# Druid 日志
com.alibaba.druid: DEBUG
# 连接池日志
com.alibaba.druid.pool: INFO
5)UserController.java文件
package org.example.controller;
import org.example.biz.BizUser;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HashMap;
import java.util.Map;
@RestController
@RequestMapping("user")
public class UserController {
@Autowired
private BizUser bizUser;
// 从docker-compose中各个实例配置的instanceId来设置值
@Value("${INSTANCE_ID:default-instance}")
private String instanceId;
@Value("${INSTANCE_NAME:Default Instance}")
private String instanceName;
@GetMapping(value = "getUserById/{id}",produces = MediaType.APPLICATION_JSON_VALUE)
public Map<String,Object> getUserById(@PathVariable long id) {
Map<String, Object> response = new HashMap<>();
response.put("timestamp", LocalDateTime.now().format(DateTimeFormatter.ISO_LOCAL_DATE_TIME));
response.put("instanceId", instanceId);
response.put("instanceName", instanceName);
var user = bizUser.getUserById(id);
if (user != null) {
response.put("user", user);
}
return response;
}
}
6)bean,biz,dao,mapper,model,service目录下的文件则比较简单,单纯是一个根据用户id查询用户信息接口而已,比较简单,不再赘述。
7) 生成war包

2.安装好docker和docker-compose
使用命令查看是否已安装,没安装的话则自行去安装。
docker -v
docker-compose -v

3.拉取镜像mysql8.0以及nginx到本地
为什么要将镜像拉取到本地呢,因为拉取镜像到本地后,后续再使用镜像的话都是先从本地去拿如果有直接拿来用,就不会再去远程仓库拉取了。
1)拉取mysql8.0镜像
方式一:
docker pull mysql:8.0
方式一多次拉取后仍然不行,再试方式二。
方式二:
# 1.安装 EPEL 仓库(如果已经安装,此步会提示,可以忽略)
yum install epel-release -y
# 2.安装 skopeo
yum install skopeo -y
# 3.验证是否成功
skopeo --version
# 清理悬空镜像
docker image prune -f
# 将超时时间设置为5分钟(可根据网络情况调整)
export SKOPEO_TIMEOUT=5m
# 执行拉取命令,从下面三个备选源选一个去执行
# 备选源1:使用docker.mirrors.ustc.edu.cn(中科大)
skopeo copy docker://docker.mirrors.ustc.edu.cn/library/mysql:8.0 docker-archive:/tmp/mysql-8.0.tar
# 备选源2:使用 docker.m.daocloud.io(道客云)
skopeo copy docker://docker.m.daocloud.io/library/mysql:8.0 docker-archive:/tmp/mysql-8.0.tar
# 备选源3:使用 hub-mirror.c.163.com(网易)
skopeo copy docker://hub-mirror.c.163.com/library/mysql:8.0 docker-archive:/tmp/mysql-8.0.tar
# 导入tar文件,这里要注意返回的的imageId
docker load -i /tmp/mysql-8.0.tar
# 假设你执docker load命令后返回的imageId为6cd09145362dxxx又或者是其他,都可以通过前面的部分字符来搜索出来镜像
docker images | grep 6cd09145362d
# 给这个镜像打上mysql8.0的标签(6cd09145362d 为实际你自己的镜像id的前部分内容或者是完整的镜像id)
docker tag 6cd09145362d mysql:8.0
# 查看镜像
docker images | grep mysql
# 如果能看到 MySQL 的版本号输出(比如 mysql Ver 8.0.41 for Linux on x86_64),就大功告成了!
docker run -it --rm mysql:8.0 --version
2)拉取nginx镜像
# 默认会拉latest版本
docker pull nginx
#查看是否拉取成功
docker images | grep nginx
# 问 http://你的服务器IP:8080,如果能看到 Nginx 欢迎页,就说明一切正常
docker run -d -p 8080:80 --name my-nginx nginx
3)再查看两个镜像是否都拉取成功
docker images

4.创建部署项目的目录以及文件
1)目录结构
- opt/springboot-cluster
- app
- Dockerfile
- composeDemo-0.0.1-SNAPSHOT.war(换成你实际的war包)
- mysql
- nginx
- docker-compose.yaml
- app
2)创建目录
cd /
cd opt
mkdir springboot-cluster
cd springboot-cluster
mkdir app
mkdir mysql
mkdir nginx
3)创建文件
3.1)创建docker-compose.yaml文件
cd /
cd opt/springboot-cluster
vi docker-compose.yaml
复制以下代码进去
version: '3.3'
services:
mysql:
image: mysql:8.0
container_name: mysql-db
restart: always
environment:
MYSQL_ROOT_PASSWORD: root123
MYSQL_DATABASE: demo
MYSQL_USER: testuser
MYSQL_PASSWORD: user123
TZ: Asia/Shanghai
ports:
- "3306:3306"
volumes:
- ./mysql/data:/var/lib/mysql
- ./mysql/init.sql:/docker-entrypoint-initdb.d/init.sql
- ./mysql/my.cnf:/etc/mysql/conf.d/my.cnf:ro
networks:
- app-network
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "root", "-proot123"]
interval: 10s
timeout: 5s
retries: 5
command:
- --character-set-server=utf8mb4
- --collation-server=utf8mb4_unicode_ci
- --default-authentication-plugin=mysql_native_password
app1:
build:
context: ./app
dockerfile: Dockerfile
container_name: springboot-app1
restart: always
environment:
- TZ=Asia/Shanghai
- JAVA_OPTS=-Xmx512m -Xms256m -XX:+UseG1GC
- SPRING_DATASOURCE_URL=jdbc:mysql://mysql:3306/demo?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai&characterEncoding=UTF-8
- SPRING_DATASOURCE_USERNAME=testuser
- SPRING_DATASOURCE_PASSWORD=user123
- INSTANCE_ID=app1-instance
- INSTANCE_NAME="App Instance 1"
depends_on:
- mysql
ports:
- "8081:8080"
networks:
- app-network
app2:
build:
context: ./app
dockerfile: Dockerfile
container_name: springboot-app2
restart: always
environment:
- TZ=Asia/Shanghai
- JAVA_OPTS=-Xmx512m -Xms256m -XX:+UseG1GC
- SPRING_DATASOURCE_URL=jdbc:mysql://mysql:3306/demo?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai&characterEncoding=UTF-8
- SPRING_DATASOURCE_USERNAME=testuser
- SPRING_DATASOURCE_PASSWORD=user123
- INSTANCE_ID=app2-instance
- INSTANCE_NAME="App Instance 2"
depends_on:
- mysql
ports:
- "8082:8080"
networks:
- app-network
nginx:
image: nginx:latest
container_name: nginx-lb
restart: always
ports:
- "80:80"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro
depends_on:
- app1
- app2
networks:
- app-network
networks:
app-network:
driver: bridge
3.2)创建mysql相关文件
cd /
cd opt/springboot-cluster/mysql
mkdir data
vi my.cnf
my.cnf
[client]
default-character-set=utf8mb4
[mysql]
default-character-set=utf8mb4
[mysqld]
character-set-server=utf8mb4
collation-server=utf8mb4_unicode_ci
init-connect='SET NAMES utf8mb4'
max_connections=200
max_allowed_packet=16M
default-time-zone='+08:00'
vi init.sql
init.sql
use demo;
create table if not exists user(
id int(11) primary key AUTO_INCREMENT,
name varchar(36) not null default '' comment '姓名',
age int(6) not null default '0' comment '年龄',
create_time datetime not null default current_timestamp comment '创建时间'
) engine=innodb default charset=utf8mb4 collate=utf8mb4_unicode_ci comment '用户表';
insert into user(name,age,create_time) values('tom',18,now()),
('york',20,now()),
('marry',17,now()),
('kerry',16,now()),
('mark',15,now());
3.3)创建nginx的配置文件
cd /
cd opt/springboot-cluster/nginx
vi nginx.cnf
nginx.cnf
events {
worker_connections 1024;
}
http {
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
'$status $body_bytes_sent "$http_referer" '
'"$http_user_agent" "$http_x_forwarded_for" '
'upstream: $upstream_addr';
upstream springboot_backend {
server app1:8080 max_fails=3 fail_timeout=30s;
server app2:8080 max_fails=3 fail_timeout=30s;
}
server {
listen 80;
server_name localhost;
access_log /var/log/nginx/access.log main;
error_log /var/log/nginx/error.log;
location / {
proxy_pass http://springboot_backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_connect_timeout 30s;
proxy_send_timeout 30s;
proxy_read_timeout 30s;
}
}
}
3.4)创建app内的Dockerfile文件
cd /
cd opt/springboot-cluster/app
# 注意:得先拉取jdk11的运行时环境镜像
docker pull eclipse-temurin:11-jre-alpine
vi Dockerfile
Dockerfile文件
FROM eclipse-temurin:11-jre-alpine
WORKDIR /app
# composeDemo-0.0.1-SNAPSHOT.war换成你实际的war包名称
COPY composeDemo-0.0.1-SNAPSHOT.war /app/app.jar
RUN mkdir -p /app/logs
ENV TZ=Asia/Shanghai
RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && echo $TZ > /etc/timezone
ENV JAVA_OPTS="-Xmx512m -Xms256m -XX:+UseG1GC -Duser.timezone=Asia/Shanghai"
EXPOSE 8080
CMD ["sh", "-c", "java $JAVA_OPTS -jar /app/app.jar"]
3.5)将你的war包放到app目录下

二、启动
cd /
cd opt/springboot-cluster
docker-compose build --no-cache
docker-compose up -d
# 5. 查看所有容器状态,如果都为up则代表正常
docker-compose ps
# 6.查看项目的日志信息
docker logs springboot-app1 --tail 100
docker logs springboot-app2 --tail 100
# 查看 mysql-db 容器的所有日志
docker logs mysql-db
# 查看nginx-lb容器的所有日志
docker logs nginx-lb
# 8. 测试接口,有返回数据则代表项目启动成功了的
curl http://localhost/user/getUserById/1
三、重启
cd /
cd opt/springboot-cluster
# 1. 停止并删除旧容器和数据卷(重启的情况)
docker-compose down -v
# 2. 删除旧的 MySQL 数据目录(避免权限冲突)(重启的情况)
sudo rm -rf ./mysql/data
# 3. 创建新的数据目录(重启的情况)
mkdir -p ./mysql/data
# 4. 启动或重新启动
docker-compose up -d
四、测试
在物理机器上打开浏览器,打开两个窗口,分别访问http://xxx(你的虚拟机centos的ip地址)/user/getUserById/1和http://xxx(你的虚拟机centos的ip地址)/user/getUserById/2,这里,虚拟机设置那里记得要设置为桥接模式,结果:可以看到有用户数据返回且由instanceName看到确实是有做负载均衡的。

更多推荐
所有评论(0)