1. 程式人生 > >在微服務領域Spring Boot自動伸縮如何實現

在微服務領域Spring Boot自動伸縮如何實現

shutdown app 有一個 line down 滿足 51cto 完成 pivotal

自動伸縮是每個人都想要的,尤其是在微服務領域。讓我們看看如何在基於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 來關閉一些正在運行的實例。在此之前,我們需要知道當前有那些實踐在提供服務,只有這樣我們才能在需要的時候關閉空閑的實例或啟動新的新例。

技術分享圖片

圖片描述(最多50字)

在討論了系統架構之後,我們就可以繼續開發了。這個應用程序需要滿足以下要求:它必須有公開的可以優雅地關閉應用程序和用來獲取應用程序運行狀態 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
br/>@EnableEurekaServer
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["INSTANCECOUNT"] = app.instance.size()
app.instance.each {
if (it.status == ‘UP‘) {
def address = "http://${it.ipAddr}:${it.port}"
env["INSTANCE
${index++}"] = address}
}
}
}
}
@NonCPS
br/>}
}
}
}
}
@NonCPS
return new XmlSlurper(false, false).parseText(text)
}
下面是 Eureka API對我們的微服務的示例響應。響應 content-type 是 XML 。

技術分享圖片

圖片描述(最多50字)

使用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端點來完成。

圖片描述(最多50字)

假設我們已經成功地在目標機器上啟動了一些代理,我們需要對流水線進行參數化,以便能夠動態地選擇代理(以及目標機器)。

當擴容應用程序時,我們必須將代理標簽傳遞給下遊流水線。

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 代理。

在微服務領域Spring Boot自動伸縮如何實現