天天看點

在微服務領域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

屬性設定為

。因為我們的應用程式會在

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

代理。

歡迎工作一到五年的Java工程師朋友們加入Java進階之路:878249276,群内提供免費的Java架構學習資料(裡面有高可用、高并發、高性能及分布式、Jvm性能調優、Spring源碼,MyBatis,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多個知識點的架構資料)合理利用自己每一分每一秒的時間來學習提升自己,不要再用"沒有時間“來掩飾自己思想上的懶惰!趁年輕,使勁拼,給未來的自己一個交代!

原文連結:http://www.spring4all.com/article/1594