在微服務(wù)領(lǐng)域Spring Boot自動伸縮如何實現(xiàn)

自動伸縮是每個人都想要的失乾,尤其是在微服務(wù)領(lǐng)域。讓我們看看如何在基于Spring Boot的應(yīng)用程序中實現(xiàn)邮偎。

我們決定使用?Kubernetes?讯赏、?Pivotal Cloud Foundry?或?HashiCorp's Nomad?等工具的一個更重要的原因是為了讓系統(tǒng)可以自動伸縮。當(dāng)然诉瓦,這些工具也提供了許多其他有用的功能川队,在這里力细,我們只是用它們來實現(xiàn)系統(tǒng)的自動伸縮。乍一看固额,這似乎很困難眠蚂,但是,如果我們使用?Spring Boot?來構(gòu)建應(yīng)用程序斗躏,并使用?Jenkins?來實現(xiàn)?CI?逝慧,那么就用不了太多工作。

今天啄糙,我將向您展示如何使用以下框架/工具實現(xiàn)這樣的解決方案:

Spring Boot

Spring Boot Actuator

Spring Cloud Netflix Eureka

Jenkins CI

它是如何工作的

每一個包含?Spring Boot Actuator?庫的?Spring Boot?應(yīng)用程序都可以在?/actuator/metrics端點下公開?metric?馋艺。許多有價值的?metric?都可以提供應(yīng)用程序運行狀態(tài)的詳細(xì)信息。在討論自動伸縮時迈套,其中一些?metric?可能特別重要:?JVM?、CPU?metric?碱鳞、正在運行的線程數(shù)和HTTP請求數(shù)桑李。有專門的?Jenkins?流水線通過按一定頻率輪詢?/actuator/metrics?端點來獲取應(yīng)用程序的指標(biāo)。如果監(jiān)控的任何?metric?【指標(biāo)】低于或高于目標(biāo)范圍窿给,則它會啟動新實例或使用另一個?Actuator?端點?/actuator/shutdown?來關(guān)閉一些正在運行的實例贵白。在此之前,我們需要知道當(dāng)前有那些實踐在提供服務(wù)崩泡,只有這樣我們才能在需要的時候關(guān)閉空閑的實例或啟動新的新例禁荒。

在討論了系統(tǒng)架構(gòu)之后,我們就可以繼續(xù)開發(fā)了角撞。這個應(yīng)用程序需要滿足以下要求:它必須有公開的可以優(yōu)雅地關(guān)閉應(yīng)用程序和用來獲取應(yīng)用程序運行狀態(tài)?metric?【指標(biāo)】的端點呛伴,它需要在啟動完成的同時就完成在Eureka的注冊,在關(guān)閉時取消注冊谒所,最后热康,它還應(yīng)該能夠從空閑端口池中隨機(jī)獲取一個可用的端口。感謝?Spring Boot?劣领,只需要約五分鐘姐军,我們可以輕松地實現(xiàn)所有這些機(jī)制。

動態(tài)端口分配

由于可以在一臺機(jī)器上運行多個應(yīng)用程序?qū)嵗馓裕晕覀儽仨毐WC端口號不沖突奕锌。幸運的是,?Spring Boot?為應(yīng)用程序提供了這樣的機(jī)制村生。我們只需要將?application.yml?中的?server.port屬性設(shè)置為?0?惊暴。因為我們的應(yīng)用程序會在?Eureka?中注冊,并且發(fā)送唯一的標(biāo)識?instanceId?梆造,默認(rèn)情況下這個唯一標(biāo)識是將字段?spring.cloud.client.hostname?,?spring.application.name和?server.port?拼接而成的缴守。

示例應(yīng)用程序的當(dāng)前配置如下所示葬毫。

可以看到,我通過將端口號替換為隨機(jī)生成的數(shù)字來改變了生成?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?設(shè)置為?'*'?來暴露?Actuator?的端點〈迳埃現(xiàn)在烂斋,所有可用的指標(biāo)名稱列表都可以在?/actuator/metrics?端點中找到,每個指標(biāo)的詳細(xì)信息可以通過?/actuator/metrics/{metricName}?端點查看础废。

優(yōu)雅地停止應(yīng)用程序

除了查看?metric?端點外汛骂,?Spring Boot Actuator?還提供了停止應(yīng)用程序的端點。然而评腺,與其他端點不同的是帘瞭,缺省情況下,此端點是不可用的蒿讥。我們必須把?management.endpoint.shutdown.enabled?設(shè)為?true?蝶念。在那之后,我們就可以通過發(fā)送一個?POST?請求到?/actuator/shutdown?端點來停止應(yīng)用程序了芋绸。

這種停止應(yīng)用程序的方法保證了服務(wù)在停止之前從?Eureka?服務(wù)器注銷媒殉。

啟用Eureka自動發(fā)現(xiàn)

Eureka?是最受歡迎的發(fā)現(xiàn)服務(wù)器,特別是使用?Spring Cloud?來構(gòu)建微服務(wù)的架構(gòu)摔敛。所以廷蓉,如果你已經(jīng)有了微服務(wù),并且想要為他們提供自動伸縮機(jī)制马昙,那么?Eureka?將是一個自然的選擇桃犬。它包含每個應(yīng)用程序注冊實例的IP地址和端口號。為了啟用?Eureka?客戶端行楞,您只需要將下面的依賴項添加到?pom.xml?中疫萤。

<dependency>

? ? <groupId>org.springframework.cloud</groupId>

? ? <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>

</dependency>

正如之前提到的,我們還必須保證通過客戶端應(yīng)用程序發(fā)送到?Eureka?服務(wù)器的?instanceId?的唯一性敢伸。在“動態(tài)端口分配”中已經(jīng)描述了它扯饶。

下一步需要創(chuàng)建一個包含內(nèi)嵌?Eureka?服務(wù)器的應(yīng)用程序。為了實現(xiàn)這個功能池颈,首先我們需要在?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);

? ? }

}

默認(rèn)情況下,客戶端應(yīng)用程序嘗試使用?8761?端口連接?Eureka?服務(wù)器躯砰。我們只需要單獨的每币、獨立的?Eureka?節(jié)點,因此我們將禁用注冊琢歇,并嘗試從另一個?Eureka?服務(wù)器實例中獲取服務(wù)列表兰怠。

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?容器來測試上面的自動伸縮系統(tǒng)梦鉴,因此需要使用?Eureka?服務(wù)器來準(zhǔn)備和構(gòu)建?image?。

Dockerfile?和?image?的定義如下所示揭保。

我們可以使用命令?docker build -t piomin/discovery-server:2.0?來進(jìn)行構(gòu)建肥橙。

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"]

為彈性伸縮構(gòu)建一個Jenkins流水線

第一步是準(zhǔn)備?Jenkins?流水線,負(fù)責(zé)自動伸縮秸侣。我們將創(chuàng)建?Jenkins?聲明式流水線存筏,它每分鐘運行一次∥堕唬可以使用?triggers?指令配置執(zhí)行周期椭坚,它定義了自動化觸發(fā)流水線的方法。我們的流水線將與?Eureka?服務(wù)器和每個使用?Spring Boot Actuator?的微服務(wù)中公開的?metric?端點進(jìn)行通信搏色。

測試服務(wù)的名稱是?EXAMPLE-SERVICE?善茎,它和定義在?application.yml?文件?spring.application.name?的屬性值(大寫字母)相同。被監(jiān)控的?metric?是運行在Tomcat容器中的HTTP?listener線程數(shù)频轿。這些線程負(fù)責(zé)處理客戶端的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流水線

流水線的第一個階段負(fù)責(zé)獲取在?discovery?服務(wù)器上注冊的服務(wù)列表。?Eureka?發(fā)現(xiàn)了幾個HTTP API端點略吨。其中一個是?GET /eureka/apps/{serviceName}?,它返回一個給定服務(wù)名稱的所有活動實例列表考阱。我們正在保存運行實例的數(shù)量和每個實例?metric?端點的URL翠忠。這些值將在流水線的下一個階段中被訪問。

