SpringBoot运维实用篇

SpringBoot运维实用篇

SpringBoot程序的打包与运行

在实际开发完成后,我们的项目是不可能运行在自己的电脑上的。 需要把制作的程序运行在专用的服务器上。 这里面就存在两个过程,一个是打包的过程,另一个是运行的过程。

温馨提示

企业项目上线为了保障环境适配性会采用下面流程发布项目

  1. 开发部门使用Git、SVN等版本控制工具上传工程到版本服务器
  2. 服务器使用版本控制工具下载工程
  3. 服务器上使用Maven工具在当前真机环境下重新构建项目
  4. 启动服务

继续说我们的打包和运行过程。所谓打包指将程序转换成一个可执行的文件,所谓运行指不依赖开发环境执行打包产生的文件。上述两个操作都有对应的命令可以快速执行。

程序打包

SpringBoot程序是基于Maven创建的,在Maven中提供有打包的指令,叫做package。本操作可以在Idea环境下执行。

1
mvn package

打包后会产生一个与工程名类似的jar文件,其名称是由模块名+版本号+.jar组成的。

程序运行

程序包打好以后,就可以直接执行了。在程序包所在路径下,执行指令。

1
java -jar 工程包名.jar

执行程序打包指令后,程序正常运行,与在Idea下执行程序没有区别。

注意

  1. 如果你的计算机中没有安装java的jdk环境,是无法正确执行上述操作的,因为程序执行使用的是java指令。

  2. 在使用向导创建SpringBoot工程时,pom.xml文件中会有如下配置,这一段配置千万不能删除,否则打包后无法正常执行程序。

    1
    2
    3
    4
    5
    6
    7
    8
    <build>
    <plugins>
    <plugin>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-maven-plugin</artifactId>
    </plugin>
    </plugins>
    </build>

总结

  1. SpringBoot工程可以基于java环境下独立运行jar文件启动服务
  2. SpringBoot工程执行mvn命令package进行打包
  3. 执行jar命令:java –jar 工程名.jar

SpringBoot程序打包失败处理

有些小伙伴打包以后执行会出现一些问题,导致程序无法正常执行,例如下面的现象

image-20211201094223991

搞java开发平时会接触很多jar包,比如mysql的驱动jar包,而上面我们打包程序后得到的也是一个jar文件。

这个时候如果你使用上面的java -jar指令去执行mysql的驱动jar包就会出现上述不可执行的现象,而我们的SpringBoot项目为什么能执行呢?其实是因为打包方式不一样。

在SpringBoot工程的pom.xml中有下面这组配置,这组配置决定了打包出来的程序包是否可以执行。

1
2
3
4
5
6
7
8
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

我们分别开启这段配置和注释掉这段配置分别执行两次打包,然后观察两次打包后的程序包的差别,共有3处比较明显的特征

  • 打包后文件的大小不同

    image-20211201095610270

    不难看出,带有配置的程序包体积比不带配置的大了30倍,具体差别如下

    image-20211201101541267

    image-20211201101652868

  • 打包后所包含的内容不同

    我们发现内容也完全不一样,仅有一个目录是一样的,叫做META-INF。打开容量大的程序包中的BOOT-INF目录下的classes目录,我们发现其中的内容居然和容量小的程序包中的内容完全一样。

    image-20211201101805569

    image-20211201101652868

  • 打包程序中个别文件内容不同

    原来大的程序包中除了包含小的程序包中的内容,还有别的东西。都有什么呢?回到BOOT-INF目录下,打开lib目录,里面显示了很多个jar文件。

    image-20211201102025791

​ 仔细翻阅不难发现,这些jar文件都是我们制作这个工程时导入的坐标对应的文件。大概可以想明白了,SpringBoot程序为了让自己打包生成的程序可以独立运行,不仅将项目中自己开发的内容进行了打包,还把当前工程运行需要使用的jar包全部打包进来了

再看看大程序包还有什么不同之处,在最外层目录包含一个org目录,进入此目录,目录名是org\springframework\boot\loader,在里面可以找到一个JarLauncher.class的文件,先记得这个文件。再看这套目录名,明显是一个Spring的目录名,为什么要把Spring框架的东西打包到这个程序包中呢?

