ZooKeeper分布式專題(六)-- Dubbo入門到重構(gòu)服務(wù)

ZooKeeper分布式專題與Dubbo微服務(wù)入門

本項(xiàng)目地址

Dubbo入門到重構(gòu)服務(wù)

本文將以原理+實(shí)戰(zhàn)的方式,首先對(duì)“微服務(wù)”相關(guān)的概念進(jìn)行知識(shí)點(diǎn)掃盲,然后開始手把手教你搭建這一整套的微服務(wù)系統(tǒng)。

微服務(wù)

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

要講清楚微服務(wù),我先要從一個(gè)系統(tǒng)架構(gòu)的演進(jìn)過程講起。

單機(jī)結(jié)構(gòu)

我想大家最最最熟悉的就是單機(jī)結(jié)構(gòu),一個(gè)系統(tǒng)業(yè)務(wù)量很小的時(shí)候所有的代碼都放在一個(gè)項(xiàng)目中就好了,然后這個(gè)項(xiàng)目部署在一臺(tái)服務(wù)器上就好了。整個(gè)項(xiàng)目所有的服務(wù)都由這臺(tái)服務(wù)器提供。這就是單機(jī)結(jié)構(gòu)。 那么,單機(jī)結(jié)構(gòu)有啥缺點(diǎn)呢?我想缺點(diǎn)是顯而易見的,單機(jī)的處理能力畢竟是有限的,當(dāng)你的業(yè)務(wù)增長到一定程度的時(shí)候,單機(jī)的硬件資源將無法滿足你的業(yè)務(wù)需求。此時(shí)便出現(xiàn)了集群模式,往下接著看。

集群結(jié)構(gòu)

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

單機(jī)處理到達(dá)瓶頸的時(shí)候,你就把單機(jī)復(fù)制幾份,這樣就構(gòu)成了一個(gè)“集群”。集群中每臺(tái)服務(wù)器就叫做這個(gè)集群的一個(gè)“節(jié)點(diǎn)”,所有節(jié)點(diǎn)構(gòu)成了一個(gè)集群。每個(gè)節(jié)點(diǎn)都提供相同的服務(wù),那么這樣系統(tǒng)的處理能力就相當(dāng)于提升了好幾倍(有幾個(gè)節(jié)點(diǎn)就相當(dāng)于提升了這么多倍)。

但問題是用戶的請(qǐng)求究竟由哪個(gè)節(jié)點(diǎn)來處理呢?最好能夠讓此時(shí)此刻負(fù)載較小的節(jié)點(diǎn)來處理,這樣使得每個(gè)節(jié)點(diǎn)的壓力都比較平均。要實(shí)現(xiàn)這個(gè)功能,就需要在所有節(jié)點(diǎn)之前增加一個(gè)“調(diào)度者”的角色,用戶的所有請(qǐng)求都先交給它,然后它根據(jù)當(dāng)前所有節(jié)點(diǎn)的負(fù)載情況,決定將這個(gè)請(qǐng)求交給哪個(gè)節(jié)點(diǎn)處理。這個(gè)“調(diào)度者”有個(gè)牛逼了名字——負(fù)載均衡服務(wù)器。

集群結(jié)構(gòu)的好處就是系統(tǒng)擴(kuò)展非常容易。如果隨著你們系統(tǒng)業(yè)務(wù)的發(fā)展,當(dāng)前的系統(tǒng)又支撐不住了,那么給這個(gè)集群再增加節(jié)點(diǎn)就行了。但是,當(dāng)你的業(yè)務(wù)發(fā)展到一定程度的時(shí)候,你會(huì)發(fā)現(xiàn)一個(gè)問題——無論怎么增加節(jié)點(diǎn),貌似整個(gè)集群性能的提升效果并不明顯了。這時(shí)候,你就需要使用微服務(wù)結(jié)構(gòu)了。

微服務(wù)結(jié)構(gòu)

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

OK,下面開始介紹所謂的微服務(wù)。 微服務(wù)就是將一個(gè)完整的系統(tǒng),按照業(yè)務(wù)功能,拆分成一個(gè)個(gè)獨(dú)立的子系統(tǒng),在微服務(wù)結(jié)構(gòu)中,每個(gè)子系統(tǒng)就被稱為“服務(wù)”。這些子系統(tǒng)能夠獨(dú)立運(yùn)行在web容器中,它們之間通過RPC方式通信。

