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