手把手0基礎項目實戰(一)——教你搭建一套可自動化構建的微服務框架(SpringBoot+Dubbo+Docker+Jenkins)...

本文你將學到什么?

本文將以原理+實戰的方式,首先對“微服務”相關的概念進行知識點掃盲,然后開始手把手教你搭建這一整套的微服務系統。

項目完整源碼下載

https://github.com/bz51/SpringBoot-Dubbo-Docker-Jenkins

這套微服務框架能干啥?

這套系統搭建完之后,那可就厲害了:

  • 微服務架構 你的整個應用程序將會被拆分成一個個功能獨立的子系統,獨立運行,系統與系統之間通過RPC接口通信。這樣這些系統之間的耦合度大大降低,你的系統將非常容易擴展,團隊協作效率提升了N個檔次。這種架構通過眼下流行的SpringBoot和阿里巴巴吊炸天的Dubbo框架來實現。

  • 容器化部署 你的各個微服務將采用目前處于浪潮之巔的Docker來實現容器化部署,避免一切因環境引起的各種問題,讓你們團隊的全部精力集中在業務開發上。

  • 自動化構建 項目被微服務化后,各個服務之間的關系錯中復雜,打包構建的工作量相當可怕。不過沒關系,本文將借助Jenkins,幫助你一鍵自動化部署,從此你便告別了加班。


知識點掃盲篇

咳咳,敲黑板啦!筆記趕緊記起來,課后我要檢查的!檢查不合格的同學放學后留下來!

知識點1:微服務

微服務一次近幾年相當火,成為程序猿飯前便后裝逼熱門詞匯,你不對它有所了解如何在程序猿裝逼圈子里混?下面我用最為通俗易懂的語言介紹它。

要講清楚微服務,我先要從一個系統架構的演進過程講起。

單機結構

我想大家最最最熟悉的就是單機結構,一個系統業務量很小的時候所有的代碼都放在一個項目中就好了,然后這個項目部署在一臺服務器上就好了。整個項目所有的服務都由這臺服務器提供。這就是單機結構。 那么,單機結構有啥缺點呢?我想缺點是顯而易見的,單機的處理能力畢竟是有限的,當你的業務增長到一定程度的時候,單機的硬件資源將無法滿足你的業務需求。此時便出現了集群模式,往下接著看。

集群結構

集群模式在程序猿界由各種裝逼解釋,有的讓你根本無法理解,其實就是一個很簡單的玩意兒,且聽我一一道來。

單機處理到達瓶頸的時候,你就把單機復制幾份,這樣就構成了一個“集群”。集群中每臺服務器就叫做這個集群的一個“節點”,所有節點構成了一個集群。每個節點都提供相同的服務,那么這樣系統的處理能力就相當于提升了好幾倍(有幾個節點就相當于提升了這么多倍)。

但問題是用戶的請求究竟由哪個節點來處理呢?最好能夠讓此時此刻負載較小的節點來處理,這樣使得每個節點的壓力都比較平均。要實現這個功能,就需要在所有節點之前增加一個“調度者”的角色,用戶的所有請求都先交給它,然后它根據當前所有節點的負載情況,決定將這個請求交給哪個節點處理。這個“調度者”有個牛逼了名字——負載均衡服務器。

集群結構的好處就是系統擴展非常容易。如果隨著你們系統業務的發展,當前的系統又支撐不住了,那么給這個集群再增加節點就行了。但是,當你的業務發展到一定程度的時候,你會發現一個問題——無論怎么增加節點,貌似整個集群性能的提升效果并不明顯了。這時候,你就需要使用微服務結構了。

微服務結構

先來對前面的知識點做個總結。 從單機結構到集群結構,你的代碼基本無需要作任何修改,你要做的僅僅是多部署幾臺服務器,沒太服務器上運行相同的代碼就行了。但是,當你要從集群結構演進到微服務結構的時候,之前的那套代碼就需要發生較大的改動了。所以對于新系統我們建議,系統設計之初就采用微服務架構,這樣后期運維的成本更低。但如果一套老系統需要升級成微服務結構的話,那就得對代碼大動干戈了。所以,對于老系統而言,究竟是繼續保持集群模式,還是升級成微服務架構,這需要你們的架構師深思熟慮、權衡投入產出比。

OK,下面開始介紹所謂的微服務。 微服務就是將一個完整的系統,按照業務功能,拆分成一個個獨立的子系統,在微服務結構中,每個子系統就被稱為“服務”。這些子系統能夠獨立運行在web容器中,它們之間通過RPC方式通信。

舉個例子,假設需要開發一個在線商城。按照微服務的思想,我們需要按照功能模塊拆分成多個獨立的服務,如:用戶服務、產品服務、訂單服務、后臺管理服務、數據分析服務等等。這一個個服務都是一個個獨立的項目,可以獨立運行。如果服務之間有依賴關系,那么通過RPC方式調用。

這樣的好處有很多:

  1. 系統之間的耦合度大大降低,可以獨立開發、獨立部署、獨立測試,系統與系統之間的邊界非常明確,排錯也變得相當容易,開發效率大大提升。
  2. 系統之間的耦合度降低,從而系統更易于擴展。我們可以針對性地擴展某些服務。假設這個商城要搞一次大促,下單量可能會大大提升,因此我們可以針對性地提升訂單系統、產品系統的節點數量,而對于后臺管理系統、數據分析系統而言,節點數量維持原有水平即可。
  3. 服務的復用性更高。比如,當我們將用戶系統作為單獨的服務后,該公司所有的產品都可以使用該系統作為用戶系統,無需重復開發。

那么問題來了,當采用微服務結構后,一個完整的系統可能有很多獨立的子系統組成,當業務量漸漸發展起來之后,而這些子系統之間的關系將錯綜復雜,而且為了能夠針對性地增加某些服務的處理能力,某些服務的背后可能是一個集群模式,由多個節點構成,這無疑大大增加了運維的難度。微服務的想法好是好,但開發、運維的復雜度實在是太高。為了解決這些問題,阿里巴巴的Dubbo就橫空出世了。

