微服務最佳實踐,零改造實現 Spring Cloud & Apache Dubbo 互通

很遺憾,這不是一篇關於中間件理論或原理講解的文章,沒有高深晦澀的工作原理分析,文後也沒有令人驚歎的工程數字統計。本文以實際項目和代碼爲示例,一步一步演示如何以最低成本實現 Apache Dubbo 體系與 Spring Cloud 體系的互通,進而實現不同微服務體系的混合部署、遷移等,幫助您解決實際架構及業務問題。

背景與目標

如果你在微服務開發過程中正面臨以下一些業務場景需要解決,那麼這篇文章可以幫到您:

  • 您已經有一套基於 Dubbo 構建的微服務應用,這時你需要將部分服務通過 REST HTTP 的形式(非接口、方法模式)發佈出去,供一些標準的 HTTP 端調用(如 Spring Cloud 客戶端),整個過程最好是不用改代碼,直接爲寫好的 Dubbo 服務加一些配置、註解就能實現。
  • 您已經有一套基於 Spring Cloud 構建的微服務體系,而後又構建了一套 Dubbo 體系的微服務,你想兩套體系共存,因此現在兩邊都需要調用到對方發佈的服務。也就是 Dubbo 應用作爲消費方要調用到 Spring Cloud 發佈的 HTTP 接口,Dubbo 應用作爲提供方還能發佈 HTTP 接口給 Spring Cloud 調用。
  • 出於一些歷史原因,你正規劃從一個微服務體系遷移到另外一個微服務體系,前提條件是要保證中間過程的平滑遷移。

對於以上幾個場景,我們都可以藉助 Dubbo3 內置的 REST 編程範式支持實現,這讓 Dubbo 既可以作爲消費方調用 HTTP 接口的服務,又可以作爲提供方對外發布 REST 風格的 HTTP 服務,同時整個編碼過程支持業界常用的 REST 編程範式(如 JAX-RS、Spring MVC 等),因此可以做到基本不改動任何代碼的情況下實現 Dubbo 與 Spring Cloud 體系的互相調用。

  • 關於這一部分更多的設計與理論闡述請參見這裏的博客文章[1]
  • 關於 Dubbo REST 的更多配置方式請參見 rest 使用參考手冊[2]

示例一:Dubbo 調用 Spring Cloud

在已經有一套 Spring Cloud 微服務體系的情況下,演示如何使用 Dubbo 調用 Spring Cloud 服務(包含自動的地址發現與協議傳輸)。在註冊中心方面,本示例使用 Nacos 作爲註冊中心,對於 Zookeeper、Consul 等兩種體系都支持的註冊中心同樣適用。

設想你已經有一套 Spring Cloud 的微服務體系,現在我們將引入 Dubbo 框架,讓 Dubbo 應用能夠正常的調用到 Spring Cloud 發佈的服務。本示例完整源碼請參見 samples/dubbo-call-sc[3]

啓動 Spring Cloud Server

示例中 Spring Cloud 應用的結構如下:

應用配置文件如下:

server:
  port: 8099
spring:
  application:
    name: spring-cloud-provider-for-dubbo
  cloud:
    nacos:
      serverAddr: 127.0.0.1:8848 #註冊中心

以下是一個非常簡單的 Controller 定義,發佈了一個 /users/list/的 http 端點。

@RestController
@RequestMapping("/users")
public class UserController {
    @GetMapping("/list")
    public List<User> getUser() {
        return Collections.singletonList(new User(1L, "spring cloud server"));
    }
}

啓動 SpringCloudApplication,通過 cURL 或瀏覽器訪問 http://localhost:8099/users/list 可以測試應用啓動成功。

使用 Dubbo Client 調用服務

Dubbo client 也是一個標準的 Dubbo 應用,項目基本結構如下:

其中,一個比較關鍵的是如下接口定義(正常情況下,以下接口可以直接從原有的 Spring Cloud client 應用中原樣拷貝過來即可,無需做任何修改)。

如果之前沒有基於 OpenFeign 的 Spring Cloud 消費端應用,那麼就需要自行定義一個接口,此時不一定要使用 OpenFeign 註解,使用 Spring MVC 標準註解即可。

通過 DubboReference 註解將 UserServiceFeign 接口註冊爲 Dubbo 服務。

@DubboReference
private UserServiceFeign userService;

接下來,我們就可以用 Dubbo 標準的方式對服務發起調用了。

List<User> users = userService.users();

通過 DubboConsumerApplication 啓動 Dubbo 應用,驗證可以成功調用到 Spring Cloud 服務。

示例二:Spring Cloud 調用 Dubbo

在接下來的示例中,我們將展示如何將 Dubbo server 發佈的服務開放給 Spring Cloud client 調用。

示例的相關源碼在 samples/sc-call-dubbo[4]

啓動 Dubbo Server

Dubbo server 應用的代碼結構非常簡單,是一個典型的 Dubbo 應用。

相比於普通的 Dubbo 服務定義,我們要在接口上加上如下標準 Spring MVC 註解:

@RestController
@RequestMapping("/users")
public interface UserService {
    @GetMapping(value = "/list")
    List<User> getUsers();
}

除了以上註解之外,其他服務發佈等流程都一致,使用 DubboService 註解發佈服務即可:

@DubboService
public class UserServiceImpl implements UserService {
    @Override
    public List<User> getUsers() {
        return Collections.singletonList(new User(1L, "Dubbo provider!"));
    }
}

在服務配置上,特別注意我們需要將服務的協議配置爲 rest protocol: rest,地址發現模式使用 register-mode: instance:

dubbo:
  registry:
    address: nacos://127.0.0.1:8848
    register-mode: instance
  protocol:
    name: rest
    port: 8090

啓動 Dubbo 應用,此時訪問以下地址可以驗證服務運行正常:http://localhost:8090/users/list

使用 Spring Cloud 調用 Dubbo

使用 OpenFeign 開發一個標準的 Spring Cloud 應用,即可調用以上發佈的 Dubbo 服務,項目代碼結構如下:

其中,我們定義了一個 OpenFeign 接口,用於調用上面發佈的 Dubbo rest 服務。

@FeignClient(name = "dubbo-provider-for-spring-cloud")
public interface UserServiceFeign {
    @RequestMapping(value = "/users/list", method = RequestMethod.GET)
    List<User> getUsers();
}

定義以下 controller 作爲 OpenFeign 和 RestTemplate 測試入口:

public class UserController {

    private final RestTemplate restTemplate;
    private final UserServiceFeign userServiceFeign;

    public UserController(RestTemplate restTemplate,
                          UserServiceFeign userServiceFeign) {
        this.restTemplate = restTemplate;
        this.userServiceFeign = userServiceFeign;
    }

    @RequestMapping("/rest/test1")
    public String doRestAliveUsingEurekaAndRibbon() {
        String url = "http://dubbo-provider-for-spring-cloud/users/list";
        System.out.println("url: " + url);
        return restTemplate.getForObject(url, String.class);
    }

    @RequestMapping("/rest/test2")
    public List<User> doRestAliveUsingFeign() {
        return userServiceFeign.getUsers();
    }
}

根據以上 Controller 定義,我們可以分別訪問以下地址進行驗證:

  • OpenFeign 方式:http://localhost:8099/dubbo/rest/test1
  • RestTemplage 方式:http://localhost:8099/dubbo/rest/test2

爲 Dubbo Server 發佈更多的服務

我們可以利用 Dubbo 的多協議發佈機制,爲一些服務配置多協議發佈。接下來,我們就爲上面提到的 Dubbo server 服務增加 dubbo tcp 協議發佈,從而達到以下部署效果,讓這個 Dubbo 應用同時服務 Dubbo 微服務體系和 Spring Cloud 微服務體系。

爲了實現這個效果,我們只需要在配置中增加多協議配置即可:

dubbo:
  protocols:
    - id: rest
      name: rest
      port: 8090
    - id: dubbo
      name: dubbo
      port: 20880

同時,服務註解中也配置爲多協議發佈:

@DubboService(protocol="rest,dubbo")
public class UserServiceImpl implements UserService {}

這樣,我們就成功的將 UserService 服務以 dubbo 和 rest 兩種協議發佈了出去(多端口多協議的方式),dubbo 協議爲 Dubbo 體系服務,rest 協議爲 Spring Cloud 體系服務。

注意:Dubbo 爲多協議發佈提供了單端口、多端口兩種方式,這樣的靈活性對於不同部署環境下的服務會有比較大的幫助。在確定您需要的多協議發佈方式前,請提仔細閱讀以下多協議配置 [5]文檔。

總結

基於 Dubbo 的 rest 編程範式、多協議發佈等特性,可以幫助你輕鬆的實現 Dubbo 服務的 http 協議發佈,讓後端服務基於 RPC 高效通信的同時,能夠更容易的與 http 服務體系打通,本示例通過 Dubbo 與 Spring Cloud 兩套體系的共存、互通示例非常清晰的演示了編碼過程。

此部分內容的正式版本將在 Dubbo 3.3.0 版本正式發佈,同時還包含 Triple 協議的重磅升級,敬請期待!

相關鏈接:

[1] 博客文章

https://cn.dubbo.apache.org/zh-cn/blog/2023/01/05/dubbo-%e8%bf%9e%e6%8e%a5%e5%bc%82%e6%9e%84%e5%be%ae%e6%9c%8d%e5%8a%a1%e4%bd%93%e7%b3%bb-%e5%a4%9a%e5%8d%8f%e8%ae%ae%e5%a4%9a%e6%b3%a8%e5%86%8c%e4%b8%ad%e5%bf%83/

[2] rest 使用參考手冊

https://cn.dubbo.apache.org/zh-cn/overview/reference/proposals/protocol-http/

[3] samples/dubbo-call-sc

https://github.com/apache/dubbo-samples/tree/master/2-advanced/dubbo-samples-springcloud/dubbo-call-sc

[4] samples/sc-call-dubbo

https://github.com/apache/dubbo-samples/tree/master/2-advanced/dubbo-samples-springcloud/sc-call-dubbo

[5] 多協議配置

https://cn.dubbo.apache.org/zh-cn/overview/mannual/java-sdk/advanced-features-and-usage/service/multi-protocols/

作者:孫彩榮

點擊立即免費試用雲產品 開啓雲上實踐之旅!

原文鏈接

本文爲阿里雲原創內容,未經允許不得轉載

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章