微服务领域Spring Boot自动伸缩的实现方法

前言

自动伸缩是每个人都想要的,尤其是在微服务领域。让我们看看如何在基于Spring Boot的应用程序中实现。

我们决定使用Kubernetes、Pivotal Cloud Foundry或HashiCorp's Nomad等工具的一个更重要的原因是为了让系统可以自动伸缩。当然,这些工具也提供了许多其他有用的功能,在这里,我们只是用它们来实现系统的自动伸缩。乍一看,这似乎很困难,但是,如果我们使用Spring Boot来构建应用程序,并使用Jenkins来实现CI,那么就用不了太多工作。

今天,我将向您展示如何使用以下框架/工具实现这样的解决方案:

  • Spring Boot
  • Spring Boot Actuator
  • Spring Cloud Netflix Eureka
  • Jenkins CI

它是如何工作的

每一个包含Spring Boot Actuator库的Spring Boot应用程序都可以在/actuator/metrics端点下公开metric。许多有价值的metric都可以提供应用程序运行状态的详细信息。在讨论自动伸缩时,其中一些metric可能特别重要:JVM、CPU metric、正在运行的线程数和HTTP请求数。有专门的Jenkins流水线通过按一定频率轮询/actuator/metrics 端点来获取应用程序的指标。如果监控的任何metric【指标】低于或高于目标范围,则它会启动新实例或使用另一个Actuator端点/actuator/shutdown来关闭一些正在运行的实例。在此之前,我们需要知道当前有那些实践在提供服务,只有这样我们才能在需要的时候关闭空闲的实例或启动新的新例。

在讨论了系统架构之后,我们就可以继续开发了。这个应用程序需要满足以下要求:它必须有公开的可以优雅地关闭应用程序和用来获取应用程序运行状态metric【指标】的端点,它需要在启动完成的同时就完成在Eureka的注册,在关闭时取消注册,最后,它还应该能够从空闲端口池中随机获取一个可用的端口。感谢Spring Boot,只需要约五分钟,我们可以轻松地实现所有这些机制。

动态端口分配

由于可以在一台机器上运行多个应用程序实例,所以我们必须保证端口号不冲突。幸运的是,Spring Boot为应用程序提供了这样的机制。我们只需要将application.yml中的server.port属性设置为0。因为我们的应用程序会在Eureka中注册,并且发送唯一的标识instanceId,默认情况下这个唯一标识是将字段spring.cloud.client.hostname, spring.application.name和server.port拼接而成的。

示例应用程序的当前配置如下所示。

可以看到,我通过将端口号替换为随机生成的数字来改变了生成instanceId字段值的模板。

spring:
 application:
 name: example-service
server:
 port: ${PORT:0}
eureka:
 instance:
 instanceId: ${spring.cloud.client.hostname}:${spring.application.name}:${random.int[1,999999]}

启用Actuator的Metric

为了启用Spring Boot Actuator,我们需要将下面的依赖添加到pom.xml。

<dependency>
 <groupId>org.springframework.boot</groupId>
 <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

我们还必须通过HTTP API将属性management.endpoints.web.exposure.include设置为'*'来暴露Actuator的端点。现在,所有可用的指标名称列表都可以在/actuator/metrics端点中找到,每个指标的详细信息可以通过/actuator/metrics/{metricName}端点查看。

优雅地停止应用程序

除了查看metric端点外,Spring Boot Actuator还提供了停止应用程序的端点。然而,与其他端点不同的是,缺省情况下,此端点是不可用的。我们必须把management.endpoint.shutdown.enabled设为true。在那之后,我们就可以通过发送一个POST请求到/actuator/shutdown端点来停止应用程序了。

这种停止应用程序的方法保证了服务在停止之前从Eureka服务器注销。

启用Eureka自动发现

Eureka是最受欢迎的发现服务器,特别是使用Spring Cloud来构建微服务的架构。所以,如果你已经有了微服务,并且想要为他们提供自动伸缩机制,那么Eureka将是一个自然的选择。它包含每个应用程序注册实例的IP地址和端口号。为了启用Eureka客户端,您只需要将下面的依赖项添加到pom.xml中。

<dependency>
 <groupId>org.springframework.cloud</groupId>
 <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>

正如之前提到的,我们还必须保证通过客户端应用程序发送到Eureka服务器的instanceId的唯一性。在“动态端口分配”中已经描述了它。

下一步需要创建一个包含内嵌Eureka服务器的应用程序。为了实现这个功能,首先我们需要在pom.xml中添加下面这个依赖:

<dependency>
 <groupId>org.springframework.cloud</groupId>
 <artifactId>spring-cloud-starter-netflix-eureka-server</artifactId>
</dependency>

这个main类需要添加@EnableEurekaServer注解。

@SpringBootApplication
@EnableEurekaServer
public class DiscoveryApp {
 public static void main(String[] args) {
  new SpringApplicationBuilder(DiscoveryApp.class).run(args);
 }
}

默认情况下,客户端应用程序尝试使用8761端口连接Eureka服务器。我们只需要单独的、独立的Eureka节点,因此我们将禁用注册,并尝试从另一个Eureka服务器实例中获取服务列表。

spring:
 application:
 name: discovery-service
server:
 port: ${PORT:8761}
eureka:
 instance:
 hostname: localhost
 client:
 registerWithEureka: false
 fetchRegistry: false
 serviceUrl:
  defaultZone: http://localhost:8761/eureka/

我们将使用Docker容器来测试上面的自动伸缩系统,因此需要使用Eureka服务器来准备和构建image。

Dockerfile和image的定义如下所示。

我们可以使用命令docker build -t piomin/discovery-server:2.0来进行构建。

FROM openjdk:8-jre-alpine
ENV APP_FILE discovery-service-1.0-SNAPSHOT.jar
ENV APP_HOME /usr/apps
EXPOSE 8761
COPY target/$APP_FILE $APP_HOME/
WORKDIR $APP_HOME
ENTRYPOINT ["sh", "-c"]
CMD ["exec java -jar $APP_FILE"]

为弹性伸缩构建一个Jenkins流水线

第一步是准备Jenkins流水线,负责自动伸缩。我们将创建Jenkins声明式流水线,它每分钟运行一次。可以使用triggers指令配置执行周期,它定义了自动化触发流水线的方法。我们的流水线将与Eureka服务器和每个使用Spring Boot Actuator的微服务中公开的metric端点进行通信。

测试服务的名称是EXAMPLE-SERVICE,它和定义在application.yml文件spring.application.name的属性值(大写字母)相同。被监控的metric是运行在Tomcat容器中的HTTP listener线程数。这些线程负责处理客户端的HTTP请求。

pipeline {
 agent any
 triggers {
  cron('* * * * *')
 }
 environment {
  SERVICE_NAME = "EXAMPLE-SERVICE"
  METRICS_ENDPOINT = "/actuator/metrics/tomcat.threads.busy?tag=name:http-nio-auto-1"
  SHUTDOWN_ENDPOINT = "/actuator/shutdown"
 }
 stages { ... }
}

使用Eureka整合Jenkins流水线

流水线的第一个阶段负责获取在discovery服务器上注册的服务列表。Eureka发现了几个HTTP API端点。其中一个是GET /eureka/apps/{serviceName},它返回一个给定服务名称的所有活动实例列表。我们正在保存运行实例的数量和每个实例metric端点的URL。这些值将在流水线的下一个阶段中被访问。

下面的流水线片段可以用来获取活动应用程序实例列表。stage名称是Calculate。我们使用HTTP请求插件 来发起HTTP连接。

stage('Calculate') {
 steps {
 script {
 def response = httpRequest "http://192.168.99.100:8761/eureka/apps/${env.SERVICE_NAME}"
 def app = printXml(response.content)
 def index = 0
 env["INSTANCE_COUNT"] = app.instance.size()
 app.instance.each {
 if (it.status == 'UP') {
  def address = "http://${it.ipAddr}:${it.port}"
  env["INSTANCE_${index++}"] = address
 }
 }
 }
 }
}
@NonCPS
def printXml(String text) {
 return new XmlSlurper(false, false).parseText(text)
}

下面是Eureka API对我们的微服务的示例响应。响应content-type是XML。

使用Spring Boot Actuator整合Jenkins流水线

Spring Boot Actuator使用metric来公开端点,这使得我们可以通过名称和选择性地使用标签找到metric。在下面可见的流水线片段中,我试图找到metric低于或高于阈值的实例。如果有这样的实例,我们就停止循环,以便进入下一个阶段,它执行向下或向上的伸缩。应用程序的IP地址是从带有INSTANCE_前缀的流水线环境变量获取的,这是在前一阶段中被保存了下来的。

stage('Metrics') {
steps {
script {
def count = env.INSTANCE_COUNT
for(def i=0;i 100)
return "UP"
else if (value.toInteger() < 20)
return "DOWN"
else
return "NONE"
}

关闭应用程序实例

在流水线的最后一个阶段,我们将关闭运行的实例,或者根据在前一阶段保存的结果启动新的实例。通过调用Spring Boot Actuator端点可以很容易执行停止操作。在接下来的流水线片段中,首先选择了Eureka实例。然后我们将发送POST请求到那个ip地址。

如果需要扩展应用程序,我们将调用另一个流水线,它负责构建fat JAR并让这个应用程序在机器上跑起来。

stage('Scaling') {
 steps {
 script {
 if (env.SCALE_TYPE == 'DOWN') {
 def ip = env["INSTANCE_0"] + env.SHUTDOWN_ENDPOINT
 httpRequest url: ip, contentType: 'APPLICATION_JSON', httpMode: 'POST'
 } else if (env.SCALE_TYPE == 'UP') {
 build job: 'spring-boot-run-pipeline'
 }
 currentBuild.description = env.SCALE_TYPE
 }
 }
}

下面是spring-boot-run-pipeline流水线的完整定义,它负责启动应用程序的新实例。它先从git仓库中拉取源代码,然后使用Maven命令编译并构建二进制的jar文件,最后通过在java -jar命令中添加Eureka服务器地址来运行应用程序。

pipeline {
 agent any
 tools {
  maven 'M3'
 }
 stages {
  stage('Checkout') {
   steps {
    git url: 'https://github.com/piomin/sample-spring-boot-autoscaler.git', credentialsId: 'github-piomin', branch: 'master'
   }
  }
  stage('Build') {
   steps {
    dir('example-service') {
     sh 'mvn clean package'
    }
   }
  }
  stage('Run') {
   steps {
    dir('example-service') {
     sh 'nohup java -jar -DEUREKA_URL=http://192.168.99.100:8761/eureka target/example-service-1.0-SNAPSHOT.jar 1>/dev/null 2>logs/runlog &'
    }
   }
  }
 }
}

扩展到多个机器

在前几节中讨论的算法只适用于在单个机器上启动的微服务。如果希望将它扩展到更多的机器上,我们将不得不修改我们的架构,如下所示。每台机器都有Jenkins代理运行并与Jenkins master通信。如果想在选定的机器上启动一个微服务的新实例,我们就必须使用运行在该机器上的代理来运行流水线。此代理仅负责从源代码构建应用程序并将其启动到目标机器上。这个实例的关闭仍然是通过调用HTTP端点来完成。

假设我们已经成功地在目标机器上启动了一些代理,我们需要对流水线进行参数化,以便能够动态地选择代理(以及目标机器)。

当扩容应用程序时,我们必须将代理标签传递给下游流水线。

build job:'spring-boot-run-pipeline', parameters:[string(name: 'agent', value:"slave-1")]

调用流水线具体由那个标签下的代理运行,是由"${params.agent}"决定的。

pipeline {
 agent {
  label "${params.agent}"
 }
 stages { ... }
}

如果有一个以上的代理连接到主节点,我们就可以将它们的地址映射到标签中。由于这一点,我们能够将从Eureka服务器获取的微服务实例的IP地址映射到与Jenkins代理的目标机器上。

pipeline {
 agent any
 triggers {
  cron('* * * * *')
 }
 environment {
  SERVICE_NAME = "EXAMPLE-SERVICE"
  METRICS_ENDPOINT = "/actuator/metrics/tomcat.threads.busy?tag=name:http-nio-auto-1"
  SHUTDOWN_ENDPOINT = "/actuator/shutdown"
  AGENT_192.168.99.102 = "slave-1"
  AGENT_192.168.99.103 = "slave-2"
 }
 stages { ... }
}

总结

在本文中,我演示了如何使用Spring Boot Actuato metric来自动伸缩Spring Boot应用程序。使用Spring Boot提供的特性以及Spring Cloud Netflix Eureka和Jenkins,您就可以实现系统的自动伸缩,而无需借助于任何其他第三方工具。本文也假设远程服务器上也是使用Jenkins代理来启动新的实例,但是您也可以使用Ansible这样的工具来启动。如果您决定从Jenkins运行Ansible脚本,那么将不需要在远程机器上启动Jenkins代理。

好了,以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对我们的支持。

(0)

相关推荐

  • Spring Boot 快速搭建微服务框架详细教程

    前言: SpringBoot是为了简化Spring应用的创建.运行.调试.部署等而出现的,使用它可以做到专注于Spring应用的开发,而无需过多关注XML的配置. 简单来说,它提供了一堆依赖打包,并已经按照使用习惯解决了依赖问题---习惯大于约定. Spring Boot默认使用tomcat作为服务器,使用logback提供日志记录. Spring Boot的主要优点: 为所有Spring开发者更快的入门 开箱即用,提供各种默认配置来简化项目配置 内嵌式容器简化Web项目 没有冗余代码生成和XM

  • springboot 打包部署 共享依赖包(分布式开发集中式部署微服务)

    1.此文初衷 平常我们在进行微服务开发完毕后,单个微服务理应部署单个虚机上(docker也可),然后服务集中发布到服务注册中心上,但是有些小的项目,这样做未免太过繁杂增加了部署难度,这里主要讲述的是如何在单机上通过共享jar包的方式来部署多个微服务,解决以上部署难度同时在带宽不够或者网速慢的情况下如何快速的发布部署. 2.部署目录结构   部署目录解答-> 各个微服务与依赖包(lib文件夹下)在同一级目录下,此为图1内容.图二内容展示的是单个微服务内的文件结构,部署配置文件以及所打的jar包,这

  • 浅谈Spring Boot 微服务项目的推荐部署方式

    如果开发过spring boot的程序,应该都知道,使用spring boot官方的maven打包插件(spring-boot-maven-plugin) 来打包,打出来的jar包一般有40M以上. 如果公司的服务器上传带宽不高,那么手动上传一个jar或者jenkins部署一次jar,都是非常痛苦的........ 但是,如果打包的时候不引入lib,那么打出来的jar包一般只有几十k而已,非常小,想怎么传就怎么传......... 本文会提供一个bash启动脚本,只需要稍做更改,即可适应你的程序

  • 微服务领域Spring Boot自动伸缩的实现方法

    前言 自动伸缩是每个人都想要的,尤其是在微服务领域.让我们看看如何在基于Spring Boot的应用程序中实现. 我们决定使用Kubernetes.Pivotal Cloud Foundry或HashiCorp's Nomad等工具的一个更重要的原因是为了让系统可以自动伸缩.当然,这些工具也提供了许多其他有用的功能,在这里,我们只是用它们来实现系统的自动伸缩.乍一看,这似乎很困难,但是,如果我们使用Spring Boot来构建应用程序,并使用Jenkins来实现CI,那么就用不了太多工作. 今天

  • spring boot 自动更新静态文件和后台代码的实例

    在spring boot使用的过程中, 发现我修改了静态文件, 前台刷新后, 没有任何变化, 必须重新启动, 才能看到, 这简直不能让人接受. 那有什么方法来解决这个问题呢? Baidu之后, 得到了想要的答案,在这里记录下来. 1. pom.xml 修改 <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-devtools</artifact

  • 详解Spring Boot自动装配的方法步骤

    在<Spring Boot Hello World>中介绍了一个简单的spring boot例子,体验了spring boot中的诸多特性,其中的自动配置特性极大的简化了程序开发中的工作(不用写一行XML).本文我们就来看一下spring boot是如何做到自动配置的. 首先阐明,spring boot的自动配置是基于spring framework提供的特性实现的,所以在本文中,我们先介绍spring framework的相关特性,在了解了这些基础知识后,我们再来看spring boot的自

  • Spring Boot 自动配置的实现

    Spring Boot 自动配置 来看下 spring boot中自动配置的注解 @SuppressWarnings("deprecation") @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented @Inherited @AutoConfigurationPackage @Import(EnableAutoConfigurationImportSelector.class) public

  • 详解redis在微服务领域的贡献

    前言 说到redis,可能大家的脑海中蹦出的关键词是:NoSQL.KV.高性能.缓存等.但今天的文章从另一个角度--微服务来展开. 这篇文章的起因也是源自一次面试经历,在面试一位来自陌陌的候选人(就是那个交友的陌陌)时,他提到一点让我觉得很有意思,他说redis在陌陌被使用的非常广泛,除了常规的缓存外,某些场景下也当NoSQL数据库来使用,还用redis作为微服务的注册中心,甚至连RPC的调用协议都用了redis协议. 注册中心 最早了解到redis可以作为注册中心是从dubbo的源码中看到,但

  • .NET微服务架构CI/CD自动构建Jenkins+Gitee

    目录 CI/CD 准备工作 安装Jenkins 添加并配置Gitee 新建工作流 CI/CD 它的意思是 持续集成/持续部署,这也不是新概念.那些八股文就不写了,说话的方式简单点:如果成功搭建CI/CD环境,当你需要迭代线上程序时,只需通过git提交代码就可以,其他什么都不用做.是不是很爽?这样你就拥有了快速迭代的能力,微服务大环境下,这也是必要的. 准备工作 1.准备一个.NET6项目: 2.准备一个gitee仓库 3.准备一台服务器(非必要) 安装Jenkins 首先在docker中运行这段

  • .NET微服务架构CI/CD自动打包镜像

    目录 准备工作 一.开启docker的tcp 二.Jenkins安装Docker插件 配置Docker 配置工作流 小结 准备工作 一.开启docker的tcp 我的服务器是linux,以端口2376为例,找到docker.service,在ExecStart下新增这段代码即可: -H tcp://0.0.0.0:2376 -H unix:///var/run/docker.sock 效果图: 然后重载服务列表,重启docker,语句: systemctl daemon-reload syste

  • Spring Boot自动配置的原理及@Conditional条件注解

    目录 1 @SpringBootApplication自动配置原理 2 @Conditional系列条件注解 1 @SpringBootApplication自动配置原理 @SpringBootApplication是一个组合注解,主要由@ComponentScan.@SpringBootConfiguration.@EnableAutoConfiguration这三个注解组成.@EnableAutoConfiguration是Spring Boot实现自动配置的关键注解. @Component

  • 详解Spring Boot中MyBatis的使用方法

    orm框架的本质是简化编程中操作数据库的编码,发展到现在基本上就剩两家了,一个是宣称可以不用写一句SQL的hibernate,一个是可以灵活调试动态sql的mybatis,两者各有特点,在企业级系统开发中可以根据需求灵活使用.发现一个有趣的现象:传统企业大都喜欢使用hibernate,互联网行业通常使用mybatis. hibernate特点就是所有的sql都用Java代码来生成,不用跳出程序去写(看)sql,有着编程的完整性,发展到最顶端就是spring data jpa这种模式了,基本上根据

  • spring boot测试打包部署的方法

    有很多网友会时不时的问我,spring boot项目如何测试,如何部署,在生产中有什么好的部署方案吗?这篇文章就来介绍一下spring boot 如何开发.调试.打包到最后的投产上线. 开发阶段 单元测试 在开发阶段的时候最重要的是单元测试了,springboot对单元测试的支持已经很完善了. 1.在pom包中添加spring-boot-starter-test包引用 <dependency> <groupId>org.springframework.boot</groupI

随机推荐