到目前為止,我們僅僅在本地機器上部署了我們的微服務(wù)碎捺。當我們部署一個微服務(wù)在云端會發(fā)生什么距糖?大多數(shù)云平臺提供了讓你部署和運行更容易的服務(wù)玄窝。伸縮能力、負載均衡是常見特性悍引,這些是與部署響應(yīng)式微服務(wù)尤其相關(guān)的恩脂。在這一章節(jié),我們將看到這些特性怎樣被用來開發(fā)和部署響應(yīng)式微服務(wù)趣斤。
為了展示這些優(yōu)勢俩块,我們將使用OpenShift(https://www.openshift.org/)。然而浓领,大多數(shù)現(xiàn)代的云平臺包含我們在這兒所用的這些特性玉凯。這一章的最后,你將看到云是怎樣讓響應(yīng)式變得容易联贩。
OpenShift是什么漫仆?
RedHat OpenShift v3是一個開源的容器平臺。用OpenShift部署運行在容器中的應(yīng)用泪幌,OpenShift使得構(gòu)建和管理變得容易盲厌。OpenShift構(gòu)建在Kubernetes(https://kubernetes.io/)之上。
Kubernetes(圖5-1藍色部分)是一個項目祸泪,擁有在大規(guī)模Linux容器里運行微服務(wù)集群的許多功能吗浩。Google打包十多年的容器經(jīng)驗到Kubernetes。OpenShift構(gòu)建在這個經(jīng)驗之上没隘,在構(gòu)建和部署自動化(圖5-1綠色部分)方面擴展它懂扼,比如提供開箱即用的滾動更新、金絲雀部署右蒲、持續(xù)交付管道阀湿。
OpenShift有一些簡單的實體(Entity),如圖5-2所描述瑰妄,投入到工作之前我們需要理解他們:
構(gòu)建配置
構(gòu)建是創(chuàng)建容器鏡像的過程炕倘,鏡像被OpenShift用來實例化構(gòu)成應(yīng)用的不同的容器。OpenShift構(gòu)建可以使用不同的策略:
. Docker: 從dockerfile文件構(gòu)建一個鏡像
. 源代碼到鏡像(S2I):基于OpenShift構(gòu)建鏡像(builder
image)翰撑,從應(yīng)用源代碼構(gòu)建一個鏡像
. Jenkins管道:用Jenkins管道(https://jenkins.io/doc/book/pipeline)構(gòu)建一個鏡像罩旋,潛在地包含多個步驟比如構(gòu)建、測試和部署
構(gòu)建配置能夠被git push自動地觸發(fā)眶诈,配置變化或者依賴的鏡像發(fā)生更新涨醋;顯示地,手工觸發(fā)逝撬。
部署配置
部署配置了構(gòu)建生成的鏡像的實例化浴骂,它定義了哪一個鏡像被用來創(chuàng)建容器、需要保持活著的實例的數(shù)量宪潮。它也描述了什么時候部署應(yīng)該被觸發(fā)溯警。一個部署也作為一個復(fù)制控制器趣苏,負責保持容器活著。為了達到這個目的梯轻,你設(shè)定了期望的實例數(shù)量食磕。期望的實例數(shù)量能隨時間或者基于負載波動而調(diào)整(自動伸縮)。部署也能夠指定健康檢查喳挑、管理滾動更新彬伦、監(jiān)測死容器。
Pods
一個Pod是包含一個或更多容器的容器組伊诵,然而单绑,通常是一個單一的容器構(gòu)成。Pod的編排曹宴、計劃搂橙、管理被委托給Kubernetes。Pods是可代替的笛坦,能夠在任何時候被另一個實例所代替区转。舉個例子,如果容器崩潰弯屈,另一個實例將被生成。
服務(wù)和路由
因為Pod是動態(tài)實體(實例的數(shù)量隨時間而變化)恋拷,我們不能依賴他們直接的IP地址(每個pod有它自己的IP地址)资厉。服務(wù)允許我們和Pod通訊,不依賴于Pod的地址蔬顾、而是使用service虛擬地址宴偿。一個服務(wù)作為一組Pods的前端代理,它也實現(xiàn)了負載均衡策略诀豁。
運行在OpenShift上的別的應(yīng)用能夠用服務(wù)訪問Pods提供的功能窄刘,但是OpenShift外面的應(yīng)用需要一個路由。一個路由暴露一個服務(wù)在一個象www.myservice.com這樣的主機名上舷胜,因此外面的客戶端能夠通過主機名訪問它娩践。
在你的機器上安裝OpenShift
這些是足夠抽象的概念。現(xiàn)在是時候動手了烹骨。我們將在你的機器上安裝MiniShift(https://github.com/minishift/minishift)翻伺。或者沮焕,你可以用OpenShiftOnline(https://www.openshift.com/devpreview/)吨岭,或者RedHat容器開發(fā)套件V3(https://developers.redhat.com/products/cdk/download/)。
安裝MiniShift(https://github.com/minishift/minishift#installation)需要hypervisor來運行容納OpenShift的虛擬機峦树。取決于你的主機的操作系統(tǒng)辣辫,你可以選擇hypervisor旦事,查看MiniShift安裝向?qū)б粤私饧毠?jié)。
為了安裝MiniShift急灭,僅僅從MiniShift發(fā)布頁(https://github.com/minishift/minishift/releases)下載最近的適合你操作系統(tǒng)的壓縮包姐浮,解壓它到你指定的位置,加minishift執(zhí)行目錄到你的PATH環(huán)境變量化戳。一旦安裝完成单料,啟動MiniShift:
minishift start
一旦啟動,你應(yīng)該能夠訪問https://192.168.64.12:8443連接到你的OpenShift實例点楼。你可能不得不確認SSL認證扫尖。用developer/developer登錄。
我們還需要OpenShift客戶端oc掠廓,一個命令行工具用來與你的OpenShift實例交互换怖。從https://github.com/openshift/origin/releases/latest下載最近OpenShift客戶端版本。解壓它到你指定的位置蟀瞧,加oc執(zhí)行目錄到你的PATH環(huán)境變量沉颂。
然后,連接你的OpenShift實例:
oc login https://192.168.64.12:8443 -u developer -pdeveloper
OpenShift有一個命名空間的概念稱之為project悦污。為了創(chuàng)建我們打算部署的例子的project铸屉,執(zhí)行:
oc new-project reactive-microservices
oc policy add-role-to-user admin developer –n reactive-microservices
oc policy add-role-to-user view -n reactive-microservices-z default
用你的瀏覽器,打開https://192.168.64.12:8443/console/project/reactive-microservices/切端。你應(yīng)該能夠看到這個project彻坛,這時它是沒什么東西的,因為我們還沒有部署任何東西(圖5-3):
OpenShift部署微服務(wù)
是時候部署一個微服務(wù)到OpenShift踏枣。我們打算部署的代碼放在代碼倉庫的openshift/hello-
microservice-openshift目錄昌屉。Verticle是與我們前面部署的hello微服務(wù)很接近:
package io.vertx.book.openshift;
import io.vertx.core.AbstractVerticle;
import io.vertx.core.http.HttpHeaders;
import io.vertx.core.json.JsonObject;
import io.vertx.ext.web.*;
public class HelloHttpVerticle extends AbstractVerticle {
static finalString HOSTNAME = System.getenv("HOSTNAME");
@Override
public voidstart() {
Router router =Router.router(vertx);
router.get("/").handler(this::hello);
router.get("/:name").handler(this::hello);
vertx.createHttpServer()
.requestHandler(router::accept)
.listen(8080);
}
private voidhello(RoutingContext rc) {
String message= "hello";
if(rc.pathParam("name") != null) {
message += " " + rc.pathParam("name");
}
JsonObject json= new JsonObject()
.put("message",message)
.put("served-by",HOSTNAME);
rc.response()
.putHeader(HttpHeaders.CONTENT_TYPE,"application/json")
.end(json.encode());
}
}
代碼沒有依賴特定的OpenShift API或者是結(jié)構(gòu)。它與你部署在你的機器的應(yīng)用一樣茵瀑。Java代碼與部署選擇分離必須是一個深思熟慮的設(shè)計選擇间驮,讓代碼能夠在任何云平臺上運行。
我們將手工地創(chuàng)建所有OpenShift實體马昨,但是讓我們使用Fabric8提供的Maven插件(https://maven.fabric8.io/)竞帽,一個為Kubernetes提供的端到端的的開發(fā)平臺。如果你打開pom.xml文件鸿捧,你將看到這個插件被配置在openshift profile抢呆,與Vert.X Maven插件協(xié)作一起創(chuàng)建OpenShift實體。
打包和部署微服務(wù)到OpenShift笛谦,執(zhí)行:
mvnfabric8:deploy –Popenshift
這個命令與你用oc登錄的OpenShift實例交互抱虐,創(chuàng)建一個構(gòu)建(用源代碼到鏡像策略)并觸發(fā)它。首次構(gòu)建會花一些時間因為它需要獲得builder鏡像饥脑。不用擔心---一旦被緩存恳邀,構(gòu)建將很快被創(chuàng)建懦冰。構(gòu)建的輸出(鏡像)被部署配置使用,部署配置也是被Fabric8 Maven插件創(chuàng)建谣沸,缺省地刷钢,它創(chuàng)建一個Pod。一個Service也被這個插件創(chuàng)建乳附。你可以在OpenShift儀表盤上找到這些信息内地,象圖5-4所顯示的那樣:
Fabric8 Maven插件缺省地并不創(chuàng)建路由(route)。然而赋除,我們從它的定義文件(src/main/fabric8/
route.yml)中創(chuàng)建一個阱缓。
如果你在你的瀏覽器打開:http://hello-microservice-reactive-microservices.192.168.64.12.nip.io/Luke
你應(yīng)該看到像這樣的結(jié)果:
{"message":"helloLuke","served-by":"hello-microservice-1-9r8uv"}
hello-microservice-1-9r8uv即是為這個請求提供服務(wù)的pod的主機名。
服務(wù)發(fā)現(xiàn)
現(xiàn)在举农,我們部署了hello微服務(wù)荆针,讓我們用一個微服務(wù)消費它。在這一小節(jié)里我們將要部署的代碼放在代碼倉庫的openshift/hello-microservice-consumer-openshift目錄颁糟。
為了消費一個微服務(wù)航背,我們首先不得不找到它。OpenShift提供了一個服務(wù)發(fā)現(xiàn)機制棱貌。服務(wù)查找能夠用環(huán)境變量玖媚、DNS或者Vert.X服務(wù)發(fā)現(xiàn)來實現(xiàn),這們這里用Vert.X的服務(wù)發(fā)現(xiàn)婚脱。
項目的pom.xml配置了引入Vert.X服務(wù)發(fā)現(xiàn)今魔,Kubernetes服務(wù)引入,一個服務(wù)端的服務(wù)發(fā)現(xiàn)起惕。你不必在提供者側(cè)顯示地注冊服務(wù)涡贱,象Fabric8 Maven聲明一個服務(wù)那樣咏删。消費者將得到OpenShift服務(wù)而不是Pods惹想。
@Override
public voidstart() {
Router router = Router.router(vertx);
router.get("/").handler(this::invokeHelloMicroservice);
// Create the service discovery instance
ServiceDiscovery.create(vertx, discovery -> {
// Look for an HTTP endpoint named "hello-microservice"
// you can also filter on 'label'
Single single =HttpEndpoint.rxGetWebClient(discovery,
rec -> rec.getName().equals("hello-microservice"),
new JsonObject().put("keepAlive", false)
);
single.subscribe(client -> {
// the configured client to call the microservice
this.hello = client;
vertx.createHttpServer()
.requestHandler(router::accept)
.listen(8080);
},
err -> System.out.println("Oh no, no service")
);
});
}
在start方法里,我們用服務(wù)發(fā)現(xiàn)來找到hello微服務(wù)督函。然后嘀粱,如果服務(wù)可獲得,我們啟動http server辰狡、保持一個得到的web客戶端的引用锋叨。我們也傳遞了一個配置給web客戶端、keep-alive設(shè)為false(一會兒我們將明白原因)宛篇。在調(diào)用hello微服務(wù)里娃磺,我們沒必要象前面所做的那樣傳遞端口和主機給rxSend方法,實際上叫倍,web客戶端被配置為目標是hello服務(wù):
HttpRequestrequest1 = hello.get("/Luke").as(BodyCodec.jsonObject());
HttpRequestrequest2 = hello.get("/Leia").as(BodyCodec.jsonObject());
Singles1 = request1.rxSend().map(HttpResponse::body);
Singles2 = request2.rxSend().map(HttpResponse::body);
// ...
在終端控制臺偷卧,切換至openshift/hello-microservice-consumer-openshift目錄豺瘤,構(gòu)建和部署這個消費者:
mvnfabric8:deploy –Popenshift
在OpenShift儀表盤上,你應(yīng)該看到第二個服務(wù)和路由听诸。如果你打開與hello消費者服務(wù)關(guān)聯(lián)的路由坐求,你應(yīng)該會看到:
{
"luke": "hello Luke hello-microservice-1-sa5pf",
"leia": "hello Leia hello-microservice-1-sa5pf"
}
你可能看到503錯誤頁,因為pod仍然沒有起來晌梨。僅僅刷新直到你得到正確的頁面桥嗤。到現(xiàn)在為止,沒有什么令人吃驚的仔蝌。顯示的served-by值總是指向同一個pod(因為僅有一個)泛领。
伸縮
如果我們正在用一個云平臺,主要是因為可伸縮性的原因掌逛。我們希望能夠根據(jù)負載增加/減少我們的應(yīng)用的實例數(shù)量师逸。在OpenShift儀表盤上,我們可以調(diào)節(jié)pods的數(shù)量的多少豆混,正如圖5-5所顯示的:
你也可以用oc命令行來設(shè)置副本的數(shù)量:
# 增加至2個副本
oc scale --replicas=2 dc hello-microservice
# 減少到0個副本
oc scale --replicas=0 dc hello-microservice
讓我們創(chuàng)建hello微服務(wù)的第二個實例篓像。然后,等到第二個微服務(wù)實例正確地起來(等待是令人厭煩的皿伺,后面我們將解決這個問題)员辩,在瀏覽上返回至hello消費者頁,你應(yīng)該看到像這樣:
{
"luke" : "hello Luke hello-microservice-1-h6bs6",
"leia" : "hello Leia hello-microservice-1-keq8s"
}
如果你刷新幾次鸵鸥,你將看到OpenShift在兩個實例間均衡負載奠滑。你還記得keep-alive設(shè)置為false? 當http連接使用一個keep-alive連接時,OpenShift轉(zhuǎn)發(fā)請求到同一個pod妒穴。注意在實踐中宋税,keep-alive是非常值得有的頭,因為它允許重用連接讼油。
在前面的情形里存在一個小問題杰赛。當我們伸展(scale
up)時,OpenShift開始分發(fā)請求到新的pod矮台,并沒有檢查應(yīng)用是否就緒能夠服務(wù)這些請求乏屯。因此,消費者可能請求了一個還沒有就緒的微服務(wù)瘦赫、得到了一個失敗辰晕。解決這個有兩種方式:
[if !supportLists]1)??????[endif]在微服務(wù)里面用健康檢測;
[if !supportLists]2)??????[endif]在消費者代碼里準備應(yīng)對失敗确虱。
健康檢查和失敗轉(zhuǎn)移
在OpenShift里面你能夠定義兩種類型的檢測含友。就緒檢測(Readiness Check)用來避免更新一個微服務(wù)的時候出現(xiàn)停機。在滾動更新下,OpenShift直到新版本就緒才停掉前一個版本窘问,它ping新版本微服務(wù)的就緒檢測點直到它就緒扎唾、驗證微服務(wù)被成功地初始化∧匣海活著檢測(Liveness Check)用來判定一個容器是否活著胸遇,OpenShift周期地向活著檢測點發(fā)請求,如果一個容器沒有正確地應(yīng)答汉形,它將會被重啟纸镊。活著檢測聚焦在微服務(wù)所需求的關(guān)鍵資源上概疆。在下面的例子逗威,兩個檢測我們將使用同樣的檢測點,然而岔冀,最好是使用不同的檢測點凯旭。
這個例子的代碼放在openshift/hello-microservice-openshift-health-checks目錄。如果你打開verticle使套,你將看到驗證http服務(wù)是否起來的健康檢測處理器:
privateboolean started;
@Override
publicvoid start() {
Router router = Router.router(vertx);
router.get("/health").handler(
HealthCheckHandler.create(vertx)
.register("http-server-running",
future ->future.complete(started ? Status.OK() : Status.KO())
)
);
router.get("/").handler(this::hello);
router.get("/:name").handler(this::hello);
vertx.createHttpServer()
.requestHandler(router::accept)
.listen(8080, ar -> started =ar.succeeded());
}
Fabric8
Maven插件被配置為使用/health作為就緒和活著健康檢測罐呼。一旦這個版本的hello微服務(wù)被部署,所有后續(xù)的部署將使用就緒檢測來避免出現(xiàn)停機侦高,下如圖5-6所示:
當容器就緒時嫉柴,OpenShift路由請求到這個容器、停掉老版本的容器奉呛。當我們擴展時(scale up)计螺,OpenShift不會路由請求到一個尚未就緒的容器。
使用熔斷器
盡管健康檢測避免了請求一個未就緒瞧壮、重啟死掉的微服務(wù)登馒,我們?nèi)匀恍枰獜膭e的失敗比如超時、網(wǎng)絡(luò)中斷咆槽、微服務(wù)的bug等等中保護自己陈轿,在這一小節(jié)我們打算用熔斷器來保護hello消費者,這一小節(jié)的代碼放在openshift/hello-microservice-consumer-openshift-circuit-breaker目錄罗晕。
在verticle里济欢,我們用一個簡單的熔斷器來保護對hello微服務(wù)的兩個請求赠堵,下面的代碼使用這個設(shè)計小渊,然而,這僅僅是大量可行的途徑中的一種茫叭,比如每個請求獨立地用一個熔斷器酬屉、而不是用一個簡單的熔斷器保護兩個請求:
privatevoid invokeHelloMicroservice(RoutingContext rc) {
circuit.rxExecuteCommandWithFallback(
future -> {
HttpRequest request1= hello.get("/Luke").as(BodyCodec.jsonObject());
HttpRequest request2= hello.get("/Leia").as(BodyCodec.jsonObject());
Single s1 = request1.rxSend().map(HttpResponse::body);
Single s2 = request2.rxSend().map(HttpResponse::body);
Single.zip(s1, s2, (luke, leia) -> {
// We have the result of both requestin Luke and Leia
return new JsonObject()
.put("Luke",luke.getString("message") + " " +luke.getString("served-by"))
.put("Leia",leia.getString("message") + " " +leia.getString("served-by"));
})
.subscribe(future::complete,future::fail);
},
error -> newJsonObject().put("message", "hello (fallback, "+circuit.state().toString() + ")")
).subscribe(
x ->rc.response().end(x.encodePrettily()),
t ->rc.response().end(t.getMessage())
);
}
在error情況下,我們提供一個回退(fallback)消息,指示熔斷器的狀態(tài)呐萨。這將幫助我們理解發(fā)生了什么杀饵。部署這個工程:
mvnfabric8:deploy –Popenshift
現(xiàn)在讓我們收縮(scale
down)hello微服務(wù)到0,做這個谬擦,我們可以在OpenShift Web控制臺上點擊容器旁邊的向下箭頭或者運行:
oc scale--replicas=0 dc hello-microservice
現(xiàn)在如果你刷新消費者頁面(http://hello-consumer-reactive-microservices.192.168.64.12.nip.io/)切距,你應(yīng)該看到回退(fallback)消息。前面3個請求顯示:
{
"message": "hello (fallback, CLOSED)"
}
一旦失敗次數(shù)達到閥值惨远,它會返回:
{
"message": "hello (fallback, OPEN)"
}
如果你恢復(fù)hello微服務(wù)的副本(replicas)到1:
oc scale--replicas=1 dc hello-microservice
一旦微服務(wù)就緒你應(yīng)該會獲得正常的輸出谜悟。
等等,我們是響應(yīng)式的么北秽?
是的葡幸,我們是響應(yīng)式的了。讓我們看看為什么贺氓。
所有的交互是異步的蔚叨,使用異步的、非阻塞的http請求和響應(yīng)辙培。另外蔑水,感謝OpenShift的service,我們發(fā)送請求到一個虛擬地址扬蕊,這使得有彈性肤粱。Service在一組容器中均衡負載。我們能夠很容易擴展或收縮厨相,通過調(diào)整容器的數(shù)量或者使用自動伸縮领曼。我們也有了可恢復(fù)性。感謝健康檢測蛮穿,我們有了失敗轉(zhuǎn)移機制來確笔荆總是有正常數(shù)量的容器在運行。在消費者一側(cè)践磅,我們能夠使用幾種恢復(fù)模式比如超時单刁、重試、或者熔斷器來從失敗中保護微服務(wù)府适。因此羔飞,當處于負載且面對失敗的情況下,我們的系統(tǒng)能夠及時地處理請求檐春,我們是響應(yīng)式的逻淌!
任何使用非阻塞http、在云端提供負載均衡和可恢復(fù)特性的系統(tǒng)是響應(yīng)式的嗎疟暖?是的卡儒,但是不要忘記了成本田柔。Vert.X使用事件輪詢器(event loop)實現(xiàn)用少數(shù)線程來處理大量并發(fā)請求,展示了云的重要本質(zhì)骨望。當使用依賴于線程池的途徑時硬爆,你需要:1)調(diào)整線程池找到合適的大小擎鸠;2)在你的代碼里處理并發(fā)缀磕,這意味著調(diào)試死鎖、競爭劣光、瓶頸虐骑;3)監(jiān)控性能。云環(huán)境是基于虛擬機的赎线,當你有很多線程時廷没,線程安排可能變成一個大問題。
有許多非阻塞技術(shù)垂寥,并不是所有的用同樣的執(zhí)行模式來處理異步特性颠黎,我們可以把這些技術(shù)歸為三大類:
[if !supportLists]1.??????[endif]在后臺使用一個線程池的途徑---然后面臨著調(diào)整,安排滞项,運維時不斷變化的負荷的并發(fā)挑戰(zhàn)狭归;
[if !supportLists]2.??????[endif]使用回調(diào)線程的途徑---你仍然需要管理你代碼的線程安全,避免死鎖和瓶頸文判;
[if !supportLists]3.??????[endif]用同一個線程的途徑过椎,比如Vert.X---使用少數(shù)線程,從調(diào)試死鎖中解放出來戏仓。
我們可以在云端使用消息系統(tǒng)來實現(xiàn)響應(yīng)式微服務(wù)系統(tǒng)么疚宇?當然可以。在OpenShift里面我們能夠用Vert.X事件總線(event bus)來構(gòu)建我們的響應(yīng)式微服務(wù)赏殃,但是這將不會展示虛擬服務(wù)地址敷待、OpenShift提供的負載均衡,而是Vert.X它自己來處理仁热。這里我們決定用http榜揖,無限選擇中一種設(shè)計。按你所想的方式打造你的系統(tǒng)吧抗蠢!
小結(jié)
在這一章節(jié)举哟,我們在OpenShift里部署了微服務(wù),看到了Vert.X和OpenShift怎樣組合來構(gòu)建響應(yīng)式微服務(wù)迅矛。組合異步的http服務(wù)端和客戶端妨猩,OpenShift Services、負載均衡诬乞、失敗轉(zhuǎn)移以及消費側(cè)可恢復(fù)性給了我們響應(yīng)式特性册赛。
這本書聚焦在響應(yīng)式。然而震嫉,當構(gòu)建一個微服務(wù)系統(tǒng)森瘪,許多其它方面需要被管理比如安全、配置票堵、日志等等扼睬。大多數(shù)云平臺,包括OpenShift悴势,提供了處理這些方面的服務(wù)窗宇。
關(guān)于這些topic,如果你想了解更多特纤,查看下面的資源:
.OpenShift官網(wǎng)(http://openshift.org/)
.OpenShift核心概念(https://docs.openshift.com/enterprise/3.0/architecture/core_concepts/)
.Kubernetes官網(wǎng)(https://kubernetes.io/)
.OpenShift健康檢測文檔(https://docs.openshift.com/enterprise/3.0/dev_guide/application_health.html)