微服務8:通信之RPC實踐篇(附源碼)

★微服務系列

微服務1:微服務及其演進史
微服務2:微服務全景架構
微服務3:微服務拆分策略
微服務4:服務註冊與發現
微服務5:服務註冊與發現(實踐篇)
微服務6:通信之網關
微服務7:通信之RPC
微服務8:通信之RPC實踐篇(附源碼)

1 說明

上一節我們我們詳細學習了RPC的概念和原理,以及它能夠提供的能力。也對目前業內主流的RPC的框架有了一定的了解。接下來以Dobbo為例子,來學習下怎麼使用RPC框架來進行服務之間的通信。

2 Dubbo框架功能介紹

Apache Dubbo 是一款分佈式微服務開發框架,它提供了 RPC通信 與 微服務治理 兩大關鍵能力。這意味着,使用 Dubbo 開發的微服務,將具備相互之間的遠程發現與通信能力, 同時利用 Dubbo 提供的豐富服務治理能力,可以實現諸如服務發現、負載均衡、流量調度等服務治理訴求。同時 Dubbo 是高度可擴展的,用戶幾乎可以在任意功能點去定製自己的實現,以改變框架的默認行為來滿足自己的業務需求。

2.1 服務發現

服務發現,即消費端自動發現服務地址列表的能力,是微服務框架需要具備的關鍵能力,藉助於自動化的服務發現,微服務之間可以在無需感知對端部署位置與 IP 地址的情況下實現通信。

實現服務發現的方式有很多種,Dubbo 提供的是一種 Client-Based 的服務發現機制,通常還需要部署額外的第三方註冊中心組件來協調服務發現過程,如常用的 Nacos、Consul、Zookeeper 等,Dubbo 自身也提供了對多種註冊中心組件的對接,用戶可以靈活選擇。

Dubbo 基於消費端的自動服務發現能力,其基本工作原理如下圖:
image

2.2 RPC通信

Dubbo3 提供了 Triple(Dubbo3)、Dubbo2 協議,這是 Dubbo 框架的原生協議。除此之外,Dubbo3 也對眾多第三方協議進行了集成,並將它們納入 Dubbo 的編程與服務治理體系, 包括 gRPC、Thrift、JsonRPC、Hessian2、REST 等。

2.3 服務流量管理

通過 Dubbo 定義的路由規則,實現對流量分佈的控制,可以實現 A/B測試、金絲雀發佈、藍綠發佈等能力。

2.4 配置管理

描述 Dubbo 支持的配置,Dubbo 的動態配置能力,包含幾大類: 啟動階段配置項、服務治理規則、動態配置項。

2.5 部署架構(註冊、配置、元數據中心)

作為一個微服務框架,Dubbo sdk 跟隨着微服務組件被部署在分佈式集群各個位置,為了在分佈式環境下實現各個微服務組件間的協作, Dubbo 定義了一些中心化組件,這包括:

  • 註冊中心。協調 Consumer 與 Provider 之間的地址註冊與發現
  • 配置中心。
    • 存儲 Dubbo 啟動階段的全局配置,保證配置的跨環境共享與全局一致性
    • 負責服務治理規則(路由規則、動態配置等)的存儲與推送。
  • 元數據中心。
    • 接收 Provider 上報的服務接口元數據,為 Admin 等控制台提供運維能力(如服務測試、接口文檔等)
    • 作為服務發現機制的補充,提供額外的接口/方法級別配置信息的同步能力,相當於註冊中心的額外擴展。
      image

2.6 高可擴展性

Dubbo 通過 SPI 機制提供了非常靈活的可擴展性

3 Dubbo實現簡易的RPC通信

3.1 框架依賴

image

  • Protocol, Proxy, Service, Container, Registry, Monitor 代表層或模塊,藍色的表示與業務的交互,綠色表示 Dubbo 內部的交互。
  • 主模塊 RPC Consumer,RPC Provider, Registry, Monitor 代表部署邏輯拓撲節點。
  • 藍色虛線為init初始化時調用,紅色虛線為async運行時異步調用,紅色實線為sync運行時同步調用。
  • Remoting 過程整體都隱含在 Protocol 中。

3.2 核心角色說明

  • Provider 暴露服務的服務提供方
  • Consumer 調用遠程服務的服務消費方(負載均衡)
  • Registry 服務註冊與發現的註冊中心(監控、心跳、踢出、重入)
  • Monitor 服務消費者和提供者在內存中累計調用次數和調用時間,主動定時每分鐘發送一次統計數據到監控中心。
  • Service 服務:執行遠程調用、數據序列化

