十三、SpringBoot容器化部署
13.1 安装docker环境
为了节约资源,在生产环境中我们更多的是使用Docker容器部署SpringBoot应用,首先我们准备Docker环境:
1、准备一台centos7系统的虚拟机,连接虚拟机。
2、关闭虚拟机防火墙
# 关闭运行的防火墙 systemctl stop firewalld.service # 禁止防火墙自启动 systemctl disable firewalld.service
13.2 Dockerfile制作镜像
由于在SpringBoot中嵌入了Web容器,所以在制作SpringBoot项目的镜像时无需依赖Web容器,基于JDK制作镜像即可,接下来我们使用Dockerfile制作镜像:
1、进入opt目录
cd /opt
2、使用rz命令(或者xftp)将项目Jar包上传至虚拟机
使用rz前提是下载lrsz,yum -y install lrzsz
3、编写DockerFile
# 基于JDK11 FROM openjdk:8 # 作者 MAINTAINER zj # 拷贝到容器opt目录 ADD sb_logback.jar /opt #保留端口 EXPOSE 8080 # 启动容器后执行的命令 CMD java -jar /opt/sb_logback.jar
4、构建镜像
docker build -t sb_logback .
5、查看所有的镜像,出现springbootdocker代表镜像构建成功
6、使用镜像创建并启动容器
docker run -d -p 8080:8080 sb_logback
7、访问查看是否启动成功
13.3 Maven插件制作镜像
除了DockerFile,我们还可以使用Maven插件制作镜像。使用方法如下:
1、开启远程docker服务
# 修改docker配置文件 vim /lib/systemd/system/docker.service # 在ExecStart=后添加配置,远程访问docker的端口为2375 ExecStart=/usr/bin/dockerd-current -H tcp://0.0.0.0:2375 -H unix://var/run/docker.sock \ --add-runtime docker-runc=/usr/libexec/docker/docker-runc-current \ --default-runtime=docker-runc \ --exec-opt native.cgroupdriver=systemd \ --userland-proxy-path=/usr/libexec/docker/docker-proxy-current \ --init-path=/usr/libexec/docker/docker-init-current \ --seccomp-profile=/etc/docker/seccomp.json \ $OPTIONS \ $DOCKER_STORAGE_OPTIONS \ $DOCKER_NETWORK_OPTIONS \ $ADD_REGISTRY \ $BLOCK_REGISTRY \ $INSECURE_REGISTRY \ $REGISTRIES # 重启docker systemctl daemon-reload systemctl restart docker
2、在项目的pom文件中添加docker-maven-plugin
插件
<!-- docker-maven-plugin--> <plugin> <groupId>com.spotify</groupId> <artifactId>docker-maven-plugin</artifactId> <version>1.2.2</version> <configuration> <!-- Docker路径 --> <dockerHost>http://192.168.25.101:2375</dockerHost> <!-- Dockerfile定义 --> <baseImage>openjdk:8</baseImage> <!-- 作者 --> <maintainer>zj</maintainer> <resources> <resource> <!-- 复制jar包到docker容器指定目录 --> <targetPath>/opt</targetPath> <!-- 从哪个包拷贝文件,target包 --> <directory>${project.build.directory}</directory> <!-- 拷贝哪个文件 --> <include>${project.build.finalName}.jar</include> </resource> </resources> <workdir>/</workdir> <entryPoint>["java", "-jar", "${project.build.finalName}.jar","--spring.profiles.active=dev"]</entryPoint> <forceTags>true</forceTags> <!-- 镜像名 --> <imageName>${project.artifactId}</imageName> <!-- 镜像版本 --> <imageTags> <imageTag>${project.version}</imageTag> </imageTags> </configuration> </plugin>
3、使用maven的package命令给项目打包
4、使用maven的docker插件制作镜像
5、查看所有的镜像
6、创建并访问容器
十四、Spring Task
定时任务即系统在特定时间执行一段代码,它的场景应用非常广泛:
- 购买游戏的月卡会员后,系统每天给会员发放游戏资源。
- 管理系统定时生成报表。
- 定时清理系统垃圾。
- ......
定时任务的实现主要有以下几种方式:
- Java自带的java.util.Timer类,这个类允许调度一个java.util.TimerTask任务。使用这种方式可以让程序按照某一个频度执行,但不能在指定时间运行。一般用的较少。
- Quartz。这是一个功能比较强大的的调度器,可以让程序在指定时间执行,也可以按照某一个频度执行,配置起来稍显复杂。
- Spring3.0以后自带Spring Task,可以将它看成一个轻量级的Quartz,使用起来比 Quartz简单许多,在课程中我们使用Spring Task实现定时任务
14.1 入门案例
1、创建SpringBoot项目,在启动类开启定时任务。
@SpringBootApplication @EnableScheduling //开启定时任务 public class DemoApplication { public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }
2、编写定时任务类
@Component public class MyTask { //定时方法,每秒实行一次 @Scheduled(cron="* * * * * *") public void task1(){ //当前时间 System.out.println("当前时间:"+new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())); } }
3、运行
14.2 Cron表达式
Spring Task依靠Cron表达式配置定时规则。Cron表达式是一个字符串,分为6或7个域,每一个域代表一个含义,以空格隔开。有如下两种语法格式:
Seconds
Minutes
Hours
DayofMonth
Month
DayofWeek
Year
Seconds
Minutes
Hours
DayofMonth
Month
DayofWeek
Seconds(秒):域中可出现 ,
-
*
/
四个字符,以及0-59的整数
*
:表示匹配该域的任意值,在Seconds域使用*
,表示每秒钟都会触发,
:表示列出枚举值。在Seconds域使用5,20
,表示在5秒和20秒各触发一次。
@Scheduled(cron="5,15,30,40 * * * * *") //每分钟的第5,15,30,40秒执行一次
-
:表示范围。在Seconds域使用5-20
,表示从5秒到20秒每秒触发一次
@Scheduled(cron="5-20 * * * * *") //定时方法,每分钟的第5到20秒执行
/
:表示起始时间开始触发,然后每隔固定时间触发一次。在Seconds域使用5/20
, 表示5秒触发一次,25秒,45秒分别触发一次。
@Scheduled(cron="5/10 * * * * *")//定时方法,在当前分钟的第5开始执行,每间隔10秒执行一次
Minutes(分):域中可出现
,
-
*
/
四个字符,以及0-59的整数Hours(时):域中可出现
,
-
*
/
四个字符,以及0-23的整数DayofMonth(日期):域中可出现
,
-
*
/
?
L
W
C
八个字符,以及1-31的整数
C
:表示和当前日期相关联。在DayofMonth域使用5C
,表示在5日后的那一天触发,且每月的那天都会触发。比如当前是10号,那么每月15号都会触发。
@Scheduled(cron="0 0 0 5c * * *")//今天是1号,那么本月的6号0点和以后每月的6号0点都执行
L
:表示最后,在DayofMonth域使用L
,表示每个月的最后一天触发。
@Scheduled(cron="0 0 0 L * * *")//本月的最后一天0点执行
W
:表示工作日,在DayofMonth域用15W
,表示最接近这个月第15天的工作日触发,如果15号是周六,则在14号即周五触发;如果15号是周日,则在16号即周一触发;如果15号是周二则在当天触发。注:
- 该用法只会在当前月计算,不会到下月触发。比如在DayofMonth域用
31W
,31号是周日,那么会在29号触发而不是下月1号。- 在DayofMonth域用
LW
,表示这个月的最后一个工作日触发。Month(月份):域中可出现
,
-
*
/
四个字符,以及1-12的整数或JAN-DEC的单词缩写
@Scheduled(cron="0 0 0 * 6-8 * *")//6-8月每天晚上的0点执行
DayofWeek(星期):可出现
,
-
*
/
?
L
#
C
八个字符,以及1-7的整数或SUN-SAT 单词缩写,1代表星期天,7代表星期六
C
:在DayofWeek域使用2C
,表示在2日后的那一天触发,且每周的那天都会触发。比如当前是周一,那么每周三都会触发。L
:在DayofWeek域使用L
,表示在一周的最后一天即星期六触发。在DayofWeek域使用5L
,表示在一个月的最后一个星期四触发。#
:用来指定具体的周数,#
前面代表星期几,#
后面代表一个月的第几周,比如5#3
表示一个月第三周的星期四。?
:在无法确定是具体哪一天时使用,用于DayofMonth和DayofWeek域。例如在每月的20日零点触发1次,此时无法确定20日是星期几,写法如下:0 0 0 20 * ?
;或者在每月的最后一个周日触发,此时无法确定该日期是几号,写法如下:0 0 0 ? * 1L
Year(年份):域中可出现
,
-
*
/
四个字符,以及1970~2099的整数。该域可以省略,表示每年都触发。
14.3 Cron实战案例
含义 | 表达式 |
每隔5分钟触发一次 | 0 0/5 * * * * |
每小时触发一次 | 0 0 * * * * |
每天的7点30分触发 | 0 30 7 * * * |
周一到周五的早上6点30分触发 | 0 30 7 ? * 2-6 |
每月最后一天早上10点触发 | 0 0 10 L * ? |
每月最后一个工作日的18点30分触发 | 0 30 18 LW * ? |
2030年8月每个星期六和星期日早上10点触发 | 0 0 10 ? 8 1,7 2030 |
每天10点、12点、14点触发 | 0 0 10,12,14 * * * |
朝九晚五工作时间内每半小时触发一次 | 0 0 0/30 9-17 ? * 2-6 |
每周三中午12点触发一次 | 0 0 12 ? * 4 |
每天12点触发一次 | 0 0 12 * * * |
每天14点到14:59每分钟触发一次 | 0 * 14 * * * |
每天14点到14:59每5分钟触发一次 | 0 0/5 14 * * * |
每天14点到14:05每分钟触发一次 | 0 0-5 14 * * * |
每月15日上午10:15触发 | 0 15 10 15 * ? |
每月最后一天的上午10:15触发 | 0 15 10 L * ? |
每月的第三个星期五上午10:15触发 | 0 15 10 ? * 6#3 |
14.4 @Scheduled
@Scheduled写在方法上方,指定该方法定时执行。常用参数如下:
- cron:cron表达式,定义方法执行的时间规则。
- fixedDelay:任务立即执行,之后每隔多久执行一次,单位是毫秒,上一次任务结束后计算下次执行的时间。
// 立即执行,任务结束后每5秒执行一次 @Scheduled(fixedDelay=5000) public void task1() throws InterruptedException { SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss"); Thread.sleep(1000); System.out.println(sdf.format(new Date())); }
- fixedRate:任务立即执行,之后每隔多久执行一次,单位是毫秒,上一次任务开始后计算下次执行的时间。
// 立即执行,之后每5秒执行一次 @Scheduled(fixedRate=5000) public void task2() throws InterruptedException { SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss"); Thread.sleep(1000); System.out.println(sdf.format(new Date())); }
- initialDelay:项目启动后不马上执行定时器,根据initialDelay的值延时执行。
// 项目启动3秒后执行,之后每5秒执行一次。 @Scheduled(fixedRate=5000,initialDelay = 3000) public void task3() throws InterruptedException { SimpleDateFormat sdf = new SimpleDateFormat("HH:mm:ss"); Thread.sleep(1000); System.out.println(sdf.format(new Date())); }
14.5 多线程任务
Spring Task定时器默认是单线程的,如果项目中使用多个定时器,使用一个线程会造成效率低下。代码如下:
@Scheduled(cron="* * * * * *") private void task1() throws InterruptedException { System.out.println(Thread.currentThread().getId()+"线程执行任务1"); Thread.sleep(5000); } @Scheduled(cron="* * * * * *") private void task2() { System.out.println(Thread.currentThread().getId()+"线程执行任务2"); }
任务1较浪费时间,会阻塞任务2的运行。此时我们可以给Spring Task配置线程池。
@Configuration public class SchedulingConfig implements SchedulingConfigurer { public void configureTasks(ScheduledTaskRegistrar taskRegistrar) { // 创建线程池 taskRegistrar.setScheduler(Executors.newScheduledThreadPool(5)); } }
此时任务1不会阻塞任务2的运行。