跟我极速尝鲜 Spring Boot 2.3

来自专辑
Spring 系列
跟我极速尝鲜 Spring Boot 2.3
古时的风筝第 80 篇原创文章 html

做者 | 风筝
公众号:古时的风筝(ID:gushidefengzheng)
转载请联系受权,扫码文末二维码加微信java

Spring Boot 2.3 已经发布一个月了,这两天才想起来尝一尝鲜儿。除了常规的升级外,很大部分的升级是针对 Docker 的,让你不得不相信,Docker 容器化微服务已然大势所趋。尚未用过的同窗,再不下手就晚了。程序员

这次升级主要包括以下几个方面,接下来就跟着我一块儿来尝一尝吧。
跟我极速尝鲜 Spring Boot 2.3spring

准备工做

为了说明 Spring Boot 2.3 的新特性,必须建立一个项目,以便试验。docker

建立一个项目并启动数据库

一、建立一个 Spring Boot 项目,能够到 https://start.spring.io/ 上建立,也可使用 IDEA 自带的功能建立。选择版本 2.3.1,JDK 仍是选择亲爱的 Java 8,引入 Web 和 Actuator 两个依赖包。编程

跟我极速尝鲜 Spring Boot 2.3
image-20200623155810851
有一点要注意一下,在我写本文的时候,Spring Boot 2.3.1 还不能从中央仓库下载,须要添加 Spring Boot 官方的里程碑仓库。api

<repositories>
  <repository>
    <id>spring-milestone</id>
    <name>Spring Milestone Repository</name>
    <url>https://repo.spring.io/milestone</url>
  </repository>
</repositories>

二、在 pom 文件中引入 Maven 插件浏览器

<build>
  <plugins>
    <plugin>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-maven-plugin</artifactId>
      <version>2.3.1.RELEASE</version>
    </plugin>
  </plugins>
</build>

三、添加一个 Controller,作测试用。缓存

@RestController
public class PlayController {

    @GetMapping(value = "play")
    public String play(){
        return "hey, play with me!";
    }
}

四、启动项目

mvn spring-boot:run

五、访问 http://localhost:8080/play,说明项目启动成功

跟我极速尝鲜 Spring Boot 2.3
image-20200623161822953

更好的 Docker 支持

若是不使用 Docker 呢,那就直接打成 jar 包,使用以下命令

mvn package spring-boot:repackage

跟我极速尝鲜 Spring Boot 2.3
image-20200623162023503

而后就能够把这个 Jar包部署到服务器了,固然这个过程多是用自动化部署工具实现的,不如 jenkins 或者自研系统。

以前 Docker 打包方式

抛开公司(尤为是大厂)里成熟的自动化部署流程不谈,我这里说的是通常性小厂或者是我的项目。

若是你在以前的版本就已经用 Docker 方式,那基本上都是本身写 Dockerfile ,而后本身写脚本使用 Dockerfile 打镜像包,或者使用 Maven 插件,好比 dockerfile-maven-plugin,我以前写过一篇 Spring Boot 和 Docker 实现微服务部署,就是用的这种方式,能够对比着看一下。

Cloud Native Buildpacks

若是你了解 Dockerfiles 的话,那你确定了解用 Dockerfiles 构建镜像的过程,须要你建立一个 Dockerfile 文件而后在里面写上构建镜像所需的一系列动做,而 Cloud Native Buildpacks 则无需配置相似的过程文件,很大程度上减轻了开发者的工做,提升了效率。这还不是最重要的,最重要的是它提供了更高层次的抽象能力,使镜像的分层更加清晰,而且合理有效的利用层缓存,这样一来,当咱们对应用程序进行修改以后,再次构建镜像时的速度飞快,好比咱们的应用只改了几行代码,那当咱们使用 Buildpacks 构建镜像时,只须要在应用程序层进行从新构建,其余层使用缓存就能够,也就是只对变化了的层从新构建。

Spring Boot 2.3 Docker 方式

首先要确保你本地已经正常启动了 Docker 服务。

Spring Boot 2.3 官方的 Docker Maven 插件,今后不用再借助第三方了。咱们前面建立项目的时候已经引入了这个 Maven 插件。

此插件不只提供了打镜像包的功能,还有其余的经常使用功能,好比 run、repackage 等。

跟我极速尝鲜 Spring Boot 2.3
image-20200623154127475

为何前面要说 Cloud Native Buildpacks 呢,不是跑题啊,是由于 Spring Boot 2.3 生成 Docker 镜像包的方式就是集成了 Cloud Native Buildpacks。

那咱们就打个镜像包试一下吧

mvn spring-boot:build-image

你觉得立刻就能看到成果了吗,仍是太年轻。

大中华区开发者怎么了

对于中国的开发者来讲,打包这一步不会太顺利,缘由你们都很清楚。不出意外的话,应该会出现这样的错误,不出错可能才是意外。

[ERROR] Failed to execute goal org.springframework.boot:spring-boot-maven-plugin:2.3.1.RELEASE:build-image (default-cli) on project play: Execution default-cli of goal org.springframework.boot:spring-boot-maven-plugin:2.3.1.RELEASE:build-image failed: Docker API call to 'localhost/v1.24/images/create?fromImage=gcr.io%2Fpaketo-buildpacks%2Fbuilder%3Abase-platform-api-0.3' failed with status code 500 "Internal Server Error" and message "Get https://gcr.io/v2/: net/http: request canceled while waiting for connection (Client.Timeout exceeded while awaiting headers)" -> [Help 1]

出现这个问题的缘由是由于 Buildpacks 调用 Docker API 建立镜像的方法,要访问 https://gcr.io ,从上面 pull 一些基础镜像下来,这是 Google 的 Google Cloud ,是 Google 的容器仓库,然而对于中国的开发者来讲,这个地址是 404 的。

因此咱们要加个系统级别代理,或者专门为 Docker 配置代理。我是在 Docker 中配置的代理,系统代理的影响太大。我本机安装的是 Docker Desktop,直接打开设置,在里面加上代理就能够了(别问我代理怎么搞,问我就是没有代理)。
跟我极速尝鲜 Spring Boot 2.3

image-20200623174349112

好了,经过上面一顿猛如虎的操做,再次运行命令

mvn spring-boot:build-image

根据你的网速,等上一段时间,就会出现下面的结果,说明镜像建立成功了。
跟我极速尝鲜 Spring Boot 2.3

以后你可使用 docker images命令查看。这时间也是醉了,40 years ago。

跟我极速尝鲜 Spring Boot 2.3
image-20200623222649235

使用此镜像启动容器

使用命令直接启动容器。

docker run -it -p8080:8080 play:0.0.1-SNAPSHOT

而后访问 8080 端口,获得正确的返回结果,说明启动成功了。
跟我极速尝鲜 Spring Boot 2.3

image-20200623161822953

Docker Image 的一个特色是,每一个层都是前一层变化的增量。有一个工具叫作 dive,能够清楚的查看分层结构里面包含的内容。具体安装和使用请自行搜索。

使用 dive 查看的一个小技巧,由于镜像层包含的指令不少,因此咱们选择只查看相对于上一层的增量内容,使用 Ctrl+L组合键。
跟我极速尝鲜 Spring Boot 2.3

image-20200622231229994
而后按 Tab进入视图,而后按 Ctrl+U,去掉没有更改的选项,也就是只看变化的部分。
跟我极速尝鲜 Spring Boot 2.3

image-20200622225041292

而后上下箭头能够切换层查看,好比下面这个图展现了一个 18 M 的层相对于上一层的变化内容,能够看出来这个层实际上就是应用程序层,包含了不少当前应用程序的类和第三方依赖包等。

跟我极速尝鲜 Spring Boot 2.3

image-20200623223512781

分层 jar 包

分层打包配置很方便,最简单的方式就是在 pom 文件中加上以下配置:

<plugin>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-maven-plugin</artifactId>
  <version>2.3.1.RELEASE</version>
  <configuration>
    <layers>
      <enabled>true</enabled>
    </layers>
  </configuration>
</plugin>

加上分层配置以后,仍然使用常规的命令打包

mvn package spring-boot:repackage

分层打包其实和之前的打包方式没有什么不一样,打出来的包几乎和以前是彻底同样的,分层其实只是逻辑上的抽象而已。打出的 jar 包结构以下(jar包其实就是个压缩包,能够解压缩查看目录结构)
跟我极速尝鲜 Spring Boot 2.3

image-20200624073901950

在 jar 包的 BOOT-INF 目录下能够看到 classpath.idx和layers.idx两个文件,这两个就是为了分层 jar 的关键。

默认状况下会分红以下四个层。

  • dependencies 对版本没有要求的依赖包,也就是你的应用程序不管怎么改,都几乎不会影响的依赖包。

  • spring-boot-loader Spring Boot 加载类。

  • snapshot-dependencies对应用版本有要求的依赖包,好比应用升级后,可能同时须要升级的依赖包。

  • application 应用程序编译类和配置文件等。

在 layers.idx能够看出这个分层结构,用普通的文本编辑器就能够打开,好比 sublime。打开以后看到这样一个相似于 yaml 的结构,四个层以及他们所指的目录都清晰的列出来了。

- "dependencies":
  - "BOOT-INF/lib/"
- "spring-boot-loader":
  - "org/"
- "snapshot-dependencies":
- "application":
  - "BOOT-INF/classes/"
  - "BOOT-INF/classpath.idx"
  - "BOOT-INF/layers.idx"
  - "META-INF/"

classpath.idx文件列出了依赖的 jar 包列表,到时候会按照这个顺序载入。

- "spring-boot-starter-actuator-2.3.1.RELEASE.jar"
- "spring-boot-starter-2.3.1.RELEASE.jar"
- "spring-boot-2.3.1.RELEASE.jar"
- "spring-boot-autoconfigure-2.3.1.RELEASE.jar"
- "spring-boot-starter-logging-2.3.1.RELEASE.jar"
- "logback-classic-1.2.3.jar"
- "logback-core-1.2.3.jar"
- "log4j-to-slf4j-2.13.3.jar"

自定义分层结构
若是咱们想要在默认的 4 层上增长新的分层,Spring Boot 2.3 也提供了定制分层的功能。配置也很简单,在 plugin配置以下,指定了 layers.xml做为自定义分层配置

<configuration>
  <layers>
    <enabled>true</enabled>
    <configuration>${project.basedir}/src/layers.xml</configuration>
  </layers>
</configuration>

layers.xml的配置像下面这样

<layers xmlns="http://www.springframework.org/schema/boot/layers"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/boot/layers
                      https://www.springframework.org/schema/boot/layers/layers-2.3.xsd">
 <application>
  <into layer="spring-boot-loader">
   <include>org/springframework/boot/loader/**</include>
  </into>
  <into layer="application" />
 </application>
 <dependencies>
  <into layer="snapshot-dependencies">
   <include>*:*:*SNAPSHOT</include>
  </into>
  <into layer="dependencies" />
 </dependencies>
 <layerOrder>
  <layer>dependencies</layer>
  <layer>spring-boot-loader</layer>
  <layer>snapshot-dependencies</layer>
  <layer>application</layer>
    </layerOrder>
</layers>

当你开启分层功能后,可使用 -Djarmode查看分层状况。

java -Djarmode=layertools -jar target/play-0.0.1-SNAPSHOT.jar list

显示的结果就是分层状况,好比默认状况下就是这样,列出了 4 个默认分层。

dependencies
spring-boot-loader
snapshot-dependencies
application

题外话:

Djarmode其实就是个 Java-Agent,关于Java-Agent,能够看我以前写的一篇文章,Java 调试工具、热部署、JVM 监控工具都用到了它,挺有意思的。

分层包的意义

说了半天分层包了,那分层包到底有啥用呢?

这么说吧,它实际上是为了和 Docker 配合使用的,若是你不用 Docker 方式部署,仍是用原始 jar 包的方式,能够说没什么用,若是非得说有什么用,那就是让你更加清楚项目的依赖状况。

分层包 和 Docker 结合

前面介绍 Docker 镜像包的时候说了 Buildpacks 可让你的镜像分层清晰,而 Spring Boot 2.3 提供的分层 jar 功能能够在镜像分层的基础上更上一层楼,使分层更加清晰。

那咱们开启分层配置,而后从新打个 Docker 镜像出来看一看。

mvn spring-boot:build-image

而后再使用 dive 工具看一下启用分层 jar 功能后的 Docker 镜像分层状况,是否是变得更好了。前面的层都是同样的,都是一些集成镜像和配置,从 18 MB 的这个层开始的 4 个层就是启用分层后的4个层,分别对应 dependencies、spring-boot-loader、snapshot-dependencies、application

跟我极速尝鲜 Spring Boot 2.3
image-20200624090924915
好比这个 5.4K 的 application 层。

跟我极速尝鲜 Spring Boot 2.3
image-20200624092311622

那这样作有什么好处呢,前面不是说了吗,Buildpacks 打镜像包会使用缓存的,若是这一层没变那就不用从新打这一层,只须要从新打包修改过的层,这样一来,若是你只修改了 application 中的内容,好比新加了 Controller 或者配置文件等,那么只须要从新打包这一层,也就是几 K,几十K 不会很大,这样一来打包速度就很快了,要否则一个上百兆的镜像包也得须要一段时间。

优雅停机功能

什么叫优雅停机呢,假设这是一个分布式服务,其中一台服务所在的实体机须要打安全补丁,须要关机重启,那实体机关机以前要先把这个服务停掉。

关掉服务的方式,好比:

  1. 我无论,我就直接关实体机,至于服务,你命由我不禁天。

  2. 也好办,kill -9 ,一行命令解决,也挺省心。

额,还行吧,可是有点儿问题,好比当前服务实例正在处理请求,还没处理完,你咔嚓一下就给它结束了,谁受得了,不要太刺激。

咱们把前面的那个 Controller 中的 play方法改一下,加一个延时,等待 6 秒才返回,模拟一个比较慢的请求。

@GetMapping(value = "play")
public String play() throws InterruptedException{
  Thread.sleep(6000);
  return "hey, play with me!";
}

效果就是你访问这个地址,而后等了 6 秒以后才显示出 hey, play with me!。

若是在这 6 秒钟以内我杀掉了进程,将会在浏览器中出现下面这个讨厌的界面。
跟我极速尝鲜 Spring Boot 2.3

image-20200624095526291
启用优雅关机
只须要在配置文件中增长 server.shutdown的配置,一种是 immediate,也就是当即中止,另外一种就是所谓的优雅关机 graceful。

跟我极速尝鲜 Spring Boot 2.3
image-20200624095818220

server:
  port: 8081
  shutdown: graceful

# 缓冲10s,上面定义的那个方法延时 6秒,因此10秒确定够了
spring:
  lifecycle:
    timeout-per-shutdown-phase: 10s

以后,再启动服务,而后访问这个页面,这个过程当中结束进程。而后会看到控制台有输出,提示优雅关机的过程,并提示说会等待活动状态的请求处理完成。
跟我极速尝鲜 Spring Boot 2.3

image-20200624103229034
请求也变得正常了。
跟我极速尝鲜 Spring Boot 2.3

image-20200624103359637

活动状态检测

以前版本的 spring-boot-starter-actuator就已经有健康状态检测了,不开启活性状态检测,当咱们访问 health 的时候,会看到下面的信息,说明服务是可用的。

跟我极速尝鲜 Spring Boot 2.3
image-20200624113746766
经过在配置文件中配置以下信息,可开启活动状态检测。

management:
  health:
    probes:
      enabled: true
  endpoint:
    health:
      show-details: always

开启上述配置以后,重启服务,在访问 health 页面,看到的内容以下
跟我极速尝鲜 Spring Boot 2.3

image-20200624114306674
除了状态标示外,还多了一个 groups节点。

Liveness:应用程序是否处于可用状态

可经过 /actuator/health/liveness 路径查看
跟我极速尝鲜 Spring Boot 2.3

image-20200624114517046
Readiness:应用程序是否准备好接受客户端请求了。

可经过 /actuator/health/readiness路径查看

跟我极速尝鲜 Spring Boot 2.3
image-20200624114631242

这个功能实际上是针对部署在 Kubernetes 上的服务作的支持。Kubernetes 提供了 LivenessProbe 和 cProbe 两类探针,活动状态检查即是对这两类探针提供无缝支持。

在配置文件中增长配置便可,与 kubernetes 作无缝对接。

spring:
  main:
    cloud-platform: kubernetes

那应该怎么用呢

拿 Readiness 来讲吧,假设咱们要对外宣布次服务暂时不接受请求,那就改变 readiness 的状态,当探针过来的时候发现不接受请求,那就去请求其余实例了。

具体怎么作呢,我在 Controller 中加了两个方法,一个开启接受请求,一个中止接收请求。

@RestController
public class PlayController {

    private final ApplicationEventPublisher publisher;

    public PlayController(ApplicationEventPublisher publisher) {
        this.publisher = publisher;
    }

    @GetMapping(value = "play")
    public String play() throws InterruptedException{
        Thread.sleep(6000);
        return "hey, play with me!";
    }

    @GetMapping(value = "up")
    public String up(){
        AvailabilityChangeEvent.publish(publisher,this, ReadinessState.ACCEPTING_TRAFFIC);
        return "up";
    }

    @GetMapping(value = "down")
    public String down(){
        AvailabilityChangeEvent.publish(publisher,this, ReadinessState.REFUSING_TRAFFIC);
        return "down";
    }
}

经过 AvailabilityChangeEvent这个类的 publish 方法,更改自身服务状态。当咱们访问 down 接口以后,再次查看 health/readiness的状态状况,会显示以下内容:OUT_OF_SERVICE表示离线,不接受请求。跟我极速尝鲜 Spring Boot 2.3

image-20200624120041609
而当咱们请求 up 接口后,服务状态又变成了 up,这也就实现了服务下线和上线的功能。

支持 JDK 14

Spring Boot 2.3 支持 JDK 14了,但跟我有啥关系吗,没有。我依然用个人 Java 8。真香。

Spring Data Neumann

Spring Boot 2.3发布了 Spring Data Neumann,其中包含许多主要版本和驱动程序升级。此版本还增长了对 R2DBC(Reactive Relational Database Connectivity) 的稳定版本支持。R2DBC 提供了异步编程方式访问数据库的 API,主要是配合开发异步非阻塞式的应用程序使用的。

总结

从中能够看出很大部份内容都是与 Docker 容器技术有关的,好比分层打镜像包、无缝支持 kubernetes 等,可见 docker 微服务已然成为不少开发者的选择。可是仍然有待改进,好比默认的 docker hub 是 Google Cloud,就不能灵活配置,支持国内的镜像仓库很差吗。

大家用的 Spring Boot 哪一个版本,会来尝个鲜儿吗?

参考文档:

https://docs.spring.io/spring-boot/docs/2.3.1.RELEASE/maven-plugin/reference/html/index.html#goals

https://medium.com/@TimvanBaarsen/whats-new-in-spring-boot-2-3-22d01d036f11


公众号:古时的风筝

一个兼具深度与广度的程序员鼓励师,一个本打算写诗却写起了代码的田园码农!你可选择如今就关注我,或者看看历史文章再关注也不迟。

技术交流还能够加群或者直接加我微信。

跟我极速尝鲜 Spring Boot 2.3