咱们决定使用Kubernetes
、Pivotal Cloud Foundry
或HashiCorp's Nomad
等工具的一个更重要的缘由是为了让系统能够自动伸缩。固然,这些工具也提供了许多其余有用的功能,在这里,咱们只是用它们来实现系统的自动伸缩。乍一看,这彷佛很困难,可是,若是咱们使用Spring Boot
来构建应用程序,并使用Jenkins
来实现CI
,那么就用不了太多工做。java
今天,我将向您展现如何使用如下框架/工具实现这样的解决方案:git
每个包含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
,只须要约五分钟,咱们能够轻松地实现全部这些机制。github
因为能够在一台机器上运行多个应用程序实例,因此咱们必须保证端口号不冲突。幸运的是,Spring Boot
为应用程序提供了这样的机制。咱们只须要将application.yml
中的server.port
属性设置为0
。由于咱们的应用程序会在Eureka
中注册,而且发送惟一的标识instanceId
,默认状况下这个惟一标识是将字段spring.cloud.client.hostname
, spring.application.name
和server.port
拼接而成的。web
示例应用程序的当前配置以下所示。
能够看到,我经过将端口号替换为随机生成的数字来改变了生成instanceId
字段值的模板。算法
spring:
application:
name: example-service
server:
port: ${PORT:0}
eureka:
instance:
instanceId: ${spring.cloud.client.hostname}:${spring.application.name}:${random.int[1,999999]}
复制代码
为了启用Spring Boot Actuator
,咱们须要将下面的依赖添加到pom.xml
。spring
<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}
端点查看。sql
除了查看metric
端点外,Spring Boot Actuator
还提供了中止应用程序的端点。然而,与其余端点不一样的是,缺省状况下,此端点是不可用的。咱们必须把management.endpoint.shutdown.enabled
设为true
。在那以后,咱们就能够经过发送一个POST
请求到/actuator/shutdown
端点来中止应用程序了。docker
这种中止应用程序的方法保证了服务在中止以前从Eureka
服务器注销。tomcat
Eureka
是最受欢迎的发现服务器,特别是使用Spring Cloud
来构建微服务的架构。因此,若是你已经有了微服务,而且想要为他们提供自动伸缩机制,那么Eureka
将是一个天然的选择。它包含每一个应用程序注册实例的IP地址和端口号。为了启用Eureka
客户端,您只须要将下面的依赖项添加到pom.xml
中。bash
<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
声明式流水线,它每分钟运行一次。可使用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 { ... }
}
复制代码
流水线的第一个阶段负责获取在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
使用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
代理。
欢迎工做一到五年的Java工程师朋友们加入Java进阶之路:878249276,群内提供免费的Java架构学习资料(里面有高可用、高并发、高性能及分布式、Jvm性能调优、Spring源码,MyBatis,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多个知识点的架构资料)合理利用本身每一分每一秒的时间来学习提高本身,不要再用"没有时间“来掩饰本身思想上的懒惰!趁年轻,使劲拼,给将来的本身一个交代!
原文连接:http://www.spring4all.com/article/1594