舉個(gè)例子,假設(shè)需要開發(fā)一個(gè)在線商城。按照微服務(wù)的思想,我們需要按照功能模塊拆分成多個(gè)獨(dú)立的服務(wù),如:用戶服務(wù)、產(chǎn)品服務(wù)、訂單服務(wù)、后臺(tái)管理服務(wù)、數(shù)據(jù)分析服務(wù)等等。這一個(gè)個(gè)服務(wù)都是一個(gè)個(gè)獨(dú)立的項(xiàng)目,可以獨(dú)立運(yùn)行。如果服務(wù)之間有依賴關(guān)系,那么通過RPC方式調(diào)用。

這樣的好處有很多:

  1. 系統(tǒng)之間的耦合度大大降低,可以獨(dú)立開發(fā)、獨(dú)立部署、獨(dú)立測(cè)試,系統(tǒng)與系統(tǒng)之間的邊界非常明確,排錯(cuò)也變得相當(dāng)容易,開發(fā)效率大大提升。
  2. 系統(tǒng)之間的耦合度降低,從而系統(tǒng)更易于擴(kuò)展。我們可以針對(duì)性地?cái)U(kuò)展某些服務(wù)。假設(shè)這個(gè)商城要搞一次大促,下單量可能會(huì)大大提升,因此我們可以針對(duì)性地提升訂單系統(tǒng)、產(chǎn)品系統(tǒng)的節(jié)點(diǎn)數(shù)量,而對(duì)于后臺(tái)管理系統(tǒng)、數(shù)據(jù)分析系統(tǒng)而言,節(jié)點(diǎn)數(shù)量維持原有水平即可。
  3. 服務(wù)的復(fù)用性更高。比如,當(dāng)我們將用戶系統(tǒng)作為單獨(dú)的服務(wù)后,該公司所有的產(chǎn)品都可以使用該系統(tǒng)作為用戶系統(tǒng),無需重復(fù)開發(fā)。

那么問題來了,當(dāng)采用微服務(wù)結(jié)構(gòu)后,一個(gè)完整的系統(tǒng)可能有很多獨(dú)立的子系統(tǒng)組成,當(dāng)業(yè)務(wù)量漸漸發(fā)展起來之后,而這些子系統(tǒng)之間的關(guān)系將錯(cuò)綜復(fù)雜,而且為了能夠針對(duì)性地增加某些服務(wù)的處理能力,某些服務(wù)的背后可能是一個(gè)集群模式,由多個(gè)節(jié)點(diǎn)構(gòu)成,這無疑大大增加了運(yùn)維的難度。微服務(wù)的想法好是好,但開發(fā)、運(yùn)維的復(fù)雜度實(shí)在是太高。為了解決這些問題,阿里巴巴的Dubbo就橫空出世了。

Dubbo

Dubbo是一套微服務(wù)系統(tǒng)的協(xié)調(diào)者,在它這套體系中,一共有三種角色,分別是:服務(wù)提供者(下面簡(jiǎn)稱提供者)、服務(wù)消費(fèi)者(下面簡(jiǎn)稱消費(fèi)者)、注冊(cè)中心。

你在使用的時(shí)候需要將Dubbo的jar包引入到你的項(xiàng)目中,也就是每個(gè)服務(wù)都要引入Dubbo的jar包。然后當(dāng)這些服務(wù)初始化的時(shí)候,Dubbo就會(huì)將當(dāng)前系統(tǒng)需要發(fā)布的服務(wù)、以及當(dāng)前系統(tǒng)的IP和端口號(hào)發(fā)送給注冊(cè)中心,注冊(cè)中心便會(huì)將其記錄下來。這就是服務(wù)發(fā)布的過程。與此同時(shí),也是在系統(tǒng)初始化的時(shí)候,Dubbo還會(huì)掃描一下當(dāng)前系統(tǒng)所需要引用的服務(wù),然后向注冊(cè)中心請(qǐng)求這些服務(wù)所在的IP和端口號(hào)。接下來系統(tǒng)就可以正常運(yùn)行了。當(dāng)系統(tǒng)A需要調(diào)用系統(tǒng)B的服務(wù)的時(shí)候,A就會(huì)與B建立起一條RPC信道,然后再調(diào)用B系統(tǒng)上相應(yīng)的服務(wù)。

這,就是Dubbo的作用。