下面的流水線片段可以用來獲取活動應(yīng)用程序?qū)嵗斜怼?stage?名稱是?Calculate?乞榨。我們使用HTTP請求插件來發(fā)起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對我們的微服務(wù)的示例響應(yīng)。響應(yīng)?content-type?是?XML?吃既。

使用Spring Boot Actuator整合Jenkins流水線

Spring Boot Actuator?使用?metric?來公開端點考榨,這使得我們可以通過名稱和選擇性地使用標(biāo)簽找到?metric?。在下面可見的流水線片段中鹦倚,我試圖找到?metric?低于或高于閾值的實例河质。如果有這樣的實例,我們就停止循環(huán)震叙,以便進(jìn)入下一個階段掀鹅,它執(zhí)行向下或向上的伸縮。應(yīng)用程序的IP地址是從帶有?INSTANCE_?前綴的流水線環(huán)境變量獲取的媒楼,這是在前一階段中被保存了下來的乐尊。

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"

}

關(guān)閉應(yīng)用程序?qū)嵗?/b>

在流水線的最后一個階段,我們將關(guān)閉運行的實例划址,或者根據(jù)在前一階段保存的結(jié)果啟動新的實例扔嵌。通過調(diào)用?Spring Boot Actuator?端點可以很容易執(zhí)行停止操作限府。在接下來的流水線片段中,首先選擇了?Eureka?實例痢缎。然后我們將發(fā)送?POST?請求到那個ip地址胁勺。

如果需要擴(kuò)展應(yīng)用程序,我們將調(diào)用另一個流水線牺弄,它負(fù)責(zé)構(gòu)建?fat JAR?并讓這個應(yīng)用程序在機(jī)器上跑起來姻几。

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?流水線的完整定義,它負(fù)責(zé)啟動應(yīng)用程序的新實例势告。它先從?git?倉庫中拉取源代碼蛇捌,然后使用?Maven?命令編譯并構(gòu)建二進(jìn)制的jar文件,最后通過在?java -jar?命令中添加?Eureka?服務(wù)器地址來運行應(yīng)用程序咱台。

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 &'

? ? ? ? ? ? ? ? }

? ? ? ? ? ? }

? ? ? ? }

? ? }

}

擴(kuò)展到多個機(jī)器

在前幾節(jié)中討論的算法只適用于在單個機(jī)器上啟動的微服務(wù)络拌。如果希望將它擴(kuò)展到更多的機(jī)器上,我們將不得不修改我們的架構(gòu)回溺,如下所示春贸。每臺機(jī)器都有?Jenkins?代理運行并與?Jenkins?master通信。如果想在選定的機(jī)器上啟動一個微服務(wù)的新實例遗遵,我們就必須使用運行在該機(jī)器上的代理來運行流水線萍恕。此代理僅負(fù)責(zé)從源代碼構(gòu)建應(yīng)用程序并將其啟動到目標(biāo)機(jī)器上。這個實例的關(guān)閉仍然是通過調(diào)用HTTP端點來完成车要。

假設(shè)我們已經(jīng)成功地在目標(biāo)機(jī)器上啟動了一些代理允粤,我們需要對流水線進(jìn)行參數(shù)化,以便能夠動態(tài)地選擇代理(以及目標(biāo)機(jī)器)翼岁。

當(dāng)擴(kuò)容應(yīng)用程序時类垫,我們必須將代理標(biāo)簽傳遞給下游流水線。

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

調(diào)用?流水線具體由那個標(biāo)簽下的代理運行琅坡,是由"?${params.agent}?"決定的悉患。

pipeline {

? ? agent {

? ? ? ? label "${params.agent}"

? ? }

? ? stages { ... }

}

如果有一個以上的代理連接到主節(jié)點,我們就可以將它們的地址映射到標(biāo)簽中榆俺。由于這一點售躁,我們能夠?qū)?Eureka?服務(wù)器獲取的微服務(wù)實例的IP地址映射到與?Jenkins?代理的目標(biāo)機(jī)器上。

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 { ... }

}

總結(jié)