3.3 在 SpringBoot 中實踐

3.3.1 父項目中引入依賴

首先引入 Zookeeper 和 Dubbo的依賴,Zookeeper的作用是註冊與發現,Dubbo的作用是引入RPC通信核心能力

        <!-- 包含了Zookeeper和Dubbo依賴 -->
        <dependency>
            <groupId>com.alibaba.boot</groupId>
            <artifactId>dubbo-spring-boot-starter</artifactId>
            <version>0.2.0</version>
        </dependency>

3.3.2 創建module

父項目下面創建三個Module,一個通用模塊,一個服務提供者模塊,一個服務消費者模塊

  • RpcCommon
  • RpcProvider
  • RpcConsume
    image

3.3.3 通用庫 RpcCommon

提供了公用的實體和接口,比如這邊包含了一個用戶信息的實體類和用戶信息接口,後面的服務提供者和消費這都可以引用:

/**
 * @author brand
 * @Description: 用戶信息實體
 * @Copyright: Copyright (c) 2022
 * @Company: Helenlyn, Inc. All Rights Reserved.
 * @date 2022/3/5 下午3:59
 * @Update Time:
 * @Updater:
 * @Update Comments:
 */
@Getter
@Setter
public class UserInfo implements Serializable {
    private Integer userId;
    private String userName;
    private Integer age;
    private String sex;
}
/**
 * @author brand
 * @Description: 用戶信息接口
 * @Copyright: Copyright (c) 2022
 * @Company: Helenlyn, Inc. All Rights Reserved.
 * @date 2022/3/5 下午5:29
 * @Update Time:
 * @Updater:
 * @Update Comments:
 */
public interface UserInfoService {
    UserInfo getUserInfo (int userId) ;
    String getHello (int userId) ;
}

3.3.3 服務提供者 RpcProvider

yml文件中的配置信息如下,可以看到我配置的zookeeper地址是127.0.0.1:2181,這是我本地部署到Zookeeper服務,大家可以對應修改下。
scan屬性指的是暴露服務的位置,對應位置下的類,只要聲明 Service 註解,就會被掃描並暴露出去。

# Dubbo Provider 配置
dubbo:
  application:
    name: rpc-provider # 發佈的dubbo服務名稱
  registry:
    address: 127.0.0.1:2181  # 使用Zookeeper註冊中心提供的服務地址,這邊可以配置多個,逗號隔開
    protocol: zookeeper
  protocol:
    name: dubbo
    port: 20882  # 用dubbo協議在20882端口暴露服務
  scan: # 使用註解方式暴露接口,掃描的位置
    base-packages: rpcprovider.modules.service

然後編寫需要暴露出去的接口的實現,這邊需要注意類上引入的註解為com.alibaba.dubbo.config.annotation的Service,
而不是springframework包中的service,這樣Service服務才能被註冊到dubbo中:

package rpcprovider.modules.service;

import com.alibaba.dubbo.config.annotation.Service;
import rpccommon.dto.UserInfo;
import rpccommon.service.UserInfoService;

/**
 * @author brand
 * @Description:
 * @Copyright: Copyright (c) 2022
 * @Company: Helenlyn, Inc. All Rights Reserved.
 * @date 2022/3/5 下午5:43
 * @Update Time:
 * @Updater:
 * @Update Comments:
 */
@Service
public class UserInfoServiceImpl implements UserInfoService {
    @Override
    public UserInfo getUserInfo(int userId) {
        UserInfo userInfo = new UserInfo();
        userInfo.setUserId(userId);
        userInfo.setAge(18);
        userInfo.setSex("男");
        userInfo.setUserName("Brand");
        return userInfo;
    }

    @Override
    public String getHello(int userId) {
        return " Hello, " + userId;
    }
}

啟動起來看看Zookeeper的效果,是不是被註冊到Dubbo中,這邊可以用看到,Provider中已經有註冊節點了。

[zk: 127.0.0.1:2181(CONNECTED) 68] ls /
[dubbo, zookeeper]
[zk: 127.0.0.1:2181(CONNECTED) 69] ls /dubbo
[rpccommon.service.UserInfoService]
[zk: 127.0.0.1:2181(CONNECTED) 70] ls /dubbo/rpccommon.service.UserInfoService
[configurators, consumers, providers, routers]
[zk: 127.0.0.1:2181(CONNECTED) 71] ls /dubbo/rpccommon.service.UserInfoService/providers
[dubbo%3A%2F%2F172.21.213.159%3A20882%2Frpccommon.service.UserInfoService%3Fanyhost%3Dtrue%26application%3Drpc-provider%26dubbo%3D2.6.2%26generic%3Dfalse%26interface%3Drpccommon.service.UserInfoService%26methods%3DgetHello%2CgetUserInfo%26pid%3D60138%26side%3Dprovider%26timestamp%3D1646535565910]
[zk: 127.0.0.1:2181(CONNECTED) 72] 

3.3.4 消費者 RpcConsume

yml文件中的配置信息如下:

# Dubbo Consumer 配置文件
dubbo:
  application:
    name: rpc-consumer
  registry:
    address: 127.0.0.1:2181 # 使用Zookeeper註冊中心提供的服務地址來獲取服務,這邊可以多個逗號隔開
    protocol: zookeeper

消費者使用註解方式進行RPC調用,這邊注意,通過@Reference註解, dubbo會在掃描的時候自動幫我們代理接口,然後通過rpc調用遠程服務。如下:

package rpcconsume.modules.service;

import com.alibaba.dubbo.config.annotation.Reference;
import org.springframework.stereotype.Service;
import rpccommon.dto.UserInfo;
import rpccommon.service.UserInfoService;

/**
 * @author brand
 * @Description: 消費者使用註解方式進行RPC調用
 * @Copyright: Copyright (c) 2022
 * @Company: Helenlyn, Inc. All Rights Reserved.
 * @date 2022/3/5 下午6:49
 * @Update Time:
 * @Updater:
 * @Update Comments:
 */
@Service
public class UserInfoConsumer implements UserInfoService {
    @Reference
    private UserInfoService userInfoService ;

    @Override
    public UserInfo getUserInfo(int userId) {
        return userInfoService.getUserInfo(userId);
    }

    @Override
    public String getHello(int userId) {
        return userInfoService.getHello(userId);
    }
}

寫一個Controller,通過外部調用,來測試效果

package rpcconsume.modules.controller;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import rpccommon.dto.UserInfo;
import rpcconsume.modules.service.UserInfoConsumer;

/**
 * @author brand
 * @Description: 測試消費者效果
 * @Copyright: Copyright (c) 2022
 * @Company: Helenlyn, Inc. All Rights Reserved.
 * @date 2022/3/5 下午6:48
 * @Update Time:
 * @Updater:
 * @Update Comments:
 */
@RestController
@Slf4j
@RequestMapping("/v1.0/consumer")
public class ConsumerController {
    @Autowired
    private UserInfoConsumer userInfoConsumer ;

    /**
     * 獲取用戶信息
     * @return
     */
    @RequestMapping(value = "/userinfo/{user_id}", method = RequestMethod.GET)
    public UserInfo getUserInfo(@PathVariable("user_id") int userId) {
        return userInfoConsumer.getUserInfo(userId);
    }


    /**
     * 獲取問候信息
     * @return
     */
    @RequestMapping(value = "/hello/{user_id}", method = RequestMethod.GET)
    public String getHello(@PathVariable("user_id") int userId) {
        return userInfoConsumer.getHello(userId);
    }
}

查一下Zookeeper 上的消費者信息:

[zk: 127.0.0.1:2181(CONNECTED) 73] ls /dubbo/rpccommon.service.UserInfoService/consumers
[consumer%3A%2F%2F172.21.213.159%2Frpccommon.service.UserInfoService%3Fapplication%3Drpc-consumer%26category%3Dconsumers%26check%3Dfalse%26dubbo%3D2.6.2%26interface%3Drpccommon.service.UserInfoService%26methods%3DgetHello%2CgetUserInfo%26pid%3D60884%26side%3Dconsumer%26timestamp%3D1646536367371]

調用效果:
image

3.3.5 源代碼參考

GitHub地址://github.com/WengZhiHua/Helenlyn.Grocery/tree/master/parent

4 總結

RPC通信只是 Dubbo 功能的一部分,像上面介紹的那樣,除了RPC通信之外,還有服務註冊與發現、配置管理、服務流量治理等,基本能達到一個微服務的基本能力要求。
另外我們還看到,除了Dubbo之外,百度的brpc和Google的gRPC也有很大的受眾,也是不錯的選擇,我們上一章有對主流RPC框架做了對比和分析,可以根據自身的業務特性進行技術選型。