創(chuàng)建項(xiàng)目的組織結(jié)構(gòu)

  • 創(chuàng)建一個(gè)Maven Project,命名為 microserver-dubbo-learning,
    這個(gè)Project由多個(gè)Module構(gòu)成,每個(gè)Module對(duì)應(yīng)著“微服務(wù)”的一個(gè)子系統(tǒng),可獨(dú)立運(yùn)行,是一個(gè)獨(dú)立的項(xiàng)目。 這也是目前主流的項(xiàng)目組織形式,即多模塊項(xiàng)目。
  • 在這個(gè)項(xiàng)目下創(chuàng)建各個(gè)子模塊, 每個(gè)子模塊都是一個(gè)獨(dú)立的SpringBoot項(xiàng)目:
    • micro-user 用戶服務(wù)
    • micro-order 訂單服務(wù)
    • micro-product 商品服務(wù)
    • micro-analysis 數(shù)據(jù)分析服務(wù)
    • micro-controller 本系統(tǒng)的控制層,和以往三層結(jié)構(gòu)中的Controller層的作用一樣,都是用作請(qǐng)求調(diào)度,只不過在微服務(wù)架構(gòu)中,我們將它抽象成一個(gè)單獨(dú)的系統(tǒng),可以獨(dú)立運(yùn)行。
    • micro-common 它處于本系統(tǒng)的最底層,被所有模塊依賴,一些公用的類庫都放在這里。
    • micro-api 接口服務(wù),
    • micro-redis 我們將Redis封裝成一個(gè)單獨(dú)的服務(wù),運(yùn)行在獨(dú)立的容器中,當(dāng)哪一個(gè)模塊需要使用Redis的時(shí)候,僅需要引入該服務(wù)即可,就免去了各種繁瑣的、重復(fù)的配置。而這些配置均在micro-redis系統(tǒng)中完成了。


      image.png

下面我們開始動(dòng)手創(chuàng)建項(xiàng)目

1,new 一個(gè) Project

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

image.png

2,創(chuàng)建Model,在Project上創(chuàng)建model

image.png

依次創(chuàng)建好所有的model,如圖所示:

image.png

3,構(gòu)建模塊之間的依賴關(guān)系:

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

image.png

micro-user、micro-analysis、micro-product、micro-order這四個(gè)系統(tǒng)相當(dāng)于以往三層結(jié)構(gòu)的Service層,提供系統(tǒng)的業(yè)務(wù)邏輯,只不過在微服務(wù)結(jié)構(gòu)中,Service層的各個(gè)模塊都被抽象成一個(gè)個(gè)單獨(dú)的子系統(tǒng),它們提供RPC接口供上面的micro-controller調(diào)用。它們之間的調(diào)用由Dubbo來完成,所以它們的pom文件中并不需要作任何配置。而這些模塊和micro-common之間是本地調(diào)用,因此需要將micro-common打成jar包,并讓這些模塊依賴這個(gè)jar,因此就需要在所有模塊的pom中配置和micro-common的依賴關(guān)系。

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

  • 首先將micro-common的打包方式設(shè)成jar,當(dāng)打包這個(gè)模塊的時(shí)候,Maven會(huì)將它打包成jar,并安裝在本地倉庫中。這樣其他模塊打包的時(shí)候就可以引用這個(gè)jar。
<dependency>
    <artifactId>micro-common</artifactId>
    <groupId>cn.haoxy.micro.server.dubbo.common</groupId>
    <version>v1.0.0</version>
    <packaging>jar</packaging>
</dependency>
  • 將其他模塊的打包方式設(shè)為war,除了micro-common外,其他模塊都是一個(gè)個(gè)可獨(dú)立運(yùn)行的子系統(tǒng),需要在web容器中運(yùn)行,所以我們需要將這些模塊的打包方式設(shè)成war
    <artifactId>micro-user</artifactId>
    <groupId>cn.haoxy.micro.server.dubbo.user</groupId>
    <version>v1.0.0</version>
    <packaging>war</packaging>
  • 在總pom中指定子模塊modules標(biāo)簽指定了當(dāng)前模塊的子模塊是誰,但是僅在父模塊的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>

到此為止,模塊的依賴關(guān)系配置完畢!但要注意模塊打包的順序。由于所有模塊都依賴于micro-common模塊,因此在構(gòu)建模塊時(shí),首先需要編譯、打包、安裝micro-common,將它打包進(jìn)本地倉庫中,這樣上層模塊才能引用到。當(dāng)該模塊安裝完畢后,再構(gòu)建上層模塊。否則在構(gòu)建上層模塊的時(shí)候會(huì)出現(xiàn)找不到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>

當(dāng)父模塊的pom中配置了公用依賴后,子模塊的pom文件將非常簡(jiǎn)潔,如下所示:

<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一共定義了三種角色,分別是:服務(wù)提供者、服務(wù)消費(fèi)者、注冊(cè)中心。注冊(cè)中心是服務(wù)提供者和服務(wù)消費(fèi)者的橋梁,服務(wù)消費(fèi)者會(huì)在初始化的時(shí)候?qū)⒆约旱腎P和端口號(hào)發(fā)送給注冊(cè)中心,而服務(wù)消費(fèi)者通過注冊(cè)中心知道服務(wù)提供者的IP和端口號(hào)。

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

