利用springboot+dubbo,構建分布式微服務,全程註解開發

java領域佼佼者 發佈 2020-05-13T16:43:02+00:00

隨著網際網路的發展,網站應用的規模不斷擴大,常規的垂直應用架構已無法應對,分布式服務架構以及流動計算架構勢在必行,亟需一個治理系統確保架構有條不紊的演進。一、先來一張圖說起 Dubbo,相信大家都不會陌生!

隨著網際網路的發展,網站應用的規模不斷擴大,常規的垂直應用架構已無法應對,分布式服務架構以及流動計算架構勢在必行,亟需一個治理系統確保架構有條不紊的演進。

一、先來一張圖

說起 Dubbo,相信大家都不會陌生!阿里巴巴公司開源的一個高性能優秀的服務框架,可以使得應用可通過高性能的 RPC 實現服務的輸出和輸入功能,同時可以和 Spring 框架無縫集成。

節點角色說明:

  • Provider :暴露服務的服務提供方
  • Consumer :調用遠程服務的服務消費方
  • Registry :服務註冊與發現的註冊中心
  • Monitor :統計服務的調用次數和調用時間的監控中心
  • Container :服務運行容器

二、實現思路

今天,我們以一個 用戶選擇商品下訂單 這個流程,將其拆分成3個業務服務: 用戶中心、商品中心、訂單中心 ,使用 Springboot + Dubbo 來實現一個小 Demo!

服務交互流程如下:

本文主要是介紹 Springboot 與 Dubbo 的框架整合以及開發實踐,而真實的業務服務拆分是一個非常複雜的過程,比我們介紹的這個要複雜的多,上文提到的三個服務只是為了項目演示,不必過於糾結為什麼要這樣拆分!

好了,廢話也不多說了,下面我們來擼!

  • 1.在虛擬機創建 4 台 centos7,任意選擇一台安裝 zookeeper
  • 2.構建微服務項目並編寫代碼
  • 3.在 centos7 上部署微服務
  • 4.遠程服務調用測試

三、zookeeper安裝

在使用 Dubbo 之前,我們需要一個註冊中心,目前 Dubbo 可以選擇的註冊中心有 zookeeper、Nacos 等,一般建議使用 zookeeper!

首先在安裝 Zookeeper 之前,需要安裝並配置好 JDK,本機採用的是Oracle Java8 SE。

  • 安裝JDK(已經安裝可以忽略)
yum -y install java-1.8.0-openjdk
  • 查看java安裝情況
java -version
  • JDK安裝完成之後,下載安裝Zookeeper
#創建一個zookeeper文件夾
cd /usr
mkdir zookeeper

#下載zookeeper-3.4.14版本
wget http://mirrors.hust.edu.cn/apache/zookeeper/zookeeper-3.4.14/zookeeper-3.4.14.tar.gz

#解壓
tar -zxvf zookeeper-3.4.14.tar.gz
  • 創建數據、日誌目錄
#創建數據和日誌存放目錄
cd /usr/zookeeper/
mkdir data
mkdir log

#把conf下的zoo_sample.cfg備份一份,然後重命名為zoo.cfg
cd conf/
cp zoo_sample.cfg zoo.cfg
  • 配置zookeeper
#編輯zoo.cfg文件
vim zoo.cfg
  • 啟動Zookeeper
#進入Zookeeper的bin目錄
cd zookeeper/zookeeper-3.4.14/bin

#啟動Zookeeper
./zkServer.sh start

#查詢Zookeeper狀態
./zkServer.sh status

#關閉Zookeeper狀態
./zkServer.sh stop

出現如下信息,表示啟動成功!

四、項目介紹

  • springboot版本:2.1.1.RELEASE
  • zookeeper版本:3.4.14
  • dubbo版本:2.7.3
  • mybtais-plus版本:3.0.6
  • 資料庫:mysql-8
  • 構建工具:maven
  • 服務模塊:用戶中心、商品中心、訂單中心

五、代碼實踐

5.1、初始化資料庫

首先在 mysql 客戶端,創建3個資料庫,分別是: dianshang-user 、 dianshang-platform、 dianshang-business 。

  • 在 dianshang-user 資料庫中,創建用戶表 tb_user,並初始化數據
  • 在 dianshang-platform 資料庫中,創建商品表 tb_product,並初始化數據
  • 在 dianshang-platform 資料庫中,創建訂單表 tb_order、訂單詳情表 tb_order_detail

5.2、創建工程