回到两个程序包的最外层目录,查看名称相同的文件夹META-INF下都有一个叫做MANIFEST.MF的文件,但是大小不同,打开文件,比较内容区别

  • 小容量文件的MANIFEST.MF

    1
    2
    3
    4
    5
    Manifest-Version: 1.0
    Implementation-Title: springboot_08_ssmp
    Implementation-Version: 0.0.1-SNAPSHOT
    Build-Jdk-Spec: 1.8
    Created-By: Maven Jar Plugin 3.2.0
  • 大容量文件的MANIFEST.MF

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    Manifest-Version: 1.0
    Spring-Boot-Classpath-Index: BOOT-INF/classpath.idx
    Implementation-Title: springboot_08_ssmp
    Implementation-Version: 0.0.1-SNAPSHOT
    Spring-Boot-Layers-Index: BOOT-INF/layers.idx
    Start-Class: com.itheima.SSMPApplication
    Spring-Boot-Classes: BOOT-INF/classes/
    Spring-Boot-Lib: BOOT-INF/lib/
    Build-Jdk-Spec: 1.8
    Spring-Boot-Version: 2.5.4
    Created-By: Maven Jar Plugin 3.2.0
    Main-Class: org.springframework.boot.loader.JarLauncher

    大文件中明显比小文件中多了几行信息,其中最后一行信息是Main-Class: org.springframework.boot.loader.JarLauncher

    如果使用java -jar执行此程序包,将执行Main-Class属性配置的类,这个类恰巧就是前面看到的那个文件。原来SpringBoot打包程序中出现Spring框架的东西是为这里服务的。而这个org.springframework.boot.loader.JarLauncher类内部要查找Start-Class属性中配置的类,并执行对应的类。这个属性在当前配置中也存在,对应的就是我们的引导类类名。

总结

  1. SpringBoot程序添加配置后会打出一个特殊的包,包含Spring框架部分功能,原始工程内容,原始工程依赖的jar包
  2. 首先读取MANIFEST.MF文件中的Main-Class属性,用来标记执行java -jar命令后运行的类
  3. JarLauncher类执行时会找到Start-Class属性,也就是启动类类名
  4. 运行启动类时会运行当前工程的内容
  5. 运行当前工程时会使用依赖的jar包,从lib目录中查找

再来看之前的报错信息:由于打包时没有使用那段配置,结果打包后形成了一个普通的jar包,在MANIFEST.MF文件中也就没有了Main-Class对应的属性了,所以运行时提示找不到主清单属性,这就是报错的原因。

命令行启动常见问题及解决方案

在DOS环境下启动SpringBoot工程时,可能会遇到端口占用的问题

1
2
3
4
5
6
7
8
9
10
# 查询端口
netstat -ano
# 查询指定端口
netstat -ano |findstr "端口号"
# 根据进程PID查询进程名称
tasklist |findstr "进程PID号"
# 根据PID杀死任务
taskkill /F /PID "进程PID号"
# 根据进程名称杀死任务
taskkill -f -t -im "进程名称"

SpringBoot项目快速启动(Linux版)

  • 基于Linux(CentOS7)
  • 安装JDK,且版本不低于打包时使用的JDK版本
  • 安装包保存在/usr/local/自定义目录中或$HOME下
  • 其他操作参照Windows版本进行

正常启动jar包

1
java -jar xxxx.jar

这种方式,打印的日志显示在控制台,直接CTRL+C停止程序即可

后台启动jar包

1
nohup java -jar xxxx.jar > server.log 2>&1 &
  • nohup java -jar xxxx.jar

    linux中提供后台启动jar包

  • > server.log

    表示项目输出的日志到一个server.log的文件中

  • 标准输入文件(stdin):stdin的文件描述符为0,Unix程序默认从stdin读取数据。

  • 标准输出文件(stdout):stdout 的文件描述符为1,Unix程序默认向stdout输出数据。

  • 标准错误文件(stderr):stderr的文件描述符为2,Unix程序会向stderr流中写入错误信息

  • server.log 2>&1

    表示将 stdout 和 stderr 合并后重定向到 server.log

  • 命令结尾添加&,表示在当前ssh窗口,可按CTRL + C打断程序运行,或者直接关闭窗口,则程序直接退出。

  • 命令结尾没有&,表示在当窗口关闭时,程序才会中止运行。&代表让该命令在后台执行