dubbo官網(wǎng)

  • 父pom文件中引入dubbo依賴
<dependency>
   <groupId>com.alibaba.boot</groupId>
   <artifactId>dubbo-spring-boot-starter</artifactId>
   <version>0.2.0</version>
</dependency>
  • 發(fā)布服務(wù)

假設(shè),我們需要將micro-user項(xiàng)目中的UserService發(fā)布成一項(xiàng)RPC服務(wù),供其他系統(tǒng)遠(yuǎn)程調(diào)用,那么我們究竟該如何借助Dubbo來實(shí)現(xiàn)這一功能呢?

在micro-common中定義UserService的接口,由于服務(wù)的發(fā)布和引用都依賴于接口,但服務(wù)的發(fā)布方和引用方在微服務(wù)架構(gòu)中往往不在同一個(gè)系統(tǒng)中,所以需要將需要發(fā)布和引用的接口放在公共類庫中,從而雙方都能夠引用。接口如下所示:


public interface UserService {


    UserEntity login(LoginReq loginReq);

}

在micro-user中定義接口的實(shí)現(xiàn),在實(shí)現(xiàn)類上需要加上Dubbo的@Service注解,從而Dubbo會(huì)在項(xiàng)目啟動(dòng)的時(shí)候掃描到該注解,將它發(fā)布成一項(xiàng)RPC服務(wù)。

@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 ....

    }
}

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

## Dubbo 服務(wù)提供者配置
dubbo.application.name=user-provider # 本服務(wù)的名稱
dubbo.registry.address=127.0.0.1:2181 # ZooKeeper所在服務(wù)器的IP和端口號(hào)
dubbo.registry.protocol=zookeeper
dubbo.protocol.name=dubbo  # RPC通信所采用的協(xié)議
dubbo.protocol.port=20880  # 本服務(wù)對(duì)外暴露的端口號(hào)
dubbo.scan.base-packages=cn.haoxy.micro.server.dubbo.user.service  # 服務(wù)實(shí)現(xiàn)類所在的路徑

按照上面配置完成后,當(dāng)micro-user系統(tǒng)初始化的時(shí)候,就會(huì)掃描dubbo.scan.base-packages所指定的路徑下的@Service注解,該注解標(biāo)識(shí)了需要發(fā)布成RPC服務(wù)的類。Dubbo會(huì)將這些類的接口信息+本服務(wù)器的IP+dubbo.protocol.port所指定的端口號(hào)發(fā)送給Zookeeper,Zookeeper會(huì)將這些信息存儲(chǔ)起來。 這就是服務(wù)發(fā)布的過程,下面來看如何引用一項(xiàng)RPC服務(wù)。

  • 引用服務(wù)

假設(shè),micro-controller需要調(diào)用micro-user 提供的登錄功能,此時(shí)它就需要引用UserService這項(xiàng)遠(yuǎn)程服務(wù)。下面來介紹服務(wù)引用的方法。

聲明需要引用的服務(wù),引用服務(wù)非常簡(jiǎn)單,你只需要在引用的類中聲明一項(xiàng)服務(wù),然后用@Reference標(biāo)識(shí),如下所示:

@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的值一定要一致;

配置服務(wù)消費(fèi)者(micro-controller)

## Dubbo 服務(wù)消費(fèi)者配置
dubbo.application.name=controller-consumer # 本服務(wù)的名稱
dubbo.registry.address=zookeeper://127.0.0.1:2181  # zookeeper所在服務(wù)器的IP和端口號(hào)
dubbo.scan.base-packages=cn.haoxy.micro.server.dubbo  # 引用服務(wù)的路徑

上述操作完成后,當(dāng)micro-controller初始化的時(shí)候,Dubbo就會(huì)掃描dubbo.scan.base-packages所指定的路徑,并找到所有被@Reference修飾的成員變量;然后向Zookeeper請(qǐng)求該服務(wù)所在的IP和端口號(hào)。當(dāng)調(diào)用userService.login()的時(shí)候,Dubbo就會(huì)向micro-user發(fā)起請(qǐng)求,完成調(diào)用的過程。這個(gè)調(diào)用過程是一次RPC調(diào)用,但作為程序猿來說,這和調(diào)用一個(gè)本地函數(shù)沒有任何區(qū)別,遠(yuǎn)程調(diào)用的一切都由Dubbo來幫你完成。這就是Dubbo的作用。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容