知識點2:Dubbo

Dubbo是一套微服務系統的協調者,在它這套體系中,一共有三種角色,分別是:服務提供者(下面簡稱提供者)、服務消費者(下面簡稱消費者)、注冊中心。

你在使用的時候需要將Dubbo的jar包引入到你的項目中,也就是每個服務都要引入Dubbo的jar包。然后當這些服務初始化的時候,Dubbo就會將當前系統需要發布的服務、以及當前系統的IP和端口號發送給注冊中心,注冊中心便會將其記錄下來。這就是服務發布的過程。與此同時,也是在系統初始化的時候,Dubbo還會掃描一下當前系統所需要引用的服務,然后向注冊中心請求這些服務所在的IP和端口號。接下來系統就可以正常運行了。當系統A需要調用系統B的服務的時候,A就會與B建立起一條RPC信道,然后再調用B系統上相應的服務。

這,就是Dubbo的作用。

知識點3:容器化部署

當我們使用了微服務架構后,我們將一個原本完整的系統,按照業務邏輯拆分成一個個可獨立運行的子系統。為了降低系統間的耦合度,我們希望這些子系統能夠運行在獨立的環境中,這些環境之間能夠相互隔離。

在Docker出現之前,若使用虛擬機來實現運行環境的相互隔離的話成本較高,虛擬機會消耗較多的計算機硬件/軟件資源。Docker不僅能夠實現運行環境的隔離,而且能極大程度的節約計算機資源,它成為一種輕量級的“虛擬機”。

知識點4:自動化構建

當我們使用微服務架構后,隨著業務的逐漸發展,系統之間的依賴關系會日益復雜,而且各個模塊的構建順序都有所講究。對于一個小型系統來說,也許只有幾個模塊,那么你每次采用人肉構建的方式也許并不感覺麻煩。但隨著系統業務的發展,你的系統之間的依賴關系日益復雜,子系統也逐漸增多,每次構建一下你都要非常小心謹慎,稍有不慎整個服務都無法正常啟動。而且這些構建的工作很low,但卻需要消耗大量的精力,這無疑降低了開發的效率。不過沒關系,Jenkins就是來幫助你解決這個問題的。

我們只需在Jenkins中配置好代碼倉庫、各個模塊的構建順序和構建命令,在以后的構建中,只需要點擊“立即構建”按鈕,Jenkins就會自動到你的代碼倉庫中拉取最新的代碼,然后根據你事先配置的構建命令進行構建,最后發布到指定的容器中運行。你也可以讓Jenkins定時檢查代碼倉庫版本的變化,一旦發現變動就自動地開始構建過程,并且讓Jenkins在構建成功后給你發一封郵件。這樣你連“立即構建”的按鈕也不需要按,就能全自動地完成這一切構建過程。


實戰動手篇

1. 學習目標

接下來我會帶著大家,以一個在線商城為例,搭建一套能夠自動化部署的微服務框架。這個框架能做如下幾件事情:

  1. 基于SpringBoot快速開發 我們將選擇目前熱度很高的SpringBoot,最大限度地降低配置復雜度,把大量的精力投入到我們的業務開發中來。
  2. 基于Dubbo的微服務化 我們會使用阿里巴巴的開源框架Dubbo,將我們的系統拆分成多個獨立的微服務,然后用Dubbo來管理所有服務的發布和引用。有了Dubbo之后,調用遠程服務就像調用一個本地函數一樣簡單,Dubbo會幫我們完成遠程調用背后所需要的一切。
  3. 基于Docker的容器化部署 由于使用了微服務架構后,我們的系統將會由很多子系統構成。為了達到多個系統之間環境隔離的目的,我們可以將它們部署在多臺服務器上,可這樣的成本會比較高,而且每臺服務器的性能可能都沒有充分利用起來。所以我們很自然地想到了虛擬機,在同一臺服務器上運行多個虛擬機,從而實現環境的隔離,每個虛擬機上運行獨立的服務。然而虛擬機的隔離成本依舊很高,因為它需要占用服務器較多的硬件資源和軟件資源。所以,在微服務結構下,要實現服務環境的隔離,Docker是最佳選擇。它比虛擬機更加輕量級,占用資源較少,而且能夠實現快速部署。
  4. 基于Jenkins的自動化構建 當我們采用了微服務架構后,我們會發現這樣一個問題。整個系統由許許多多的服務構成,這些服務都需要運行在單獨的容器中,那么每次發布的復雜度將非常高。首先你要搞清楚這些服務之間的依賴關系、啟動的先后順序,然后再將多個子系統挨個編譯、打包、發布。這些操作技術難度低,卻又容易出錯。那么有什么工具能夠幫助我們解決這些問題呢?答案就是——Jenkins。 它是一款自動化構建的工具,簡單的來說,就是我們只需要在它的界面上按一個按鈕,就可以實現上述一系列復雜的過程。

2. 項目背景介紹

本文我以一個大家都非常熟悉的在線商城作為例子,一步步教大家如何搭建微服務框架,它有如下功能:

  • 產品管理 產品的增刪改查。
  • 訂單管理 訂單的增刪改查、購物車功能。
  • 用戶管理 用戶的登錄、注冊、權限管理、收貨地址等等。
  • 數據分析 提供對本系統數據分析的功能。

注意:本文的IDE使用的是intelliJ IDEA,推薦大家也用這個,用了都說好,用了你就會愛上它。

3. 創建項目的組織結構