資料庫表設計完成之後,在 IDEA 下創建一個名稱為 dianshang 的 Springboot 工程。

最終的目錄如下圖:

目錄結構說明:

  • dianshang-common:主要存放一些公共工具庫,所有的服務都可以依賴使用
  • dianshang-business:訂單中心,其中 api 模塊主要是提供dubbo服務暴露接口, provider模塊是一個 springboot 項目,提供服務處理操作
  • dianshang-user:用戶中心,其中 api 模塊和 provider 模塊,設計與之類似
  • dianshang-platform:商品中心,其中 api 模塊和 provider 模塊,設計與之類似

在父類 pom 文件中加入 dubbo 和 zookeeper 客戶端,所有依賴的項目都可以使用。

<!-- lombok -->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.4</version>
    <scope>provided</scope>
</dependency>

<!-- Dubbo Spring Boot Starter -->
<dependency>
    <groupId>org.apache.dubbo</groupId>
    <artifactId>dubbo-spring-boot-starter</artifactId>
    <version>2.7.3</version>
</dependency>
<!-- 因為使用的是 zookeeper 作為註冊中心,所以要添加 zookeeper 依賴 -->
<dependency>
    <groupId>org.apache.zookeeper</groupId>
    <artifactId>zookeeper</artifactId>
    <version>3.4.13</version>
    <exclusions>
        <exclusion>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
        </exclusion>
        <exclusion>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
        </exclusion>
        <exclusion>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
        </exclusion>
    </exclusions>
</dependency>
<!--使用curator 作為zookeeper客戶端-->
<dependency>
    <groupId>org.apache.curator</groupId>
    <artifactId>curator-framework</artifactId>
    <version>4.2.0</version>
</dependency>
<dependency>
    <groupId>org.apache.curator</groupId>
    <artifactId>curator-recipes</artifactId>
    <version>4.2.0</version>
</dependency>

溫馨提示:小編在搭建環境的時候,發現一個坑,工程中依賴的 zookeeper 版本與伺服器的版本,需要儘量一致,例如,本例中 zookeeper 伺服器的版本是 3.4.14 ,那麼在依賴 zookeeper 文件庫的時候,也儘量保持一致,如果依賴 3.5.x 版本的 zookeeper ,項目在啟動的時候會各種妖魔鬼怪的報錯!

5.3、創建用戶中心項目

在 IDEA 中,創建 dianshang-user 子模塊,並依賴 dianshang-common 模塊

<dependencies>
    <dependency>
        <groupId>org.project.demo</groupId>
        <artifactId>dianshang-common</artifactId>
        <version>1.0.0</version>
    </dependency>
</dependencies>

同時,創建 dianshang-user-provider 和 dianshang-user-api 模塊。

  • dianshang-user-api:主要對其他服務提供接口暴露
  • dianshang-user-provider:類似一個web工程,主要負責基礎業務的 crud ,同時依賴 dianshang-user-api 模塊

5.3.1、配置dubbo服務

在 dianshang-user-provider 的 application.yml 文件中配置 dubbo 服務,如下:

#用戶中心服務埠
server:
  port: 8080
#數據源配置
spring:
  datasource:
    druid:
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: "jdbc:mysql://localhost:3306/dianshang-user"
      username: root
      password: 111111
#dubbo配置
dubbo:
  scan:
    # 包名根據自己的實際情況寫
    base-packages: org.project.dianshang.user
  protocol:
    port: 20880
    name: dubbo
  registry:
    #zookeeper註冊中心地址
    address: zookeeper://192.168.0.107:2181

5.3.2、編寫服務暴露接口以及實現類

在 dianshang-user-api 模塊中,創建一個 UserApi 接口,以及返回參數對象 UserVo !

public interface UserApi {

    /**
     * 查詢用戶信息
     * @param userId
     * @return
     */
    UserVo findUserById(String userId);
}

其中 UserVo ,需要實現序列化,如下:

@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
public class UserVo implements Serializable {

    private static final long serialVersionUID = 1L;

    /**
     * 用戶ID
     */
    private String userId;

    /**
     * 用戶中文名
     */
    private String userName;
}

在 dianshang-user-provider 模塊中,編寫 UserApi 接口實現類,如下:

@Service(interfaceClass =UserApi.class)
@Component
public class UserProvider implements UserApi {

    @Autowired
    private UserService userService;