在本文中茴晋,我演示了如何使用?Spring Boot Actuato?metric?來自動伸縮?Spring Boot?應(yīng)用程序迂求。使用?Spring Boot?提供的特性以及?Spring Cloud Netflix Eureka?和?Jenkins?,您就可以實現(xiàn)系統(tǒng)的自動伸縮晃跺,而無需借助于任何其他第三方工具揩局。本文也假設(shè)遠(yuǎn)程服務(wù)器上也是使用?Jenkins?代理來啟動新的實例,但是您也可以使用?Ansible?這樣的工具來啟動掀虎。如果您決定從?Jenkins?運行?Ansible?腳本凌盯,那么將不需要在遠(yuǎn)程機(jī)器上啟動?Jenkins?代理付枫。

歡迎工作一到五年的Java工程師朋友們加入Java架構(gòu)開發(fā): 855835163

群內(nèi)提供免費的Java架構(gòu)學(xué)習(xí)資料(里面有高可用、高并發(fā)驰怎、高性能及分布式阐滩、Jvm性能調(diào)優(yōu)、Spring源碼县忌,MyBatis掂榔,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多個知識點的架構(gòu)資料)合理利用自己每一分每一秒的時間來學(xué)習(xí)提升自己,不要再用"沒有時間“來掩飾自己思想上的懶惰症杏!趁年輕装获,使勁拼,給未來的自己一個交代厉颤!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末穴豫,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子逼友,更是在濱河造成了極大的恐慌精肃,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,884評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件帜乞,死亡現(xiàn)場離奇詭異司抱,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)黎烈,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,755評論 3 385
  • 文/潘曉璐 我一進(jìn)店門习柠,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人怨喘,你說我怎么就攤上這事≌穸ǎ” “怎么了必怜?”我有些...
    開封第一講書人閱讀 158,369評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長后频。 經(jīng)常有香客問我梳庆,道長,這世上最難降的妖魔是什么卑惜? 我笑而不...
    開封第一講書人閱讀 56,799評論 1 285
  • 正文 為了忘掉前任膏执,我火速辦了婚禮,結(jié)果婚禮上露久,老公的妹妹穿的比我還像新娘更米。我一直安慰自己,他們只是感情好毫痕,可當(dāng)我...
    茶點故事閱讀 65,910評論 6 386
  • 文/花漫 我一把揭開白布征峦。 她就那樣靜靜地躺著迟几,像睡著了一般。 火紅的嫁衣襯著肌膚如雪栏笆。 梳的紋絲不亂的頭發(fā)上类腮,一...
    開封第一講書人閱讀 50,096評論 1 291
  • 那天,我揣著相機(jī)與錄音琅摩,去河邊找鬼朝氓。 笑死妇蛀,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的厂抽。 我是一名探鬼主播,決...
    沈念sama閱讀 39,159評論 3 411
  • 文/蒼蘭香墨 我猛地睜開眼打厘,長吁一口氣:“原來是場噩夢啊……” “哼修肠!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起户盯,我...
    開封第一講書人閱讀 37,917評論 0 268
  • 序言:老撾萬榮一對情侶失蹤嵌施,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后莽鸭,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體吗伤,經(jīng)...
    沈念sama閱讀 44,360評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,673評論 2 327
  • 正文 我和宋清朗相戀三年硫眨,在試婚紗的時候發(fā)現(xiàn)自己被綠了足淆。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,814評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡礁阁,死狀恐怖巧号,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情姥闭,我是刑警寧澤丹鸿,帶...
    沈念sama閱讀 34,509評論 4 334
  • 正文 年R本政府宣布,位于F島的核電站棚品,受9級特大地震影響靠欢,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜铜跑,卻給世界環(huán)境...
    茶點故事閱讀 40,156評論 3 317
  • 文/蒙蒙 一门怪、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧锅纺,春花似錦掷空、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽疼电。三九已至,卻和暖如春减拭,著一層夾襖步出監(jiān)牢的瞬間蔽豺,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,123評論 1 267
  • 我被黑心中介騙來泰國打工拧粪, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留修陡,地道東北人。 一個月前我還...
    沈念sama閱讀 46,641評論 2 362
  • 正文 我出身青樓可霎,卻偏偏與公主長得像魄鸦,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子癣朗,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,728評論 2 351

推薦閱讀更多精彩內(nèi)容