在動手之前,我先來說一說這一步的目標:

  • 創建一個Maven Project,命名為“Gaoxi” 這個Project由多個Module構成,每個Module對應著“微服務”的一個子系統,可獨立運行,是一個獨立的項目。 這也是目前主流的項目組織形式,即多模塊項目。
  • 在Gaoxi這個項目下創建各個子模塊,每個自模塊都是一個獨立的SpringBoot項目:
    • Gaoxi-User 用戶服務
    • Gaoxi-Order 訂單服務
    • Gaoxi-Product 產品服務
    • Gaoxi-Analysis 數據分析服務
    • Gaoxi-Controller 本系統的控制層,和以往三層結構中的Controller層的作用一樣,都是用作請求調度,只不過在微服務架構中,我們將它抽象成一個單獨的系統,可以獨立運行。
    • Gaoxi-Common-Service-Facade 它處于本系統的最底層,被所有模塊依賴,一些公用的類庫都放在這里。
    • Gaoxi-Redis 我們將Redis封裝成一個單獨的服務,運行在獨立的容器中,當哪一個模塊需要使用Redis的時候,僅需要引入該服務即可,就免去了各種繁瑣的、重復的配置。而這些配置均在Gaoxi-Redis系統中完成了。

下面開始動手。

3.1 創建Project

  • New一個Project

  • 選擇Spring Initializr

  • 設置groupId、artifactId、version

<groupId>com.gaoxi</groupId>
<artifactId>gaoxi</artifactId>
<version>0.0.1-SNAPSHOT</version>
復制代碼
  • Project創建完畢!接下來在Project下面創建Module

3.2 創建Module

  • 在Project上New Module

  • 和剛才一樣,選擇Spring Initializr,設置groupId、artifactId、version

  • 依次創建好所有的Module,如下圖所示:

3.3 構建模塊的依賴關系

目前為止,模塊之間沒有任何聯系,下面我們要通過pom文件來指定它們之間的依賴關系,依賴關系如下圖所示:

Gaoxi-User、Gaoxi-Analysis、Gaoxi-Product、Gaoxi-Order這四個系統相當于以往三層結構的Service層,提供系統的業務邏輯,只不過在微服務結構中,Service層的各個模塊都被抽象成一個個單獨的子系統,它們提供RPC接口供上面的Gaoxi-Controller調用。它們之間的調用由Dubbo來完成,所以它們的pom文件中并不需要作任何配置。而這些模塊和Gaoxi-Common-Service-Facade之間是本地調用,因此需要將Gaoxi-Common-Service-Facade打成jar包,并讓這些模塊依賴這個jar,因此就需要在所有模塊的pom中配置和Gaoxi-Common-Service-Facade的依賴關系。

此外,為了簡化各個模塊的配置,我們將所有模塊的通用依賴放在Project的pom文件中,然后讓所有模塊作為Project的子模塊。這樣子模塊就可以從父模塊中繼承所有的依賴,而不需要自己再配置了。

下面開始動手:

  • 首先將Common-Service-Facade的打包方式設成jar 當打包這個模塊的時候,Maven會將它打包成jar,并安裝在本地倉庫中。這樣其他模塊打包的時候就可以引用這個jar。
<groupId>com.gaoxi</groupId>
<artifactId>gaoxi-common-service-facade</artifactId>
<version>0.0.1</version>
<packaging>jar</packaging>
復制代碼
  • 將其他模塊的打包方式設為war 除了Gaoxi-Common-Service-Facade外,其他模塊都是一個個可獨立運行的子系統,需要在web容器中運行,所以我們需要將這些模塊的打包方式設成war
<groupId>com.gaoxi</groupId>
<artifactId>gaoxi-user</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging>
復制代碼
  • 在總pom中指定子模塊 modules標簽指定了當前模塊的子模塊是誰,但是僅在父模塊的pom文件中指定子模塊還不夠,還需要在子模塊的pom文件中指定父模塊是誰。
<modules><module>Gaoxi-Analysis</module><module>Gaoxi-Order</module><module>Gaoxi-Product</module><module>Gaoxi-User</module><module>Gaoxi-Redis</module><module>Gaoxi-Controller</module><module>Gaoxi-Common-Service-Facade</module>
</modules>
復制代碼
  • 在子模塊中指定父模塊
<parent><groupId>com.gaoxi</groupId><artifactId>gaoxi</artifactId><version>0.0.1-SNAPSHOT</version><relativePath>../pom.xml</relativePath>
</parent>
復制代碼

到此為止,模塊的依賴關系配置完畢!但要注意模塊打包的順序。由于所有模塊都依賴于Gaoxi-Common-Servie-Facade模塊,因此在構建模塊時,首先需要編譯、打包、安裝Gaoxi-Common-Servie-Facade,將它打包進本地倉庫中,這樣上層模塊才能引用到。當該模塊安裝完畢后,再構建上層模塊。否則在構建上層模塊的時候會出現找不到Gaoxi-Common-Servie-Facade中類庫的問題。

3.4 在父模塊的pom中添加所有子模塊公用的依賴

<dependencies><!-- Spring Boot --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter</artifactId></dependency><!-- Spring MVC --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- Spring Boot Test --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-test</artifactId><scope>test</scope></dependency><!-- MyBatis --><dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>1.3.1</version></dependency><!-- Mysql --><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><scope>runtime</scope></dependency><!-- Dubbo --><dependency><groupId>io.dubbo.springboot</groupId><artifactId>spring-boot-starter-dubbo</artifactId><version>1.0.0</version></dependency><!-- gaoxi-common-service-facade --><dependency><groupId>com.gaoxi</groupId><artifactId>gaoxi-common-service-facade</artifactId><version>0.0.1</version></dependency><!-- AOP --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency><!-- guava --><dependency><groupId>com.google.guava</groupId><artifactId>guava</artifactId><version>23.3-jre</version></dependency>
</dependencies>
復制代碼

當父模塊的pom中配置了公用依賴后,子模塊的pom文件將非常簡潔,如下所示:

<groupId>com.gaoxi</groupId>
<artifactId>gaoxi-user</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>war</packaging><name>gaoxi-user</name><parent><groupId>com.gaoxi</groupId><artifactId>gaoxi</artifactId><version>0.0.1-SNAPSHOT</version><relativePath>../pom.xml</relativePath>
</parent>
復制代碼