    @Override
    public UserVo findUserById(String userId) {
        QueryWrapper<User> queryWrapper = new QueryWrapper<User>();
        queryWrapper.eq("user_id",userId);
        User source = userService.getOne(queryWrapper);
        if(source != null){
            UserVo vo = new UserVo();
            BeanUtils.copyProperties(source,vo);
            return vo;
        }
        return null;
    }
}

其中的註解@Service指的是org.apache.dubbo.config.annotation.Service下的註解,而不是Spring下的註解哦!

接著,我們繼續創建商品中心項目!

5.4、創建商品中心項目

與用戶中心項目類似,在 IDEA 中,創建 dianshang-platform 子模塊,並依賴 dianshang-common 模塊

<dependencies>
    <dependency>
        <groupId>org.project.demo</groupId>
        <artifactId>dianshang-common</artifactId>
        <version>1.0.0</version>
    </dependency>
</dependencies>

同時,創建 dianshang-platform-provider 和 dianshang-platform-api 模塊。

  • dianshang-platform-api:主要對其他服務提供接口暴露
  • dianshang-platform-provider:類似一個web工程,主要負責基礎業務的 crud ,同時依賴 dianshang-platform-api 模塊

5.4.1、配置dubbo服務

在 dianshang-platform-provider 的 application.yml 文件中配置 dubbo 服務,如下:

#用戶中心服務埠
server:
  port: 8081
#數據源配置
spring:
  datasource:
    druid:
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: "jdbc:mysql://localhost:3306/dianshang-platform"
      username: root
      password: 111111
#dubbo配置
dubbo:
  scan:
    # 包名根據自己的實際情況寫
    base-packages: org.project.dianshang.platform
  protocol:
    port: 20881
    name: dubbo
  registry:
    #zookeeper註冊中心地址
    address: zookeeper://192.168.0.107:2181

5.4.2、編寫服務暴露接口以及實現類

在 dianshang-platform-api 模塊中,創建一個 ProductApi 接口,以及返回參數對象 ProductVo !

public interface ProductApi {

    /**
     * 通過商品ID,查詢商品信息
     * @param productId
     * @return
     */
    ProductVo queryProductInfoById(String productId);
}

其中 ProductVo ,需要實現序列化,如下:

@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
public class ProductVo implements Serializable {

    private static final long serialVersionUID = 1L;

    /**商品ID*/
    private String productId;

    /**商品名稱*/
    private String productName;

    /**商品價格*/
    private BigDecimal productPrice;
}

在 dianshang-platform-provider 模塊中,編寫 ProductApi 接口實現類,如下:

@Service(interfaceClass = ProductApi.class)
@Component
public class ProductProvider implements ProductApi {

    @Autowired
    private ProductService productService;

    @Override
    public ProductVo queryProductInfoById(String productId) {
        //通過商品ID查詢信息
        Product source = productService.getById(productId);
        if(source != null){
            ProductVo vo = new ProductVo();
            BeanUtils.copyProperties(source,vo);
            return vo;
        }
        return null;
    }
}

接著,我們繼續創建訂單中心項目!

5.5、創建訂單中心項目

與商品中心項目類似,在 IDEA 中,創建 dianshang-business 子模塊,並依賴 dianshang-common 模塊

<dependencies>
    <dependency>
        <groupId>org.project.demo</groupId>
        <artifactId>dianshang-common</artifactId>
        <version>1.0.0</version>
    </dependency>
</dependencies>

同時,創建 dianshang-business-provider 和 dianshang-business-api 模塊。

  • dianshang-business-api:主要對其他服務提供接口暴露
  • dianshang-business-provider:類似一個web工程,主要負責基礎業務的 crud ,同時依賴 dianshang-business-api 模塊

5.5.1、配置dubbo服務

在 dianshang-business-provider 的 application.yml 文件中配置 dubbo 服務,如下:

#用戶中心服務埠
server:
  port: 8082
#數據源配置
spring:
  datasource:
    druid:
      driver-class-name: com.mysql.cj.jdbc.Driver
      url: "jdbc:mysql://localhost:3306/dianshang-business"
      username: root
      password: 111111
#dubbo配置
dubbo:
  scan:
    # 包名根據自己的實際情況寫
    base-packages: org.project.dianshang.business
  protocol:
    port: 20882
    name: dubbo
  registry:
    #zookeeper註冊中心地址
    address: zookeeper://192.168.0.107:2181

5.5.2、編寫服務暴露接口以及實現類

在 dianshang-business-api 模塊中,創建一個 OrderApi 接口,以及返回參數對象 OrderVo !

public interface OrderApi {

