概述

此案例为使用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

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看到确实是有做负载均衡的。
在这里插入图片描述

在这里插入图片描述

更多推荐