當項目的結構搭建完成之后,接下來你需要配置Docker環境,并將這些項目打包進容器中,驗證下是否能正常啟動。

4. 創建Docker容器

4.1 安裝Docker

在使用Docker之前,你當然先要安裝Docker,安裝過程較為簡單,基本上就是傻瓜式操作,這里就不作過多介紹了,你可以在Docker的官網下載相應系統的安裝包。 https://www.docker.com/

4.2 獲取Tomcat鏡像

在微服務架構中,一個完整的系統被拆分成了多個被稱為“微服務”的子系統,這些子系統可以獨立運行在Web容器中。所以我們需要為這些系統提供運行的Web容器,這里我們選擇大家較為熟悉的Tomcat。

我們知道,Tomcat依賴于Java環境,安裝Tomcat之前要進行一系列環境的配置:安裝Java、配置環境變量、安裝Tomcat等等。這些操作還是有些繁瑣的。不過沒關系,當使用了Docker之后,這些過程都可以輕而易舉地完成。

我們只需從Docker Hub上找到Tomcat的鏡像資源,然后從上面拉取下來就可以使用。你可以使用Tomcat官方的鏡像,也可以使用我發布在Docker Hub上的Tomcat鏡像。

注意點:推薦使用我的Tomcat鏡像資源chaimm/tomcat,因為這個鏡像中除了配置Tomcat的安裝環境以外,還有一些本項目中要用到的Jenkins相關的配置。

采用如下命令從Docker Hub上拉取鏡像:

docker pull chaimm/tomcat:1.1
復制代碼

簡單解釋下,docker pull是從從Docker Hub上拉取鏡像的命令,后面的chaimm/tomcat是鏡像的名稱,:1.1是鏡像的版本號。目前這個鏡像的最新版本號是1.1,推薦大家拉取這個。

4.3 創建Tomcat容器

這里再簡單介紹下“鏡像”和“容器”的關系。 “鏡像”就好比是面向對象中的“類”,“容器”就好比“類”創建的“對象”。在面向對象中,“類”定義了各種屬性,“類”可以實例化出多個“對象”;而在Docker中,“鏡像”定義了各種配置信息,它可以實例化出多個“容器”。“容器”就是一臺可以運行的“虛擬機”。

接下來我們需要為所有的微服務創建各自的容器:

  • gaoxi-user
  • gaoxi-product
  • gaoxi-order
  • gaoxi-analysis
  • gaoxi-controller
  • gaoxi-redis

以創建gaoxi-user容器為例,采用如下命令創建容器:

docker run --name gaoxi-user-1 -p 8082:8080 -v /usr/web/gaoxi-log:/opt/tomcat/gaoxi-log chaimm/tomcat:1.1
復制代碼
  • --name:指定容器的名字
  • -p:指定容器的端口映射 -p 8082:8080 表示將容器的8080端口映射到宿主機的8082端口上
  • -v:指定容器數據卷的映射 xxx:yyy 表示將容器yyy目錄映射到宿主機的xxx目錄上,從而訪問宿主機的xxx目錄就相當于訪問容器的yyy目錄。
  • chaimm/tomcat:1.1:表示容器所對應的鏡像。

這條命令執行成功后,你就可以通過你的IP:8082 訪問到gaoxi-user-1容器的tomcat了。如果你看到了那只眼熟了貓,那就說明容器啟動成功了!

接下來,你需要按照上面的方法,給剩下幾個系統創建好Tomcat容器。

注意點:這里要注意的是,你需要給這些Tomcat容器指定不同的端口號,防止端口號沖突。當然,在實際開發中,你并不需要將容器的8080端口映射到宿主機上,這里僅僅是為了驗證容器是否啟動成功才這么做的。

5. 整合Dubbo

5.1 創建zookeeper容器

Dubbo一共定義了三種角色,分別是:服務提供者、服務消費者、注冊中心。注冊中心是服務提供者和服務消費者的橋梁,服務消費者會在初始化的時候將自己的IP和端口號發送給注冊中心,而服務消費者通過注冊中心知道服務提供者的IP和端口號。

在Dubbo中,注冊中心有多種選擇,Dubbo最為推薦的即為ZooKeeper,本文采用ZooKeepeer作為Dubbo的注冊中心。

創建ZooKeeper容器也較為簡單,大家可以直接使用我創建的ZooKeeper鏡像,通過如下命令即可下載鏡像:

docker pull chaimm/zookeeper-dubbo:1.0
復制代碼

該鏡像中不僅運行了一個zookeeper,還運行了一個擁有dubbo-admin項目的tomcat。dubbo-admin是Dubbo的一個可視化管理工具,可以查看服務的發布和引用的情況。

使用如下命令啟動容器:

docker run --name zookeeper-debug -p 2182:2181 -p 10000:8080 chaimm/zookeeper-dubbo:1.0
復制代碼
  • -p 2182:2181:將容器的2181端口映射到宿主機的2182端口上,該端口是ZooKeeper的端口號。
  • -p 10000:8080:將容器的8080端口映射到宿主機的10000端口上,該端口是Dubbo-Admin所在Tomcat的端口號。

啟動成功后,你就可以通過你的IP:10000/dubbo-admin-2.8.4/訪問到Dubbo-Admin,如下圖所示:

5.2 父pom文件中引入dubbo依賴

<!-- Spring Boot Dubbo 依賴 -->
<dependency><groupId>io.dubbo.springboot</groupId><artifactId>spring-boot-starter-dubbo</artifactId><version>1.0.0</version>
</dependency>
復制代碼

5.3 發布服務

假設,我們需要將Gaoxi-User項目中的UserService發布成一項RPC服務,供其他系統遠程調用,那么我們究竟該如何借助Dubbo來實現這一功能呢?

  • 在Gaoxi-Common-Service-Facade中定義UserService的接口 由于服務的發布和引用都依賴于接口,但服務的發布方和引用方在微服務架構中往往不在同一個系統中,所以需要將需要發布和引用的接口放在公共類庫中,從而雙方都能夠引用。接口如下所示:
public interface UserService {public UserEntity login(LoginReq loginReq);
}
復制代碼
  • 在Gaoxi-User中定義接口的實現 在實現類上需要加上Dubbo的@Service注解,從而Dubbo會在項目啟動的時候掃描到該注解,將它發布成一項RPC服務。
@Service(version = "1.0.0")
public class UserServiceImpl implements UserService {@Overridepublic UserEntity login(LoginReq loginReq) {// 具體的實現代碼}
}
復制代碼
  • 在Gaoxi-User的application.properties中配置服務提供者的信息
spring.dubbo.application.name=user-provider # 本服務的名稱
spring.dubbo.registry.address=zookeeper://IP:2182 # ZooKeeper所在服務器的IP和端口號
spring.dubbo.protocol.name=dubbo # RPC通信所采用的協議
spring.dubbo.protocol.port=20883 # 本服務對外暴露的端口號
spring.dubbo.scan=com.gaoxi.user.service # 服務實現類所在的路徑
復制代碼

按照上面配置完成后,當Gaoxi-User系統初始化的時候,就會掃描spring.dubbo.scan所指定的路徑下的@Service注解,該注解標識了需要發布成RPC服務的類。Dubbo會將這些類的接口信息+本服務器的IP+spring.dubbo.protocol.port所指定的端口號發送給Zookeeper,Zookeeper會將這些信息存儲起來。 這就是服務發布的過程,下面來看如何引用一項RPC服務。

5.4 引用服務

假設,Gaoxi-Controller需要調用Gaoxi-User 提供的登錄功能,此時它就需要引用UserService這項遠程服務。下面來介紹服務引用的方法。

  • 聲明需要引用的服務 引用服務非常簡單,你只需要在引用的類中聲明一項服務,然后用@Reference標識,如下所示:
@RestController
public class UserControllerImpl implements UserController {@Reference(version = "1.0.0")private UserService userService;@Overridepublic Result login(LoginReq loginReq, HttpServletResponse httpRsp) {// 登錄鑒權UserEntity userEntity = userService.login(loginReq);}
}
復制代碼
  • 在Gaoxi-Controller的application.properties中配置服務消費者的信息
spring.dubbo.application.name=controller-consumer # 本服務的名稱
spring.dubbo.registry.address=zookeeper://IP:2182 # zookeeper所在服務器的IP和端口號
spring.dubbo.scan=com.gaoxi # 引用服務的路徑
復制代碼

上述操作完成后,當Gaoxi-Controller初始化的時候,Dubbo就會掃描spring.dubbo.scan所指定的路徑,并找到所有被@Reference修飾的成員變量;然后向Zookeeper請求該服務所在的IP和端口號。當調用userService.login()的時候,Dubbo就會向Gaoxi-User發起請求,完成調用的過程。這個調用過程是一次RPC調用,但作為程序猿來說,這和調用一個本地函數沒有任何區別,遠程調用的一切都由Dubbo來幫你完成。這就是Dubbo的作用。

6. 自動化構建

Jenkins是一個自動化構建工具,它可以幫助我們擺脫繁瑣的部署過程,我們只需要在一開始配置好構建策略,以后部署只需要一鍵完成。

6.1 創建Jenkins容器

Jenkins采用Java開發,也需要Java環境,但我們使用Docker后,一切都采用容器化部署,Jenkins也不例外。

  • 拉取鏡像 這里我們使用Jenkins官方提供的鏡像,大家只需執行如下命令拉取即可:
docker pull docker.io/jenkins/jenkins
復制代碼
  • 啟動容器 由于Jenkins運行在Tomcat容器中,因此我們將容器的8080端口映射到宿主機的10080端口上:
docker run --name jenkins -p 10080:8080 docker.io/jenkins/jenkins
復制代碼
  • 初始化Jenkins 然后你需要訪問IP:10080Jenkins會帶著你進行一系列的初始化設置,你只要跟著它一步步走就行了,比較傻瓜式。

6.2 在Jenkins中創建項目

接下來我們要做的是,在Jenkins中為每一個服務創建一個項目,每個項目中定義了構建的具體流程。由于我們將整個項目分成了6個微服務,所以我們需要在Jenkins中分別為這6個服務創建項目。那句開始吧~

  • 點擊頁面左側的“新建”按鈕:

  • 輸入項目名稱gaoxi-user,選擇“構建一個Maven項目”,然后點擊“OK”:

  • 配置Git倉庫 選擇Git,然后輸入本項目Git倉庫的URL,并在Credentials中輸入Git的用戶名和密碼,如下圖所示:

  • 構建觸發器 選擇第一項,如下圖所示:

  • Pre Step Pre Step會在正式構建前執行,由于所有項目都依賴于Gaoxi-Common-Service—Facade,因此在項目構建前,需要將它安裝到本地倉庫,然后才能被當前項目正確依賴。 因此,在Pre Step中填寫如下信息:

  • Build 然后就是正式構建的過程,填寫如下信息即可:

OK,Gaoxi-User的構建過程就配置完成了。當我們點擊“立即構建”按鈕時,Jenkins首先會從我們指定的Git倉庫中拉取代碼,然后執行Pre Step中的Maven命令,將Gaoxi-Common-Serivce-Facade打包安裝到本地倉庫。然后執行Build過程,將Gaoxi-User進行編譯打包。 但此時Gaoxi-User仍然只是一個本地war包,并沒有部署到Tomcat容器中,而我們采用了容器化部署后,Jenkins服務和Gaoxi-User服務并不在同一個Docker容器中,那么究竟該如何才能將Jenkins本地編譯好的war包發送到Gaoxi-User容器中呢?這就需要使用Jenkins的一個插件——Deploy Plugin。

6.3 遠程部署

  • 下載插件 首先你需要下載Deploy Plugin,下載地址如下: https://wiki.jenkins.io/display/JENKINS/Deploy+Plugin