    /**
     * 通過用戶ID,查詢用戶訂單信息
     * @param userId
     * @return
     */
    List<OrderVo> queryOrderByUserId(String userId);
}

其中 OrderVo ,需要實現序列化,如下:

@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
public class OrderVo implements Serializable {

    private static final long serialVersionUID = 1L;

    /**訂單ID*/
    private String orderId;

    /**訂單編號*/
    private String orderNo;

    /**訂單金額*/
    private BigDecimal orderPrice;

    /**下單時間*/
    private Date orderTime;
}

在 dianshang-business-provider 模塊中,編寫 OrderApi 接口實現類,如下:

@Service(interfaceClass = OrderApi.class)
@Component
public class OrderProvider implements OrderApi {

    @Autowired
    private OrderService orderService;

    @Override
    public List<OrderVo> queryOrderByUserId(String userId) {
        QueryWrapper<Order> queryWrapper = new QueryWrapper<Order>();
        queryWrapper.eq("user_id",userId);
        List<Order> sourceList = orderService.list(queryWrapper);
        if(!CollectionUtils.isEmpty(sourceList)){
            List<OrderVo> voList = new ArrayList<>();
            for (Order order : sourceList) {
                OrderVo vo = new OrderVo();
                BeanUtils.copyProperties(order, vo);
                voList.add(vo);
            }
            return voList;
        }
        return null;
    }
}

至此,3個項目的服務暴露接口已經開發完成!接下來我們來編寫怎麼進行遠程調用!

5.6、遠程調用

5.6.1、編寫創建訂單服務

在 dianshang-business-provider 模塊中,編寫創建訂單接口之前,先依賴 dianshang-business-api 和 dianshang-user-api ,如下:

<!--商品服務接口暴露 api-->
<dependency>
    <groupId>org.project.demo</groupId>
    <artifactId>dianshang-platform-api</artifactId>
    <version>1.0.0</version>
</dependency>
<!--用戶服務接口暴露 api-->
<dependency>
    <groupId>org.project.demo</groupId>
    <artifactId>dianshang-user-api</artifactId>
    <version>1.0.0</version>
</dependency>

在 dianshang-business-provider 模塊中,編寫創建訂單服務,如下:

@RestController
@RequestMapping("/order")
public class OrderController {

        @Autowired
    private OrderService orderService;

        @Autowired
        private OrderDetailService orderDetailService;

    @Reference(check =false)
        private ProductApi productApi;

    @Reference(check =false)
    private UserApi userApi;

        /**
         * 新增
         */
        @JwtIgnore
        @RequestMapping(value = "/add")
        public boolean add(String productId,String userId){
            LocalAssert.isStringEmpty(productId,"產品Id不能為空");
        LocalAssert.isStringEmpty(userId,"用戶Id不能為空");
        ProductVo productVo = productApi.queryProductInfoById(productId);
        LocalAssert.isObjectEmpty(productVo,"未查詢到產品信息");
        UserVo userVo = userApi.findUserById(userId);
        LocalAssert.isObjectEmpty(userVo,"未查詢到用戶信息");
        Order order = new Order();
        order.setOrderId(IdGenerator.uuid());
        order.setOrderNo(System.currentTimeMillis() + "");
        order.setOrderPrice(productVo.getProductPrice());
        order.setUserId(userId);
        order.setOrderTime(new Date());
        orderService.save(order);

        OrderDetail orderDetail = new OrderDetail();
        orderDetail.setOrderDetailId(IdGenerator.uuid());
        orderDetail.setOrderId(order.getOrderId());
        orderDetail.setProductId(productId);
        orderDetail.setSort(1);
        orderDetailService.save(orderDetail);
                return true;
        }
}

其中的 @Reference 註解,是屬於 org.apache.dubbo.config.annotation.Reference 下的註解,表示遠程依賴服務。

參數 check =false 表示啟動服務時,不做遠程服務狀態檢查,這樣設置的目的就是為了防止當前服務啟動不了,例如用戶中心項目沒有啟動成功,但是訂單中心又依賴了用戶中心,如果 check=true ,此時訂單中心啟動會報錯!

5.6.2、編寫用戶查詢自己的訂單信息

同樣的,在 dianshang-user-provider 模塊中,編寫用戶查詢自己的訂單信息接口之前,先依賴 dianshang-business-api 和 dianshang-user-api ,如下:

<dependency>
    <groupId>org.project.demo</groupId>
    <artifactId>dianshang-business-api</artifactId>
    <version>1.0.0</version>
