1. 程式人生 > 程式設計 >ZooKeeper分散式專題(六)-- Dubbo入門到重構服務

ZooKeeper分散式專題(六)-- Dubbo入門到重構服務

ZooKeeper分散式專題與Dubbo微服務入門

本專案地址

Dubbo入門到重構服務

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

微服務

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

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

單機結構

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

叢集結構

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

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

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

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

微服務結構

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

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

舉個例子,假設需要開發一個線上商城。按照微服務的思想,我們需要按照功能模組拆分成多個獨立的服務,如:使用者服務、產品服務、訂單服務、後臺管理服務、資料分析服務等等。這一個個服務都是一個個獨立的專案,可以獨立執行。如果服務之間有依賴關係,那麼通過RPC方式呼叫。

這樣的好處有很多:

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

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

Dubbo

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

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

這,就是Dubbo的作用。

建立專案的組織結構

  • 建立一個Maven Project,命名為 microserver-dubbo-learning,這個Project由多個Module構成,每個Module對應著“微服務”的一個子系統,可獨立執行,是一個獨立的專案。 這也是目前主流的專案組織形式,即多模組專案。
  • 在這個專案下建立各個子模組,每個子模組都是一個獨立的SpringBoot專案:
    • micro-user 使用者服務
    • micro-order 訂單服務
    • micro-product 商品服務
    • micro-analysis 資料分析服務
    • micro-controller 本系統的控制層,和以往三層結構中的Controller層的作用一樣,都是用作請求排程,只不過在微服務架構中,我們將它抽象成一個單獨的系統,可以獨立執行。
    • micro-common 它處於本系統的最底層,被所有模組依賴,一些公用的類庫都放在這裡。
    • micro-api 介面服務,
    • micro-redis 我們將Redis封裝成一個單獨的服務,執行在獨立的容器中,當哪一個模組需要使用Redis的時候,僅需要引入該服務即可,就免去了各種繁瑣的、重複的配置。而這些配置均在micro-redis系統中完成了。
      image.png

下面我們開始動手建立專案

1,new 一個 Project

groupId:cn.haoxy.micro.server.dubbo artifactId:microserver-dubbo-learning version:v1.0.0

image.png

2,建立Model,在Project上建立model

image.png

依次建立好所有的model,如圖所示:

image.png

3,構建模組之間的依賴關係:

目前為止,模組之間沒有任何聯絡,下面我們要通過pom檔案來指定它們之間的依賴關係,依賴關係如下圖所示:

image.png

micro-user、micro-analysis、micro-product、micro-order這四個系統相當於以往三層結構的Service層,提供系統的業務邏輯,只不過在微服務結構中,Service層的各個模組都被抽象成一個個單獨的子系統,它們提供RPC介面供上面的micro-controller呼叫。它們之間的呼叫由Dubbo來完成,所以它們的pom檔案中並不需要作任何配置。而這些模組和micro-common之間是本地呼叫,因此需要將micro-common打成jar包,並讓這些模組依賴這個jar,因此就需要在所有模組的pom中配置和micro-common的依賴關係。

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

  • 首先將micro-common的打包方式設成jar,當打包這個模組的時候,Maven會將它打包成jar,並安裝在本地倉庫中。這樣其他模組打包的時候就可以引用這個jar。
<dependency>
    <artifactId>micro-common</artifactId>
    <groupId>cn.haoxy.micro.server.dubbo.common</groupId>
    <version>v1.0.0</version>
    <packaging>jar</packaging>
</dependency>
複製程式碼
  • 將其他模組的打包方式設為war,除了micro-common外,其他模組都是一個個可獨立執行的子系統,需要在web容器中執行,所以我們需要將這些模組的打包方式設成war
    <artifactId>micro-user</artifactId>
    <groupId>cn.haoxy.micro.server.dubbo.user</groupId>
    <version>v1.0.0</version>
    <packaging>war</packaging>
複製程式碼
  • 在總pom中指定子模組modules標籤指定了當前模組的子模組是誰,但是僅在父模組的pom檔案中指定子模組還不夠,還需要在子模組的pom檔案中指定父模組是誰。
<modules>
  <module>micro-user</module>
  <module>micro-order</module>
  <module>micro-product</module>
  <module>micro-api</module>
  <module>micro-controller</module>
  <module>micro-analysis</module>
  <module>micro-common</module>
  <module>micro-redis</module>
</modules>
複製程式碼
  • 在子模組中指定父模組

例如在 micro-user子模組中的 pom.xml中指定父模組

<parent>
   <artifactId>microserver-dubbo-learning</artifactId>
   <groupId>cn.haoxy.micro.server.dubbo</groupId>
   <version>v1.0.0</version>
</parent>
複製程式碼

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

4,在父模組的pom中新增所有子模組公用的依賴

<dependencies>
        <dependency>
            <artifactId>micro-common</artifactId>
            <groupId>cn.haoxy.micro.server.dubbo.common</groupId>
            <version>v1.0.0</version>
        </dependency>
        <!--Spring MVC-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--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>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>

        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.0.11</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>

        <!--fastjson-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.31</version>
        </dependency>
    </dependencies>
複製程式碼

當父模組的pom中配置了公用依賴後,子模組的pom檔案將非常簡潔,如下所示:

<modelVersion>4.0.0</modelVersion>
<parent>
  <artifactId>microserver-dubbo-learning</artifactId>
  <groupId>cn.haoxy.micro.server.dubbo</groupId>
  <version>v1.0.0</version>
</parent>


<artifactId>micro-user</artifactId>
<groupId>cn.haoxy.micro.server.dubbo.user</groupId>
<version>v1.0.0</version>

複製程式碼

5,整合dubbo

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

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

dubbo官網

  • 父pom檔案中引入dubbo依賴
<dependency>
   <groupId>com.alibaba.boot</groupId>
   <artifactId>dubbo-spring-boot-starter</artifactId>
   <version>0.2.0</version>
</dependency>
複製程式碼
  • 釋出服務

假設,我們需要將micro-user專案中的UserService釋出成一項RPC服務,供其他系統遠端呼叫,那麼我們究竟該如何藉助Dubbo來實現這一功能呢?

在micro-common中定義UserService的介面,由於服務的釋出和引用都依賴於介面,但服務的釋出方和引用方在微服務架構中往往不在同一個系統中,所以需要將需要釋出和引用的介面放在公共類庫中,從而雙方都能夠引用。介面如下所示:


public interface UserService {


    UserEntity login(LoginReq loginReq);

}

複製程式碼

在micro-user中定義介面的實現,在實現類上需要加上Dubbo的@Service註解,從而Dubbo會在專案啟動的時候掃描到該註解,將它釋出成一項RPC服務。

@Service(version = "v1.0.0")
@org.springframework.stereotype.Service
public class UserServiceImpl implements UserService {

    @Autowired
    UserDAO userDAO;
    @Override
    public UserEntity login(LoginReq loginReq) {
        // do something ....

    }
}
複製程式碼

配置服務提供者(micro-user、micro-analysis、micro-product、micro-order)

## Dubbo 服務提供者配置
dubbo.application.name=user-provider # 本服務的名稱
dubbo.registry.address=127.0.0.1:2181 # ZooKeeper所在伺服器的IP和埠號
dubbo.registry.protocol=zookeeper
dubbo.protocol.name=dubbo  # RPC通訊所採用的協議
dubbo.protocol.port=20880  # 本服務對外暴露的埠號
dubbo.scan.base-packages=cn.haoxy.micro.server.dubbo.user.service  # 服務實現類所在的路徑
複製程式碼

按照上面配置完成後,當micro-user系統初始化的時候,就會掃描dubbo.scan.base-packages所指定的路徑下的@Service註解,該註解標識了需要釋出成RPC服務的類。Dubbo會將這些類的介面資訊+本伺服器的IP+dubbo.protocol.port所指定的埠號傳送給Zookeeper,Zookeeper會將這些資訊儲存起來。 這就是服務釋出的過程,下面來看如何引用一項RPC服務。

  • 引用服務

假設,micro-controller需要呼叫micro-user 提供的登入功能,此時它就需要引用UserService這項遠端服務。下面來介紹服務引用的方法。

宣告需要引用的服務,引用服務非常簡單,你只需要在引用的類中宣告一項服務,然後用@Reference標識,如下所示:

@RestController
public class UserControllerImpl implements UserController {


    @Reference(version = "v1.0.0")
    UserService userService;

    @PostMapping(value = "login")
    @Override
    public Result login(@RequestBody LoginReq loginReq,HttpServletResponse httpRsp) {
        UserEntity userEntity = userService.login(loginReq);
        return Result.newSuccessResult(userEntity);
    }
}

複製程式碼

注意: @Reference(version = "v1.0.0")和@Service(version = "v1.0.0")的version的值一定要一致;

配置服務消費者(micro-controller)

## Dubbo 服務消費者配置
dubbo.application.name=controller-consumer # 本服務的名稱
dubbo.registry.address=zookeeper://127.0.0.1:2181  # zookeeper所在伺服器的IP和埠號
dubbo.scan.base-packages=cn.haoxy.micro.server.dubbo  # 引用服務的路徑
複製程式碼

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