  • 安裝插件 在系統管理–>插件管理–>高級上傳deploy.hpi進行安裝。

  • 在父項目的pom文件中增加遠程部署插件:

<plugin><groupId>org.codehaus.cargo</groupId><artifactId>cargo-maven2-plugin</artifactId><version>1.6.5</version><configuration><container><!-- 指明使用的tomcat服務器版本 --><containerId>tomcat8x</containerId><type>remote</type></container><configuration><type>runtime</type><cargo.remote.username>Tomcat的用戶名</cargo.remote.username><cargo.remote.password>Tomcat的密碼</cargo.remote.password></configuration></configuration><executions><execution><phase>deploy</phase><goals><goal>redeploy</goal></goals></execution></executions>
</plugin>
復制代碼
  • 為Tomcat設置用戶名和密碼 修改gaoxi-user容器中tomcat的tomcat-users.xml文件,增加tomcat的manager用戶

注意:如果你使用了chaimm/tomcat鏡像,那么其中Tomcat配置都已經完成,默認用戶名:admin、默認密碼:jishimen2019。強烈建議修改用戶名和密碼。

  • 修改Jenkins中gaoxi-user的配置 在“構建后操作”中增加如下配置:
    • WAR/EAR files:表示你需要發布的war包
    • Containers:配置目標Tomcat的用戶名和密碼

7. Maven的profile功能

在實際開發中,我們的系統往往有多套環境構成,如:開發環境、測試環境、預發環境、生產環境。而不同環境的配置各不相同。如果我們只有一套配置,那么當系統從一個環境遷移到另一個環境的時候,就需要通過修改代碼來更換配置,這樣無疑增加了工作的復雜度,而且易于出錯。但好在Maven提供了profile功能,能幫助我們解決這一個問題。

  • 父項目的pom中添加profile元素 首先,我們需要在總pom的中添加多套環境的信息,如下所示:
<profiles><profile><id>dev</id><properties><profileActive>dev</profileActive></properties><activation><activeByDefault>true</activeByDefault></activation></profile><profile><id>test</id><properties><profileActive>test</profileActive></properties></profile><profile><id>prod</id><properties><profileActive>prod</profileActive></properties></profile>
</profiles>
復制代碼
  • 父項目的pom中添加resource元素 resource標識了不同環境下需要打包哪些配置文件。
<resources><resource><!-- 標識配置文件所在的目錄 --><directory>src/main/resources</directory><filtering>true</filtering><!-- 構建時將這些配置文件全都排除掉 --><excludes><exclude>application.properties</exclude><exclude>application-dev.properties</exclude><exclude>application-test.properties</exclude><exclude>application-prod.properties</exclude></excludes></resource><resource><directory>src/main/resources</directory><filtering>true</filtering><!-- 標識構建時所需要的配置文件 --><includes><include>application.properties</include><!-- ${profileActive}這個值會在maven構建時傳入 --><include>application-${profileActive}.properties</include></includes></resource>
</resources>
復制代碼
  • 父項目的pom中添加插件maven-resources-plugin 該插件用來在Maven構建時參數替換
<plugin><artifactId>maven-resources-plugin</artifactId><version>3.0.2</version><configuration><delimiters><delimiter>@</delimiter></delimiters><useDefaultDelimiters>false</useDefaultDelimiters></configuration>
</plugin>
復制代碼
  • 在子項目中創建配置 分別為dev環境、test環境、prod環境創建三套配置,application.proerpties中存放公用的配置。

  • 在application.properties中添加spring.profiles.active=@profileActive@

spring.profiles.active=@profileActive@
復制代碼
  • 修改Jenkins的配置 在所有Jenkins中所有Maven命令的末尾添加-P test,在打包的時候-P后面的參數將會作為@profileActive@的值傳入系統中,從而根據該值打包相應的application-{profileActive}.properties文件。

8. 開發流程

到此為止,所有準備工作都已經完成,接下來就可以進入代碼開發階段。下面我以一個例子,帶著大家感受下有了這套微服務框架后,我們的開發流程究竟有了哪些改變?下面以開發一個用戶登錄功能為例,介紹下使用本框架之后開發的流程。

8.1 開發目標