</dependency>
<dependency>
    <groupId>org.project.demo</groupId>
    <artifactId>dianshang-user-api</artifactId>
    <version>1.0.0</version>
</dependency>

在 dianshang-user-provider 模塊中,編寫用戶查詢自己的訂單信息接口,如下:

@RestController
@RequestMapping("/user")
public class UserController {

        @Reference(check =false)
        private OrderApi orderApi;


    /**
     * 通過用戶ID,查詢訂單信息
     * @param userId
     * @return
     */
    @RequestMapping("/list")
    public List<OrderVo> queryOrderByUserId(String userId){
        return orderApi.queryOrderByUserId(userId);
    }
}

至此,遠程服務調用,編寫完成!

六、服務測試

在將項目部署在伺服器之前,咱們先本地測試一下,看服務是否都可以跑通?

  • 啟動用戶中心 dianshang-user-provider
  • 繼續啟動商品中心 dianshang-platform-provider
  • 接著啟動訂單中心 dianshang-business-provider

最後,我們來測試一下服務接口是否為我們預期的結果?

打開瀏覽器,輸入 http://127.0.0.1:8082/order/add?productId=1&userId=1 測試創建訂單接口,頁面運行結果顯示正常!

我們再來看看資料庫,訂單是否生成?

ok!很清晰的看到,數據已經進去了,沒啥問題!

我們再來測試一下在用戶中心訂單查詢接口,輸入 http://127.0.0.1:8080/user/list?userId=1 ,頁面運行結果如下!

到此,本地服務測試基本通過!

七、伺服器部署

在上文中,我們介紹了服務的構建、開發和測試,那如何在伺服器端部署呢?

首先,修改各個項目的 application.yml 文件,將其中的數據源地址、dubbo註冊中心地址修改為線上能聯通的地址,然後在 dianshang 目錄下使用 maven 工具對整個工程執行如下命令進行打包!

mvn clean install

也可以在 IDEA 環境下,通過 maven 配置 clean install 命令執行打包。

將各個項目 target 目錄下的 dianshang-user-provider.jar 、 dianshang-platform-provider.jar 、 dianshang-business-provider.jar 拷貝出來。

分別上傳到對應的伺服器目錄,本伺服器採用的是 CentOS7,總共4台伺服器,其中一台部署 zookeeper ,另外三台部署三個微服務項目。

登錄伺服器,輸入如下命令,確保 JDK 已經安裝完成!

java -version

關閉所有伺服器的防火牆,進行埠訪問!

#關閉防火牆
systemctl stop firewalld.service

#禁止開機啟動
systemctl disable firewalld.service
  • 啟動用戶中心服務,日誌信息輸出到 service.log (虛擬機ip:192.168.0.108)
nohup java -jar  dianshang-user-provider.jar > service.log 2>&1 &
  • 啟動商品中心服務,日誌信息輸出到 service.log (虛擬機ip:192.168.0.107)
nohup java -jar  dianshang-platform-provider.jar > service.log 2>&1 &
  • 啟動訂單中心服務,日誌信息輸出到 service.log (虛擬機ip:192.168.0.109)
nohup java -jar  dianshang-business-provider.jar > service.log 2>&1 &

打開瀏覽器,輸入 http://192.168.0.109:8082/order/add?productId=1&userId=1 測試創建訂單接口,頁面運行結果顯示正常!

我們再來測試一下在用戶中心訂單查詢接口,輸入輸入 http://192.168.0.108:8080/user/list?userId=1 ,頁面運行結果如下!

很清晰的看到,輸出了2條信息,第二條訂單是在測試環境伺服器生成的,第一條是本地開發環境生成的。

到此,伺服器部署基本已經完成!

如果是生產環境,可能就需要多台 zookeeper 來保證高可用,至少2台伺服器來部署業務服務,通過負載均衡來路由!

八、總結

整篇文章比較長,主要是圍繞 springboot + dubbo 的整合,通過註解開發實現遠程服務調用像傳統的 springmvc 開發一樣輕鬆,當然還可以通過 xml 配置方式實現dubbo服務的調用,這個會在後期去介紹。

同時也介紹了伺服器的部署,從中可以看出,開發雖然簡單,但是由於分布式部署,如何保證伺服器可用成了開發人員頭等工作任務,所以,分布式微服務開發雖然開發簡單,但是如何確保部署的服務高可用?運維方面會帶來不少的挑戰!

關鍵字: