当前位置: 首页 > news >正文

公司网站做推广支出分录seo优化是什么意思

公司网站做推广支出分录,seo优化是什么意思,网站建设的理由,中国城乡住房建设厅网站在 4.1.2 节中#xff0c;我们介绍了 Spring Boot 的四大核心组成部分#xff0c;第 4 章主要介绍了其中的起步依赖与自动配置#xff0c;本章将重点介绍 Spring Boot Actuator#xff0c;包括如何通过 Actuator 提供的各种端点#xff08;endpoint#xff09;了解系统的…在 4.1.2 节中我们介绍了 Spring Boot 的四大核心组成部分第 4 章主要介绍了其中的起步依赖与自动配置本章将重点介绍 Spring Boot Actuator包括如何通过 Actuator 提供的各种端点endpoint了解系统的运行情况使用 Micrometer 为各种监控系统提供度量指标数据最后还要了解如何打包部署 Spring Boot 应用程序。 5.1 Spring Boot Actuator 概述 Spring Boot Actuator 是 Spring Boot 的重要功能模块能为系统提供一系列在生产环境中运行所必需的功能比如监控、度量、配置管理等。只需引入 org.springframework.boot:spring-boot-starter-actuator 起步依赖后我们就可以通过 HTTP 来访问这些功能也可以使用 JMX 来访问。Spring Boot 还为我们预留了很多配置可以根据自己的需求对 Spring Boot Actuator 的功能进行定制。 5.1.1 端点概览 不知道大家有没有尝试解决过类似下面的问题 Spring 上下文中到底存在哪些 BeanSpring Boot 中的哪些自动配置最终生效了应用究竟获取到了哪些配置项系统中存在哪些 URL它们又映射到了哪里 在没有 Spring Boot Actuator 的时候获取这些信息还是需要费一番功夫的但现在就不一样了Spring Boot Actuator 内置了大量的端点这些端点可以帮助大家了解系统内部的运行情况并针对一些功能做出调整。 根据功能的不同我们可以将这些端点划分成四类信息类端点、监控类端点、操作类端点、集成类端点其中部分端点需要引入特定的依赖或者配置特定的 Bean。接下来我们会依次介绍这四类端点首先是用于获取系统运行信息的端点如表 5-1 所示。 表 5-1 Spring Boot Actuator 中的信息类端点列表 端点 ID默认开启 HTTP默认开启 JMX端点说明auditevents否是提供系统的审计信息beans否是提供系统中的 Bean 列表caches否是提供系统中的缓存信息conditions否是提供配置类的匹配情况及条件运算结果configprops否是提供 ConfigurationProperties 的列表env否是提供 ConfigurableEnvironment 中的属性信息flyway否是提供已执行的 Flyway 数据库迁移信息httptrace否是提供 HTTP 跟踪信息默认最近 100 条info是是显示事先设置好的系统信息integrationgraph否是提供 Spring Integration 图信息liquibase否是提供已执行的 Liquibase 数据库迁移信息logfile否无此功能如果设置了 logging.file.name 或 logging.file.path 属性则显示日志文件内容mappings否是提供 RequestMapping 的映射列表scheduledtasks否是提供系统中的调度任务列表 第二类端点是监控与度量相关的端点具体如表 5-2 所示。 表 5-2 Spring Boot Actuator 中的监控类端点列表 端点 ID默认开启 HTTP默认开启 JMX端点说明health是是提供系统运行的健康状态metrics否是提供系统的度量信息prometheus否无此功能提供 Prometheus 系统可解析的度量信息 我们对第 1 章的 helloworld 示例稍作调整在其 pom.xml 的 dependencies/ 中增加如下内容即可引入 Spring Boot Actuator 的依赖 dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-actuator/artifactId/dependency从上述两张表中我们可以发现默认只有 info 和 health 两个端点是开启了 HTTP 访问的因此在运行程序后通过浏览器或者其他方式访问 http://localhost:8080/actuator/health 就能访问到 health 端点的信息。如果在 macOS 或 Linux 上我们可以使用 curl 命令具体运行结果如下 ▸ curl -v http://localhost:8080/actuator/health* Trying ::1...* TCP_NODELAY set* Connected to localhost (::1) port 8080 (#0) GET /actuator/health HTTP/1.1 Host: localhost:8080 User-Agent: curl/7.64.1 Accept: */* HTTP/1.1 200 Content-Type: application/vnd.spring-boot.actuator.v3json Transfer-Encoding: chunked Date: Fri, 10 Jul 2020 15:38:54 GMT* Connection #0 to host localhost left intact{status:UP}* Closing connection 0如果使用浏览器访问的效果如图 5-1 所示。 图 5-1 通过 Chrome 浏览器查看 health 端点 第三类端点可以执行一些实际的操作例如调整日志级别具体如表 5-3 所示。 表 5-3 Spring Boot Actuator 中的操作类端点列表 端点 ID默认开启 HTTP默认开启 JMX端点说明heapdump否无此功能执行 Heap Dump 操作loggers否是查看并修改日志信息sessions否是针对使用了 Spring Session 的系统可获取或删除用户的 Sessionshutdown否否优雅地关闭系统threaddump否是执行 Thread Dump 操作 最后一类端点比较特殊它的功能与集成有关就只有一个 jolokia见表 5-4。 表 5-4 Spring Boot Actuator 中的集成类端点列表 端点 ID默认开启 HTTP默认开启 JMX端点说明jolokia否无此功能通过 HTTP 来发布 JMX Bean 5.1.2 端点配置 在了解了 Spring Boot 提供的端点后我们就要将它们投入具体的生产使用当中了。Spring Boot Actuator 非常灵活它提供了大量的开关配置还有两种不同的访问方式可供我们选择。接下来大家就来一起了解一下这些配置。 开启或禁用端点 默认情况下除了 shutdown 以外所有的端点都是处于开启状态的只是访问方式不同或者保护状态不同。如果要开启或者禁用某个端点可以调整 management.endpoint.id.enabled 属性。例如想要开启 shutdown 端点就可以这样配置 management.endpoint.shutdown.enabledtrue也可以调整默认值禁用所有端点随后开启指定端点。例如只开启 health 端点 management.endpoints.enabled-by-defaultfalsemanagement.endpoint.health.enabledtrue通过 HTTP 访问端点 默认仅有 health 和 info 端点是可以通过 HTTP 方式来访问的在 5.1.1 节中我们已经看到了如何通过 curl 命令和浏览器来访问 health 端口。那如何才能开启其他端点的 HTTP 访问功能呢可以使用 management.endpoints.web.exposure.include 和 management.endpoints.web.exposure.exclude 这两个属性来控制哪些端点可以通过 HTTP 方式发布哪些端点不行。前者的默认值为 health,info后者的默认值为空。 例如我们希望在原有基础上再增加 beans 和 env 端点就可以这样来设置 management.endpoints.web.exposure.includebeans,env,health,info如果希望放开所有的端点让它们都能通过 HTTP 方式来访问我们就可以将上述属性设置为 * : management.endpoints.web.exposure.include*要是一个端点同时出现在 management.endpoints.web.exposure.include 和 management.endpoints.web.exposure.exclude 这两个属性里那么后者的优先级会高于前者也就是说该端点会被排除。 如果我们希望了解 HTTP 方式可以访问哪些端点可以直接访问 /actuator 地址会得到类似下面的 JSON 信息 {_links: {health: {href: http://localhost:8080/actuator/health,templated: false},health-path: {href: http://localhost:8080/actuator/health/{*path},templated: true},info: {href: http://localhost:8080/actuator/info,templated: false},self: {href: http://localhost:8080/actuator,templated: false}}}其中 templated 为 true 的 URL 可以用具体的值去代替 {} 里的内容比如 http://localhost:8080/actuator/metrics/ 的 就可以用 http://localhost:8080/actuator/metrics 里所罗列的名称代替。 需要特别说明一点要发布 HTTP 端点必须要有 Web 支持因此项目需要引入 spring-boot-starter-web 起步依赖。 通过 JMX 访问端点 与 HTTP 方式类似JMX 也有两个属性即 management.endpoints.jmx.exposure.include 和 management.endpoints.jmx.exposure.exclude。前者的默认值为 *后者的默认值为空。 有不少工具可以用来访问 JMX 端点比如 JVisualVM 和 JConsole它们都是 JDK 自带的工具。以 JConsole 为例启动 JConsole 后会弹出新建连接界面从中可以选择想要连接的本地 Java 进程也可以通过指定信息连接远程的进程具体如图 5-2 所示。 图 5-2 JConsole 的新建连接界面 选中目标进程后点击连接按钮稍过一段时间后就能连上目标进程了。随后选中 MBean 标签页在 org.springframework.boot 目录下找到 Endpoint其中列出的就是可以访问的 JMX 端点。图 5-3 就是选择了 Health 后界面的样子点击 health 按钮即可获得健康检查的 JSON 结果。 图 5-3 通过 JMX 方式访问 health 端点 保护端点 如果在工程中引入了 Spring Security那么 Spring Boot Actuator 会自动对各种端点进行保护例如默认通过浏览器访问时浏览器会显示一个页面要求输入用户名和密码。如果我们没有配置过相关信息那么在系统启动时可以在日志中查找类似下面的日志 Using generated security password: 4fbc8059-fdb8-46f9-a54e-21d5cb2e9eb2默认的用户名是 user密码就是上面这段随机生成的内容。关于 Spring Security 的更多细节我们会在第 10 章中详细展开。此处给出两个示例它们都需要在 pom.xml 中增加 spring-boot-starter-actuator 和 spring-boot-starter-security 起步依赖。这里先来演示如何不用登录页而是使用 HTTP Basic 的方式进行验证但 health 端点除外具体的配置如代码示例 5-1 所示注意其中的 EndpointRequest 用的是 org.springframework.boot.actuate.autoconfigure.security.servlet.EndpointRequest。 代码示例 5-1 需要认证才可访问端点的配置代码片段 Configurationpublic class ActuatorSecurityConfigurer extends WebSecurityConfigurerAdapter {Overrideprotected void configure(HttpSecurity http) throws Exception {http.requestMatcher(EndpointRequest.toAnyEndpoint().excluding(health)).authorizeRequests((requests) - requests.anyRequest().authenticated());http.httpBasic();}}第二个演示针对所有端点可以提供匿名访问具体如代码示例 5-2 所示。 代码示例 5-2 可匿名访问端点的配置代码片段 Configurationpublic class ActuatorSecurityConfigurer extends WebSecurityConfigurerAdapter {Overrideprotected void configure(HttpSecurity http) throws Exception {http.requestMatcher(EndpointRequest.toAnyEndpoint()).authorizeRequests((requests) - requests.anyRequest().anonymous());http.httpBasic();}}茶歇时间针对 Web 和 Actuator 使用不同端口的好处 试想一下我们的系统在对外提供 HTTP 服务一般会在集群前增加一个负载均衡设备比如 Nginx将外部对 80 端口的请求转发至系统的 8080 端口。如果 Spring Boot Actuator 的端口也在 8080而我们又没对端点做足够的保护黑客很轻松地就能获取系统的信息。就算做了保护黑客也能通过端点信息推断出这个系统是通过 Spring Boot 实现的、可能存在哪些漏洞。 一般我们都会在系统启动后通过 health 端点来判断系统的健康情况如果对这个端点做了过多保护反而不便于获取健康检查结果。 一种做法是在防火墙或者负载均衡层面禁止外部访问 Spring Boot Actuator 的 URL例如直接禁止访问 /actuator 及其子路径。另一种做法就是索性让 Actuator 的端点暴露在与业务代码不同的 HTTP 端口上比如不再共用 8080 端口而是单独提供一个 8081 端口而防火墙和负载均衡设备只知道 8080 端口的存在也只会转发请求到 8080 端口就不用担心外部能访问到 8081 端口的问题了。 通过 management.server.port8081 能实现设置 Actuator 专属端口的功能。更进一步我们还可以使用 management.server.base-path 属性以前是 management.server.servlet.context-path为 Spring Boot Actuator 设置 Servlet 上下文默认为空使用 management.endpoints.web.base-path 属性来调整 /actuator 这个默认的基础路径。如果像下面这样来做设置就能将 health 端点访问的 URL 调整为 http://localhost:8081/management/my-actuator/health 了 management.server.port8081management.server.base-path/managementmanagement.endpoints.web.base-path/my-actuator5.1.3 定制端点信息 Spring Boot Actuator 中的每个端点或多或少会有一些属于自己的配置属性大家可以在 org.springframework.boot:spring-boot-actuator-autoconfigure 包中查看各种以 Properties 结尾的属性类也可以直接通过 configprops 端点来查看属性类。 例如 EnvironmentEndpointProperties 就对应了 management.endpoint.env 中的属性其中的 keysToSanitize 就是环境中要过滤的自定义敏感信息键名清单根据代码注释其中可以设置匹配的结尾字符串也可以使用正则表达式。在设置了 management.endpoint.env.keys-to-sanitizejava.*,sun.* 后 env 端点返回的属性中所有 java 和 sun 打头的属性值都会以 * 显示。 Spring Boot Actuator 默认为 info 和 health 端点开启了 HTTP 访问支持那么就让我们来详细了解一下这两个端点有哪些可以定制的地方吧。 定制 info 端点信息 根据 InfoEndpointAutoConfiguration 可以得知 InfoEndpoint 中会注入 Spring 上下文中的所有 InfoContributor Bean 实例。 InfoContributorAutoConfiguration 自动注册了 env、 git 和 build 这三个 InfoContributorSpring Boot Actuator 提供的 InfoContributor 列表如表 5-5 所示。 表 5-5 内置 InfoContributor 列表 类名默认开启说明BuildInfoContributor是提供 BuildProperties 的信息通过 spring.info.build 来设置默认读取 META-INF/build-info.propertiesEnvironmentInfoContributor是将配置中以 info 打头的属性通过端点暴露GitInfoContributor是提供 GitProperties 的信息通过 spring.info.git 来设置默认读取git.propertiesInfoPropertiesInfoContributor否抽象类一般作为其他 InfoContributor 的父类MapInfoContributor否将内置 Map 作为信息输出SimpleInfoContributor否仅包含一对键值对的信息 假设在配置文件中设置了如下内容 info.appHelloWorldinfo.welcomeWelcome to the world of Spring.再提供如下的 Bean Beanpublic SimpleInfoContributor simpleInfoContributor() {return new SimpleInfoContributor(simple, HelloWorld!);}那 info 端点输出的内容大致如下所示 {app: HelloWorld,simple: HelloWorld!,welcome: Welcome to the world of Spring.}定制 health 端点信息 健康检查是一个很常用的功能可以帮助我们了解系统的健康状况例如系统在启动后是否准备好对外提供服务了所依赖的组件是否已就绪等。 健康检查主要是依赖 HealthIndicator 的各种实现来完成的。Spring Boot Actuator 内置了近 20 种不同的实现表 5-6 列举了一些常用的 HealthIndicator 实现基本可以满足日常使用的需求。 表 5-6 常用的 HealthIndicator 实现 实现类作用DataSourceHealthIndicator检查 Spring 上下文中能取到的所有 DataSource 是否健康DiskSpaceHealthIndicator检查磁盘空间LivenessStateHealthIndicator检查系统的存活Liveness情况一般用于 Kubernetes 中ReadinessStateHealthIndicator检查系统是否处于就绪Readiness状态一般用于 Kubernetes 中RedisHealthIndicator检查所依赖的 Redis 健康情况 每个 HealthIndicator 检查后都会有自己的状态信息Spring Boot Actuator 最后会根据所有结果的状态信息综合得出系统的最终状态。 org.springframework.boot.actuate.health.Status 定义了几种默认状态按照优先级降序排列分别为 DOWN、 OUT_OF_SERVICE、 UP 和 UNKNOWN所有结果状态中优先级最高的状态会成为 health 端点的最终状态。如果有需要我们也可以通过 management.endpoint.health.status.order 来更改状态的优先级。 Spring Boot Actuator 默认开启了所有的 HealthIndicator它们会根据情况自行判断是否生效也可以通过 management.health.defaults.enabledfalse 开关默认关闭随后使用 management.health.name.enabled 选择性地开启 HealthIndicator。例如 DataSourceHealthContributorAutoConfiguration 是这样定义的 Configuration(proxyBeanMethods false)ConditionalOnClass({ JdbcTemplate.class, AbstractRoutingDataSource.class })ConditionalOnBean(DataSource.class)ConditionalOnEnabledHealthIndicator(db)AutoConfigureAfter(DataSourceAutoConfiguration.class)public class DataSourceHealthContributorAutoConfiguration extendsCompositeHealthContributorConfigurationAbstractHealthIndicator, DataSourceimplements InitializingBean {}那么它的生效条件是这样的 CLASSPATH 中存在 JdbcTemplate 和 AbstractRoutingDataSource 类Spring 上下文中存在 DataSource 类型的 Bean默认开关打开或者 management.health.db.enabledtrue此处 ConditionalOnEnabledHealthIndicator 中的 db 就是 name 。 知道了 health 中的各个 HealthIndicator 后怎么才能看到结果呢我们可以通过配置 management.endpoint.health.show-details 和 management.endpoint.health.show-components 的属性值来查看结果默认是 never将其调整为 always 后就会始终显示具体内容了。如果依赖中存在 Spring Security也可以仅对授权后的用户开放将属性值配置为 when-authorized这时我们可以通过 management.endpoint.health.roles 来设置可以访问的用户的角色。一般情况下可以考虑将其调整为 always。 如今越来越多的系统运行在 Kubernetes 环境中而 Kubernetes 需要检查系统是否存活是否就绪对此 health 端点也提供了对应的 LivenessStateHealthIndicator 和 ReadinessStateHealthIndicator默认 URL 分别为 /actuator/health/liveness 和 /actuator/health/readiness。 5.1.4 开发自己的组件与端点 在上一节中我们看到的基本都是对现有端点与组件的配置Spring Boot Actuator 提供了让我们自己扩展端点或者实现新端点的功能。例如在进行健康检查时我们可以加入自己的检查逻辑只需实现 HealthIndicator 即可。 开发自己的 HealthIndicator 为了增加自己的健康检查逻辑我们可以定制一个 HealthIndicator 实现通常会选择扩展 AbstractHealthIndicator 类实现其中的 doHealthCheck() 方法。 根据 HealthEndpointConfiguration 类的代码我们可以知道 healthContributorRegistry 会从 Spring 上下文获取所有 HealthContributor 类型 HealthIndicator 继承了这个接口的 Bean并进行注册所以我们也只需要把写好的 HealthIndicator 配置为 Bean 即可。 Configuration(proxyBeanMethods false)class HealthEndpointConfiguration {BeanConditionalOnMissingBeanHealthContributorRegistry healthContributorRegistry(ApplicationContext applicationContext,HealthEndpointGroups groups) {MapString, HealthContributor healthContributors new LinkedHashMap(applicationContext.getBeansOfType(HealthContributor.class));if (ClassUtils.isPresent(reactor.core.publisher.Flux, applicationContext.getClassLoader())) {healthContributors.putAll(new AdaptedReactiveHealthContributors(applicationContext).get());}return new AutoConfiguredHealthContributorRegistry(healthContributors, groups.getNames());}// 省略其他代码}接下来我们以第 4 章的 BinaryTea 项目作为基础在其中添加自己的 HealthIndicator。在 ShopReadyHealthIndicator 上添加 Component 注解以便在扫描到它后就能将其注册为 Bean。通过构造方法注入 BinaryTeaProperties检查时如果没有 binaryTeaProperties 或者属性中的 ready 为 false检查即为失败除此之外都算成功。具体如代码示例 5-3 所示。 代码示例 5-3 ShopReadyHealthIndicator 健康检查器 Componentpublic class ShopReadyHealthIndicator extends AbstractHealthIndicator{private BinaryTeaProperties binaryTeaProperties;public ShopReadyHealthIndicator(ObjectProviderBinaryTeaProperties binaryTeaProperties) {this.binaryTeaProperties binaryTeaProperties.getIfAvailable();}Overrideprotected void doHealthCheck(Health.Builder builder) throws Exception {if (binaryTeaProperties null || !binaryTeaProperties.isReady()) {builder.down();} else {builder.up();}}}运行代码前还需要在 pom.xml 中加入 Spring Web 和 Spring Boot Actuator 的依赖具体如下 dependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-web/artifactId/dependencydependencygroupIdorg.springframework.boot/groupIdartifactIdspring-boot-starter-actuator/artifactId/dependency同时在 application.properties 中增加 management.endpoint.health.show-detailsalways以便可以看到 ShopReadyHealthIndicator 的效果。运行 BinaryTeaApplication 后在浏览器中访问 http://localhost:8080/actuator/health就能看到类似下面的 JSON 输出 {components: {// 省略部分内容ping: {status: UP},shopReady: {status: UP}},status: UP}为了能够进行自动测试我们还可以在 ShopConfigurationEnableTest 和 ShopConfigurationDisableTest 中分别加入对应的测试用例再写个测试检查是否注册了对应的信息如代码示例 5-4 所示。 代码示例 5-4 针对 ShopReadyHealthIndicator 的各种单元测试用例 // ShopConfigurationEnableTest中的测试用例Testvoid testIndicatorUp() {ShopReadyHealthIndicator indicator applicationContext.getBean(ShopReadyHealthIndicator.class);assertEquals(Status.UP, indicator.getHealth(false).getStatus());}// ShopConfigurationDisableTest中的测试用例Testvoid testIndicatorDown() {ShopReadyHealthIndicator indicator applicationContext.getBean(ShopReadyHealthIndicator.class);assertEquals(Status.DOWN, indicator.getHealth(false).getStatus());}// 独立的ShopReadyHealthIndicatorTest测试是否注册了shopReadySpringBootTestpublic class ShopReadyHealthIndicatorTest {Autowiredprivate HealthContributorRegistry registry;Testvoid testRegistryContainsShopReady() {assertNotNull(registry.getContributor(shopReady));}}如果一切顺利执行 mvn test 后我们应该就能看到测试通过的信息了 [INFO] Results:[INFO][INFO] Tests run: 7, Failures: 0, Errors: 0, Skipped: 0[INFO][INFO] ------------------------------------------------------------------------[INFO] BUILD SUCCESS[INFO] ------------------------------------------------------------------------茶歇时间为什么要优先通过 ObjectProvider 获取 Bean 当我们需要从 Spring 上下文中获取其他 Bean 时最直接的方法是使用 Autowired 注解但系统运行时的不确定性太多了比如不确定是否存在需要的依赖这时就需要加上 requiredfalse 也有可能目标类型的 Bean 不止一个而我们只需要一个构造方法有多个参数…… 这时就该 ObjectProviderT 上场了它大多用于构造方法注入的场景让我们有能力处理那些尴尬的场面其中的 getIfAvailable() 方法在存在对应 Bean 时返回对象不存在时则返回 null getIfUnique() 方法在有且仅有一个对应 Bean 时返回对象Bean 不存在或不唯一且不唯一时没有标注 Primary 的情况下返回 null。再加上一些检查和遍历的方法通过明确的编码我们就可以确保自己的代码获取到必要的依赖或判断出缺少的东西并加以处理。 开发自己的端点 如果内置的端点无法满足我们的需求那最后一招就是写一个自己的端点。好在 Spring Boot Actuator 支持到位只需简单几步就能帮助我们实现一个端点。 首先在 Bean 上添加 Endpoint 注解其中带有 ReadOperation、 WriteOperation 和 DeleteOperation 的方法能被发布出来而且能通过 JMX 或者 HTTP 的方式访问到这些方法 接下来如果我们希望限制只用其中的一种方式来发布则可以将 Endpoint 替换为 JmxEndpoint 或 WebEndpoint。 如果是通过 HTTP 方式访问的默认的 URL 是 /actuator/id其中的 id 就是 Endpoint 注解中指定的 id而 ReadOperation、 WriteOperation 和 DeleteOperation 的方法分别对应了 HTTP 的 GET、 POST 和 DELETE 方法。HTTP 的响应码则取决于方法的返回值如果存在返回内容则响应码是 200 OK否则 ReadOperation 方法会返回 404 Not Found而另两个则返回 204 No Content对于需要参数但又获取不到的情况方法会返回 400 Bad Request。 我们为 BinaryTea 编写了一个返回商店状态的端点具体如代码示例 5-5 所示大部分逻辑与 ShopReadyHealthIndicator 是类似的这里就不再赘述了。 代码示例 5-5 ShopEndpoint 代码片段 ComponentEndpoint(id shop)public class ShopEndpoint {private BinaryTeaProperties binaryTeaProperties;public ShopEndpoint(ObjectProviderBinaryTeaProperties binaryTeaProperties) {this.binaryTeaProperties binaryTeaProperties.getIfAvailable();}ReadOperationpublic String state() {if (binaryTeaProperties null || !binaryTeaProperties.isReady()) {return Were not ready.;} else {return We open binaryTeaProperties.getOpenHours() .;}}}为了能访问到我们的端点需要在 application.properties 中允许它以 Web 形式发布 management.endpoints.web.exposure.includehealth,info,shop启动系统后通过 http://localhost:8080/actuator/shop 即可访问 ShopEndpoint 的输出。 5.2 基于 Micrometer 的系统度量 系统在生产环境中运行时我们需要通过各种方式了解系统的运作是否正常。之前提到的 health 端点只能判断最基本的情况至于更多细节还需要获取详细的度量指标 metrics 端点就是用来提供系统度量信息的。 5.2.1 Micrometer 概述 从 2.0 版本开始Spring Boot 就把 Micrometer 作为默认的系统度量指标获取途径了因此本节也先从 Micrometer 讲起。 Java 开发者应该都很熟悉 SLF4JSimple Logging Facade for Java它提供了一套日志框架的抽象屏蔽了底层不同日志框架比如 Commons Logging、Log4j 和 Logback实现上的差异让开发者能以统一的方式在代码中打印日志。如果说 Micrometer 的目标就是成为度量界的 SLF4J相信大家就能理解 Micrometer 是干什么的了。 Micrometer 为很多主流的监控系统提供了一套简单且强大的客户端门面先是定义了一套 SPIService Provider Interface再为不同的监控系统提供实现。接入 Micrometer 后开发者通过 Micrometer 进行埋点时就不会被绑定在某个特定的监控系统上。它支持的监控系统以及这些系统的特性如表 5-7 所示。 表 5-7 Micrometer 支持的监控系统清单 监控系统是否支持多维度数据聚合方式数据获取方式AppOptics是客户端聚合客户端推Atlas是客户端聚合客户端推Azure Monitor是客户端聚合客户端推Cloudwatch是客户端聚合客户端推Datadog是客户端聚合客户端推Datadog StatsD是客户端聚合服务端拉Dynatrace是客户端聚合客户端推Elastic是客户端聚合客户端推Etsy StatsD否客户端聚合服务端拉Ganglia否客户端聚合客户端推Graphite否客户端聚合客户端推Humio是客户端聚合客户端推Influx是客户端聚合客户端推JMX否客户端聚合客户端推KairosDB是客户端聚合客户端推New Relic是客户端聚合客户端推Prometheus是服务端聚合服务端拉SignalFx是客户端聚合客户端推Sysdig StatsD是客户端聚合服务端拉Telegraf StatsD是客户端聚合服务端拉Wavefront是服务端聚合客户端推 Micrometer 通过 Meter 接口来收集系统的度量数据由 MeterRegistry 来创建并管理 MeterMicrometer 支持的各种监控系统都有自己的 MeterRegistry 实现。内置的 Meter 实现分为几种具体如表 5-8 所示。 表 5-8 几种主要的 Meter 实现 Meter 类型说明Timer计时器用来记录一个事件的耗时Counter计数器用来表示一个单调递增的值Gauge计量仪用来表示一个变化的值通常能用 Counter 就不用 GaugeDistributionSummary分布统计用来记录事件的分布情况可以设置一个范围获取范围内的直方图和百分位数LongTaskTimer长任务计时器记录一个长时间任务的耗时可以记录已经耗费的时间FunctionCounter函数计数器追踪某个单调递增函数的计数器FunctionTimer函数计时器追踪两个单调递增函数一个计数另一个计时 要创建 Meter既可以通过 MeterRegistry 上的方法例如 registry.timer(foo)也可以通过 Fluent 风格的构建方法例如 Timer.builder(foo).tags(bar).register(registry)。 Meter 的命名采用以 . 分隔的全小写单词组合不同的监控系统功能有不同的命名方式Micrometer 会负责将 Meter 的名称转为合适的方式在官方文档中就给出了这样一个例子 registry.timer(http.server.requests);在使用 Prometheus 时这个 Timer 的名字就会被转为 http_server_requests_duration_seconds。 标签也遵循一样的命名方式Micrometer 同样也会负责帮我们将 Timer 的标签转换成不同监控系统所推荐的名称。下面的标签名为 uri值为 /api/orders registry.timer(http.server.requests, uri, /api/orders);针对通用的标签Micrometer 还贴心地提供了公共标签的功能在 MeterRegistry 上设置标签 registry.config().commonTags(prod, region, cn-shanghai-1);5.2.2 常用度量指标 Spring Boot Actuator 中提供了 metrics 端点通过 /actuator/metrics 我们可以获取系统的度量值。而且 Spring Boot 还内置了很多实用的指标可以直接拿来使用。 首先介绍的是 Micrometer 本身支持的 JVM 相关指标具体见表 5-9。 表 5-9 Micrometer 支持的 JVM 度量指标 度量指标说明ClassLoaderMetrics收集加载和卸载的类信息JvmMemoryMetrics收集 JVM 内存利用情况JvmGcMetrics收集 JVM 的 GC 情况ProcessorMetrics收集 CPU 负载情况JvmThreadMetrics收集 JVM 中的线程情况 用下面的语句就能绑定一个 ClassLoaderMetrics new ClassLoaderMetrics().bindTo(registry);但在 Spring Boot Actuator 的帮助下我们无须自己来绑定这些度量指标Spring Boot 中 JvmMetricsAutoConfiguration 之类的自动配置类已经替我们做好了绑定的工作。此外它还在此基础上提供了 Spring MVC、Spring WebFlux、HTTP 客户端和数据源等其他度量指标。 仍然以 5.1.4 节的 binarytea-endpoint 为例我们在 application.properties 中做些修改将 metrics 端点加入 Web 可访问的端点中 management.endpoints.web.exposure.includehealth,info,shop,metrics启动程序后通过 http://localhost:8080/actuator/metrics 可以看到类似下面这样的一个清单其中列举的 names 就是具体的度量指标名称 {names: [http.server.requests,jvm.buffer.count,jvm.buffer.memory.used,jvm.buffer.total.capacity,jvm.classes.loaded,jvm.classes.unloaded,jvm.gc.live.data.size,jvm.gc.max.data.size,jvm.gc.memory.allocated,jvm.gc.pause,jvm.memory.max,jvm.memory.used,jvm.threads.live,jvm.threads.peak,logback.events,process.cpu.usage,process.files.max,process.start.time,process.uptime,system.cpu.usage,system.load.average.1m,tomcat.sessions.active.current,tomcat.sessions.active.max,tomcat.sessions.rejected// 列表中省略了一些内容]}在 URI 后增加具体的名称例如 http://localhost:8080/actuator/metrics/jvm.classes.loaded就能查看具体的度量信息 {availableTags: [],baseUnit: classes,description: The number of classes that are currently loaded in the Java virtual machine,measurements: [{statistic: VALUE,value: 7232.0}],name: jvm.classes.loaded}Spring MVC 默认所有基于 Spring MVC 的 Web 请求都会被记录下来通过 /actuator/metrics/http.server.requests 我们可以查看类似下面这样的输出其中包含了大量的信息比如用过的 HTTP 方法、访问过的地址、返回的 HTTP 响应码、请求的总次数和总耗时以及最大单次耗时等 {availableTags: [{ tag: exception, values: [None] },{ tag: method, values: [GET] },{tag: uri,values: [/actuator/metrics/,/actuator/metrics]},{ tag: outcome, values: [SUCCESS] },{ tag: status, values: [200] }],baseUnit: seconds,description: null,measurements: [{ statistic: COUNT, value: 5.0 },{ statistic: TOTAL_TIME, value: 0.085699938 },{ statistic: MAX, value: 0.042694752 }],name: http.server.requests}management.metrics.web.server.request.autotime.enabled 默认为 true它能自动统计所有的 Web 请求如果我们将它设置为 false则需要自己在类上或者方法上添加 Timed 来标记要统计的 Controller 方法 Timed 注解可以做些更精细的设置例如添加额外的标签计算百分位数等等 Controllerpublic class SampleController {RequestMapping(/)Timed(extraTags { region, cn-shanghai-1 }, percentiles 0.99)public String foo() {}}HTTP 客户端 当使用 RestTemplate 和 WebClient 访问 HTTP 服务时Spring Boot Actuator 提供了针对 HTTP 客户端的度量指标但这并不是自动的还需要做一些配置它们才能生效。这里建议通过如下方式来创建 RestTemplate Beanpublic RestTemplate restTemplate(RestTemplateBuilder builder) {return builder.build();}使用 RestTemplateBuilder 来构建 RestTemplate 时会对其应用所有配置在 Spring 上下文里的 RestTemplateCustomizer而其中就有与度量相关的 MetricsRestTemplateCustomizer。针对 WebClient 也是一样的可以用 WebClient.Builder。 如果大家有通过 RestTemplate 访问过 HTTP 服务访问 /actuator/metrics/http.client.requests 后就能看到类似下面这样的输出 {availableTags: [{ tag: method, values: [ GET ] },{ tag: clientName, values: [ localhost ] },{ tag: uri, values: [ /actuator/metrics ] },{ tag: outcome, values: [ SUCCESS ] },{ tag: status, values: [ 200 ] }],baseUnit: seconds,description: Timer of RestTemplate operation,measurements: [{ statistic: COUNT, value: 1.0 },{ statistic: TOTAL_TIME, value: 0.115555898 },{ statistic: MAX, value: 0.115555898 }],name: http.client.requests}数据源 对于那些使用了数据库的系统了解数据源的具体情况是非常有必要的例如当前的连接池配置的大小是什么样的有多少个连接处于活跃状态等。如果连接池经常被占满导致业务代码无法获取连接那么无论是让业务线程等待连接还是等待超时后报错都可能影响业务。 只要在 Spring 上下文中存在 DataSource BeanSpring Boot Actuator 就会自动配置对应的度量指标如果我们使用的是 HikariCP还会有额外的信息。访问 /actuator/metrics 后能看到下面这些显示 {names: [hikaricp.connections,hikaricp.connections.acquire,hikaricp.connections.active,hikaricp.connections.creation,hikaricp.connections.idle,hikaricp.connections.max,hikaricp.connections.min,hikaricp.connections.pending,hikaricp.connections.timeout,hikaricp.connections.usage,jdbc.connections.max,jdbc.connections.min// 省略其他无关的内容]}而具体到 /actuator/metrics/hikaricp.connections 则是这样的 {availableTags: [{ tag: pool, values: [ HikariPool-1 ] }],baseUnit: null,description: Total connections,measurements: [{ statistic: VALUE, value: 10.0 }],name: hikaricp.connections}通过这些度量指标我们可以轻松地掌握系统中数据源的大概情况在遇到问题时更好地进行决策。 5.2.3 自定义度量指标 Spring Boot Actuator 内置的度量指标可以帮助我们掌握系统的情况但光了解系统是远远不够的系统运行正常但业务指标却一路下滑的情况并不少见因此还需要针对各种业务做对应的数据埋点通过业务指标我们也可以反过来进一步了解系统的情况。 有两种绑定 Meter 的方法一般可以考虑使用后者 注入 Spring 上下文中的 MeterRegistry通过它来绑定 Meter让 Bean 实现 MeterBinder在其 bindTo() 方法中绑定 Meter。 下面让我们通过二进制奶茶店项目中的一个例子来了解一下如何自定义度量指标。 需求描述 门店开始营业后我们要频繁关注经营情况像订单总笔数、总金额、客单价等都是常见的经营指标最好能有个地方可以让经营者方便地看到这些信息。 以 5.1.4 节的 binarytea-endpoint 作为基础增加如表 5-10 所示的三个经营指标用来记录自开始营业起的经营情况相信是个经营者都关心自己的店是不是赚钱吧大家可以在本书配套示例的 ch5/binarytea-metrics 目录中找到这个例子。 表 5-10 三个经营指标 指标名称类型含义order.countCounter总订单数order.amount.sumCounter总订单金额order.amount.averageGauge客单价 除此之外为了演示 DistributionSummary 的用法我们还额外增加了一个 order.summary 的指标可以输出 95 分位的订单情况。对应的 Meter 配置与绑定代码如代码示例 5-6 所示其中绑定了四个 Meter为了方便演示客单价使用了一个整数还提供了一个新的下订单方法用来改变各 Meter 的值。 代码示例 5-6 SalesMetrics 的代码片段 Componentpublic class SalesMetrics implements MeterBinder {private Counter orderCount;private Counter totalAmount;private DistributionSummary orderSummary;private AtomicInteger averageAmount new AtomicInteger();Overridepublic void bindTo(MeterRegistry registry) {this.orderCount registry.counter(order.count, direction, income);this.totalAmount registry.counter(order.amount.sum, direction, income);this.orderSummary registry.summary(order.summary, direction, income);registry.gauge(order.amount.average, averageAmount);}public void makeNewOrder(int amount) {orderCount.increment();totalAmount.increment(amount);orderSummary.record(amount);averageAmount.set((int) orderSummary.mean());}}由于现阶段还没有开发下单的客户端我们可以在 BinaryTeaApplication 这个主程序中通过 2.4.2 节中介绍的定时任务来定时下单如代码示例 5-7 所示其中金额为 0 至 100 元内的随机整数每隔 5 秒会下一单并打印日志。 代码示例 5-7 可以定时下单的主程序代码片段 SpringBootApplicationEnableSchedulingpublic class BinaryTeaApplication {private static Logger logger LoggerFactory.getLogger(BinaryTeaApplication.class);private Random random new Random();Autowiredprivate SalesMetrics salesMetrics;public static void main(String[] args) {SpringApplication.run(BinaryTeaApplication.class, args);}Scheduled(fixedRate 5000, initialDelay 1000)public void periodicallyMakeAnOrder() {int amount random.nextInt(100);salesMetrics.makeNewOrder(amount);logger.info(Make an order of RMB {} yuan., amount);}}此时通过 /actuator/metrics 我们会看到多出了如下几个指标 {names: [order.amount.average,order.amount.sum,order.count,order.summary,// 省略其他内容] }具体访问 /actuator/metrics/order.summary 则能看到类似下面这样的输出 {availableTags: [{ tag: direction, values: [ income ] }],baseUnit: null,description: null,measurements: [{ statistic: COUNT, value: 168.0 },{ statistic: TOTAL, value: 7890.0 },{ statistic: MAX, value: 95.0 }],name: order.summary}Spring Boot Actuator 中可以对 Micrometer 的度量指标做很多定制我们既可以按照 Micrometer 的官方做法用 MeterFilter 精确地进行调整也可以简单地使用配置来做些常规的改动。 例如可以像下面这样来设置公共标签将 region 标签的值设置为 cn-shanghai-1 management.metrics.tags.regioncn-shanghai-1针对每个 Meter 也有一些对应的属性如表 5-11 所示表中给出的是前缀在其后面带上具体的度量指标名称后即可有针对性地进行设置了例如 management.metrics.enable.order.amount.averagefalse。 表 5-11 部分针对单个 Meter 的属性前缀 属性前缀说明适用范围management.metrics.enable是否开启全部management.metrics.distribution.minimum-expected-value分布统计时范围的最小值Timer 与 DistributionSummarymanagement.metrics.distribution.maximum-expected-value分布统计时范围的最大值Timer 与 DistributionSummarymanagement.metrics.distribution.percentiles分布统计时希望计算的百分位值Timer 与 DistributionSummary 如果希望输出 95 分位的订单情况可以像代码示例 5-8 那样修改 application.properties 文件。 代码示例 5-8 修改后的 application.properties binarytea.readytruebinarytea.open-hours8:30-22:00management.endpoint.health.show-detailsalwaysmanagement.endpoints.web.exposure.includehealth,info,shop,metricsmanagement.metrics.distribution.percentiles.order.summary0.95配置后会多出一个 order.summary.percentile 的度量指标具体的内容大致如下所示 {availableTags: [{ tag: phi, values: [ 0.95 ] },{ tag: direction, values: [ income ] }],baseUnit: null,description: null,measurements: [{ statistic: VALUE, value: 87 }],name: order.summary.percentile}茶歇时间性能分析时的 95 线与 99 线是什么含义 说到衡量一个接口的耗时怎么样大家的第一反应大多是使用平均响应时间。的确平均耗时能代表大部分情况下接口的表现。假设一个接口的最小耗时为 4 毫秒平均耗时为 8 毫秒—看起来性能挺好的但如果经常会有那么几个请求的时间超过 1000 毫秒那我们是否应该继续去优化它呢 答案是肯定的对于这类长尾的请求我们还需要去做进一步的分析这其中必然隐藏着一些问题。在性能测试中我们往往会更多地关注 TP95 或者 TP99TP 是 Top Percentile 的缩写也就是通常所说的 95 线和 99 线指标。在 100 个请求中按耗时从小到大排序第 95 个就是耗时的 95 线95%的请求都能在这个时间内完成。 还有更苛刻的条件是要去分析 TP9999也就是 99.99%的情况这样才能确保绝大部分请求的耗时都达到要求。 5.2.4 度量值的输出 通过 /actuator/metrics 虽然可以看到每个度量值的情况但我们无法一直盯着这个 URI 看输出。在实际生产环境中我们需要一个更成熟的度量值输出和收集的方案。好在 Micrometer 和 Spring Boot 早已经考虑到了这些为我们准备好了。 输出到日志 Micrometer 提供了几个基本的 MeterRegistry其中之一就是 LoggingMeterRegistry它可以定时将系统中的各个度量指标输出到日志中。有了结构化的日志信息就能通过 ELKElasticsearch、Logstash 和 Kibana等方式将它们收集起来并加以分析。即使不做后续处理把这些日志放着作为日后回溯的材料也是可以的。 前一节的例子中在 BinaryTeaApplication 里加上代码示例 5-9 的代码用来定义一个组合的 MeterRegistry其中添加了基础的 SimpleMeterRegistry 和输出日志的 LoggingMeterRegistry。 代码示例 5-9 组合多个 MeterRegistry 的定义 Beanpublic MeterRegistry customMeterRegistry() {CompositeMeterRegistry meterRegistry new CompositeMeterRegistry();meterRegistry.add(new SimpleMeterRegistry());meterRegistry.add(new LoggingMeterRegistry());return meterRegistry;}运行后过一段时间就能在控制台输出的日志中看到类似下面的内容 2022-02-06 22:44:00.029 INFO 50342 --- [trics-publisher] i.m.c.i.logging.LoggingMeterRegistry : jvm.gc. pause throughput0.033333/s mean0.012s max0.012s 2022-02-06 22:44:00.030 INFO 50342 --- [trics-publisher] i.m.c.i.logging.LoggingMeterRegistry : order. summary throughput0.133333/s mean40.125 max84在生产环境中使用时我们可以调整日志的配置文件将 LoggingMeterRegistry 输出的日志打印到单独的日志中方便管理。 输出到 Prometheus 在介绍 Mircometer 时我们提到过它支持多种不同的监控系统将度量信息直接输出到监控系统也是一种常见做法。下面以 Prometheus 为例介绍一下它在 Spring Boot 系统中该如何操作。 首先需要在项目的 pom.xml 中添加 Micrometer 为 Prometheus 编写的 MeterRegistry 依赖有了这个依赖后面的事交给 Spring Boot 的自动配置即可 dependencygroupIdio.micrometer/groupIdartifactIdmicrometer-registry-prometheus/artifactId/dependency随后在 application.properties 中放开对对应端点的控制让 prometheus 端点可以通过 Web 访问 management.endpoints.web.exposure.includehealth,info,shop,metrics,prometheus再次运行我们的程序在浏览器中访问 /actuator/prometheus 就能看到一个文本输出Prometheus 在经过适当配置后会读取其中的内容可以看到其中的名称已经从 Micrometer 的以点分隔变为了 Prometheus 的下划线分隔这也是 Micrometer 实现的 # 省略了很多内容以下仅为片段jvm_memory_max_bytes 2.44105216E8# HELP order_count_total# TYPE order_count_total counterorder_count_total 7.0# HELP order_summary_max# TYPE order_summary_max gaugeorder_summary_max 76.0# HELP order_summary# TYPE order_summary summaryorder_summary 79.5order_summary_count 7.0order_summary_sum 347.0# HELP tomcat_sessions_active_current_sessions# TYPE tomcat_sessions_active_current_sessions gaugetomcat_sessions_active_current_sessions 0.05.3 部署 Spring Boot 应用程序 在 Spring Boot Actuator 的帮助下我们早早地就准备好了一些手段来掌握系统在运行时的各种指标现在就差临门一脚让系统在服务器上跑起来了而这也是一门学问在这一节里就让我们一同来了解一下其中的秘诀。 5.3.1 可执行 Jar 及其原理 放到以前要运行 Java EE 的应用程序需要一个应用容器比如 JBoss 或者 Tomcat。但随着技术的发展外置容器已经不再是必选项了Spring Boot 可以内嵌 Tomcat、Jetty 等容器一句简单的 java -jar 命令就能让我们的工程像个普通进程一样运行起来。 通过 Maven 打包整个工程 在用 Maven 命令打包前先来回顾一下 pom.xml 中配置的插件 buildpluginsplugingroupIdorg.springframework.boot/groupIdartifactIdspring-boot-maven-plugin/artifactId/plugin/plugins /buildspring-boot-maven-plugin 会在 Maven 的打包过程中自动介入除了生成普通的 Jar 包外还会生成一个包含所有依赖的 Fat Jar。以上一节用到的 ch5/binarytea-export 为例打开一个终端macOS 中的终端对应 Windows 中的 CMD 或者 PowerShell在工程目录中即 pom.xml 的目录键入如下命令 ▸ mvn clean package -Dmaven.test.skip随后查看 target 目录的内容大致会是下面这样的 total 38712 -rw-r--r-- 1 digitalsonic staff 19M 2 7 22:43 binarytea-0.0.1-SNAPSHOT.jar -rw-r--r-- 1 digitalsonic staff 8.3K 2 7 22:43 binarytea-0.0.1-SNAPSHOT.jar.original drwxr-xr-x 5 digitalsonic staff 160B 2 7 22:43 classes drwxr-xr-x 3 digitalsonic staff 96B 2 7 22:43 generated-sources drwxr-xr-x 3 digitalsonic staff 96B 2 7 22:43 maven-archiver drwxr-xr-x 3 digitalsonic staff 96B 2 7 22:43 maven-status其中的 binarytea-0.0.1-SNAPSHOT.jar.original 是原始的 Jar 包仅包含工程代码编译后的内容大小只有 8.3KB而 binarytea-0.0.1-SNAPSHOT.jar 则有 19MB这就是生成的可执行 Jar 包。只需简单的一条命令就能将工程运行起来 ▸ java -jar target/binarytea-0.0.1-SNAPSHOT.jar可执行 Jar 背后的原理 binarytea-0.0.1-SNAPSHOT.jar 相比 binarytea-0.0.1-SNAPSHOT.jar.original 而言简直就是一个庞然大物通过 unzip 命令查看 Jar 包内容我们可以看到它基本由以下几部分组成 META-INF工程的元数据例如 Maven 的描述文件与 spring.factories 文件org/springframework/boot/loaderSpring Boot 用来引导工程启动的 Loader 相关类BOOT-INF/classes工程自身的类与资源文件BOOT-INF/lib工程所依赖的各种其他 Jar 文件。 大概就像下面这样 ▸ unzip -l binarytea-0.0.1-SNAPSHOT.jar Archive: binarytea-0.0.1-SNAPSHOT.jarLength Date Time Name --------- ---------- ----- ----0 02-07-2022 22:43 META-INF/472 02-07-2022 22:43 META-INF/MANIFEST.MF103 02-07-2022 22:43 META-INF/spring.factories # 省略大量META-INF/下的文件0 02-01-1980 00:00 org/0 02-01-1980 00:00 org/springframework/0 02-01-1980 00:00 org/springframework/boot/0 02-01-1980 00:00 org/springframework/boot/loader/5871 02-01-1980 00:00 org/springframework/boot/loader/ClassPathIndexFile.class # 省略大量org/下的文件0 02-07-2022 22:43 BOOT-INF/0 02-07-2022 22:43 BOOT-INF/classes/ ​242 02-07-2022 22:43 BOOT-INF/classes/application.properties0 02-07-2022 22:43 BOOT-INF/classes/learning/0 02-07-2022 22:43 BOOT-INF/classes/learning/spring/ # 省略大量BOOT-INF/classes/下的文件0 02-07-2022 22:43 BOOT-INF/lib/1423985 01-20-2022 14:03 BOOT-INF/lib/spring-boot-2.6.3.jar1627754 01-20-2022 14:02 BOOT-INF/lib/spring-boot-autoconfigure-2.6.3.jar # 省略大量BOOT-INF/lib/下的文件 --------- -------19930105 141 files ​运行这个 Jar 所需要的信息都记录在了 META-INF/MANIFEST.MF 中具体内容如下所示 Manifest-Version: 1.0 Created-By: Maven JAR Plugin 3.2.2 Build-Jdk-Spec: 11 Implementation-Title: BinaryTea Implementation-Version: 0.0.1-SNAPSHOT Main-Class: org.springframework.boot.loader.JarLauncher Start-Class: learning.spring.binarytea.BinaryTeaApplication Spring-Boot-Version: 2.6.3 Spring-Boot-Classes: BOOT-INF/classes/ Spring-Boot-Lib: BOOT-INF/lib/ Spring-Boot-Classpath-Index: BOOT-INF/classpath.idx Spring-Boot-Layers-Index: BOOT-INF/layers.idxjava 命令会找到 Main-Class 作为启动类Spring Boot 提供了三种不同的 Launcher可以从内嵌文件中加载启动所需的资源 JarLauncher从 Jar 包的固定位置加载内嵌资源即 BOOT-INF/lib/WarLauncher从 War 包的固定位置加载内嵌资源分别为 WEB-INF/lib/ 和 WEB-INF/lib-provided/PropertiesLauncher 默认从 BOOT-INF/lib/ 加载资源但可以通过环境变量来指定额外的位置。 默认生成的工程会使用 JarLauncher 生成可执行 Jar 包而启动后执行的具体代码则由 StartClass 指定我们可以看到这个类就是添加了 SpringBootApplication 注解的类。 用这种方式打包有两个 局限。 Jar 文件其实就是一个 ZIP 文件ZIP 文件可以设置压缩力度从仅存储不压缩到最大化压缩在内嵌 Jar 的情况下我们只能使用 ZipEntry.STORED即仅存储不压缩的方式。要获取 ClassLoader 时必须使用 Thread.getContextClassLoader()而不能使用 ClassLoader.getSystemClassLoader()。 Spring Boot 中还有一种更激进的方式生成可以直接在 Linux 中运行的 Jar 文件不再需要 java -jar 命令当然这个文件中不包含 JRE具体修改 spring-boot-maven-plugin 配置的方式如下 buildpluginsplugingroupIdorg.springframework.boot/groupIdartifactIdspring-boot-maven-plugin/artifactIdconfigurationexecutabletrue/executable/configuration/plugin/plugins /build这样通过 mvn package 打出的 Jar 包会更特殊一些用文本编辑器或者 less 命令查看这个文件我们会发现这个文件的头部其实是个 Shell 脚本后面才是压缩的 Jar 包内容。这背后的“魔法”正是 利用了 Shell 脚本是从前往后解析而 Jar 文件则是从后往前解析的特性。 现在要运行这个文件只需简单的 ./target/binarytea-0.0.1-SNAPSHOT.jar 就可以了。对应的一些类似 JVM 参数和运行的参数可以放在同名的 .conf 配置文件中一些基本的执行配置项见表 5-12。 表 5-12 一些基本的执行配置项 配置项说明CONF_FOLDER.conf 文件的目录位置因为要靠它加载配置文件所以这个参数必须放在环境变量里JAVA_OPTS启动 Java 程序使用的 JVM 参数比如 JVM 内存和 GC 相关的参数RUN_ARGS运行程序所需提供的运行时参数 这样一来就可以把这个 Jar 文件放在 init.d 或 systemd 里运行了但不得不说相比 java -jar这种用法还是很少见的。此外更新的技术出现了不仅能代替这种方式而且还带来了更好的体验这就是 GraalVM。GraalVM 能把 Java 程序编译为 Linux 二进制系统启动速度更快所占内存更小。 5.3.2 构建启动代码 在之前的例子中我们已经多次看到过带有 SpringBootApplication 注解的主类其中用 SpringApplication.run() 方法来运行我们的代码。Spring Boot 为我们预留了很多自定义工程启动的扩展点比如可以设置启动的各种参数能针对失败进行各种处理、还能定制自己的 Banner 栏等。 自定义 SpringApplication 通过 Spring Initializr 生成的工程其中的 main() 方法应该是下面这样的它可以满足绝大部分的需求 public static void main(String[] args) {SpringApplication.run(BinaryTeaApplication.class, args); }我们也完全可以自己新建一个 SpringApplication 对象设置各种属性例如 bannerMode 和 lazyInitialization 等随后再调用它的 run() 方法。 当然这样的操作略显繁琐Spring Boot 贴心地提供了一个 SpringApplicationBuilder 构造器通过它我们可以流畅地编写类似代码示例 5-10 这样的代码。 代码示例 5-10 通过 SpringApplicationBuilder 启动应用程序 public static void main(String[] args) {new SpringApplicationBuilder().sources(BinaryTeaApplication.class).main(BinaryTeaApplication.class).bannerMode(Banner.Mode.OFF).web(WebApplicationType.SERVLET).run(args); }上面的一些设置代码也可以改用配置文件的方式在 application.properties 或 application.yml 里进行设置例如在 application.properties 里可以像下面这样来关闭 Web 容器和 Banner 栏 spring.main.web-application-typenone spring.main.banner-modeoff通过 FailureAnalyzer 提供失败原因分析 程序启动时如果遇到异常就会退出这时需要查看日志分析原因那有没有可能让系统自己分析原因然后告诉我们为什么失败了呢答案是肯定的因为 Spring Boot 可以通过 FailureAnalyzer 来分析失败并打印分析出的原因。表 5-13 罗列了一些内置的 FailureAnalyzer 实现类Spring Boot 内置了近 20 种不同的分析器表 5-13 里展示的只是其中的一小部分。 表 5-13 Spring Boot 的部分内置 FailureAnalyzer 实现类 FailureAnalyzer 实现类功能BindFailureAnalyzer提示属性绑定相关异常DataSourceBeanCreationFailureAnalyzer提示数据源创建相关异常InvalidConfigurationPropertyNameFailureAnalyzer提示配置属性名不正确NoSuchBeanDefinitionFailureAnalyzer提示 Spring 上下文中找不到需要的 Bean 定义NoUniqueBeanDefinitionFailureAnalyzer提示要注入一个 Bean但实际却找到了不止一个 我们也可以根据实际情况提供自己的 FailureAnalyzer 实现类。方便起见Spring Boot 提供了一个 AbstractFailureAnalyzerT extends Throwable 抽象类其中的泛型 T 就是要分析的异常实现这个抽象类会更简单框架内置的大部分实现都是基于这个抽象类来开发的。我们以 Spring Boot 的 PortInUseFailureAnalyzer 为例看到传入待分析的异常是 PortInUseException 就能知道端口已经被占用了从而直观地提示 Web 服务器端口已被占用 class PortInUseFailureAnalyzer extends AbstractFailureAnalyzerPortInUseException {Overrideprotected FailureAnalysis analyze(Throwable rootFailure, PortInUseException cause) {return new FailureAnalysis(Web server failed to start. Port cause.getPort() was already in use.,Identify and stop the process thats listening on port cause.getPort() or configure this application to listen on another port., cause);} }自定义 Banner 栏 启动程序时默认会打出 Spring 的字样这是由 ASCII 字符组成的图案看起来非常的酷炫。我们可以通过 spring.main.banner-mode 属性来控制 Banner 的输出方式 Banner.Mode.OFF属性值为 off关闭输出Banner.Mode.CONSOLE属性值为 console输出到控制台即标准输出 STDOUTBanner.Mode.LOG属性值为 log输出到日志。 如果要自定义输出的内容可以在 CLASSPATH 中放置一个 banner.txt也可以通过 spring.banner.location 指定文件位置文件中除了 ASCII 图案还可以写一些占位符例如可以从 MANIFEST.MF 文件中获取一些信息 $ 对应 Implementation-Title$ 对应 Implementation-Version$ 同样对应 Implementation-Version但会添加 v 前缀再将版本放在括号内。 当然如果还希望做得更彻底我们可以直接写一个自己的 org.springframework.boot.Banner 接口实现在其中的 printBanner() 方法里打印 Banner。 茶歇时间如何优雅地关闭系统 系统有启动自然就会有关闭的时候。如果说异常宕机时损失一个实例对整个集群的服务造成部分影响还情有可原那常规的系统升级发布就不应该对整体服务有任何影响。 在我们打算关闭系统时可能会遇到如下的情况不仅限于此 系统仍在接收同步请求例如在处理 HTTP 请求系统仍在消费消息例如在消费 Kafka 消息系统有定时任务在运行可能是分布式调度也可能是单机的调度任务。 当系统处于处理中的状态时直接关闭系统可能会影响当前的处理。 为此我们要针对性地做一些处理例如将当前节点从负载均衡中剔除如果使用 Nginx 进行负载均衡就调整 upstream 的配置如果是动态发现的就让健康检查失败可以使用 5.1 节中提到的 health 端点让服务注册中心将节点下线。在第 13 章中我们还会聊到服务的注册中心。 消息的消费要力争做到可重复消费因为消费本身就要考虑到消息的乱序和重发等情况。如果一条消息消费到一半进程被杀掉了那么消息中间件会认为该消息未被正常处理会再重新发送。当然我们也可以做些优化在停止进程前不再接收新消息针对拉PULL模式的客户端不再从服务端拉取新消息推PUSH模式的则不再处理新消息等待或拒绝也不失为一个好办法。 对调度任务也是一样的任务需要能够支持“重跑”高频任务不必多说低频任务万一被中断需要有补偿机制能够快速恢复。例如分布式调度诸如 ElasticJob 之类的调度可以设置故障转移。 Spring Boot 提供了优雅关闭的能力通过 server.shutdowngraceful 这个配置可以开启 Web 服务器优雅关闭的支持让系统在收到关闭信号时等待一段时间通过 spring.lifecycle.timeout-per-shutdown-phase 设置以便等待当前正在处理的请求处理结束。但通过前面的描述我们不难发现仅提供这些能力并不足以在生产环境中做到无损的节点下线还有大量的工作需要我们自己来实现。 5.3.3 启动后的一次性执行逻辑 在系统启动时可能会有些初始化后需要立即执行的逻辑也有可能这就是一个命令行程序执行完特定逻辑后程序就该退出了。这时我们该怎么办写一个 Bean在它的 PostConstruct 方法中执行逻辑么其实在 Spring Boot 中有更好的选择。 Spring Boot 为我们提供了两个接口分别是 ApplicationRunner 与 CommandLineRunner它们的功能基本是相同的只是方法的参数不同具体如下所示 public interface ApplicationRunner {void run(ApplicationArguments args) throws Exception; }public interface CommandLineRunner {void run(String... args) throws Exception; }CommandLineRunner 的 run(String... args) 传入的参数与 main(String... args) 方法一样就是命令行的所有参数。而通过 ApplicationArguments我们可以更方便灵活地控制命令行中的参数。如果 Spring 上下文中存在多个 ApplicationRunner 或 CommandLineRunner Bean可以通过 Order 注解或 Ordered 接口来指定运行的顺序。接下来让我们看个例子。 需求描述 有奶茶店自然就会有顾客每次都让顾客跑到门店找服务员点单多少有些不方便我们需要一个程序让顾客可以自己下单。所以为顾客开发一个程序吧。 为了方便演示建立一个新的 Customer 工程代表二进制奶茶店的顾客放在 ch5/customer 项目中其详细信息如表 5-14 所示。 表 5-14 Customer 工程的详细信息 条目内容项目Maven Project语言JavaSpring Boot 版本2.6.3Grouplearning.springArtifactcustomer名称CustomerJava 包名learning.spring.customer打包方式JarJava 版本11依赖Lombok 这个项目会模拟顾客的操作目前我们的奶茶店还没开门营业可以让顾客选择等待奶茶店开门还是直接离开。 代码示例 5-11 是一个 CommandLineRunner 的实现它的作用是打印所有的命令行参数其中的日志输出通过 Lombok 的 Slf4j 指定了使用 SLF4J 日志框架无须我们自己定义 log 成员变量。 代码示例 5-11 ArgsPrinterRunner 代码片段 Component Slf4j Order(1) public class ArgsPrinterRunner implements CommandLineRunner {Overridepublic void run(String... args) throws Exception {log.info(共传入了{}个参数。分别是{}, args.length, StringUtils.arrayToCommaDelimitedString(args));} }代码示例 5-12 是一个 ApplicationRunner 的实现它会根据命令行上传入的参数来决定是否等待如果我们通过 wait 选项设置了等待时间则等待时间即为程序里 sleep 对应的秒数没有 wait 就直接结束。其中演示了几个方法 containsOption() 是否包含指定选项所谓选项其形式是 -- 选项名 值getOptionValues() 获取指定选项的值返回的是一个 List因为可以多次设值例如 --wait5--wait6getNonOptionArgs() 获取非选项类型的其他参数。 代码示例 5-12 WaitForOpenRunner 代码片段 Component Slf4j Order(2) public class WaitForOpenRunner implements ApplicationRunner {Overridepublic void run(ApplicationArguments args) throws Exception {boolean needWait args.containsOption(wait);if (!needWait) {log.info(如果没开门就不用等了。);return;}ListString waitSeconds args.getOptionValues(wait);if (!waitSeconds.isEmpty()) {int seconds NumberUtils.parseNumber(waitSeconds.get(0), Integer.class);log.info(还没开门先等{}秒。, seconds);Thread.sleep(seconds * 1000);}log.info(其他参数{},StringUtils.collectionToCommaDelimitedString(args.getNonOptionArgs()));} }使用 mvn clean package -Dmaven.test.skip 命令打包后通过如下命令启动程序 ▸ java -jar target/customer-0.0.1-SNAPSHOT.jar --wait3 Hello运行的结果应该与下面的输出类似 INFO 85578 --- [main] l.spring.customer.CustomerApplication : Started CustomerApplication in 1.059 seconds (JVM running for 1.482) INFO 85578 --- [main] l.spring.customer.ArgsPrinterRunner : 共传入了2个参数。分别是--wait3,Hello INFO 85578 --- [main] l.spring.customer.WaitForOpenRunner : 准备等待3秒。 INFO 85578 --- [main] l.spring.customer.WaitForOpenRunner : 其他参数Hello程序退出时可以指定一个退出码在 Linux 或 macOS 操作系统中可以用 echo $? 命令看到上一条命令的退出码。通常退出码 0 表示 正常结束其他的退出码都表示 非正常结束。在 Shell 脚本中往往都会根据某条命令的退出码决定后续的动作。 在 Java 里可以调用 System.exit() 退出程序这个方法能够传入需要返回的退出码。Spring Boot 为我们提供了 ExitCodeGenerator 接口通过实现该接口我们可以加入自己的逻辑来控制退出码如果存在多个 Bean可以和前文一样用 Order 注解或 Ordered 接口来控制顺序。调用 SpringApplication.exit() 方法即可获得最终计算出的退出码把它传入 System.exit() 就可以了。 作为经营者我们当然是希望顾客来自己店里而且如果顾客愿意等我们开门最好了为此我们编写一个自己的 ExitCodeGenerator 实现命令行里提供了 wait 选项则视为正常否则不正常具体代码如代码示例 5-13 所示。 代码示例 5-13 直接在 Bean 方法中实现 ExitCodeGenerator SpringBootApplication public class CustomerApplication {public static void main(String[] args) {SpringApplication.run(CustomerApplication.class, args);}/*** 如果命令行里给了wait选项返回0否则返回1*/Beanpublic ExitCodeGenerator waitExitCodeGenerator(ApplicationArguments args) {return () - (args.containsOption(wait) ? 0 : 1);} }框架会自动把上下文中的 ApplicationArguments 作为参数传入 waitExitCodeGenerator()这里取到的值和我们在 ApplicationRunner 里取到的值是一样的。 随后简单调整一下 WaitForOpenRunner在执行完日志输出后调用退出逻辑如代码示例 5-14 所示。 SpringApplication.exit() 需要传入 ApplicationContext因此我们让 WaitForOpenRunner 实现 ApplicationContextAware这里的 Setter 方法直接通过 Lombok 的 Setter 来实现。 代码示例 5-14 调整后的 WaitForOpenRunner 代码片段 Component Slf4j Order(2) public class WaitForOpenRunner implements ApplicationRunner, ApplicationContextAware {Setterprivate ApplicationContext applicationContext;Overridepublic void run(ApplicationArguments args) throws Exception {boolean needWait args.containsOption(wait);if (!needWait) {log.info(如果没开门就不用等了。);} else {ListString waitSeconds args.getOptionValues(wait);if (!waitSeconds.isEmpty()) {int seconds NumberUtils.parseNumber(waitSeconds.get(0), Integer.class);log.info(还没开门先等{}秒。, seconds);Thread.sleep(seconds * 1000);}log.info(其他参数{}, StringUtils.collectionToCommaDelimitedString(args.getNonOptionArgs()));}System.exit(SpringApplication.exit(applicationContext));} }现在重新打包运行我们的程序如果命令行里没有 --wait退出码就是 1。 茶歇时间通过 Lombok 简化代码 相信只要是写过 Java 的人都会写过 Getter 和 Setter 方法虽然绝大多数人会选择让 IDE 来自动生成但本着“懒惰是程序员的第一大美德”的理念能不要这些代码我们还是希望就不出现这些代码。与之类似的还有简单的用于成员变量赋值的构造方法、 Logger 对象的定义语句等。 Lombok 就是这样一个解放生产力的利器它通过一系列注解消灭了上述冗长繁琐的语句。常用的一些注解如表 5-15 所示。 表 5-15 常用的 Lombok 注解 注解作用Getter / Setter自动生成成员属性的 Getter 和 Setter 方法ToString自动生成 toString() 方法默认拼接所有的成员属性也可以排除指定的属性NoArgsConstructor / RequiredArgsConstructor / AllArgsConstructor自动生成无参数的构造方法必要参数的构造方法以及包含全部参数的构造方法EqualsAndHashCode自动生成 equals() 与 hashCode() 方法Data相当于添加了 ToString、 EqualsAndHashCode、 Getter、 Setter 和 RequiredArgsConstructor 注解Builder提供了一个灵活的构造器能够设置各个成员变量再据此创建对象实例Slf4j / CommonsLog / Log4j2自动生成对应日志框架的日志类例如定义了一个 Logger 类型的 log方便输出日志 需要注意的是虽然编译时没有什么特殊设置但在 IDE 中为了开启对 Lombok 的支持我们需要安装对应 IDE 的 Lombok 插件例如IDEA 中要安装 IntelliJ Lombok plugin。 5.4 小结 本章我们学习了 Spring Boot 提供的面向生产环境的诸多功能例如Spring Boot Actuator 的各种端点如何自己定制健康检查信息。还学习了生产环境中所必不可少的度量方法通过 Micrometer 输出各种内容帮助我们了解系统的运行情况。最后把开发好的代码打包成了实际可以部署的 Jar 包Spring Boot 的 Jar 包可以直接执行我们对这背后的原理也做了说明。除此之外还聊了聊怎么编写用来启动整个工程的 SpringAppliation 代码控制整个启动和运行的逻辑。 在代码示例中我们第一次引入了 Lombok它可以在很大程度上简化我们的代码在日常工作中也强烈建议大家使用。 专栏的第一部分到此就告一段落了我们学习了 Spring Framework 与 Spring Boot 的一些基础知识和用法从下一章开始我们将进入新的环节编写与数据库交互的系统。 二进制奶茶店项目开发进度 本章我们为二进制奶茶店的程序增加了一些面向生产环境的功能 添加了可在运行时了解营业情况的健康检查项和端点添加了可获得累计营收情况的监控项。 此外我们还初始化了代表顾客的 Customer 工程它目前可以接受一些命令行参数判断是否等待店铺开门。
http://www.hkea.cn/news/14562834/

相关文章:

  • 外贸网站建设ppt模板怎么做自己的购物网站
  • 美食网站页面设计源代码快速建站公司有哪些
  • 程序员做的导航网站制作网页的基本技术标准
  • 美词原创网站建设做网站前期预算
  • 手机做点击赚钱的网站网线制作实训报告心得体会
  • 南京中天园林建设网站北京装修公司十大排名
  • 怎么用ps做购物网站免费开发网站大全
  • 如何自建网站做淘客南宁优质手机网站建设公司
  • 哪个网站有手机运营最好的网站
  • 建设网站有哪些农村电商网站有哪些
  • 网站建设和管理情况顺义建站设计
  • 怎么找做网站的网站前端设计要做什么
  • 杭州网站建站公司网站开发招标前提
  • 17网站一起做网店登录品牌推广方案思维导图
  • 手机网站怎么做的软件开发培训学校排名又简单又紧
  • 网站建设 2018md短视频传媒免费版怎么下载
  • 外包网站建设是什么意思模板网站建设的公司
  • 做h5商城网站宁夏交通建设质监局官方网站
  • 衡阳做网站优化网站制作的主要技术
  • 企业网站建设一条页面设计工资有多少
  • 确定网站推广目标搜索引擎优化心得体会
  • 静态网站如何共用一个头部和尾部企业网站建设多少钱
  • 网站建设公司薪酬淘宝官网网页版
  • 网站建站工具购买空间安装wordpress
  • asp装修网站源码旅游电子商务网站建设目的
  • 网站建设指导思想广州哪家网站建设公司好
  • 完全免费建站系统软件开发app制作公司
  • 网站建设的实验报告做外贸网站卖什么东西好
  • 上海做网站的公司哪个好网站项目管理系统
  • 阜阳企业做网站wordpress固定链自定义结构