查询端口关闭jar进程

1
2
3
ps -ef | grep "java -jar"   #查找包含java -jar 的进程

kill -9 27125 # 杀死进程号为27125的进程

配置高级

临时属性设置

​ 程序包打好了,可以发布了。但是程序包打好以后,里面的配置都已经是固定的了,比如配置了服务器的端口是8080。如果我要启动项目,发现当前我的服务器上已经有应用启动起来并且占用了8080端口,这个时候就尴尬了。

​ SpringBoot提供了灵活的配置方式,如果你发现你的项目中有个别属性需要重新配置,可以使用临时属性的方式快速修改某些配置。方法也特别简单,在启动的时候添加上对应参数就可以了。

1
java –jar springboot.jar –-server.port=80

​ 上面的命令是启动SpringBoot程序包的命令,在命令输入完毕后,空一格,然后输入两个-号。下面按照属性名=属性值的形式添加对应参数就可以了。记得,这里的格式不是yaml中的书写格式,当属性存在多级名称时,中间使用点分隔,和properties文件中的属性格式完全相同。

​ 如果你发现要修改的属性不止一个,可以按照上述格式继续写,属性与属性之间使用空格分隔。

1
java –jar springboot.jar –-server.port=80 --logging.level.root=debug

总结

  1. 使用jar命令启动SpringBoot工程时可以使用临时属性替换配置文件中的属性
  2. 临时属性添加方式:java –jar 工程名.jar –-属性名=值
  3. 多个临时属性之间使用空格分隔
  4. 临时属性必须是当前boot工程支持的属性,否则设置无效

属性加载优先级

​ 现在我们的程序配置受两个地方控制了,第一配置文件,第二临时属性。并且我们发现临时属性的加载优先级要高于配置文件的。

查看配置读取的优先顺序

image-20211206100859236

​ 命令行临时属性比配置文件的加载优先级高,所以这个列表上面的优先级低,下面的优先级高。

​ 比如你现在加载了一个user.name属性。结果你发现出来的结果和你想的不一样,那肯定是别的优先级比你高的属性覆盖你的配置属性了,那你就可以看着这个顺序挨个排查。哪个位置有可能覆盖了你的属性。

开发环境中使用临时属性

​ 临时使用目前是有了,但是上线的时候通过命令行输入的临时属性必须是正确的啊,那这些属性配置值我们必须在开发环境中测试好才行。下面说一下开发环境中如何使用临时属性,其实就是Idea界面下如何操作了。

​ 打开SpringBoot引导类的运行界面,在里面找到配置项。其中Program arguments对应的位置就是添加临时属性的,可以加几个试试效果。

image-20211206101947622

​ 做到这里其实可以产生一个思考了,如果对java编程熟悉的小伙伴应该知道,我们运行main方法的时候,如果想使用main方法的参数,也就是下面的args参数,其实就是在上面图片这个位置添加的参数。

1
 public static void main(String[] args) {}

​ 原来是这样,通过这个args就可以获取到参数。再来看我们的引导类是如何书写的

1
2
3
public static void main(String[] args) {
SpringApplication.run(SSMPApplication.class,args);
}

​ 这个args参数居然传递给了run方法,看来在Idea中配置的临时参数就是通过这个位置传递到我们的程序中的。言外之意,这里如果不用这个args是不是就断开了外部传递临时属性的入口呢?是这样的,我们可以使用下面的调用方式,这样外部临时属性就无法进入到SpringBoot程序中了。

1
2
3
public static void main(String[] args) {
SpringApplication.run(SSMPApplication.class);
}

​ 或者还可以使用如下格式来玩这个操作,就是将配置不写在配置文件中,直接写成一个字符串数组,传递给程序入口。当然,这种做法并没有什么实际开发意义。

1
2
3
4
5
public static void main(String[] args) {
String[] arg = new String[1];
arg[0] = "--server.port=8082";
SpringApplication.run(SSMPApplication.class, arg);
}
总结
  1. 启动SpringBoot程序时,可以选择是否使用命令行属性为SpringBoot程序传递启动属性

配置文件分类

​ SpringBoot提供了配置文件和临时属性的方式来对程序进行配置。前面一直说的是临时属性,现在我们来说下配置文件

SpringBoot提供了4级配置文件的级别(其实就是4种配置文件书写的位置,功能都是一样的,都是做配置的)。4个级别分别是:

  • 类路径下配置文件(一直使用的是这个,也就是resources目录中的application.yml文件)
  • 类路径下config目录下配置文件
  • 程序包所在目录中配置文件
  • 程序包所在目录中config目录下配置文件

4种配置文件如果都存在的话,有一个优先级的问题

  1. file :config/application.yml 【最高】
  2. file :application.yml
  3. classpath:config/application.yml
  4. classpath:application.yml 【最低】

总结

  1. 配置文件分为4种

    • 项目类路径配置文件:服务于开发人员本机开发与测试
    • 项目类路径config目录中配置文件:服务于项目经理整体调控
    • 工程路径配置文件:服务于运维人员配置涉密线上环境
    • 工程路径config目录中配置文件:服务于运维经理整体调控
  2. 多层级配置文件间的属性采用叠加并覆盖的形式作用于程序

自定义配置文件

方式1

使用临时属性设置配置文件名,注意仅仅是名称,不要带扩展名

image-20211206105548238

方式2

使用临时属性设置配置文件路径,这个是全路径名

image-20211206105716450

也可以设置加载多个配置文件

image-20211206105750285

使用的属性一个是spring.config.name,另一个是spring.config.location,这个一定要区别清楚。

总结

  1. 配置文件可以修改名称,通过启动参数设定
  2. 配置文件可以修改路径,通过启动参数设定
  3. 微服务开发中配置文件通过配置中心进行设置

多环境开发

​ 多环境其实就是说你的电脑上写的程序最终要放到别人的服务器上去运行。每个计算机环境不一样,这就是多环境。

​ 常见的多环境开发主要兼顾3种环境设置,开发环境——自己用的,测试环境——自己公司用的,生产环境——甲方爸爸用的。因为这是绝对不同的三台电脑,所以环境肯定有所不同,比如连接的数据库不一样,设置的访问端口不一样等等。

image-20211206110958819

yaml单一文件版

比如你自己开发时,配置你的端口如下:

1
2
server:
port: 80

设计两组环境,中间使用三个减号分隔开

1
2
3
4
5
server:
port: 80
---
server:
port: 81

如何区分两种环境呢?起名字呗

1
2
3
4
5
6
7
8
9
spring:
profiles: pro
server:
port: 80
---
spring:
profiles: dev
server:
port: 81

那用哪一个呢?设置默认启动哪个就可以了

1
2
3
4
5
6
7
8
9
10
11
12
13
spring:
profiles:
active: pro # 启动pro
---
spring:
profiles: pro
server:
port: 80
---
spring:
profiles: dev
server:
port: 81

其中关于环境名称定义上述格式是过时格式,标准格式如下

1
2
3
4
spring:
config:
activate:
on-profile: pro

总结

  1. 多环境开发需要设置若干种常用环境,例如开发、生产、测试环境
  2. yaml格式中设置多环境使用—区分环境设置边界
  3. 每种环境的区别在于加载的配置属性不同
  4. 启用某种环境时需要指定启动时使用该环境

多环境开发(yaml多文件版)

​ 将所有的配置都放在一个配置文件中,尤其是每一个配置应用场景都不一样,这显然不合理,于是就有了将一个配置文件拆分成多个配置文件的想法。拆分后,每个配置文件中写自己的配置,主配置文件中写清楚用哪一个配置文件就好了。

主配置文件

1
2
3
spring:
profiles:
active: pro # 启动pro

环境配置文件

1
2
server:
port: 80

环境配置文件因为每一个都是配置自己的项,所以连名字都不用写里面了。那问题是如何区分这是哪一组配置呢?使用文件名区分。

application-pro.yaml
1
2
server:
port: 80
application-dev.yaml
1
2
server:
port: 81

文件的命名规则为:application-环境名.yml。

在配置文件中,如果某些配置项所有环境都一样,可以将这些项写入到主配置中,只有哪些有区别的项才写入到环境配置文件中。

  • 主配置文件中设置公共配置(全局)
  • 环境分类配置文件中常用于设置冲突属性(局部)

多环境开发(properties多文件版)

SpringBoot最早期提供的配置文件格式是properties格式的

主配置文件

1
spring.profiles.active=pro

环境配置文件

application-pro.properties

1
server.port=80

application-dev.properties

1
server.port=81

文件的命名规则为:application-环境名.properties。

多环境开发独立配置文件书写技巧

基于多环境开发做配置独立管理,务必掌握。

准备工作

将所有的配置根据功能对配置文件中的信息进行拆分,并制作成独立的配置文件,命名规则如下

  • application-devDB.yml
  • application-devRedis.yml
  • application-devMVC.yml

使用

使用include属性在激活指定环境的情况下,同时对多个环境进行加载使其生效,多个环境间使用逗号分隔

1
2
3
4
spring:
profiles:
active: dev
include: devDB,devRedis,devMVC

比较一下,现在相当于加载dev配置时,再加载对应的3组配置,从结构上就很清晰,用了什么,对应的名称是什么

注意

当主环境dev与其他环境有相同属性时,主环境属性生效;其他环境中有相同属性时,最后加载的环境属性生效

改良

但是上面的设置也有一个问题,比如我要切换dev环境为pro时,include也要修改。因为include属性只能使用一次,这就比较麻烦了。SpringBoot从2.4版开始使用group属性替代include属性,降低了配置书写量。简单说就是我先写好,你爱用哪个用哪个。

1
2
3
4
5
6
7
spring:
profiles:
active: dev
group:
"dev": devDB,devRedis,devMVC
"pro": proDB,proRedis,proMVC
"test": testDB,testRedis,testMVC

多环境开发控制

  • 先在maven环境中设置用什么具体的环境
  • 在SpringBoot中读取maven设置的环境即可

maven中设置多环境(使用属性方式区分环境)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<profiles>
<profile>
<id>env_dev</id>
<properties>
<profile.active>dev</profile.active>
</properties>
<activation>
<activeByDefault>true</activeByDefault> <!--默认启动环境-->
</activation>
</profile>
<profile>
<id>env_pro</id>
<properties>
<profile.active>pro</profile.active>
</properties>
</profile>
</profiles>

SpringBoot中读取maven设置值

1
2
3
spring:
profiles:
active: @profile.active@

总结

  1. 当Maven与SpringBoot同时对多环境进行控制时,以Mavn为主,SpringBoot使用@..@占位符读取Maven对应的配置属性值
  2. 基于SpringBoot读取Maven配置属性的前提下,如果在Idea下测试工程时pom.xml每次更新需要手动compile方可生效

日志

日志作用如下

  • 编程期调试代码
  • 运营期记录信息
  • 记录日常运营重要信息(峰值流量、平均响应时长……)
  • 记录应用报错信息(错误堆栈)
  • 记录运维过程数据(扩容、宕机、报警……)

代码中使用日志工具记录日志

日志的使用格式非常固定,直接上操作步骤:

  1. 添加日志记录操作

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    @RestController
    @RequestMapping("/books")
    public class BookController extends BaseClass{
    private static final Logger log = LoggerFactory.getLogger(BookController.class);
    @GetMapping
    public String getById(){
    log.debug("debug...");
    log.info("info...");
    log.warn("warn...");
    log.error("error...");
    return "springboot is running...2";
    }
    }

    上述代码中log对象就是用来记录日志的对象,下面的log.debug,log.info这些操作就是写日志的API了。

  2. 设置日志输出级别

    日志设置好以后可以根据设置选择哪些参与记录。这里是根据日志的级别来设置的。日志的级别分为6种,分别是:

    • TRACE:运行堆栈信息,使用率低
    • DEBUG:程序员调试代码使用
    • INFO:记录运维过程数据
    • WARN:记录运维过程报警数据
    • ERROR:记录错误堆栈信息
    • FATAL:灾难信息,合并计入ERROR

    一般情况下,开发时候使用DEBUG,上线后使用INFO,运维信息记录使用WARN即可。下面就设置一下日志级别:

    1
    2
    # 开启debug模式,输出调试信息,常用于检查系统运行状况
    debug: true

    这么设置太简单粗暴了,日志系统通常都提供了细粒度的控制

    1
    2
    3
    4
    5
    6
    7
    # 开启debug模式,输出调试信息,常用于检查系统运行状况
    debug: true

    # 设置日志级别,root表示根节点,即整体应用日志级别
    logging:
    level:
    root: debug

    还可以再设置更细粒度的控制

  3. 设置日志组,控制指定包对应的日志输出级别,也可以直接控制指定包对应的日志输出级别

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    logging:
    # 设置日志组
    group:
    # 自定义组名,设置当前组中所包含的包
    ebank: com.itheima.controller
    level:
    root: warn
    # 为对应组设置日志级别
    ebank: debug
    # 为对包设置日志级别
    com.itheima.controller: debug

    说白了就是总体设置一下,每个包设置一下,如果感觉设置的麻烦,就先把包分个组,对组设置,没了,就这些。

总结

  1. 日志用于记录开发调试与运维过程消息
  2. 日志的级别共6种,通常使用4种即可,分别是DEBUG,INFO,WARN,ERROR
  3. 可以通过日志组或代码包的形式进行日志显示级别的控制

优化日志对象创建代码

写代码的时候每个类都要写创建日志记录对象,这个可以优化一下,使用前面用过的lombok技术给我们提供的工具类即可。

1
2
3
4
5
@RestController
@RequestMapping("/books")
public class BookController extends BaseClass{
private static final Logger log = LoggerFactory.getLogger(BookController.class); //这一句可以不写了
}

导入lombok后使用注解搞定,日志对象名为log

1
2
3
4
5
6
@Slf4j		//这个注解替代了下面那一行
@RestController
@RequestMapping("/books")
public class BookController extends BaseClass{
private static final Logger log = LoggerFactory.getLogger(BookController.class); //这一句可以不写了
}

日志输出格式控制

日志已经能够记录了,但是目前记录的格式是SpringBoot给我们提供的,如果想自定义控制就需要自己设置了。先分析一下当前日志的记录格式。

image-20211206123431222

对于单条日志信息来说,日期,触发位置,记录信息是最核心的信息。级别用于做筛选过滤,PID与线程名用于做精准分析

  • %d:日期
  • %m:消息
  • %n:换行
  • %clr:设置颜色
  • %t:线程名称(%16t是指显示线程名称的位置占16个位置)
  • %-40.40c:类名占40个位置,减号代表左对齐,不写的话默认右对齐(%-40)。(.40)表示截取前40个位置的值
  • {cyan}:指定对应的颜色,这个是青色
1
2
3
logging:
pattern:
console: "%d %clr(%p) --- [%16t] %clr(%-40.40c){cyan} : %m %n"

日志文件

​ 日志信息显示,记录已经控制住了,下面就要说一下日志的转存了。日志不能仅显示在控制台上,要把日志记录到文件中,方便后期维护查阅。

​ 对于日志文件的使用存在各种各样的策略,例如每日记录,分类记录,报警后记录等。这里主要研究日志文件如何记录

1
2
3
logging:
file:
name: server.log

​ 虽然使用上述格式可以将日志记录下来了,但是面对线上的复杂情况,一个文件记录肯定是不能够满足运维要求的,通常会每天记录日志文件,同时为了便于维护,还要限制每个日志文件的大小。下面给出日志文件的常用配置方式:

1
2
3
4
5
logging:
logback:
rollingpolicy:
max-file-size: 3KB
file-name-pattern: server.%d{yyyy-MM-dd}.%i.log

​ 以上格式是基于logback日志技术设置每日日志文件的设置格式,要求容量到达3KB以后就转存信息到第二个文件中。文件命名规则中的%d标识日期,%i是一个递增变量,用于区分日志文件。


SpringBoot运维实用篇
http://example.com/2024/01/24/SpringBoot/SpringBoot运维实用篇/
作者
zhuixun
发布于
2024年1月24日
许可协议