  • 在Gaoxi-User系統中實現登錄的業務邏輯,并發布成RPC服務
  • 在Gaoxi-Controller中遠程調用登錄服務,并向前端提供登錄的REST接口

8.2 開發登錄服務

首先需要在Gaoxi-Common-Service-Facade中創建UserService接口,并在其中聲明登錄的抽象函數。

public interface UserService {public UserEntity login(LoginReq loginReq);
}
復制代碼

PS:為什么要將UserService放在Gaoxi-Common-Service-Facade中? 在這個項目中,Gaoxi-User是UserService服務的提供方,Gaoxi-Controller是UserService服務的引用方。由于二者并不在同一個系統中,所以必須要借助于Dubbo來實現遠程方法調用。而Dubbo發布服務和引用服務的時候,都是根據服務的接口標識服務的,即服務引用方和發布方都需要使用服務的接口,因此需要將服務的接口放在所有項目共同依賴的基礎模塊——Gaoxi-Common-Service-Facade中。

然后在Gaoxi-User中開發UserService的實現——UserServiceImpl。 UserServiceImpl上必須要加上Dubbo的@Service注解,從而告訴Dubbo,在本項目初始化的時候需要將這個類發布成一項服務,供其他系統調用。

@Service(version = "1.0.0")
@org.springframework.stereotype.Service
public class UserServiceImpl implements UserService {@Autowiredprivate UserDAO userDAO;@Overridepublic UserEntity login(LoginReq loginReq) {// 校驗參數checkParam(loginReq);// 創建用戶查詢請求UserQueryReq userQueryReq = buildUserQueryReq(loginReq);// 查詢用戶List<UserEntity> userEntityList = userDAO.findUsers(userQueryReq);// 查詢失敗if (CollectionUtils.isEmpty(userEntityList)) {throw new CommonBizException(ExpCodeEnum.LOGIN_FAIL);}// 查詢成功return userEntityList.get(0);}
}
復制代碼

8.3 引用登錄服務

當UserService開發完畢后,接下來Gaoxi-Controller需要引用該服務,并向前端提供一個登錄的REST接口。 若要使用userService中的函數,僅需要在userService上添加@Reference注解,然后就像調用本地函數一樣使用userService即可。Dubbo會幫你找到UserService服務所在的IP和端口號,并發送調用請求。但這一切對于程序猿來說是完全透明的。

@RestController
public class UserControllerImpl implements UserController {@Reference(version = "1.0.0")private UserService userService;/*** 登錄* @param loginReq 登錄請求參數* @param httpRsp HTTP響應* @return 登錄是否成功*/@GetMapping("/login")@Overridepublic Result login(LoginReq loginReq, HttpServletResponse httpRsp) {// 登錄鑒權UserEntity userEntity = userService.login(loginReq);// 登錄成功doLoginSuccess(userEntity, httpRsp);return Result.newSuccessResult();}
}
復制代碼

8.4 自動構建服務

上面的代碼完成后,接下來你需要將代碼提交至你的Git倉庫。接下來就是自動化部署的過程了。

你需要進入Jenkins,由于剛才修改了Gaoxi-User和Gaoxi-Controller的代碼,因此你需要分別構建這兩個項目。 接下來Jenkins會自動從你的Git倉庫中拉取最新的代碼,然后依次執行Pre Step、Build、構建后操作的過程。由于我們在Pre Step中設置了編譯Gaoxi-Common-Service-Facade,因此Jenkins首先會將其安裝到本地倉庫;然后再執行Build過程,構建Gaoxi-User,并將其打包成war包。最后將執行“構建后操作”,將war包發布到相應的tomcat容器中。 至此,整個發布流程完畢!

8.5 查看服務的狀態

當Jenkins構建完成后,我們可以登錄Dubbo-Admin查看服務發布和引用的狀態。

當我們搜索UserService服務后,可以看到,該服務的提供者已經成功發布了服務:

點擊“消費者”我們可以看到,該服務已經被controller-consumer成功訂閱:

9. 總結

總結一下,這套框架有如下優勢:

  1. 微服務架構 我們借助于SpringBoot和Dubbo實現了微服務架構。微服務架構的理念就是將一個原本龐大、復雜的系統,按照業務功能拆分成一個個具有獨立功能、可以獨立運行的子系統,系統之間若有依賴,則通過RPC接口通信。從而最大限度地降低了系統之間的耦合度,從而更加易于擴展、更加易于維護。

  2. 容器化部署 我們借助于Docker實現了容器化部署。容器能夠幫助我們屏蔽不同環境下的配置問題,使得我們只需要有一個Dockerfile文件,就可以處處運行。和虛擬機一樣,Docker也擁有環境隔離的能力,但比虛擬機更加輕量級,由于每個容器僅僅是一條進程,因此它可以達到秒級的啟動速度。

  3. 自動化構建 我們借助于Jenkins實現了所有項目的自動化構建與部署。我們只需要點擊“立即構建”這個按鈕,Jenkins就可以幫助我們梳理好錯綜復雜的項目依賴關系,準確無誤地完成構建,并將war包發送到相應的web容器中。在啟動的過程中,Dubbo會掃描當前項目所需要發布和引用的服務,將所需要發布的服務發布到ZooKeeper上,并向ZooKeeper訂閱所需的服務。 有了Jenkins之后,這一切都是自動化完成。也許你并沒有太強烈地感受到Jenkins所帶來的便利。但是你想一想,對于一個具有錯綜復雜的依賴關系的微服務系統而言,如果每個服務的構建都需要你手動完成的話,你很快就會崩潰,你大把的時間將會投入在無聊但又容易出錯的服務構建上。而Jenkins的出現能讓這一切自動化完成。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/452546.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/452546.shtml
英文地址,請注明出處:http://en.pswp.cn/news/452546.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

C語言中臨時變量寫在哪里,C語言中不允許創建臨時變量,交換兩個數的內容

在C語言中可以通過建立臨時變量來實現兩個變量的交換&#xff0c;當不允許建立臨時變量時&#xff0c;應該怎樣實現兩變量的交換呢&#xff1f;假設有兩個變量num1和num2&#xff1b;下面通過兩種方法進行分析。方法一&#xff1a;利用加減法。具體算法分析如下&#xff1a;由于…

Python面試題總結(8)--操作類

1. 請寫一個 Python 邏輯&#xff0c;計算一個文件中的大寫字母數量 答&#xff1a;讀取‘A.txt’中的大寫字母數量 with open(A.txt) as f:"""計算一個文件中的大寫字母數量"""count 0for i in f.read():if i.isupper():count 1 print(cou…

聯合主鍵

一個數據庫表只能有一個主鍵&#xff0c;不允許兩個主鍵。但是允許兩個字段聯合起來設置為主鍵&#xff0c;這叫聯合主鍵。

node之post提交上傳

post文件上傳 multer 中間件 在node中 express為了性能考慮采用按需加載的方式&#xff0c;引入各種中間件來完成需求&#xff0c; 平時解析post上傳數據時候&#xff0c;是用body-parse。但這個中間件有缺點&#xff0c;只能解析post的文本內容&#xff0c;&#xff08;applic…

要有自己的核心競爭力,應對時代變遷

在之前的PC時代和互聯網時代&#xff0c;人們都有一些顧慮&#xff0c;覺得智能化新技術的到來和采用將會導致就業人數急劇減少。 但實際上&#xff0c;無論是PC還是互聯網這樣新技術的到來&#xff0c;其實都對就業有極大的促進作用&#xff0c;其中最明顯的例子&#xff0c;…

ul、li列表簡單實用代碼實例

利用ul和li可以實現列表效果&#xff0c;下面就是一個簡單的演示。 代碼如下: 010203040506070809101112131415161718192021222324252627282930313233<!DOCTYPE html><html> <head> <meta charset" utf-8"> <meta name"author"…

Flask--讀取配置參數的方式

文章目錄方法1. 使用配置文件方法2. 使用對象配置參數方法3. 直接操作config的字典對象項目實例方法1. 使用配置文件 首先將配置參數寫在文件中&#xff0c;例如&#xff1a;config.cfg 然后導入: app Flask("__name__") app.config.from_pyfile("config.cf…

g開頭的C語言編程軟件,C語言函數大全(g開頭)

函數名: gcvt功 能: 把浮點數轉換成字符串用 法: char *gcvt(double value, int ndigit, char *buf);程序例:#include#includeint main(void){char str[25];double num;int sig 5; /* significant digits *//* a regular number */num 9.876;gcvt(num, sig, str);printf(&quo…

什么是總體設計

總體設計的基本目的就是回答“概括地說&#xff0c;系統應該如何實現”這個問題&#xff0c;因此&#xff0c;總體設計又稱為概要設計或初步設計。總體設計階段的另一項重要任務是設計軟件的結構&#xff0c;也就是要確定系統中每個程序是由哪些模塊組成的&#xff0c;以及這些…

程序員成熟的標志《程序員成長路線圖:從入門到優秀》

對好書進行整理&#xff0c;把好內容共享。 我見證過許多的程序員的成長&#xff0c;他們很多人在進入成熟期之后&#xff0c;技術上相對較高&#xff0c;一般項目開發起來比較自信&#xff0c;沒有什么太大的困難&#xff0c;有的職位上也有所提升&#xff0c;成了項目經理、…

Diango博客--1.Django的接客之道

文章目錄0.思路引導1.實現最簡單的HelloWorld2.實現最簡單的HelloWorld(使用Templates)0.思路引導 django 的開發流程&#xff1a; 即首先配置 URL&#xff0c;把 URL 和相應的視圖函數綁定&#xff0c;一般寫在 urls.py 文件里&#xff0c;然后在工程的 urls.py 文件引入。 …

c語言is int number,C語言中NSInteger,NSNumber以及Int的區別

NSInteger和NSNumber首先:NSInteger,NSNumber并沒有什么關系,更不要想當然的以為二者還有什么繼承關系,甚至還有人問NSInteger是不是NSNumber的子類?答案當然是NO!!!NSInteger只是一個基本的數據類型,而NSNumber是OC的對象,并且NSNumber繼承自NSValue,NSValue又繼承自NSObject…

Git的GUI工具sourcetree的使用

一、Git的學習這部分學習廖雪峰的git教程&#xff0c;參加以下鏈接&#xff1a;https://www.liaoxuefeng.com/wiki/0013739516305929606dd18361248578c67b8067c8c017b0001、首先是git的基本概念&#xff0c;如下圖所示&#xff1a;整個git管理主要分為工作區、版本庫&#xff0…

YY一下,扎克伯格做了一個什么樣的AI家居助手?

對于這款令小扎太太抓狂的AI家居助手&#xff0c;難道就沒人好奇嗎&#xff1f; 據說&#xff0c;扎克伯格每年都要給自己定個目標&#xff0c;而他也即將完成今年的目標——打造一個AI家居助手。 當初&#xff0c;在定下這個目標時&#xff0c;小扎為我們簡單描述了一下&…

Diango博客--2.博客從“裸奔”到“有皮膚”

文章目錄0.思路引導1.更改視圖函數&#xff0c;從數據庫中獲取數據2.網上下載模板&#xff0c;添加靜態文件3.修改模板Templates中css、js文件的加載路徑4.修改模板&#xff0c;引入模板變量&#xff0c;獲取數據庫數據0.思路引導 前文的Hello World 級別的視圖函數特別簡單&a…

抽象

人類在認識復雜現象的過程中使用的最強有力的思維工具是抽象。人們在實踐中認識到&#xff0c;在現實世界中一定事物、狀態或過程之間總存在著某些相似的方面(共性)。把這些相似的方面集中和概括起來&#xff0c;暫時忽略它們之間的差異&#xff0c;這就是抽象。或者說抽象就是…

程序員的成長從開竅開始系列 一、如何擺脫低級錯誤的困擾

最近&#xff0c;有兩位Google Maps API的初學者向我請教他們按照最簡單例子寫的程序為什么不能正常的運行。 其中一位用GTalk跟我交流&#xff0c;我仔細了看了他的代碼&#xff0c;沒看出問題&#xff0c;把代碼保存在本地&#xff0c;打開Firefox的錯誤控制臺&#xff0c;用…

脈沖時間寬度c語言,什么是脈沖寬度_脈沖寬度是什么意思

脈沖寬度是個很廣泛的詞&#xff0c;在不同的領域&#xff0c;脈沖寬度有不同的含義。脈沖寬度從學術角度講就是電流或者電壓隨時間有規律變化的時間寬度&#xff0c;平時研究主要是方波&#xff0c;三角波&#xff0c;鋸齒波&#xff0c;正弦函數波等等&#xff0c;這些波形變…

HDU - 5919 Sequence II

題意&#xff1a; 給定長度為n的序列和q次詢問。每次詢問給出一個區間&#xff08;L&#xff0c;R&#xff09;&#xff0c;求出區間內每個數第一次出現位置的中位數&#xff0c;強制在線。 題解&#xff1a; 用主席樹從右向左的插入點。對于當前點i&#xff0c;如果a[i]出現過…

Django博客--3.創作后臺開啟

文章目錄0.創建admin后臺管理員賬號1.在 admin 后臺注冊模型2.漢化應用的標題3.漢化應用下各個模塊的名稱4.漢化應用下各個模塊的屬性的名稱5.文章列表顯示更加詳細的信息6.簡化新增文章的表單7.自動設置文章作者為當前用戶8.設定創建時間為當前時間9.設定修改建時間為保存時的…