轻量级微服务架构
目錄
- 微服務(wù)架構(gòu)設(shè)計(jì)概述
- 為什么需要微服務(wù)架構(gòu)
- 傳統(tǒng)架構(gòu)的問(wèn)題
- 如何解決傳統(tǒng)應(yīng)用架構(gòu)的問(wèn)題
- 傳統(tǒng)架構(gòu)還有那些問(wèn)題
- 微服務(wù)架構(gòu)是什么
- 微服務(wù)架構(gòu)概念
- 微服務(wù)的交付流程
- 微服務(wù)開(kāi)發(fā)規(guī)范
- 微服務(wù)架構(gòu)模式
- 微服務(wù)架構(gòu)有哪些特點(diǎn)和挑戰(zhàn)
- 微服務(wù)架構(gòu)的特點(diǎn)
- 微服務(wù)架構(gòu)的挑戰(zhàn)
- 如何搭建微服務(wù)架構(gòu)
- 微服務(wù)架構(gòu)圖
- 微服務(wù)的技術(shù)選型
- 微服務(wù)開(kāi)發(fā)框架
- Spring Boot是什么
- SpringBoot的由來(lái)
- Spring Boot 特性
- SpringBoot相關(guān)插件
- SpringBoot的應(yīng)用場(chǎng)景
- 如何使用SpringBoot框架
- 搭建SpringBoot開(kāi)發(fā)框架
- 開(kāi)發(fā)一個(gè)簡(jiǎn)單的SpringBoot應(yīng)用程序
- 運(yùn)行SpringBoot應(yīng)用程序
- SpringBoot的生產(chǎn)級(jí)特性
- 端點(diǎn)
- 健康檢查
- 應(yīng)用基本信息
- 跨域
- 外部配置
- 遠(yuǎn)程監(jiān)控
- 微服務(wù)網(wǎng)關(guān)
- Node.js是什么
- Node.js快速入門(mén)
- Node.js的應(yīng)用場(chǎng)景
- 如何使用Node.js
- 安裝Node.js
- 使用Node.js開(kāi)發(fā)WEB應(yīng)用
- 微服務(wù)注冊(cè)與發(fā)現(xiàn)
- ZooKeeper是什么
- ZooKeeper樹(shù)狀模型
- ZooKeeper的集群結(jié)構(gòu)
- 如何使用ZooKeeper
- 運(yùn)行ZooKeeper
- 使用ZooKeeper搭建集群環(huán)境
- 使用命令行客戶(hù)端鏈接ZooKeeper
- 使用java客戶(hù)端鏈接ZooKeeper
- 使用Node.js客戶(hù)端鏈接ZooKeeper
- 實(shí)現(xiàn)服務(wù)注冊(cè)組件
- 設(shè)計(jì)服務(wù)注冊(cè)表數(shù)據(jù)結(jié)構(gòu)
- 微服務(wù)封裝
- Docker是什么
- Docker簡(jiǎn)介
- 虛擬機(jī)與Docker對(duì)比
- Docker的特點(diǎn)
- 微服務(wù)的部署
- Jenkins簡(jiǎn)介
微服務(wù)架構(gòu)設(shè)計(jì)概述
自從Martin Fowler(馬丁)在2014年提出了Micro Service(微服務(wù))的概念之后,業(yè)界就卷起了一股關(guān)于微服務(wù)的熱潮,大家討論多年的SOA(Service-Oriented Architecture,面向服務(wù)的架構(gòu))終于有了新的解決方案,人們不再需要笨重的ESB(Enterprise Service Bus,企業(yè)服務(wù)總線(xiàn))。恰逢Docker技術(shù)逐漸普及,一個(gè)嶄新的輕量級(jí)SOA架構(gòu)MSA(Micro Service Architecture,微服務(wù)架構(gòu))伴隨著Docker容器技術(shù)正向我們攜手走來(lái)。
為什么需要微服務(wù)架構(gòu)
微服務(wù)架構(gòu)(MSA)的出現(xiàn)絕不是偶然的,由于傳統(tǒng)應(yīng)用架構(gòu)的不合理,從而產(chǎn)生了新的架構(gòu)模式,這類(lèi)現(xiàn)象再正常不過(guò)了。那么,傳統(tǒng)的架構(gòu)究竟有哪些問(wèn)題呢?下面進(jìn)行分析。
傳統(tǒng)架構(gòu)的問(wèn)題
下圖是一個(gè)經(jīng)典的Java Web應(yīng)用程序,它包括Web UI部分,還包括若干個(gè)業(yè)務(wù)模塊,就像這里出現(xiàn)的ModelA、ModelB、ModelC等。
WebUI與這些Model封裝在一個(gè)war包中,因此需要將此war部署到Web Server(例如TOMCAT)上才能運(yùn)行,該應(yīng)用程序會(huì)連接到DataBase(如MySQL)上操作數(shù)據(jù)。
在系統(tǒng)運(yùn)行過(guò)程中,我們通過(guò)監(jiān)視程序發(fā)現(xiàn),ModelA和ModelB都需要消耗10%的系統(tǒng)資源,加起來(lái)占總系統(tǒng)的20%,而ModelC卻需要占用80%的系統(tǒng)資源。運(yùn)行一段時(shí)間后,ModelC將會(huì)成為系統(tǒng)的瓶頸,從而降低系統(tǒng)的性能。
那么如何解決這個(gè)問(wèn)題呢?人們想到了一個(gè)辦法。
如何解決傳統(tǒng)應(yīng)用架構(gòu)的問(wèn)題
只需要將這個(gè)應(yīng)用程序復(fù)制一份相同的程序,并將其部署到另外一個(gè)Web Server上,下方還是鏈接到相同的DataBase,只是在這些Web Server的上方架設(shè)一臺(tái)Load Balancer(負(fù)載均衡器,LB),可見(jiàn)應(yīng)用程序獲得了“水平擴(kuò)展”。
請(qǐng)求首先會(huì)發(fā)送到LB上,通過(guò)LB上的路由算法(例如輪詢(xún)或哈希),將請(qǐng)求轉(zhuǎn)發(fā)到后面具體的Web Server上,這類(lèi)請(qǐng)求轉(zhuǎn)發(fā)技術(shù)被稱(chēng)為Reverse Proxy(反向代理)。
由于進(jìn)入LB的請(qǐng)求(流量)被均衡到下方各臺(tái)Web Server中了,流量得到了分?jǐn)?#xff0c;負(fù)載得到了均衡,因此該技術(shù)也被稱(chēng)為L(zhǎng)oad Balance(負(fù)載均衡)。
如果流量加大,我們還可以繼續(xù)水平擴(kuò)展更多的Web Server,該架構(gòu)理論上可以無(wú)限擴(kuò)展,只要LB能夠抗的住巨大流量就行。
通過(guò)上述方案,輕松的將負(fù)載進(jìn)行了均衡,在一定的程度上緩解了流量對(duì)Web Server的壓力,但此時(shí)卻造成了大量的系統(tǒng)資源浪費(fèi),比如對(duì)系統(tǒng)資源占用率不搞的ModelA和ModelB也進(jìn)行了水平擴(kuò)展,其實(shí)我們只想對(duì)ModelC進(jìn)行擴(kuò)展而已。
除了水平擴(kuò)展方案帶來(lái)的系統(tǒng)資源浪費(fèi),實(shí)際上傳統(tǒng)架構(gòu)還有其他的問(wèn)題,我們繼續(xù)討論。
傳統(tǒng)架構(gòu)還有那些問(wèn)題
傳統(tǒng)應(yīng)用架構(gòu)實(shí)際上是一個(gè)Monolith(單塊架構(gòu)),因?yàn)檎麄€(gè)應(yīng)用都被封裝在一個(gè)WebAPP中,就像是巨石一塊,無(wú)法拆解,我們所做的水平擴(kuò)展也只是在擴(kuò)展一塊塊的巨石。為了便于表達(dá),我們不妨將單塊架構(gòu)搭建起來(lái)的應(yīng)用簡(jiǎn)稱(chēng)為“單塊應(yīng)用”。
我們?cè)俨渴饐螇K應(yīng)用的時(shí)候,同樣也會(huì)遇到很多的麻煩,比如:
- 修改了一個(gè)Model(可能只是修改了一行代碼),就需要重新部署整個(gè)應(yīng)用。
- 部署整個(gè)應(yīng)用所消耗的時(shí)間與對(duì)系統(tǒng)帶來(lái)的開(kāi)銷(xiāo)都是非常多的。
此外,對(duì)于Java Web應(yīng)用而言,打包war包里的代碼一般是class 文件,這也就意味著,我們的單塊應(yīng)用只是基于java語(yǔ)言開(kāi)發(fā)的,無(wú)法將應(yīng)用中某個(gè)單個(gè)的Model通過(guò)其他開(kāi)發(fā)語(yǔ)言來(lái)實(shí)現(xiàn)(假如我們不考慮JVM上運(yùn)行動(dòng)態(tài)語(yǔ)言的情況下),也許其他語(yǔ)言開(kāi)發(fā)實(shí)現(xiàn)某個(gè)模塊更加合適,這樣就會(huì)產(chǎn)生技術(shù)選型的單一問(wèn)題。
綜上所述,傳統(tǒng)應(yīng)用架構(gòu)存在以下問(wèn)題:
- 系統(tǒng)資源浪費(fèi)
- 部署效率太低
- 技術(shù)選型單一
當(dāng)然,傳統(tǒng)應(yīng)用架構(gòu)的問(wèn)題還遠(yuǎn)不止這些。當(dāng)業(yè)務(wù)越來(lái)越復(fù)雜時(shí),應(yīng)用會(huì)變得越來(lái)越臃腫,“身材”越來(lái)越“胖”而無(wú)法瘦身。于是,人們找到了新的思路來(lái)解決傳統(tǒng)應(yīng)用架構(gòu)的問(wèn)題,這就是微服務(wù)架構(gòu)。
那么微服務(wù)架構(gòu)與傳統(tǒng)應(yīng)用架構(gòu)的區(qū)別到底在哪?我們繼續(xù)探討。
微服務(wù)架構(gòu)是什么
微服務(wù)架構(gòu)從字面上來(lái)理解就是:許多微小服務(wù)搭建起來(lái)的應(yīng)用架構(gòu)。
這句話(huà)涉及到很多問(wèn)題,我們逐一討論,比如:
- 服務(wù)多“微”才能叫微服務(wù)?
- 如何管理越來(lái)越多的微服務(wù)?
- 客戶(hù)端是怎樣調(diào)用微服務(wù)的?
帶著這些問(wèn)題,開(kāi)始下面的討論,首先我們來(lái)看如何定義微服務(wù)架構(gòu)。
微服務(wù)架構(gòu)概念
當(dāng)馬丁大神提出微服務(wù)架構(gòu)這個(gè)概念的時(shí)候,同時(shí)他也對(duì)微服務(wù)架構(gòu)提出了幾條要求,也就是說(shuō),當(dāng)我們的應(yīng)用滿(mǎn)足一下條件時(shí),才能稱(chēng)之為微服務(wù)架構(gòu),具體包括:
- 根據(jù)業(yè)務(wù)模塊劃分服務(wù)種類(lèi);
- 每個(gè)服務(wù)可以獨(dú)立部署且相互隔離
- 通過(guò)輕量級(jí)API調(diào)用服務(wù)
- 服務(wù)保證良好的高可用性
我們簡(jiǎn)單的分析一下:首先根據(jù)產(chǎn)品的業(yè)務(wù)功能模塊來(lái)劃分服務(wù)種類(lèi),也就是說(shuō),我們需要按照業(yè)務(wù)功能去劃分種類(lèi),這是“垂直劃分”;而在代碼層面上進(jìn)行劃分,這是“水平劃分”。每個(gè)服務(wù)可以獨(dú)立部署,還需要相互隔離,也就是說(shuō),服務(wù)之間是沒(méi)有任何干擾的,可將每個(gè)服務(wù)放入到獨(dú)立的程序中運(yùn)行,因?yàn)檫M(jìn)程之間是完全隔離的。客戶(hù)端通過(guò)輕量級(jí)API來(lái)調(diào)用微服務(wù),比如可以通過(guò)HTTP或者RPC的方式來(lái)調(diào)用,目的是為了降低調(diào)用所產(chǎn)生的的性能開(kāi)銷(xiāo)。服務(wù)需要確保高可用性,不能長(zhǎng)時(shí)間的無(wú)法響應(yīng),需要提供多個(gè)“候補(bǔ)隊(duì)員”,在某個(gè)服務(wù)出現(xiàn)故障時(shí),可以自動(dòng)調(diào)用其中一個(gè)正常工作的服務(wù)。
微服務(wù)架構(gòu)顛覆了傳統(tǒng)應(yīng)用架構(gòu)的模式,若不定義良好的交付流程與開(kāi)發(fā)規(guī)范,則很難讓微服務(wù)發(fā)揮出真正的價(jià)值,下面我們來(lái)看一下微服務(wù)的交付流程。
微服務(wù)的交付流程
使用微服務(wù)架構(gòu)開(kāi)發(fā)應(yīng)用程序,我們實(shí)際上是針對(duì)一個(gè)個(gè)服務(wù)進(jìn)行設(shè)計(jì)、開(kāi)發(fā)、測(cè)試、部署。因?yàn)槊總€(gè)服務(wù)之間是沒(méi)有彼此依賴(lài)的,大概的交付流程付下:
在設(shè)計(jì)階段,架構(gòu)師將產(chǎn)品功能拆分成若干個(gè)服務(wù),為每個(gè)服務(wù)設(shè)計(jì)API接口(例如REST API),需要給出API文檔,包括API的名稱(chēng)、版本、請(qǐng)求參數(shù)、響應(yīng)結(jié)果、錯(cuò)誤代碼等信息。在開(kāi)發(fā)階段,開(kāi)發(fā)工程師去實(shí)現(xiàn)API接口,也包括完成API的單元測(cè)試工作。在此期間,前段工程師會(huì)并行開(kāi)發(fā)Web UI部分,可根據(jù)API文檔造出一些假數(shù)據(jù)(我們稱(chēng)之為“mock”數(shù)據(jù))。這樣一來(lái),前段工程師就不必等待后端API全部開(kāi)發(fā)完畢,才能開(kāi)始自己的工作。在測(cè)試階段,前后端工程師分別將自己的代碼部署到測(cè)試環(huán)境上,測(cè)試工程師將針對(duì)測(cè)試用例進(jìn)行手工或自動(dòng)化測(cè)試,隨后產(chǎn)品經(jīng)理將從產(chǎn)品功能上進(jìn)行驗(yàn)收。在部署階段,運(yùn)維工程師將代碼部署到預(yù)發(fā)環(huán)境中,測(cè)試工程師再一次進(jìn)行一些冒煙測(cè)試,當(dāng)不在發(fā)生任何問(wèn)題的時(shí)候,經(jīng)技術(shù)經(jīng)理確認(rèn),運(yùn)維工程師將代碼部署到生產(chǎn)環(huán)境中,這一系列的部署過(guò)程都需要做到自動(dòng)化,才能提高效率。
需要注意的是,以上過(guò)程中看似需要多種工程師參與,實(shí)際上并非每種角色都對(duì)應(yīng)具體的工程師。往往在小的團(tuán)隊(duì)里,一名工程師可以身兼多職,這些都是正常現(xiàn)象。只是對(duì)于大團(tuán)隊(duì)而言,分工比較明確,更容易實(shí)施這套交付流程。
在以上交付流程中,開(kāi)發(fā)、測(cè)試、部署這三個(gè)階段可能都會(huì)涉及對(duì)代碼的控制,我們還需要指定相關(guān)的開(kāi)發(fā)規(guī)范,以確保多人能夠良好的協(xié)作。
微服務(wù)開(kāi)發(fā)規(guī)范
無(wú)論使用傳統(tǒng)應(yīng)用架構(gòu),還是微服務(wù)架構(gòu),我們都需要定義良好的開(kāi)發(fā)規(guī)范。經(jīng)驗(yàn)表明,我們需要善用代碼版本控制系統(tǒng)。就拿Git來(lái)說(shuō),它很好的支持了多分支代碼版本,我們需要利用這個(gè)特性來(lái)提高開(kāi)發(fā)效率,下圖是一副經(jīng)典的分支管理規(guī)范:
最穩(wěn)定的代碼放在master分支上(想當(dāng)于SVN的trunk分支),我們不要直接在master分支上提交代碼,只能在分支上進(jìn)行合并操作,例如將其他分支的代碼合并到master分支上。
我們?nèi)粘i_(kāi)發(fā)中的代碼需要從master分支上拉下一條develop分支出來(lái),該分支所有人都能訪問(wèn),但一般情況下,我們也不會(huì)直接在該分支上提交代碼,代碼同樣是從其他分支上合并到develop上去的。
當(dāng)我們需要開(kāi)發(fā)某個(gè)新特性的時(shí)候,需要從develop分支上拉出一條feature分支,例如feature1和feature2,在這些分支上并行的開(kāi)發(fā)具體特性。
當(dāng)特性開(kāi)發(fā)完畢之后,我們決定發(fā)布某個(gè)版本了,此時(shí)需要從develop分支上拉出一條release分支,例如release-1.0,并將需要發(fā)布的特性從相關(guān)的feature分支合并到release分支上,隨后對(duì)release分支部署測(cè)試環(huán)境,測(cè)試工程師在該分支上做功能測(cè)試,開(kāi)發(fā)工程師在該分支上改bug。待測(cè)試工程師無(wú)法找到任何bug時(shí),我們可以將release分支部署到預(yù)發(fā)環(huán)境中。再次檢驗(yàn)以后,無(wú)任何bug,此時(shí)可以將release部署到生產(chǎn)環(huán)境上。待上線(xiàn)完成之后,將release分支上的代碼同時(shí)合并到develop分支與master分支上,并在master分支上打一個(gè)tag,例如v1.0.0。
當(dāng)在生產(chǎn)環(huán)境中發(fā)現(xiàn)bug時(shí),我們需要從對(duì)應(yīng)的tag上(例如v1.0.0)拉出一條hotfix分支(例如hotfix-1.0.1),并在該分支上進(jìn)行bug修復(fù),待bug完全修復(fù)后,需要將hotfix分支上的代碼同時(shí)合并到master和develop上。
對(duì)于版本號(hào)我們也有要求,格式為:x.y.z,其中x用于有重大重構(gòu)時(shí)才會(huì)升級(jí),y用于有新特性發(fā)布時(shí)才會(huì)升級(jí),z用于修復(fù)了某個(gè)bug后才會(huì)升級(jí)。
針對(duì)每個(gè)服務(wù)的開(kāi)發(fā)工作,我們都需要嚴(yán)格按照以上開(kāi)發(fā)規(guī)范來(lái)執(zhí)行。
實(shí)際上,我們使用的開(kāi)發(fā)規(guī)范是業(yè)界知名的Git Flow,可以通過(guò)以下博客了解到Git Flow的詳細(xì)過(guò)程:
http://nvie.com/posts/a-successful-git-branching-model/。
此外,在GitHub中有一個(gè)基于以上Git Flow的命令行工具,名為git-flow,其項(xiàng)目地址如下:
https://github.com/nive/gitflow。
我們已經(jīng)對(duì)微服務(wù)架構(gòu)的概念、交付流程、開(kāi)發(fā)規(guī)范進(jìn)行了描述,下面我們大致歸納一下微服務(wù)有哪些特點(diǎn)。
微服務(wù)架構(gòu)模式
世界著名軟件大師Chris Richardson(克里斯)創(chuàng)建了一個(gè)總結(jié)微服務(wù)架構(gòu)模式的網(wǎng)站。該網(wǎng)站上列出了大量的微服務(wù)架構(gòu)模式,分為:核心模式、部署模式、通信模式、服務(wù)發(fā)現(xiàn)模式、數(shù)據(jù)管理模式等。該網(wǎng)站的網(wǎng)址:http://microservice.io/。
微服務(wù)架構(gòu)有哪些特點(diǎn)和挑戰(zhàn)
微服務(wù)架構(gòu)相對(duì)于傳統(tǒng)的架構(gòu)有著顯著的特點(diǎn),同時(shí)微服務(wù)架構(gòu)也給我們帶來(lái)了一定的挑戰(zhàn),我們先從他的特點(diǎn)說(shuō)起。
微服務(wù)架構(gòu)的特點(diǎn)
微服務(wù)的粒度是根據(jù)業(yè)務(wù)功能來(lái)劃分的,對(duì)于某些復(fù)雜的業(yè)務(wù)來(lái)說(shuō),可能粒度較大,相對(duì)于簡(jiǎn)單的業(yè)務(wù)而言,可能粒度較小。總之,微服務(wù)的粒度可大可小,但往往我們希望他盡可能的小,但又不希望微服務(wù)之間有直接的依賴(lài),因此粒度的劃分是一件非常考驗(yàn)架構(gòu)師水平的事情。
我們需要確保每個(gè)微服務(wù)只做一件事情,也就是我們經(jīng)常提到的“單一職責(zé)原則”,該原則對(duì)微服務(wù)的劃分提供了指導(dǎo)方針。如果我們將一個(gè)服務(wù)提供多個(gè)API,那么就必須做到每個(gè)API職責(zé)單一性。
每個(gè)服務(wù)相互隔離,且互不影響。也就是說(shuō),每個(gè)服務(wù)運(yùn)行在自己的進(jìn)程中。眾所周知,進(jìn)程之間是隔離的,是安全的,而進(jìn)程內(nèi)部或線(xiàn)程之間的資源是共享的。換句話(huà)說(shuō),一個(gè)服務(wù)出了問(wèn)題,不會(huì)影響到其他服務(wù)。
隨著業(yè)務(wù)功能不斷增多,服務(wù)的數(shù)量也會(huì)逐漸增加,我們需要對(duì)服務(wù)提供自動(dòng)化部署與監(jiān)控預(yù)警的能力,這樣才能更加高效的管理這些服務(wù)。需要注意的是,我們必須借助自動(dòng)化技術(shù),才能確保管理服務(wù)更加的容易。
微服務(wù)的架構(gòu)特點(diǎn)非常明顯,可能還有很多,但同時(shí)微服務(wù)架構(gòu)也給我們帶來(lái)了許多挑戰(zhàn)。
微服務(wù)架構(gòu)的挑戰(zhàn)
運(yùn)維工程師除了需要掌握使用自動(dòng)化技術(shù)來(lái)部署微服務(wù),還需要對(duì)整個(gè)微服務(wù)系統(tǒng)進(jìn)行有效的監(jiān)控,并保障系統(tǒng)的高可用性。可見(jiàn),微服務(wù)架構(gòu)的引入會(huì)帶來(lái)運(yùn)維成本的上升。
微服務(wù)架構(gòu)的本質(zhì)還是一個(gè)分布式架構(gòu),每個(gè)服務(wù)可以部署在任意的機(jī)器上。對(duì)于分布式系統(tǒng)而言,網(wǎng)絡(luò)延遲、系統(tǒng)容錯(cuò)、分布式事務(wù)等問(wèn)題都會(huì)給我們帶來(lái)很大的挑戰(zhàn)。
對(duì)于業(yè)務(wù)復(fù)雜的情況,可能存在多個(gè)服務(wù)來(lái)共同完成一件事情,服務(wù)之間雖然沒(méi)有相互調(diào)用,但可能會(huì)有調(diào)用的順序要求。業(yè)務(wù)上的依賴(lài)性導(dǎo)致了部署的依賴(lài)性,從而在某一時(shí)間點(diǎn),同一服務(wù)可能具備多個(gè)版本。
既然微服務(wù)是隔離在自己進(jìn)程中運(yùn)行的,那么從客戶(hù)端調(diào)用微服務(wù),需要跨進(jìn)程進(jìn)行調(diào)用,而進(jìn)程間的調(diào)用一定比進(jìn)程內(nèi)的調(diào)用更加消耗資源,從而帶來(lái)通信成本上的開(kāi)銷(xiāo)。
如何搭建微服務(wù)架構(gòu)
可見(jiàn),微服務(wù)架構(gòu)的要求還是想當(dāng)高的,不僅僅對(duì)技術(shù),而且對(duì)運(yùn)維都有很高要求,我們需要設(shè)計(jì)出一款簡(jiǎn)單易用的輕量級(jí)微服務(wù)架構(gòu)來(lái)滿(mǎn)足自身的需求。下面用一張圖來(lái)描述一下我們對(duì)微服務(wù)架構(gòu)的愿景。
微服務(wù)架構(gòu)圖
我們不妨從下往上來(lái)理解這張圖。底層部署了一系列的Service,每個(gè)Service可能有自己的DB,或者多個(gè)Service公用一個(gè)DB,且同一個(gè)Service可部署多個(gè)。當(dāng)Service啟動(dòng)時(shí),會(huì)自動(dòng)的將信息注冊(cè)到Service Registry(服務(wù)注冊(cè)表)中,比如:每個(gè)服務(wù)的IP與端口。當(dāng)Web UI發(fā)出請(qǐng)求時(shí),該請(qǐng)求會(huì)發(fā)送到Service Gateway(服務(wù)網(wǎng)關(guān))中,Service Gateway讀取請(qǐng)求數(shù)據(jù),并從Service Registry中獲取對(duì)應(yīng)的Service信息(IP與端口號(hào)),最后,Service Gateway主動(dòng)調(diào)用下面對(duì)應(yīng)的Service。整個(gè)過(guò)程就是這樣,其中Service Gateway擔(dān)當(dāng)了重要角色。
大家可能會(huì)認(rèn)為Service Gateway將成為一個(gè)中心,造成單點(diǎn)故障。沒(méi)錯(cuò),完全有這個(gè)可能,所以我們將他做的越“薄”越好,所以我們?cè)偌夹g(shù)選型上,要謹(jǐn)慎考慮。
此外,對(duì)于Service Registry的高可用性也有很高的要求,他不僅在每個(gè)Service啟動(dòng)時(shí)提供“服務(wù)注冊(cè)”,還需要在Service Gateway處理每個(gè)請(qǐng)求時(shí)提供“服務(wù)發(fā)現(xiàn)”。如果他失效了,那么整個(gè)系統(tǒng)將無(wú)法工作。
微服務(wù)的技術(shù)選型
我們可以使用SpringBoot作為微服務(wù)開(kāi)發(fā)框架,SpringBoot擁有嵌入式Tomcat,可直接運(yùn)行一個(gè)jar包來(lái)運(yùn)行微服務(wù),此外,他還提供一些“開(kāi)箱即用”的插件,可大大提高我們的開(kāi)發(fā)效率,我們也可以去擴(kuò)展更多的插件。
發(fā)布微服務(wù)時(shí),可以鏈接ZooKeeper來(lái)注冊(cè)微服務(wù),實(shí)現(xiàn)“服務(wù)注冊(cè)”。實(shí)際上ZooKeeper中有一個(gè)名為ZNode的內(nèi)存樹(shù)狀模型,樹(shù)上的結(jié)點(diǎn)用于存放微服務(wù)的配置信息。使用Node.js處理瀏覽器發(fā)送的請(qǐng)求,在Node.js中鏈接ZooKeeper,發(fā)現(xiàn)服務(wù)配置,實(shí)現(xiàn)“服務(wù)發(fā)現(xiàn)”,有大量的Node.js的ZooKeeper客戶(hù)端可以完成這個(gè)任務(wù)。
通過(guò)Node.js將請(qǐng)求發(fā)送到Tomcat上,實(shí)現(xiàn)“反向代理”,同樣也有大量的Node.js庫(kù)供我們自由選擇。Node.js的“單線(xiàn)程模型”且“非阻塞異步式I/O”特性通過(guò)“事件循環(huán)”的方式來(lái)支撐大量的高并發(fā)請(qǐng)求,此外Node.js原生也提供了集群特性,可確保高可用性。
為了實(shí)現(xiàn)微服務(wù)的自動(dòng)化部署,我們可以通過(guò)Jenkins搭建自動(dòng)化部署系統(tǒng),并使用Docker將服務(wù)進(jìn)行容器化封裝。
綜上所述,微服務(wù)架構(gòu)技術(shù)選型如下所示:
SpringBoot:http://projects.spring.io/spring-boot/。
ZooKeeper:http://zookeeper.apache.org/。
Node.js:https://nodejs.org/。
Jenkins:https://jenkins.io/。
Docker:https://www.docker.com/。
我們通過(guò)一張圖來(lái)歸納一下微服務(wù)架構(gòu)的技術(shù)選型
除了上述的技術(shù)選型外,實(shí)際上還有其他可選的方案,比如Netflix公司開(kāi)源的微服務(wù)技術(shù)棧:
Netflix:http://netflix.github.io/。
Spring官方在SpringBoot的基礎(chǔ)上,封裝了Netflix相關(guān)組件,提供了一個(gè)名為SpringCloud的開(kāi)源項(xiàng)目。
SpringCloud:http://projects.spring.io/spring-cloud/。
就連曾今的JBoss也推出了自己的微服務(wù)框架WildFly Swarm。
WildFly Swarm:http://wildfly-swarm.io/。
此外,還有一個(gè)輕量級(jí)的REST框架也宣稱(chēng)具備可開(kāi)發(fā)微服務(wù)的能力。
Dropwizad:http://dropwizad.io/。
以上僅為java相關(guān)的微服務(wù)技術(shù)選型,其他開(kāi)發(fā)語(yǔ)言也有自己的微服務(wù)開(kāi)發(fā)技術(shù)棧。
微服務(wù)開(kāi)發(fā)框架
Spring猶如一股清風(fēng),吹散了EJB對(duì)javaEE的絕對(duì)統(tǒng)治地位。他的IOC、AOP等特性開(kāi)啟了我們對(duì)面向?qū)ο缶幊痰男乱曇?#xff0c;讓我們驚嘆道:原來(lái)程序還能這樣寫(xiě)!隨著SpringMVC逐漸強(qiáng)大起來(lái),贏得了JAVA WEB開(kāi)發(fā)的很大的市場(chǎng)占有率。不管是struts還是Strust2都遜色于它,就連持久層技術(shù)Hibernate的市場(chǎng)也在逐漸縮小,因?yàn)镾pring+MyBitis早已成為市場(chǎng)主流。可以斷言,Spring“一統(tǒng)江湖”指日可待。實(shí)際上,Spring已經(jīng)十多歲了,在開(kāi)源世界里他已經(jīng)不再年輕,給我們的感覺(jué)是它的體積越來(lái)越大,使用起來(lái)越來(lái)越方便 。就像事先商量好的一樣,SpringBoot的誕生讓Spring應(yīng)用程序變得更加高效,恰巧當(dāng)Spring Boot遇上“微服務(wù)”之后,讓我們更加意識(shí)到,微服務(wù)的春天已經(jīng)悄悄來(lái)到了。
Spring Boot是什么
SpringBoot是為生產(chǎn)級(jí)Spring應(yīng)用而生的,他使得開(kāi)發(fā)Spring應(yīng)用程序更加高效、簡(jiǎn)潔。那么,SpringBoot具備哪些生產(chǎn)級(jí)特性呢?我們不妨從它的由來(lái)開(kāi)始講起。
SpringBoot的由來(lái)
在Spring1.0的時(shí)代,我們習(xí)慣于用XML文件來(lái)配置Bean,在XML文件中可以輕松的進(jìn)行依賴(lài)注入,但當(dāng)Bean的數(shù)量越來(lái)越多時(shí),XML也會(huì)變得越來(lái)越復(fù)雜,少則上百行,多則上千行,沒(méi)有人愿意維護(hù)一大段XML配置。緊接著Spring2.0很快到來(lái)了,他在XML命名空間上做了一些優(yōu)化,讓配置看起來(lái)盡可能的簡(jiǎn)單,但仍沒(méi)有徹底解決配置上的問(wèn)題,知道Spring3.0的出現(xiàn),我們可以使用Spring提供的java注解取代曾今的XML配置了,似乎我們都忘記了曾今發(fā)生了過(guò)什么,Spring變得前所未有的簡(jiǎn)單。當(dāng)Spring4.0出現(xiàn)后,我們甚至連XML配置文件都不需要了,完全使用java源碼級(jí)別的配置與Spring提供的注解就能快速的開(kāi)發(fā)出Spring應(yīng)用程序。
盡管Spring已經(jīng)非常優(yōu)秀了,但仍無(wú)法改變Java Web應(yīng)用程序的運(yùn)行模式,也就是說(shuō),我們?nèi)孕枰獙ar包部署到web Server上,才能對(duì)外提供服務(wù)。能否運(yùn)行一個(gè)簡(jiǎn)單的main()方法就能啟動(dòng)web Server呢?Spring Boot滿(mǎn)足了我們的需求,我們下面就來(lái)全面的了解一下Spring Boot的所有特性。
Spring Boot 特性
使用SpringBoot所創(chuàng)建的應(yīng)用程序都是一個(gè)個(gè)獨(dú)立的jar包,而并非war包,即使是war應(yīng)用也是jar包,這方面似乎帶有一點(diǎn)顛覆性的味道。我們可以直接運(yùn)行帶有@SpringBootApplication注解的main()方法就能運(yùn)行一個(gè)Spring應(yīng)用程序,實(shí)際上是在SpringBoot應(yīng)用程序內(nèi)部嵌入了一個(gè)WebServer而已。但這些并不能說(shuō)明SpringBoot就不能以war包的形式部署到Web Server中了,我們同樣可以使用SpringBoot來(lái)開(kāi)發(fā)傳統(tǒng)的javaweb應(yīng)用。
我們不在將war包部署到WebServer中,而是啟動(dòng)SpringBoot應(yīng)用程序后,會(huì)在默認(rèn)端口8080下啟動(dòng)一個(gè)嵌入式的tomcat,也可以在SpringBoot提供的application.properties配置文件中配置具體的端口號(hào)。當(dāng)然,除了Tomcat,SpringBoot還提供了Jetty、Undertow等嵌入式的Web Server,我們可以根據(jù)實(shí)際情況,自行在Maven配置文件中添加相關(guān)的Web Server的插件,SpringBoot插件體系十分的廣泛。
在某些開(kāi)源框架中,會(huì)使用字節(jié)碼生成技術(shù)(例如CGLib、Javassist、ASM等),在程序運(yùn)行時(shí)動(dòng)態(tài)的生成class文件并將其加載到JVM中,我們稱(chēng)這種行為叫做“代碼生成技術(shù)”,在Spring Boot中沒(méi)有使用任何代碼生成技術(shù)。此外,SpringBoot不像傳統(tǒng)Spring應(yīng)用那樣配置大量的XML文件,除了使用一個(gè)application.properties配置文件,SpringBoot再無(wú)其他配置文件了,而且所有插件相關(guān)的配置也在這個(gè)配置文件中。
Spring Boot的配置都在application.properties文件中,但并不意味著在SpringBoot應(yīng)用就必須包含該文件。實(shí)際上,配置文件中包含了大量的配置項(xiàng),而許多配置項(xiàng)都有默認(rèn)值,很多配置項(xiàng)我們其實(shí)都不用去修改,使用其默認(rèn)值就行,這類(lèi)行為叫做“自動(dòng)化配置”,我們只需要使用SpringBoot提供的相關(guān)注解就能啟動(dòng)具體特性。這一特性實(shí)際上是由SpringBoot提供的一些列@ConditionalOnXxx條件注解來(lái)實(shí)現(xiàn)的,而底層使用了Spring4.0的Condition接口。
SpringBoot是為了生產(chǎn)級(jí)Spring應(yīng)用而生的,提供了大量的生產(chǎn)級(jí)特性,例如核心指標(biāo)、健康檢查、外部配置等,這類(lèi)技術(shù)對(duì)微服務(wù)架構(gòu)想當(dāng)有價(jià)值。例如,核心指標(biāo)的是我們可以隨時(shí)給SpringBoot發(fā)送/metrics請(qǐng)求,隨后可以獲取一個(gè)JSON數(shù)據(jù),包括內(nèi)存、Java堆、類(lèi)加載、處理器、線(xiàn)程池等信息。我們還能再Java命令行上直接運(yùn)行SpringBoot應(yīng)用,并帶上外部配置參數(shù),這些參數(shù)將覆蓋已有的默認(rèn)值配置參數(shù)。甚至我們還能通過(guò)發(fā)送一個(gè)URL請(qǐng)求去關(guān)閉SpringBoot應(yīng)用,在自動(dòng)化技術(shù)中會(huì)有一定的幫助。
SpringBoot提供了大量“開(kāi)箱即用”的插件,我們只需要添加一段Maven依賴(lài)配置即可開(kāi)啟使用。這些插件在SpringBoot的世界里有一個(gè)優(yōu)雅的名字,叫做Starter。每個(gè)Starter可能都會(huì)有自己的配置項(xiàng),而這些配置項(xiàng)都是可以在application.properties文件中統(tǒng)一配置。
SpringBoot是一個(gè)典型的“核心+插件”的系統(tǒng)架構(gòu),核心包含Spring最核心的功能,其他更多的功能都是通過(guò)插件的方式來(lái)擴(kuò)展。那么SpringBoot擁有哪些插件呢?
SpringBoot相關(guān)插件
SpringBoot官方提供了大量插件,涉及面非常的廣,包括Web、SQL、NoSQL、安全、驗(yàn)證、緩存、消息隊(duì)列、分布式事物、模板引擎、工作流等,還提供了Cloud、Social、Ops方面的支持。
此外,SpringBoot對(duì)某項(xiàng)技術(shù)提供了多種選型,比如:
- SQL API ------- JDBC、JPA、JOOQ等;
- 關(guān)系數(shù)據(jù)庫(kù)------MySQL、PostgreSQL等;
- 內(nèi)存數(shù)據(jù)庫(kù)------H2、HSQLDB、Derby等;
- NoSQL數(shù)據(jù)庫(kù)-----Redis、MongoDB、Cassandra等;
- 消息隊(duì)列-------RabbitMQ、Bitronix等;
- 模板引擎------Velocity、Freemarker、Mustache等。
官方還提供了一個(gè)名為“Spring Initialzr”的在線(xiàn)代碼生成器,我們只需選擇自己想要的插件,就能一件下載對(duì)應(yīng)的代碼框架。就連IDEA也支持了Spring Initialzr。
SpringBoot擁有非常強(qiáng)大的插件體系,如此之多的插件,讓我們?cè)陂_(kāi)發(fā)應(yīng)用程序的時(shí)候如虎添翼,我們可以?xún)?yōu)先從這個(gè)“插件庫(kù)”中選擇插件,如果有的插件不夠用或者不合適,我們還可以實(shí)現(xiàn)自己想要的插件。
SpringBoot所提供的功能強(qiáng)大且實(shí)用,但SpringBoot并非適合開(kāi)發(fā)所有應(yīng)用場(chǎng)景。那么那些場(chǎng)景比較適合使用SpringBoot呢?
SpringBoot的應(yīng)用場(chǎng)景
傳統(tǒng)的WebMVC架構(gòu)比較適合用SpringBoot來(lái)開(kāi)發(fā),View層可以使用JSP或者模板引擎(例如Velocity),從View層發(fā)送請(qǐng)求到Spring的controller,通過(guò)操作數(shù)據(jù)庫(kù)并將獲取的數(shù)據(jù)封裝進(jìn)Model,最后將Model返回到View中。總之,SpringMVA能夠做到的,SpringBoot都能做到,因?yàn)镾pringBoot在更高層對(duì)SpringMVC進(jìn)行了封裝。
在前后端分離的架構(gòu)中,后端可基于SpringBoot開(kāi)發(fā)REST API,前端通過(guò)REST API來(lái)獲取JSON數(shù)據(jù),從而進(jìn)行視圖渲染,生成最終的HTML界面。實(shí)際上,移動(dòng)端H5應(yīng)用我們也可以采用類(lèi)似的方式來(lái)實(shí)現(xiàn)。在前后端分離的架構(gòu)中,可能會(huì)遇到“跨域問(wèn)題”,SpringBoot對(duì)跨域問(wèn)題也做了非常好的支持。
微服務(wù)架構(gòu)要求我們對(duì)產(chǎn)品功能進(jìn)行細(xì)粒度劃分,且每個(gè)微服務(wù)之間需要使用輕量級(jí)技術(shù)進(jìn)行通信,因此每個(gè)微服務(wù)需要對(duì)外提供輕量級(jí)API接口(例如REST API),使用SpringBoot發(fā)布REST API是最方便的。此外,SpringBoot還擁有一系列的生產(chǎn)級(jí)特性,他與微服務(wù)是天作之和。
現(xiàn)在大家應(yīng)該了解到SpringBoot是什么以及他可以做什么了,下面通過(guò)幾個(gè)簡(jiǎn)單的示例來(lái)展示一下SpringBoot的基本用法,目標(biāo)是讓大家快速上手。
如何使用SpringBoot框架
不僅對(duì)SpringBoot框架,其實(shí)學(xué)習(xí)任何框架的第一步都是搭建開(kāi)發(fā)環(huán)境,然后嘗試寫(xiě)一個(gè)“helloworld”應(yīng)用程序并試圖讓他跑起來(lái),最后才去探索他的若干特性。我們現(xiàn)在就來(lái)搭建一個(gè)SpringBoot開(kāi)發(fā)框架,充分體驗(yàn)一下SpringBoot給我們開(kāi)發(fā)帶來(lái)的快樂(lè)。
搭建SpringBoot開(kāi)發(fā)框架
- 使用IDEA創(chuàng)建一個(gè)Maven項(xiàng)目
我們?cè)買(mǎi)DEA中創(chuàng)建一個(gè)Project,名稱(chēng)為msa-hello,對(duì)應(yīng)的Maven坐標(biāo)為: - groupId:demo.msa
- artifactId:msa-hello
- version:1.0.0
當(dāng)然,大家也可以按照自己的喜好來(lái)命名,不一定非要與文中相似。
在pom文件中添加如下的SpringBoot的Maven配置:
通過(guò)上面的配置,我們的應(yīng)用才算是SpringBoot應(yīng)用,該配置會(huì)繼承大量的SpringBoot插件,但這些插件都未啟用,我們下面要做的就是啟用對(duì)我們有用的插件。例如,啟用Web插件,需要繼續(xù)添加如下的配置:
<dependencies><dependency><groupId>org.springframwork.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency> </dependencies>以上配置只需要配置groupId和artifactId即可,因?yàn)樵诟竝om中已經(jīng)配置了version了。
SpringBoot提供了相應(yīng)的Maven插件,只需要通過(guò)下面的配置即可使用:
需要說(shuō)明的是spring-boot-maven-plugin并不是SpringBoot應(yīng)用必須要求的,但仍建議大家使用,下面會(huì)講到他的具體意義。
現(xiàn)在SpringBoot開(kāi)發(fā)框架已經(jīng)搭建完畢,下面要做的就是開(kāi)發(fā)一些簡(jiǎn)單的功能來(lái)進(jìn)一步體驗(yàn)它,我們就以“helloworld”應(yīng)用程序來(lái)講解。
開(kāi)發(fā)一個(gè)簡(jiǎn)單的SpringBoot應(yīng)用程序
由于msa-hello應(yīng)用的groupId是demo.msa,因此我們需要定義一個(gè)名稱(chēng)也為demo.msa的包名,所有的代碼在該包下。
import org.spingframework.boot.autoconfigure.SpringBootApplication;@SpringBootApplication public class HelloApplication{public static void main(String[] args){SpringApplication.run(HellowApplication.class,args);} }以上HelloApplication類(lèi)并不是一個(gè)普通的類(lèi),他必須擁有一下兩個(gè)特點(diǎn):
下面我們做一個(gè)簡(jiǎn)單的REST API,例如GET:/hello,表示API的請(qǐng)求方法是GET請(qǐng)求,請(qǐng)求路徑是/hello,該API只返回一個(gè)”hello“字符串。
直接在HellowApplication類(lèi)中添加如下代碼:
為了對(duì)外發(fā)布REST API,我們只需要做三件事情:
當(dāng)然,如果我們只考慮“單一職責(zé)原則”,那么應(yīng)該將@RestController與@RequestMapping注解以及所涉及的代碼從HelloApplication類(lèi)中抽取出來(lái),將其放入單獨(dú)的Controller類(lèi)中,就像下面這樣:
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController;@RestController public class HelloController{@RequestMapping(method=RequestMapping.GET,path="/hello")public String hello(){return "hello";} }對(duì)于GET請(qǐng)求,我們可以簡(jiǎn)化@RequestMapping的寫(xiě)法,可以直接寫(xiě)成下面你的方法:
@RequestMapping(“/hello”)
因?yàn)槟J(rèn)的REST API就是GET請(qǐng)求。
現(xiàn)在一個(gè)簡(jiǎn)單的SpringBoot應(yīng)用程序就開(kāi)發(fā)完畢了。
我們可以通過(guò)Maven編譯并打包,在target目錄下將看到一個(gè)msa-hello-1.0.0.jar的文件,使用解壓工具打開(kāi)jar文件,將看到如下圖的目錄結(jié)構(gòu)。
- demo
- lib
- META-INF
- org
demo目錄下存放項(xiàng)目的class文件,lib目錄下存放項(xiàng)目運(yùn)行所依賴(lài)的jar包,META-INF目錄下存放Maven構(gòu)件所生成的相關(guān)文件,其中包含一個(gè)MANIFEST.MF文件,該文件內(nèi)容如下:
Manifest-Version:1.0 Implementation-Title:msa-hello Implementation-Version:1.0.0 Archiver-Version:Plexus Archiver Built-By:huangyong Start-Class:demo.msa.HelloApplication Implementation-Vendor-Id:demo.msa Spring-Boot-Version:1.3.3.RELEASE Created-By:Apache Maven 3.0.5 Build-Jdk:1.8.0_60 Implementation-Vendor:Pivotal Software, Inc. Main-Class:org.springframework.boot.loader.JarLauncher注意最后一行的Main-Class,它表示運(yùn)行jar包所需的Main類(lèi),即提供main()方法所在的類(lèi)。可見(jiàn),其并非我們編寫(xiě)的HelloApplication類(lèi),而是SpringBoot提供的JarLauncher類(lèi),該類(lèi)存放在jar包中的org目錄下。
此外,我們也可以在IDEA中查看該項(xiàng)目jar包的依賴(lài)關(guān)系。
運(yùn)行SpringBoot應(yīng)用程序
運(yùn)行SpringBoot應(yīng)用程序非常簡(jiǎn)單,我們可以根據(jù)實(shí)際情況,自由的選擇使用一下三種方法來(lái)運(yùn)行SpringBoot應(yīng)用程序。
在IDEA中直接運(yùn)行
直接在IDEA中執(zhí)行HelloApplication類(lèi)來(lái)啟動(dòng)SpringBoot應(yīng)用程序。
該方式有利于程序的debug,在日常開(kāi)發(fā)過(guò)程中優(yōu)先使用這種方式,但debug方式肯定比run方式啟動(dòng)的慢一些。
使用Maven運(yùn)行
可以使用Maven命令來(lái)運(yùn)行
mvn spring-boot:run
以上就用到了spring-boot-maven-plugin插件,我們運(yùn)行的是spring-boot插件的run目標(biāo),此外他還提供了spring-boot:start與spring-boot:stop目標(biāo)。
使用Java命令運(yùn)行
使用java命令行來(lái)運(yùn)行SpringBoot應(yīng)用程序:
java -jar msa-hello-1.0.0.jar
該方式看似不需要spring-boot-maven-plugin插件,但實(shí)際上是需要的。通過(guò)該插件所打的jar包不是一般的jar包,它比一般的jar包體積要大許多,因?yàn)樗\(yùn)行該jar包所需要的其他jar包。
可見(jiàn),SpringBoot還是很容易上手的,只要會(huì)Maven并熟悉Spring的基本用法,就能夠迅速搭建SpringBoot框架。后面的內(nèi)容中還會(huì)繼續(xù)擴(kuò)展此框架,讓他成為一款真正的輕量級(jí)微服務(wù)開(kāi)發(fā)框架。
除了SpringBoot的基本特性外,還有一些更高級(jí)的特性,尤其是SpringBoot提供的生產(chǎn)級(jí)特性,也對(duì)我們的開(kāi)發(fā)非常的有幫助。
SpringBoot的生產(chǎn)級(jí)特性
SpringBoot提供了大量開(kāi)箱即用的插件,其中有一個(gè)名為Actuator的插件提供了大量生產(chǎn)級(jí)特性,可以通過(guò)Maven配置使用該插件。
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-actuator</artifactId> </dependency>添加Maven依賴(lài)后,我們重啟SpringBoot應(yīng)用程序,就能開(kāi)啟端點(diǎn)生產(chǎn)級(jí)特性了。
端點(diǎn)
SpringBoot的Actuator插件提供了一系列的http請(qǐng)求,我們可以發(fā)送相應(yīng)的請(qǐng)求,來(lái)獲取SpringBoot應(yīng)用程序的相關(guān)信息。這些HTTP請(qǐng)求都是GET類(lèi)型的,而且不帶任何參數(shù),他們就是所謂的“端點(diǎn)”,也許他的英文“Endpoint”更容易理解,SpringBoot的Actuator插件默認(rèn)提供了一下端點(diǎn)。
| autoconfig | 獲取自動(dòng)配置信息 |
| beans | 獲取Spring Bean基本信息 |
| configprops | 獲取配置項(xiàng)信息 |
| dump | 獲取當(dāng)前線(xiàn)程基本信息 |
| env | 獲取環(huán)境變量信息 |
| health | 獲取健康檢查信息 |
| info | 獲取應(yīng)用基本信息 |
| metrics | 獲取性能指標(biāo)信息 |
| mappings | 獲取請(qǐng)求映射信息 |
| trace | 獲取請(qǐng)求調(diào)用信息 |
例如我們?cè)贋g覽器地址發(fā)送/metrics請(qǐng)求時(shí),會(huì)看到性能指標(biāo)的返回結(jié)果。
默認(rèn)情況下,以上的端點(diǎn)都是開(kāi)啟的,我們隨時(shí)可以訪問(wèn),根據(jù)實(shí)際情況我們可以自由控制哪些端點(diǎn)需要啟用,哪些端點(diǎn)需要停用。也可以全部停用、啟用,這些端點(diǎn)的控制在application.properties文件中配置。一下給一些實(shí)際的操作。
關(guān)閉metrics端點(diǎn)
endpoints.metrics.enabled=false
在瀏覽器訪問(wèn)/metircs的時(shí)候,看到的將是“Whitelable Error Page"的錯(cuò)誤頁(yè)面,對(duì)應(yīng)的HTTP狀態(tài)碼為404(not found)。
關(guān)閉所有端點(diǎn),僅開(kāi)啟metrics端點(diǎn)
endpoints.enable=false
endpoints.metrics.enabled=true
現(xiàn)在只有metrics端點(diǎn)是啟用的,訪問(wèn)其他的端點(diǎn)會(huì)報(bào)錯(cuò)。
修改metrics端點(diǎn)的名稱(chēng)
endpotins.metrics.id=performance
這樣我們就可以通過(guò)/performance請(qǐng)求來(lái)訪問(wèn)以前的mertics端點(diǎn)了,此時(shí)繼續(xù)發(fā)送/mertics,會(huì)看到報(bào)錯(cuò)信息。
修改metrics端點(diǎn)的請(qǐng)求路勁
endpoints.mertics.path=/endpotins/mertics
通過(guò)以上的配置,我們需要在發(fā)送/endpoints/mertics請(qǐng)求后才能訪問(wèn)metrics端點(diǎn)。
如果我們想知道SpringBoot為我們提供了那些端點(diǎn),應(yīng)該如何做呢?
SpringBoot的HATEOAS插件為我們提供了幫助,實(shí)際上,HATEOAS是一個(gè)超媒體(Hypermedia)技術(shù),他也是REST應(yīng)用程序架構(gòu)的一種約束。通過(guò)他可以匯總端點(diǎn)信息,包括各個(gè)端點(diǎn)的名稱(chēng)與鏈接。開(kāi)啟HATEOAS插件,只需要添加一下依賴(lài):
此后,我們將擁有actuator端點(diǎn),當(dāng)我們發(fā)送/actuator請(qǐng)求后,將看到所有的端點(diǎn)及其鏈接方式。
健康檢查
在SpringBoot所提供的端點(diǎn)中,有一個(gè)名為health的端點(diǎn),用于查看當(dāng)前以你該用的運(yùn)行狀態(tài),即應(yīng)用的健康狀況。檢查應(yīng)用的健康狀況,我們簡(jiǎn)稱(chēng)為“健康檢查”。
當(dāng)我們?cè)跒g覽器發(fā)送/health請(qǐng)求后,將看到應(yīng)用的健康情況,可以看到應(yīng)用的運(yùn)行狀態(tài)、磁盤(pán)空間情況等信息。
實(shí)際上,SpringBoot包含了許多內(nèi)置的健康檢查功能,每項(xiàng)功能對(duì)應(yīng)具體的健康檢查指標(biāo)類(lèi)(HealthIndicator),如下所示:
| ApplicationHealthIndicator | 檢查應(yīng)用運(yùn)行狀態(tài)(對(duì)應(yīng)status部分) |
| DiskSpaceHealthIndicator | 查看磁盤(pán)空間(對(duì)應(yīng)diskSpace部分) |
| DataSourceHealthIndicator | 檢查數(shù)據(jù)庫(kù)連接 |
| MailHealthIndicator | 檢查郵箱服務(wù)器 |
| JmsHealthIndicator | 檢查JMS代理 |
| RedisHealthIndicator | 檢查Redis服務(wù)器 |
| MongoHealthIndicator | 檢查MongoDB數(shù)據(jù)庫(kù) |
| CassandraHealthIndicator | 檢查Cassandra數(shù)據(jù)庫(kù) |
| RabbitHealthIndicator | 檢查Rabbit服務(wù)器 |
| SolrHealthIndicator | 檢查Solr服務(wù)器 |
| ElasticsearchHealthIndicator | 檢查ElasticSearch集群 |
我們添加相關(guān)的SpringBoot插件后,即可開(kāi)啟對(duì)應(yīng)的健康檢查功能。默認(rèn)情況下只有ApplicationHealthIndicator與DiskSpaceHealthIndicator是啟用的。我們還可以通過(guò)management.health.defaults.enabled屬性來(lái)控制是否開(kāi)啟健康檢查特性,默認(rèn)為true,表示是開(kāi)啟的。
雖然SpringBoot提供的健康檢查已經(jīng)很全面了,但如果我們還覺(jué)得不夠用的話(huà),也可以實(shí)現(xiàn)自己的健康檢查,需實(shí)現(xiàn)ort.springframework.boot.actuate.health.HealthIndicator接口,并覆蓋health()方法即可。
實(shí)際上,我們可以利用健康檢查特性來(lái)開(kāi)發(fā)一個(gè)微服務(wù)系統(tǒng)監(jiān)控平臺(tái),用于獲取每個(gè)微服務(wù)的運(yùn)行狀態(tài)與性能指標(biāo)。當(dāng)然也有現(xiàn)成的解決方案,比如spring-boot-admin,它就是一款基于Spring Boot的開(kāi)源監(jiān)控平臺(tái)。
spring-boot-admin項(xiàng)目地址:https://github.com/codecentric/spring-boot-admin。
應(yīng)用基本信息
除了health端點(diǎn)以外,還有一個(gè)名為info的端點(diǎn),我們可以用它來(lái)獲取SpringBoot應(yīng)用程序的基本信息,比如應(yīng)用程序的名稱(chēng)、描述、版本等。當(dāng)我們發(fā)送/info請(qǐng)求時(shí),卻獲取不到任何數(shù)據(jù),因?yàn)槲覀兡壳斑€沒(méi)有配置任何的應(yīng)用信息。
應(yīng)用基本信息的相關(guān)配置都是以info為前綴的配置項(xiàng),就像下面這樣:
info.app.name=Hello
info.app.description=This is a demo of Spring Boot
info.app.version=1.0.0
隨后我們就可以通過(guò)瀏覽器獲取上面的配置信息了
跨域
使用SpringBoot開(kāi)發(fā)的REST API是想當(dāng)容易的,一般情況下,REST API是獨(dú)立部署的,如果WebUI也進(jìn)行獨(dú)立部署,那么REST API與WebUI可能在不同的域名部署,從WebUI發(fā)送的AJAX請(qǐng)求去調(diào)用REST API時(shí)就會(huì)遇到“跨域問(wèn)題”,在瀏覽器控制臺(tái)會(huì)報(bào)錯(cuò):“No’Access-Control-Allow-Origin’header is present on the requested resource.”,因?yàn)锳JAX的安全限制,它是不支持跨域的,我們需要通過(guò)技術(shù)手段來(lái)解決這個(gè)問(wèn)題。
曾今我們可以使用JSONP(JSON with Padding)來(lái)實(shí)現(xiàn)跨域問(wèn)題,簡(jiǎn)單的來(lái)說(shuō)就是客戶(hù)端發(fā)送一個(gè)AJAX請(qǐng)求,并在請(qǐng)求參數(shù)后面添加一個(gè)callback參數(shù),指向一個(gè)JS函數(shù)(成為callback回調(diào)函數(shù))。服務(wù)器返回了一個(gè)JavaScript函數(shù),該函數(shù)將JSON數(shù)據(jù)做了一個(gè)封裝(Padding),就像這樣callback({…});,這樣我們只需要在客戶(hù)端上定義一個(gè)callback回調(diào)函數(shù),就能獲取從服務(wù)器端返回的JSON數(shù)據(jù)了。
JSONP看似簡(jiǎn)單好用,實(shí)際上它也有非常明顯的限制,只支持GET請(qǐng)求,如果我們需要使用JSON技術(shù)發(fā)送其他請(qǐng)求(比如POST)就不太可能了。當(dāng)然也可以通過(guò)其他手段來(lái)實(shí)現(xiàn),比如iframe,但該方法過(guò)于繁瑣,多年前早已棄用。現(xiàn)在,我們優(yōu)先選擇的是更加輕量級(jí)的CORS(Cross-Origin Resource Sharing)來(lái)實(shí)現(xiàn)跨域問(wèn)題,他目前也加入到了W3C規(guī)范中了,而且當(dāng)前主流瀏覽器都能很好的支持該規(guī)范。
關(guān)于CORS理論知識(shí)可以參見(jiàn)它的官方網(wǎng)站:http://www.w3.org/TR/cors/。
SpringBoot很好的支持了CORS,我們只需要添加關(guān)于CORS的端點(diǎn)配置就能隨時(shí)開(kāi)啟該特性,默認(rèn)情況下他是禁用的,通過(guò)以下配置可以使用:
此外,也可以在HelloApplication類(lèi)上加上@CorssOrigin注解來(lái)實(shí)現(xiàn)跨域,就像這樣:
import org.springframework.web.bind.annotation.CrossOrigin;@SpringBootApplication @RestController @CrossOrigin public class HelloApplication{... }在@CrossOrigin注解中也提供了origins、methods等屬性,我們可以自行配置。當(dāng)然,我們也可以使用如下方法配置CORS下相關(guān)屬性。
import org.springframework.context.annotation.Configuration; import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.EnableWebMvc; import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;@Configuration @EnableWebMvc public class WebConfig extends WebMvcConfigurerAdapter{@Overridepublic void addCorsMappings(CorsRegistry registry){registry.addMapping("/**").allowedOrigins("http://www.xxx.com").allowMethods("GET","POST","PUT","DELETE");}}實(shí)際上,在Spring4.2以后才開(kāi)始支持CORS。可以閱讀Spring官方博客上的一篇關(guān)于CORS的文章。
CORS support in Spring Framework :http://spring.io/blog/2015/06/08/cors-support-in-spring-framework。
外部配置
我們可以在application.properies配置文件中指定SpringBoot的相關(guān)配置項(xiàng),還可以@…@占位符獲取Maven資源過(guò)濾的相關(guān)屬性,此外還可以通過(guò)外部配置覆蓋SpringBoot配置項(xiàng)的默認(rèn)值,可以先從以下位置獲取:
以“java命令行參數(shù)”為例,我們?cè)谶\(yùn)行SpringBoot的jar包時(shí),可以通過(guò)以下方式指定外部配置:
java -jar xxx.jar --server.port=18080
通過(guò)上面的–server.port配置,可以將默認(rèn)的8080端口改為18080
遠(yuǎn)程監(jiān)控
SpringBoot提供了一個(gè)名為Remote Shell的插件,允許我們可以通過(guò)ssh遠(yuǎn)程鏈接正在運(yùn)行中的SpringBoot應(yīng)用程序,只需要添加以下配置:
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-remote-shell</artifactId> </dependency>當(dāng)我們啟動(dòng)SpringBoot應(yīng)用程序的時(shí)候,將在控制臺(tái)看到監(jiān)控相關(guān)的信息。
微服務(wù)網(wǎng)關(guān)
在上一章節(jié)中,我們使用SpringBoot開(kāi)發(fā)了一個(gè)簡(jiǎn)單的服務(wù),也討論了前后端不在一個(gè)域名下會(huì)產(chǎn)生的“跨域問(wèn)題”,好在SpringBoot已經(jīng)提供了CORS跨域特性,我們才能在前端自由的調(diào)用后端發(fā)布的服務(wù)。這樣的架構(gòu)看似不錯(cuò),但似乎還有點(diǎn)問(wèn)題,比如,每個(gè)微服務(wù)可能部署在不同的IP與端口上,前端必須知道后端服務(wù)部署的位置,那么能否做到前端無(wú)須知道后端具體服務(wù)的具體細(xì)節(jié),通過(guò)一個(gè)統(tǒng)一的方式去調(diào)用后端服務(wù)呢?其實(shí)我們需要的是一個(gè)“API網(wǎng)關(guān)”,前端所有的請(qǐng)求都會(huì)進(jìn)入該網(wǎng)關(guān),通過(guò)網(wǎng)關(guān)去調(diào)用后端的服務(wù)。
本章我們使用Node.js搭建一個(gè)統(tǒng)一的網(wǎng)關(guān),我們?cè)谖⒎?wù)架構(gòu)中稱(chēng)其為API Gateway(API 網(wǎng)關(guān))或Service Gateway(服務(wù)網(wǎng)關(guān)),所有從前端發(fā)送的請(qǐng)求都會(huì)到這個(gè)網(wǎng)關(guān)上,Node.js通過(guò)“方向代理”技術(shù)來(lái)調(diào)用后端發(fā)布的服務(wù)。不過(guò)在完成這件事之前,我們有必要搞清楚Node.js究竟是什么。
Node.js是什么
從字面上的意思來(lái)看,Node.js更像是一款JavaScript框架,其實(shí)不完全是這樣的,我們千萬(wàn)不要被她的名字所誤導(dǎo)。
Node.js官網(wǎng)
在首頁(yè)上我們就可以清楚的看到對(duì)Node.js的詮釋:
Node.js是一個(gè)基于ChromeV8引擎的JavaScript運(yùn)行環(huán)境,它使用了一個(gè)“事件驅(qū)動(dòng)”且“異步非租塞I/O”的模型使其輕量且高效,Node.js的包管理器NPM是全球最大的開(kāi)源庫(kù)生態(tài)系統(tǒng)。
針對(duì)Node.js的定義,我們想稍微補(bǔ)充說(shuō)明一下:
此外,Node.js非常的小巧,但模塊體系卻非常的龐大,我們可以在NPM官網(wǎng)上尋找對(duì)自己有幫助的模塊。
對(duì)于“高并發(fā)”我們一般采用的“多線(xiàn)程”技術(shù)來(lái)解決,也就是說(shuō),創(chuàng)建大量的線(xiàn)程來(lái)處理更多并發(fā)的請(qǐng)求。而創(chuàng)建管理線(xiàn)程是一件非常消耗內(nèi)存的事情,且在CPU核心數(shù)不多的情況下,會(huì)出現(xiàn)大量的上下文切換現(xiàn)象,因?yàn)镃PU需要不斷的從一堆線(xiàn)程中選擇一個(gè)線(xiàn)程來(lái)處理它的內(nèi)部的指令。由此說(shuō)來(lái),該方案在內(nèi)存較大且CPU強(qiáng)的環(huán)境下還是想當(dāng)不錯(cuò)的選擇,Java提供的就是這樣的解決方案。
Node.js另辟蹊徑,采用“單線(xiàn)程”技術(shù)來(lái)解決高并發(fā)的問(wèn)題,在內(nèi)部提供一個(gè)“事件驅(qū)動(dòng)”與“異步非阻塞I/O”模型,讓我們可以在硬件資源相對(duì)普通的條件下也能扛得住高并發(fā)的壓力。
解決高并發(fā),實(shí)際上就是解決I/O問(wèn)題。我們通常所說(shuō)的I/O問(wèn)題其實(shí)包括兩方面,即“網(wǎng)絡(luò)I/O”與“磁盤(pán)I/O”。java的I/O模型是同步的,直到后來(lái)NIO(非阻塞I/O)技術(shù)的出現(xiàn),Java編程模型才有了異步的特性,而Node.js與生俱來(lái)就有異步的特性,而且利用JavaScript的回調(diào)函數(shù)可以使異步編程變得格外的簡(jiǎn)單。
可見(jiàn)Node.js只是利用了JavaScript語(yǔ)言的特性去完成服務(wù)端的事情,甚至有人提到,幾乎所有的服務(wù)端開(kāi)發(fā)語(yǔ)言(例如java、PHP、Python等)可以做的,它都能做到,而且做得更好。雖然我們對(duì)這一點(diǎn)實(shí)際上是保持懷疑態(tài)度的,但不得不否認(rèn)Node.js確實(shí)是一項(xiàng)顛覆性的技術(shù)。它讓JavaScript自AJAX以后迎來(lái)了第二次高潮,而且這次高潮明顯蓋過(guò)上一次。
Node.js快速入門(mén)
下面我們通過(guò)一段簡(jiǎn)單的Node.js代碼來(lái)快速的了解他的基本用法
var fs = require('fs');fs.readFile('/etc/hosts',function(err,data){if(err)throw err;console.log(data.toString()); })首先我們通過(guò)Node.js內(nèi)置的require()函數(shù)來(lái)引入FS模塊(它是Node.js內(nèi)置的模塊),同時(shí)創(chuàng)建了一個(gè)名為fs的變量。隨后我們通過(guò)調(diào)用fs變量的readFile()函數(shù)來(lái)讀取/etc/hosts文件,但此函數(shù)并沒(méi)有返回值。實(shí)際上,Node.js會(huì)創(chuàng)建一個(gè)讀取文件的事件,并立刻將該事件加入到事件隊(duì)列中,當(dāng)前線(xiàn)程并不會(huì)阻塞在這里。理論上后續(xù)不管有多少線(xiàn)程都會(huì)進(jìn)來(lái)并產(chǎn)生一系列事件,這些事件會(huì)加入到同樣的事件隊(duì)列中,他們會(huì)在事件隊(duì)列中進(jìn)行循環(huán),一旦某個(gè)事件被觸發(fā)(比如文件讀取成功),則會(huì)執(zhí)行后面定義的回調(diào)函數(shù)(比如readFile()函數(shù)的第二個(gè)參數(shù),回調(diào)函數(shù)一般是最后一個(gè)參數(shù))。
可見(jiàn)Node.js完全利用了JavaScript的編程范式,采用回調(diào)函數(shù)來(lái)實(shí)現(xiàn)異步行為。但也有很多人不太習(xí)慣這種異步的API,往往跳同步API會(huì)讓人覺(jué)得更加的自然,畢竟調(diào)用某函數(shù)拿到某返回值,這是非常容易理解的。
實(shí)際上,Node.js也提供了同步API。針對(duì)以上文件讀取的示例,我們用同步API來(lái)實(shí)現(xiàn),代碼如下:
我們調(diào)用JS變量的readFileSync()函數(shù)可以通過(guò)同步的方式來(lái)獲取文件的內(nèi)容,此時(shí)不在出現(xiàn)回到函數(shù),而是在調(diào)用API后直接拿到函數(shù)的返回值。
Node.js上手過(guò)程其實(shí)非常容易,代碼風(fēng)格也非常的優(yōu)雅。既然這么好的武器,我們應(yīng)該在那些場(chǎng)景下考慮使用它呢?
Node.js的應(yīng)用場(chǎng)景
Node.js是針對(duì)“實(shí)時(shí)Web”應(yīng)用程序而開(kāi)發(fā)的,非常適合為了實(shí)時(shí)性較強(qiáng)且并發(fā)量較大的應(yīng)用場(chǎng)景。
我們?nèi)粘?吹降膽?yīng)用一般分為兩大類(lèi),即“CPU密集型”和“I/O密集型”。前者對(duì)CPU要求較高,需要一個(gè)強(qiáng)大的計(jì)算過(guò)程,需要較多的CPU核心來(lái)完成具體的業(yè)務(wù),比如股票交易系統(tǒng)、數(shù)據(jù)分析系統(tǒng)等。后者更加偏重于對(duì)I/O的要求,常會(huì)有頻繁的網(wǎng)絡(luò)傳輸或者磁盤(pán)存儲(chǔ)等現(xiàn)象,比如高并發(fā)網(wǎng)站、實(shí)時(shí)Web系統(tǒng)等。
由于Node.js采用的是單線(xiàn)程模型,肯定不適合做CPU密集型的應(yīng)用,否則CPU資源將被長(zhǎng)期消耗,從而影響整個(gè)系統(tǒng)的吞吐率。因此開(kāi)發(fā)I/O密集型才是Node.js的強(qiáng)項(xiàng),它充分利用了事件驅(qū)動(dòng)與異步非阻塞技術(shù),能支持大量的并發(fā)鏈接,從而提高整個(gè)系統(tǒng)的吞吐率。
尤其在Web方面,Node.js有著強(qiáng)大的優(yōu)勢(shì),他內(nèi)置了一個(gè)HTTP服務(wù)器(實(shí)際上是一個(gè)HTTP模塊),性能與穩(wěn)定性方面都與流行的Nginx不分伯仲(業(yè)界有人做過(guò)這方面的測(cè)試)。此外,基于Node.js的模塊體系非常強(qiáng)大,其中不乏優(yōu)秀的Web框架,Express就是這樣的框架,它將基于Node.js的Web應(yīng)用開(kāi)發(fā)過(guò)程變得非常的簡(jiǎn)單與高效,下文我們也會(huì)了解到他。
Express官網(wǎng)
Node.js是為實(shí)時(shí)性而生的,Web聊天室正符合這類(lèi)實(shí)時(shí)性的要求。使用Node.js集成Socket.IO可以輕松的搭建一個(gè)Web Socket服務(wù)器,此外,Socket.IO也提供了客戶(hù)端JS類(lèi)庫(kù),我們可以在短時(shí)間內(nèi)開(kāi)發(fā)一套Web聊天室。
Socket.IO官網(wǎng)
當(dāng)我們打開(kāi)瀏覽器,進(jìn)入Web聊天室時(shí),客戶(hù)端會(huì)主動(dòng)與服務(wù)器建立一個(gè)WebSocket鏈接,而且這是一個(gè)長(zhǎng)鏈接。在同一時(shí)段內(nèi),可能會(huì)有很多人進(jìn)入聊天室,此時(shí)會(huì)有多個(gè)客戶(hù)端與服務(wù)器建立WebSocket鏈接。當(dāng)某人輸入一段文字,然后單擊“發(fā)送”按鈕,此時(shí)會(huì)將消息通過(guò)WebSocket協(xié)議發(fā)送服務(wù)端。當(dāng)服務(wù)端收到消息后,立即通過(guò)WebSocket廣播的方式,將消息推送到所有已經(jīng)建立鏈接的客戶(hù)端。
在服務(wù)器端處理客戶(hù)端發(fā)送過(guò)來(lái)的大量消息時(shí),會(huì)利用Node.js的異步特性,當(dāng)收到消息后,將產(chǎn)生一個(gè)事件并將其加入到隊(duì)列中,同時(shí)立刻返回客戶(hù)端,當(dāng)事件觸發(fā)后,立即將消息廣播到所有的客戶(hù)端。
使用Node.js可以輕松開(kāi)發(fā)命令行工具,我么可以寫(xiě)一段Node.js程序,通過(guò)NPM提供的命令將其安裝到操作系統(tǒng)中,隨時(shí)可以在命令控制行控制臺(tái)上輸入該命令來(lái)運(yùn)行Node.js程序。
Node.js可以通過(guò)異步的方式處理大量的并發(fā)請(qǐng)求,他可以作為服務(wù)端應(yīng)用程序的代理,起到充當(dāng)HTTP代理服務(wù)器的作用,類(lèi)似于Nginx、Apache等。
在Node.js的NPM中同樣存在這樣的HTTP代理模塊,我們只需要將其引入進(jìn)來(lái),并使用該模塊提供的API,就能快速的搭建一個(gè)HTTP代理服務(wù)器。
如何使用Node.js
Node.js屬于上手非常快的技術(shù),學(xué)習(xí)他沒(méi)有太高的難度,只要我們會(huì)寫(xiě)JavaScript,并且了解一些基本的編程思想就能快速使用了。但他所設(shè)計(jì)的面還是挺廣的,如果深入掌握他,還是需要一個(gè)過(guò)程的。
為了讓大家快速的學(xué)會(huì)使用Node.js,我們不放從安裝開(kāi)始。
安裝Node.js
官方網(wǎng)站提供了不同操作系統(tǒng)下的Node.js安裝包,我們只需要根據(jù)自己的需求,下載對(duì)應(yīng)的安裝包即可。
當(dāng)然,如果大家使用的是Mac操作系統(tǒng),還可以使用更加簡(jiǎn)單的方法安裝Node.js。我們可以安裝一個(gè)名為Homebrew的軟件用它來(lái)暗轉(zhuǎn)Node.js程序,當(dāng)然其他流行的命令行程序也能通過(guò)它來(lái)安裝。
Hombrew官網(wǎng):http://brew.sh/。
使用Node.js開(kāi)發(fā)WEB應(yīng)用
第一步,新建一個(gè)app.js的Node.js程序,代碼如下:
var http = require('http'); var PORT = 1234; var app = http.createServer(function(req,res){res.writeHead(200,{'Content-Type':'text/html'});res.wirte('<h1>Hellow</h1>');res.end(); )} app.listen(PORT,function(){console.log('Server is run at %d',PORT); })首先我們通過(guò)Node.js內(nèi)置的require()函數(shù)引入HTTP模塊,該模塊是開(kāi)發(fā)WEB應(yīng)用的核心模塊。隨后我們調(diào)用http對(duì)象的createServer函數(shù)來(lái)創(chuàng)建app對(duì)象。在該函數(shù)的參數(shù)中有一個(gè)function(req,res){…}回調(diào)函數(shù),包含req請(qǐng)求參數(shù)與res響應(yīng)參數(shù)。該函數(shù)用于處理所有的HTTP請(qǐng)求,我們只需要寫(xiě)入具體的數(shù)據(jù)到res響應(yīng)對(duì)象中即可。最后需要調(diào)用app對(duì)象的listen()函數(shù),并在響應(yīng)的端口上啟動(dòng)Web應(yīng)用,該函數(shù)同樣也有一個(gè)回調(diào)函數(shù),當(dāng)Web應(yīng)用啟動(dòng)后被調(diào)用。
需要注意的是,res對(duì)象的weiteHead()函數(shù)用于寫(xiě)入響應(yīng)頭(Response Head),write()函數(shù)用于寫(xiě)入響應(yīng)體(Response Body),最后一定要使用end()函數(shù)用于發(fā)送數(shù)據(jù)寫(xiě)入完畢事件這樣才能結(jié)束整個(gè)HTTP請(qǐng)求與響應(yīng)過(guò)程。
第二步,可以通過(guò)命令來(lái)執(zhí)行Node.js程序:
隨后我們可以在瀏覽其中輸入一下地址來(lái)訪問(wèn)Node.js的WEB應(yīng)用。
http://localhost:1234/
瀏覽器中將輸出“hello”字樣的HEML頁(yè)面,至此一個(gè)簡(jiǎn)單的WEB應(yīng)用就開(kāi)發(fā)完畢了。
在Node.js應(yīng)用運(yùn)行過(guò)程中,如果我們修改了源文件,此時(shí)是無(wú)法生效的。因?yàn)镹ode.js為了確保高性能,在啟動(dòng)服務(wù)的時(shí)候就將代碼加載到了內(nèi)存中運(yùn)行。修改了源文件對(duì)實(shí)際運(yùn)行的效果沒(méi)有任何影響,就算被刪除了也是一樣的。該特性確保了運(yùn)行階段的效率,但影響了開(kāi)發(fā)階段的效率。
微服務(wù)注冊(cè)與發(fā)現(xiàn)
Service Registry(服務(wù)注冊(cè)表)是整個(gè)“微服務(wù)架構(gòu)”中的“核心”,他不僅提供了Service Registry(服務(wù)注冊(cè))功能,同時(shí)也為Service Discovery(服務(wù)發(fā)現(xiàn))功能提供了支持。服務(wù)注冊(cè)很好理解,就是在服務(wù)啟動(dòng)后,將服務(wù)的相關(guān)配置信息(例如IP與端口)注冊(cè)到服務(wù)注冊(cè)表中。當(dāng)客戶(hù)調(diào)用這些服務(wù)時(shí),將通過(guò)Service Gateway(服務(wù)網(wǎng)關(guān))從服務(wù)注冊(cè)表中獲取這些服務(wù)配置,然后通過(guò)反向代理的方式去調(diào)用具體的服務(wù)接口,從服務(wù)注冊(cè)表中獲取服務(wù)配置的過(guò)程就是服務(wù)發(fā)現(xiàn)。
此外,服務(wù)注冊(cè)表會(huì)定期檢測(cè)已經(jīng)注冊(cè)的服務(wù),若發(fā)現(xiàn)某些服務(wù)已經(jīng)無(wú)法訪問(wèn)了,則將從服務(wù)注冊(cè)表中移除掉,這個(gè)定期檢查的過(guò)程被稱(chēng)為“心跳檢測(cè)”。由此可見(jiàn),服務(wù)注冊(cè)表對(duì)“分布式數(shù)據(jù)一致性”的要求是想當(dāng)高的,換句話(huà)說(shuō),服務(wù)注冊(cè)表中的服務(wù)配置一旦變更了,通知機(jī)制必須做到高性能,且服務(wù)注冊(cè)表本身還需要具備高可用。
ZooKeeper是服務(wù)注冊(cè)表的最佳解決方案之一。
ZooKeeper是什么
ZooKeeper字面上的意思就是動(dòng)物園管理員的意思,ZooKeeper在軟件世界里就是一名管理員,它被用來(lái)提供分布式環(huán)境下的協(xié)調(diào)服務(wù)。Yahoo公司使用java語(yǔ)言開(kāi)發(fā)了ZooKeeper,它是Hadoop項(xiàng)目中的子項(xiàng)目,基于Google的Chubby的開(kāi)源實(shí)現(xiàn),在Hadoop、HBase、Kafka等技術(shù)中充當(dāng)了核心組件的角色。他的設(shè)計(jì)目標(biāo)就是將哪些復(fù)雜且容易出錯(cuò)的分布式一致性服務(wù)加一封裝,構(gòu)成一個(gè)高效且可靠的服務(wù),并為用戶(hù)提供了一系列簡(jiǎn)單易用的接口。
ZooKeeper是一個(gè)經(jīng)典的分布式數(shù)據(jù)一致性解決方案,分布式應(yīng)用程序可以基于它實(shí)現(xiàn)數(shù)據(jù)發(fā)布與訂閱、負(fù)載均衡、命名服務(wù)、分布式協(xié)調(diào)服務(wù)與通知、集群管理、領(lǐng)導(dǎo)選舉、分布式鎖、分布式隊(duì)列等功能。
ZooKeeper一般是以集群的方式對(duì)外提供服務(wù),一個(gè)集群包括多個(gè)節(jié)點(diǎn),每個(gè)節(jié)點(diǎn)對(duì)應(yīng)一臺(tái)ZooKeeper服務(wù)器,所有的節(jié)點(diǎn)共同對(duì)外提供服務(wù)。整個(gè)集群環(huán)境對(duì)分布式數(shù)據(jù)一致性提供了全面的支持,具體包括以下五大特性:
從同一個(gè)客戶(hù)端發(fā)送的請(qǐng)求,最終將會(huì)嚴(yán)格按照其發(fā)送順序進(jìn)入到ZooKeeper中。可見(jiàn),這就像一個(gè)隊(duì)列,擁有“先進(jìn)先出”的特性,也就確保了請(qǐng)求的順序性。
所有請(qǐng)求的響應(yīng)結(jié)果在整個(gè)分布式集群環(huán)境中具備原子性,也就是說(shuō),要么在整個(gè)集群中所有機(jī)器都成功的處理了某一個(gè)請(qǐng)求,要么就都沒(méi)有處理,絕對(duì)不會(huì)出現(xiàn)集群中部分機(jī)器處理了某一個(gè)請(qǐng)求,而另一部分機(jī)器卻沒(méi)有處理的情況,這方面的要求與事務(wù)的原子性是一樣的。
無(wú)論客戶(hù)端鏈接到哪個(gè)ZooKeeper服務(wù)器,每個(gè)客戶(hù)端所看到的的服務(wù)端數(shù)據(jù)模型都是一致的,不可能出現(xiàn)兩種不同的數(shù)據(jù)狀態(tài)。實(shí)際上每臺(tái)ZooKeeper服務(wù)器之間會(huì)進(jìn)行數(shù)據(jù)同步的,而這個(gè)同步過(guò)程是相當(dāng)高效的。
一旦服務(wù)數(shù)據(jù)狀態(tài)發(fā)生了變化,就會(huì)立刻存儲(chǔ)起來(lái),除非此時(shí)又有另一個(gè)請(qǐng)求對(duì)其進(jìn)行了變更,否則數(shù)據(jù)一定是可靠的。
當(dāng)某個(gè)請(qǐng)求被成功處理了之后,客戶(hù)端能夠立即獲取服務(wù)端的最新數(shù)據(jù)狀態(tài),整個(gè)過(guò)程具備實(shí)時(shí)性。
ZooKeeper樹(shù)狀模型
ZooKeeper內(nèi)部擁有一個(gè)樹(shù)狀內(nèi)存模型,類(lèi)似于文件系統(tǒng),有若干個(gè)目錄,每個(gè)目錄中也有若干個(gè)文件,只是在ZooKeeper中將這些目錄和文件稱(chēng)為ZNode,每個(gè)ZNode有對(duì)應(yīng)的路徑及其包含的數(shù)據(jù)。ZNode可由ZooKeeper客戶(hù)端來(lái)創(chuàng)建,當(dāng)客戶(hù)端與服務(wù)器端建立鏈接后,服務(wù)端將為客戶(hù)端創(chuàng)建一個(gè)Session(會(huì)話(huà)),客戶(hù)端對(duì)ZNode的所有操作均在這個(gè)會(huì)話(huà)中來(lái)完成。
ZNode實(shí)際上有四類(lèi)節(jié)點(diǎn),如下表:
| Persistent(持久節(jié)點(diǎn)) | 當(dāng)會(huì)話(huà)結(jié)束后,該節(jié)點(diǎn)不會(huì)被刪除 |
| Persistent Sequence(持久順序結(jié)點(diǎn)) | 當(dāng)會(huì)話(huà)結(jié)束后,該節(jié)點(diǎn)不會(huì)被刪除,且結(jié)點(diǎn)名中帶自增數(shù)后綴 |
| Ephemeral(臨時(shí)結(jié)點(diǎn)) | 當(dāng)會(huì)話(huà)結(jié)束后,該節(jié)點(diǎn)會(huì)被刪除 |
| Ephemeral Sequence(臨時(shí)順序結(jié)點(diǎn)) | 當(dāng)會(huì)話(huà)結(jié)束后,該節(jié)點(diǎn)會(huì)被刪除,且結(jié)點(diǎn)名中帶自增數(shù)后綴 |
需要注意的是,持久性結(jié)點(diǎn)才能有子節(jié)點(diǎn),這是ZooKeeper所限制的。
ZooKeeper使用這個(gè)基于內(nèi)存的樹(shù)狀模型來(lái)存儲(chǔ)分布式數(shù)據(jù),正式因?yàn)閷⑺械臄?shù)據(jù)都存放在內(nèi)存中,所以才能實(shí)現(xiàn)高性能的目的,提高吞吐率。此外,這個(gè)樹(shù)狀模型還有助于集群環(huán)境下的數(shù)據(jù)同步,下面就來(lái)了解一下ZooKeeper的集群結(jié)構(gòu)。
ZooKeeper的集群結(jié)構(gòu)
ZooKeeper并非采用經(jīng)典的分布式一致性協(xié)議Paxos,而是參考了Paxos協(xié)議,設(shè)計(jì)了一款更加輕量級(jí)的協(xié)議,名為Zab(ZooKeeper Atomic Broadcast原子廣播協(xié)議)。Zab協(xié)議分為兩個(gè)階段:Leader Election(領(lǐng)導(dǎo)選舉)與Atomic Broadcast(原子廣播)。當(dāng)ZooKeeper集群?jiǎn)?dòng)時(shí),將會(huì)選舉出一臺(tái)節(jié)點(diǎn)為L(zhǎng)eader(領(lǐng)導(dǎo)),而其他節(jié)點(diǎn)均為Follower(追隨者)。當(dāng)Leader節(jié)點(diǎn)出現(xiàn)故障時(shí),會(huì)自動(dòng)選舉出行的Leader節(jié)點(diǎn)并讓所有節(jié)點(diǎn)恢復(fù)到一個(gè)正常的狀態(tài),這就是領(lǐng)導(dǎo)選舉階段。當(dāng)領(lǐng)導(dǎo)選舉階段完畢后,將進(jìn)入原子廣播階段,該階段同步Leader節(jié)點(diǎn)與各個(gè)Follower節(jié)點(diǎn)之間的數(shù)據(jù),確保Leader與Follower節(jié)點(diǎn)具有相同的狀態(tài)。所有的寫(xiě)操作都會(huì)發(fā)送到Leader節(jié)點(diǎn),并通過(guò)廣播的方式將數(shù)據(jù)同步到其他Follower節(jié)點(diǎn)。
一個(gè)ZooKeeper集群通常由一組節(jié)點(diǎn)組成,在一般情況下,3~5個(gè)節(jié)點(diǎn)就可以組成一個(gè)可用的ZooKeeper集群,理論上,節(jié)點(diǎn)越多越好。
組成ZooKeeper集群的每個(gè)結(jié)點(diǎn)都會(huì)在內(nèi)存中維護(hù)當(dāng)前服務(wù)器的狀態(tài),并且每個(gè)結(jié)點(diǎn)之間都會(huì)保持相互的通信,目的就是告訴其他節(jié)點(diǎn)“自己還活著”。需要注意的是,只要集群中存在超過(guò)“半數(shù)以上”的結(jié)點(diǎn)可以正常工作,那么整個(gè)集群就能夠正常的對(duì)外提供服務(wù)。因此,我們一般提供奇數(shù)個(gè)節(jié)點(diǎn),比較節(jié)省資源。此外,ZooKeeper客戶(hù)端可以選擇集群中任意一個(gè)節(jié)點(diǎn)來(lái)建立鏈接,而一旦客戶(hù)端與某個(gè)節(jié)點(diǎn)之間斷開(kāi)鏈接,客戶(hù)端會(huì)自動(dòng)鏈接到集群中的其他節(jié)點(diǎn)。
如何使用ZooKeeper
由于ZooKeeper是由Java語(yǔ)言開(kāi)發(fā)的,因此在使用它之前,需要安裝JDK運(yùn)行環(huán)境,在生產(chǎn)環(huán)境下官方建議JDK需要1.6版本或以上,并建議使用Oracle發(fā)布的JDK,而并非開(kāi)源社區(qū)的OpenJDK。雖然在Linux、Windows、Mac OSX操作系統(tǒng)上都可以使用ZooKeeper,不過(guò)官方建議大家使用Linux操作系統(tǒng)作為生產(chǎn)環(huán)境。
運(yùn)行ZooKeeper
我們需要從ZooKeeper官方網(wǎng)站下載它的安裝包,該安裝包實(shí)際上就是一個(gè)壓縮包,加壓后可以使用。建議在生產(chǎn)環(huán)境下使用stable版本。
第一步:修改ZooKeeper配置文件
ZooKeeper提供了一份名為:zoo_sample.cfg的示例配置文件,我們必須在次基礎(chǔ)上進(jìn)行調(diào)整,才能成功運(yùn)行ZooKeeper。
我們只需要復(fù)制conf目錄下的zoo_sample.cfg文件,并將其命名為zoo.cfg即可,就像下面這樣:
cp conf/zoo_sample.cfg conf/zoo.cfg
該配置文件中帶有大量的注釋,便于我們更加清晰的了解到這些配置是做什么的,將這些注釋全部去掉之后,可以看到下面的5個(gè)重要配置:
tickTime=2000
initLimit=10
syncLimit=5
dataDir=/tmp/zookeeper
clientPort=2181
下面我們對(duì)這些配置項(xiàng)進(jìn)行解釋:
- tickTime:稱(chēng)之為“滴答時(shí)間”,用于配置ZooKeeper中最小時(shí)間單元長(zhǎng)度,實(shí)際上ZooKeeper中很多運(yùn)行時(shí)間的時(shí)間間隔都是使用tickTime的倍數(shù)來(lái)表示的。例如,ZooKeeper中會(huì)話(huà)的最小超時(shí)時(shí)間默認(rèn)兩倍的tickTime。該配置的默認(rèn)值是3000,單位為毫秒。
- initLimit:用于配置Leader節(jié)點(diǎn)等待Follower節(jié)點(diǎn)啟動(dòng)并完成數(shù)據(jù)同步的時(shí)間。Follower節(jié)點(diǎn)在啟動(dòng)過(guò)程中會(huì)與leader節(jié)點(diǎn)建立鏈接并完成數(shù)據(jù)同步,從而確定自己對(duì)外提供服務(wù)的起始狀態(tài),Leader節(jié)點(diǎn)允許Follower節(jié)點(diǎn)在initLimit時(shí)間內(nèi)完成這個(gè)工作。該配置的默認(rèn)值為10,即10*tickTime。通常情況下,我們不太注意這個(gè)配置項(xiàng),這個(gè)使用默認(rèn)值即可。如果隨著ZooKeeper集群管理的數(shù)量不斷增大,Follower節(jié)點(diǎn)在啟動(dòng)的時(shí)候,從Leader節(jié)點(diǎn)上進(jìn)行數(shù)據(jù)同步的時(shí)間也會(huì)相應(yīng)變長(zhǎng),于是無(wú)法在短時(shí)間內(nèi)完成數(shù)據(jù)同步,在這種情況下,有必要適當(dāng)調(diào)大這個(gè)參數(shù)。
- syncLimit:用于配置Leader節(jié)點(diǎn)和Follower節(jié)點(diǎn)之間進(jìn)行“心跳檢測(cè)”的最大延時(shí)時(shí)間。在ZooKeeper集群運(yùn)行過(guò)程中。Leader節(jié)點(diǎn)會(huì)與所有的Follower節(jié)點(diǎn)進(jìn)行心跳檢測(cè)來(lái)確定該節(jié)點(diǎn)是否存活。如果Leader在syncLimit時(shí)間內(nèi)無(wú)法獲取Follower節(jié)點(diǎn)的心跳檢測(cè)響應(yīng),那么Leader節(jié)點(diǎn)就會(huì)認(rèn)為該Follower節(jié)點(diǎn)已經(jīng)脫離了與自己的同步,該配置的默認(rèn)項(xiàng)為5,即5*tickTime。
- dataDir:用于配置當(dāng)前ZooKeeper服務(wù)器存儲(chǔ)快照的文件的目錄,不建議將其指定到/tmp目錄下,因?yàn)樵撃夸浀奈募赡鼙蛔詣?dòng)刪除。在ZooKeeper集群環(huán)境中。將生成一個(gè)名為myid的文件,該文件用于存放ZooKeeper集群節(jié)點(diǎn)的ID,我們需要保證在整個(gè)集群環(huán)境中,整個(gè)ID是唯一的。
- clientPort:用于配置當(dāng)前ZooKeeper服務(wù)器對(duì)外暴露的端口,客戶(hù)端會(huì)通過(guò)該端口在ZooKeeper服務(wù)器上建立鏈接并創(chuàng)建會(huì)話(huà),一般設(shè)置為2181。每臺(tái)ZooKeeper服務(wù)器都可以配置任何可用的端口,實(shí)際上,集群中的所有服務(wù)器也無(wú)需使用相同的clientPort。
第二步:啟動(dòng)ZooKeeper服務(wù)器
啟動(dòng)ZooKeeper服務(wù)器非常的簡(jiǎn)單,只需要執(zhí)行ZooKeeper提供的腳本即可。
bin/zkServer.sh start
執(zhí)行以上腳本,將在后臺(tái)啟動(dòng)ZooKeeper服務(wù)器。此外,還可以使用start-foreground參數(shù),用于在前臺(tái)啟動(dòng)ZooKeeper服務(wù)器,此時(shí)我們將看到ZooKeeper的控制臺(tái),隨后可以在控制臺(tái)中看到許多重要的日志。
實(shí)際上,我們可以直接執(zhí)行zkServer.sh腳本來(lái)獲取相關(guān)的使用幫助。
bin/zkServer.sh
第三步,驗(yàn)證ZooKeeper服務(wù)是否有效
可以執(zhí)行如下腳本來(lái)獲取ZooKeeper的服務(wù)狀態(tài):
bin/skServer.sh status
使用ZooKeeper搭建集群環(huán)境
我們以三個(gè)節(jié)點(diǎn)為例,搭建一個(gè)ZooKeeper集群環(huán)境。通過(guò)客戶(hù)端鏈接任意一個(gè)節(jié)點(diǎn),隨后可做一些數(shù)據(jù)變更,并觀察節(jié)點(diǎn)之間是否會(huì)進(jìn)行數(shù)據(jù)同步。
節(jié)點(diǎn)可以分布在不同的機(jī)器上,當(dāng)然也可以在本地搭建一個(gè)集群環(huán)境,只是需要使用不用的端口,以防止端口被占用而導(dǎo)致沖突。像這類(lèi)在本地搭建的集群環(huán)境,并非真正意義上的“集群模式”,稱(chēng)為“偽集群模式”,其本質(zhì)還是集群模模式,只不過(guò)在單機(jī)下單件而已。
為了搭建方便,下面我們?cè)诒镜卮罱ㄒ粋€(gè)為集群模式的ZooKeeper環(huán)境。
第一步:修改ZooKeeper配置文件
我們以第一個(gè)節(jié)點(diǎn)為例,配置如下:
tickTime=2000
initLimit=10
syncLimit=5
dataDir=/tmp/zookeeper1
clientPort=2181
server.1=127.0.0.0:2888:3888
server.2=127.0.0.0:2889:3889
server.3=127.0.0.0:2890:3890
首先,我們指定了dataDir配置,表示ZooKeeper數(shù)據(jù)目錄所存放的地方,需要注意的是,請(qǐng)不要在生產(chǎn)環(huán)境下使用/tmp目錄。
隨后,我們添加了一組server配置,表示集群中所包含的三個(gè)節(jié)點(diǎn),需要注意的是,server配置需要滿(mǎn)足一定的格式:
server.<id>=<ip>:<port1>:<port2>
下面我們對(duì)以上格式進(jìn)行詳細(xì)解釋:
- id:表示節(jié)點(diǎn)編號(hào),表示該節(jié)點(diǎn)在集群中的唯一編號(hào),取值范圍是1~255之間的整數(shù)。需要注意的是,我們必須在dataDir目錄下創(chuàng)建一個(gè)名為myid的文件,其內(nèi)容為該節(jié)點(diǎn)的編號(hào)。例如,針對(duì)第一個(gè)節(jié)點(diǎn)而言,我們需要?jiǎng)?chuàng)建一個(gè)/tmp/zookeeper1/myid文件,該文件內(nèi)容為1,其他節(jié)點(diǎn)也需要同樣的操作。
- ip:表示該節(jié)點(diǎn)所在的IP地址,本地為127.0.0.0或localhost
- port1:表示Leader節(jié)點(diǎn)與Follower節(jié)點(diǎn)進(jìn)心跳檢測(cè)與數(shù)據(jù)同步時(shí)所使用的端口。
- port2:表示進(jìn)行領(lǐng)導(dǎo)選舉過(guò)程中,用于投票通訊的端口。
需要主要的是,在真正的集群環(huán)境中,clientPort,Port1,Port2可以配置的完全一樣,因?yàn)榧褐械拿總€(gè)節(jié)點(diǎn)都分布在不同的機(jī)器上,每個(gè)機(jī)器都擁有自己的IP地址,端口也不會(huì)被其他幾點(diǎn)占用。
參照上面的做法,對(duì)其他兩個(gè)結(jié)點(diǎn)也做同樣的配置后,一個(gè)“偽集群”環(huán)境就搭建完畢了,它擁有“真集群”的所有特性。需要注意的是,對(duì)于“偽集群”環(huán)境而言,在每個(gè)ZooKeeper節(jié)點(diǎn)中,zoo.cfg配置文件中的dataDir與clientPort需要保持不同,對(duì)于“真集群”而言,dataDir、clientPort以及Port1和Port2都可以相同。
所有的結(jié)點(diǎn)都配置完畢了之后,我們即可以啟動(dòng)ZooKeeper集群。
第二步:啟動(dòng)ZooKeeper集群
與單機(jī)模式啟動(dòng)的方法相同,只需要一次啟動(dòng)所有的ZooKeeper節(jié)點(diǎn)即可啟動(dòng)整個(gè)集群。我們可以一個(gè)個(gè)手動(dòng)去啟動(dòng),當(dāng)然,也可以寫(xiě)一個(gè)腳本去一次性啟動(dòng)。
第三步:檢驗(yàn)ZooKeeper集群環(huán)境是否有效
同樣可以通過(guò)zkServer.sh腳本與telnet命令來(lái)查看每個(gè)節(jié)點(diǎn)的狀態(tài),此時(shí)會(huì)看到“Mode:leader”或者“Mode:Follower”的信息,表明該節(jié)點(diǎn)是Leader還是Follower。
ZooKeeper提供了一系列的腳本程序,他們?nèi)看娣旁赽in目錄下,例如:
- zkServer.sh用于啟動(dòng)ZooKeeper服務(wù)器。
- zkCli.sh用于鏈接ZooKeeper服務(wù)器的命令行客戶(hù)端。
- zkCleanup.sh用于清理ZooKeeper的歷史數(shù)據(jù),包括事務(wù)日志文件與快照數(shù)據(jù)文件。
- zkEnv.sh用于設(shè)置ZooKeeper的環(huán)境變量。
我們可以使用zkCli.sh腳本輕松鏈接ZooKeeper服務(wù)器,實(shí)際上它就是一個(gè)命令行客戶(hù)端。下面我們來(lái)學(xué)習(xí)如何使用zkCli.sh鏈接ZooKeeper服務(wù)器,并使用ZooKeeper提供的客戶(hù)端命令來(lái)完成一系列的操作。
使用命令行客戶(hù)端鏈接ZooKeeper
當(dāng)ZooKeepe服務(wù)器正常啟動(dòng)后,我們可以使用ZooKeeper自帶的zkCli.sh腳本,作為命令行客戶(hù)端來(lái)鏈接ZooKeeper。使用方法非常的簡(jiǎn)單,若鏈接的是本地的ZooKeeper,則只需要執(zhí)行一下腳本即可:
bin/zkCli.sh
若想在本地鏈接遠(yuǎn)程的ZooKeeper,則在zkCli.sh腳本中添加-server選項(xiàng)即可,例如:
bin/zkCli.sh -server <ip>:<port>
當(dāng)通過(guò)命令行成功鏈接ZooKeeper后,我們就可以輸入相關(guān)的命令來(lái)操作ZooKeeper了。有一個(gè)小技巧,當(dāng)輸入help命令(或者其他非法命令)后,將輸出ZooKeeper相關(guān)客戶(hù)端命令的幫助。
使用java客戶(hù)端鏈接ZooKeeper
ZooKeeper官方提供了Java客戶(hù)端的API,首先我們可以通過(guò)添加如下的maven依賴(lài)來(lái)獲取ZooKeeper客戶(hù)端jar包:
<dependency><groupId>org.apache.zookeeper</groupId><artifactId>zookeeper</artifactId><version>3.4.8</version> </dependency>隨后我們寫(xiě)一段簡(jiǎn)單的代碼,用于鏈接ZooKeeper,后面我們將基于此代碼,針對(duì)ZooKeeper客戶(hù)端的常用操作逐一進(jìn)行探索。代碼如下:
import org.apache.zookeeper.WatchedEvent; import org.apache.zookeeper.Watcher; import org.apache.zookeeper.ZooKeeper;import java.util.concurrent.CountDownLatch;public class ZooKeeperDemo{private static final String CONNECTION_STRING="127.0.0.1:2181";private static final int SESSION_TIMEOUT=5000;private static CountDownLatch latch = new CountDownLatch(1);public static void mian(String[] args)throws Exception{//鏈接ZooKeeperZooKeeper zk = new ZooKeeper(CONNECTION_STRING,SESSION_TIMEOUT,new Watcher(){@Overridepublic void process(WatchedEvent event){if(event.getState() == Event.KeeperState.SyncConnected){latch.countDown();}}})latch.await();//獲取ZooKeeper客戶(hù)端對(duì)象System.out.println(zk); } }我們需要?jiǎng)?chuàng)建一個(gè)ZooKeeper對(duì)象(對(duì)象名為zk),他的本質(zhì)是對(duì)ZooKeeper會(huì)話(huà)的封裝,后續(xù)的所有操作都在該會(huì)話(huà)對(duì)象上完成。
創(chuàng)建zk對(duì)象需要以下三個(gè)參數(shù):
ZooKeeper(String connectString,int sessionTimeout,Watcher watcher);
- connectString:表示鏈接的字符串,需要傳入鏈接ZooKeeper的ip與port,格式為ip:port。當(dāng)鏈接ZooKeeper集群時(shí),可同時(shí)添加集群中多個(gè)節(jié)點(diǎn)并用逗號(hào)分隔,格式為:ip1:port1,ip2:port2。。。
- sessionTimeout:表示會(huì)話(huà)的超時(shí)時(shí)間,以毫秒為單位。在一個(gè)會(huì)話(huà)期內(nèi),ZooKeeper客戶(hù)端與服務(wù)器之間通過(guò)“心跳檢測(cè)”來(lái)維持會(huì)話(huà)的有效性,也就是說(shuō),一旦在sessionTimeout時(shí)間內(nèi)沒(méi)有進(jìn)行有效的心跳檢測(cè),會(huì)話(huà)將會(huì)失效。
- watcher:表示監(jiān)視器接口,我們需要實(shí)現(xiàn)該接口的process()回調(diào)方法,并監(jiān)視SyncConnected事件,在java中正是用匿名內(nèi)部類(lèi)的方式來(lái)實(shí)現(xiàn)異步回調(diào)過(guò)程的。
由于建立ZooKeeper會(huì)話(huà)的過(guò)程是異步的,也就是說(shuō),當(dāng)構(gòu)造完成zk對(duì)象之后,線(xiàn)程將繼續(xù)執(zhí)行后續(xù)的代碼,但此時(shí)會(huì)話(huà)可能尚未建立完畢。因此,我們需要使用CountDownLatch工具,當(dāng)創(chuàng)建zk對(duì)象完畢后,立即調(diào)用latch.await()方法(關(guān)閉閥門(mén)),當(dāng)使用線(xiàn)程處于等待狀態(tài),等待SyncConnected事件到來(lái)時(shí),在執(zhí)行l(wèi)atch.countDown()方法(打開(kāi)閥門(mén)),此時(shí)會(huì)話(huà)已經(jīng)創(chuàng)建完畢,接下來(lái)當(dāng)前線(xiàn)程就可以繼續(xù)執(zhí)行后續(xù)的代碼了。
現(xiàn)在zk對(duì)象已經(jīng)創(chuàng)建完畢,已經(jīng)成功鏈接ZooKeeper服務(wù)器,下面所有的操作將會(huì)在該會(huì)話(huà)中進(jìn)行,我們只需要調(diào)用zk對(duì)象的相關(guān)API即可。
使用Node.js客戶(hù)端鏈接ZooKeeper
我們?cè)贜PM官網(wǎng)上,如果輸入“Zookeeper”關(guān)鍵字,將會(huì)看到大量的ZooKeeper客戶(hù)端,業(yè)界評(píng)價(jià)比較好的是node-zookeeper-client,他的API命名習(xí)慣于java的非常類(lèi)似,下面我們就對(duì)其基本用法做出簡(jiǎn)單介紹。
首先我們通過(guò)NPM安裝note-zookeeper-client模塊:
npm install node-zookeeper-client
隨后我們寫(xiě)一段簡(jiǎn)單的代碼,用于鏈接ZooKeeper,后面我們將基于該代碼,這對(duì)ZooKeeper客戶(hù)端的常用操作逐一進(jìn)行探索。代碼如下:
var zookeeper = require('node-zookeeper-client');var CONNECTION_STRING = 'localhost:2181'; var OPTIONS = {sessionTimeout:5000 }; var zk = zookeeper.createClient(CONNECTION_STRING,OPTIONS); zk.on('connected',function(){console.log(zk);zk.close(); }); zk.connect();我們需要加載node-zookeeper-client模塊,并創(chuàng)建ZooKeeper客戶(hù)端對(duì)象(對(duì)象名為zk),此時(shí)需要傳入“鏈接字符串”與“相關(guān)選項(xiàng)”(包括“會(huì)話(huà)超時(shí)時(shí)間”)。隨后需要監(jiān)聽(tīng)connected事件,并調(diào)用zk.connect()方法來(lái)鏈接ZooKeeper服務(wù)器。當(dāng)觸發(fā)connected事件時(shí),說(shuō)明客戶(hù)端已經(jīng)鏈接成功了ZooKeeper服務(wù)器,在回調(diào)函數(shù)中可以將zk對(duì)象輸出至控制臺(tái),我們此時(shí)可以運(yùn)行一下該程序并觀察控制臺(tái)中zk對(duì)象的輸出情況。
若zk對(duì)象可以正常輸出,說(shuō)明可以連接ZooKeeper服務(wù)器并建立了正常的會(huì)話(huà),下面所有的操作將在該會(huì)話(huà)中進(jìn)行,我們只需要調(diào)用zk對(duì)象的相關(guān)API即可。與java客戶(hù)端API不同的是,Node.js客戶(hù)端僅提供了異步方式,我們不能通過(guò)同步來(lái)調(diào)用他。
實(shí)現(xiàn)服務(wù)注冊(cè)組件
服務(wù)注冊(cè)組件即Service Registry(服務(wù)注冊(cè)表),他內(nèi)部有一個(gè)數(shù)據(jù)結(jié)構(gòu),用于儲(chǔ)存已發(fā)布服務(wù)的信息。
設(shè)計(jì)服務(wù)注冊(cè)表數(shù)據(jù)結(jié)構(gòu)
通過(guò)ZooKeeper的學(xué)習(xí),我們可以了解到,他內(nèi)部提供了一個(gè)基于ZNode節(jié)點(diǎn)的樹(shù)狀模型,根節(jié)點(diǎn)為“/”,我們可以在根節(jié)點(diǎn)下方擴(kuò)展任意的子節(jié)點(diǎn),其子節(jié)點(diǎn)也分為四種創(chuàng)建模式,包括:持久節(jié)點(diǎn)、持久順序結(jié)點(diǎn)、臨時(shí)結(jié)點(diǎn)、臨時(shí)順序結(jié)點(diǎn)。
似乎可以借助ZNode樹(shù)狀模型來(lái)存儲(chǔ)服務(wù)配置,那么應(yīng)該如何設(shè)計(jì)呢?
我們不妨先定義一個(gè)根節(jié)點(diǎn),由于根節(jié)點(diǎn)下會(huì)有其他子節(jié)點(diǎn),因此根節(jié)點(diǎn)一定是持久節(jié)點(diǎn)的(ZooKeeper只有持久節(jié)點(diǎn)才會(huì)有子節(jié)點(diǎn)),而且根節(jié)點(diǎn)還必須只有一個(gè)。
我們?cè)俑?jié)點(diǎn)下可以添加若干子節(jié)點(diǎn),可以用服務(wù)名稱(chēng)作為這些節(jié)點(diǎn)的名稱(chēng)。為了便于描述,不妨將此類(lèi)節(jié)點(diǎn)稱(chēng)為“服務(wù)節(jié)點(diǎn)”。此外,為了服務(wù)的高可用性,我們可能發(fā)布多個(gè)相同功能的服務(wù),因此服務(wù)注冊(cè)表中會(huì)存在一些同名的服務(wù),但是服務(wù)節(jié)點(diǎn)又不允許重名的(這是ZNode樹(shù)狀節(jié)點(diǎn)限制的),因此我們要在服務(wù)節(jié)點(diǎn)下在添加一級(jí)節(jié)點(diǎn),所以服務(wù)節(jié)點(diǎn)也是持久的。
我們?cè)賮?lái)分析下服務(wù)節(jié)點(diǎn)下面的子節(jié)點(diǎn),他們實(shí)際上都對(duì)應(yīng)了某一特定的服務(wù),我們需要將服務(wù)配置存放在該節(jié)點(diǎn)中。簡(jiǎn)單情況下,服務(wù)配置中可以存放服務(wù)的IP與端口號(hào)。為了便于描述,我們不妨將該節(jié)點(diǎn)稱(chēng)之為“地址節(jié)點(diǎn)”。一旦某個(gè)服務(wù)成功注冊(cè)到了ZooKeeper中,ZooKeeper服務(wù)器就會(huì)與該服務(wù)所在的客戶(hù)端進(jìn)行心跳檢測(cè),如果某個(gè)服務(wù)出現(xiàn)了故障,心跳檢測(cè)就會(huì)失效,客戶(hù)端將自動(dòng)斷開(kāi)與服務(wù)端的對(duì)話(huà)。對(duì)應(yīng)的節(jié)點(diǎn)也及時(shí)從ZNode樹(shù)狀模型中刪除,然而如果注冊(cè)了多個(gè)相同的服務(wù),這樣的地址節(jié)點(diǎn)就可能會(huì)有多個(gè),因此地址節(jié)點(diǎn)必須為臨時(shí)且順序的。
微服務(wù)封裝
我們使用SpringBoot開(kāi)發(fā)了許多服務(wù),每個(gè)服務(wù)都是以jar包的形式存在,可將這些jar包部署到不同的服務(wù)器上面,并通過(guò)java -jar的命令來(lái)運(yùn)行這些服務(wù)。當(dāng)服務(wù)啟動(dòng)后,會(huì)將自身的配置信息注冊(cè)到“服務(wù)注冊(cè)表”中。所有的客戶(hù)端請(qǐng)求都會(huì)進(jìn)入“服務(wù)網(wǎng)關(guān)”,服務(wù)網(wǎng)關(guān)首先從服務(wù)注冊(cè)表中根據(jù)當(dāng)前請(qǐng)求中的服務(wù)名稱(chēng)來(lái)獲取對(duì)應(yīng)的服務(wù)配置(該過(guò)程成為“服務(wù)發(fā)現(xiàn)”),隨后服務(wù)網(wǎng)關(guān)通過(guò)服務(wù)配置直接調(diào)用已發(fā)布的服務(wù)(該過(guò)程稱(chēng)之為“反向代理”)。
這樣的架構(gòu)看起來(lái)不錯(cuò),但是我們發(fā)現(xiàn)維護(hù)并管理每個(gè)服務(wù)會(huì)帶來(lái)巨大的成本。比如,我們每次發(fā)布一個(gè)服務(wù)都必須做三件事情:編譯、打包、部署。這三件事看似容易,實(shí)際上卻想當(dāng)?shù)姆爆?#xff0c;尤其是服務(wù)數(shù)量較多的場(chǎng)景,其實(shí)我們想要的只是一個(gè)可運(yùn)行的jar包而已。再比如,在微服務(wù)架構(gòu)中,每個(gè)服務(wù)可能由不同的編程語(yǔ)言來(lái)實(shí)現(xiàn),運(yùn)行服務(wù)還需要不同的環(huán)境,想要讓服務(wù)跑起來(lái),必須先安裝支持他的運(yùn)行環(huán)境,然而安裝環(huán)境往往比運(yùn)行服務(wù)更加的繁瑣。
面對(duì)這些問(wèn)題,我們需要想辦法將服務(wù)及運(yùn)行環(huán)境加以封裝,并確保將這個(gè)封裝后的產(chǎn)物作為我們的交付物,這個(gè)交付物可隨時(shí)構(gòu)建、裝載、運(yùn)行。Docker正是為此而生的。
Docker是什么
Docker在英語(yǔ)里面是“碼頭工人”的意思,大家可以想象,碼頭上有很多的工人,他們正在忙于裝載貨物。首先將貨物放入集裝箱中,然后將集裝箱放在貨船上,貨船將這些集裝箱以及其他的貨物送到指定的目的地。
Docker簡(jiǎn)介
在2013年,dotCloud公司發(fā)布了一款名為Docker的開(kāi)源軟件,僅花了一年左右的時(shí)間,Docker幾乎動(dòng)搖了傳統(tǒng)虛擬化技術(shù)的統(tǒng)治地位,越來(lái)越多的公司開(kāi)始逐步使用Docker來(lái)替換現(xiàn)有的虛擬化技術(shù)。正式因?yàn)镈ocker太紅了,就連dotCloud公司也因此而改名為Docker公司了,并給予Docker推出了一系列的相關(guān)生態(tài)產(chǎn)品。比如Docker Engine、Docker Machine、Docker Toolbox、Docker Compose、Docker Hub、Docker Registry、Docker Swarm、Docker Notary、Docker Cloud、Docker Store等。
Docker源碼地址:https://github.com/docker/docker
Docker的圖標(biāo)就很生動(dòng)的表達(dá)了他的含義,是一直可愛(ài)的鯨魚(yú),拖著許多的集裝箱,漂浮在云上。在Docker的世界中,這只鯨魚(yú)就是Docker Engine(Docker引擎),上面一個(gè)個(gè)的集裝箱就是Docker容器(Docker Container),Docker引擎可以運(yùn)行在基于Docker的云平臺(tái)(Docker Cloud)上。
這里有一些概念,做一下解釋:
Docker鏡像可以理解為一個(gè)運(yùn)行在服務(wù)器上的后臺(tái)進(jìn)程,也稱(chēng)之為Docker Daemon,還有很多人稱(chēng)他為Docker服務(wù),因?yàn)樗举|(zhì)就是一個(gè)服務(wù),只要我們啟動(dòng)該服務(wù),我們就能隨時(shí)使用它。我們可以通過(guò)Docker命令客戶(hù)端發(fā)送相關(guān)的Docker命令,并與Docker引擎進(jìn)行通信。
實(shí)際上Docker客戶(hù)端有兩種,一種是我們剛剛提到的Docker命令行客戶(hù)端,只要我們打開(kāi)命令終端窗口,輸入相關(guān)的Docker 命令,就能操作Docker引擎。另一種Docker客戶(hù)端是REST API客戶(hù)端,我們一般會(huì)在程序中通過(guò)REST API與Docker引擎發(fā)生交互。
Docker鏡像有點(diǎn)類(lèi)似于我們?cè)?jīng)使用的光盤(pán),光盤(pán)上刻錄了數(shù)據(jù),我們只需要將光盤(pán)放入光驅(qū)中,就能讀取光盤(pán)中的數(shù)據(jù)。同樣,我們只需要獲取Docker鏡像(光盤(pán)),就能載入到Docker引擎(光驅(qū))中。并運(yùn)行Docker鏡像中的程序。一般情況下我們首先要將程序打包到Docker鏡像中,隨后才能將Docker鏡像交給其他人使用。
當(dāng)我們獲取到Docker鏡像后,可隨時(shí)運(yùn)行Docker鏡像,此時(shí)便會(huì)啟動(dòng)一個(gè)Docker容器,該容器中將運(yùn)行鏡像中封裝的程序。如果我們將Docker鏡像理解為java類(lèi)的話(huà),那么Docker容器就相當(dāng)于java實(shí)例。在同一個(gè)Docker鏡像上理論上是可以運(yùn)行無(wú)數(shù)個(gè)Docker容器的。
Docker官方提供了一個(gè)叫做DockerHub的鏡像注冊(cè)中心(Docker Registry),用于放公開(kāi)和私有的Docker鏡像倉(cāng)庫(kù)(Docker Responsitory)。也就是說(shuō),我們隨時(shí)可以通過(guò)Docker Hub拉去(下載)Docker鏡像,也可以自由的將自己創(chuàng)建的Docker鏡像推送(上傳)到DockerHub上去。
虛擬機(jī)與Docker對(duì)比
Docker本質(zhì)上為我們提供了一個(gè)“沙箱(Sandbox)”環(huán)境,它能將應(yīng)用程序進(jìn)行封裝,并提供了與虛擬機(jī)相似的隔離性,但這種隔離性是想當(dāng)輕量的。那么虛擬機(jī)與Docker有什么區(qū)別呢?一起來(lái)討論下。
當(dāng)我們需要在宿主機(jī)上運(yùn)行一個(gè)虛擬操作系統(tǒng)時(shí),首先需要安裝一個(gè)虛擬機(jī)軟件,常用的虛擬機(jī)軟件比如Oracle VirtualBox或者VMware等,隨后我們可以使用虛擬鏡像文件,在虛擬機(jī)上安裝虛擬操作系統(tǒng)。此時(shí),虛擬軟件需要模擬硬件與網(wǎng)絡(luò)資源,會(huì)占用大量的系統(tǒng)開(kāi)銷(xiāo)。一般情況下,在一臺(tái)普通的服務(wù)器上,最多只能啟動(dòng)十幾個(gè)虛擬機(jī),而且虛擬機(jī)的啟動(dòng)一般要幾分鐘甚至更長(zhǎng)時(shí)間。
若我們使用Docker虛擬化技術(shù),則只需要在宿主機(jī)上安裝一個(gè)Docker引擎,隨后可以從Docker鏡像倉(cāng)庫(kù)中下載所需的Docker鏡像,并啟動(dòng)相應(yīng)的Docker容器。此時(shí),Docker引擎完全利用宿主機(jī)硬件與網(wǎng)絡(luò)資源,占用的系統(tǒng)開(kāi)銷(xiāo)較少。一般情況下,在一臺(tái)普通的服務(wù)器上,可以啟動(dòng)上千個(gè)Docker容器。
Docker的特點(diǎn)
Docker是通過(guò)在底層上封裝了Linux容器技術(shù)(LXC)來(lái)實(shí)現(xiàn)的,換句話(huà)說(shuō),Docker沒(méi)有創(chuàng)造出任何新的技術(shù),僅僅只是“新瓶裝老酒”而已。下面歸納了Docker的四大特點(diǎn):
啟動(dòng)虛擬機(jī)需要幾分鐘,而啟動(dòng)Docker只需要幾秒鐘。
Docker直接運(yùn)行在Docker引擎上,可以直接利用宿主硬件資源,無(wú)需占用過(guò)多的系統(tǒng)資源。
傳統(tǒng)軟件交付物是程序,而在Docker時(shí)代的交付物卻是鏡像,鏡像不僅封裝了程序,還包含運(yùn)行程序所需的相關(guān)環(huán)境。
可以通過(guò)Docker客戶(hù)端直接操作Docker引擎,非常方便的管理Docker鏡像與容器。
微服務(wù)的部署
我們使用Git管理代碼,使用Maven構(gòu)建項(xiàng)目,使用Docker封裝服務(wù),這些事情都需要手工的方式去一步步的執(zhí)行,能否有快捷的方式呢?Jenkins就是這中快捷方式
Jenkins簡(jiǎn)介
在軟件行業(yè)發(fā)展中,持續(xù)集成(Continuous Integration,簡(jiǎn)稱(chēng)CI)是利用一系列的工具、方法與規(guī)則,快速的構(gòu)建代碼,并自動(dòng)的進(jìn)行測(cè)試,從而完成代碼開(kāi)發(fā)的效率和質(zhì)量。Jenkins是一款持續(xù)集成軟件,擁有簡(jiǎn)單的安裝、開(kāi)箱即用、易于管理、易于維護(hù)、插件擴(kuò)展等特性。只需要一個(gè)java的運(yùn)行環(huán)境,就能將Jenkins跑起來(lái),可以通過(guò)圖形化界面為每個(gè)項(xiàng)目創(chuàng)建對(duì)應(yīng)的構(gòu)建任務(wù)(也稱(chēng)為“構(gòu)建作業(yè)”)。Jenkins可以連接我們的代碼倉(cāng)庫(kù)系統(tǒng),從中獲取源碼并自動(dòng)完成構(gòu)建,當(dāng)創(chuàng)建完畢后,還能執(zhí)行一些后續(xù)的任務(wù),比如:生成單元測(cè)試報(bào)告、歸檔程序包、部署程序包到Maven倉(cāng)庫(kù)、記錄文件電子指紋、發(fā)送郵件通知等一系列的操作。為提高持續(xù)集成的執(zhí)行效率,Jenkins支持主從(Master-Slave)運(yùn)行模式,一臺(tái)Master機(jī)器可以控制多臺(tái)Slave機(jī)器,構(gòu)件任務(wù)可并行在多臺(tái)Slave機(jī)器上執(zhí)行。
總結(jié)
- 上一篇: ue4挂载其他工程生成的pak,打开le
- 下一篇: 大苏打实打实的