溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

一文詳解 Dubbo 中的 http 協(xié)議

發(fā)布時間:2020-07-29 12:45:09 來源:網(wǎng)絡 閱讀:281 作者:下一秒升華 欄目:編程語言

太陽紅彤彤,花兒五顏六色,各位讀者朋友好,又來到了分享 Dubbo 知識點的時候了。說到 Dubbo 框架支持的協(xié)議,你的第一反應是什么?大概會有 Dubbo 默認支持的 dubbo 協(xié)議,以及老生常談的由當當貢獻給 Dubbo 的 rest 協(xié)議,或者是今天的主角 http。截止到目前,Dubbo 最新版本演進到了 2.7.3,已經(jīng)支持了:dubbo,hessain,http,injvm,jsonrpc,memcached,native-thrift,thrift,redis,rest,rmi,webservice,xml 等協(xié)議,有些協(xié)議的使用方式還沒有補全到官方文檔中。原來 Dubbo 支持這么多協(xié)議,是不是有點出乎你的意料呢?

這么多 RPC 協(xié)議,可能有人會產(chǎn)生如下的疑問:rest,jsonrpc,webservice 不都是依靠 http 通信嗎?為什么還單獨有一個 http 協(xié)議?先不急著回答這個問題,而是引出今天的話題,先來介紹下 Dubbo 框架中所謂的 http 協(xié)議。

<!-- more -->

Dubbo 中的 http 協(xié)議

在 Dubbo 使用 http 協(xié)議和其他協(xié)議基本一樣,只需要指定 protocol 即可。

<dubbo:protocol name="http" port="8080" server="jetty" />

server 屬性可選值:jetty,tomcat,servlet。

配置過后,當服務消費者向服務提供者發(fā)起調(diào)用,底層便會使用標準的 http 協(xié)議進行通信??梢灾苯釉?https://github.com/apache/dubbo-samples 中找到官方示例,其中的子模塊:dubbo-samples-http 構(gòu)建了一個 http 協(xié)議調(diào)用的例子。

為避免大家誤解,特在此聲明:本文中,所有的 http 協(xié)議特指的是 dubbo 中的 http 協(xié)議,并非那個大家耳熟能詳?shù)耐ㄓ玫?http 協(xié)議。

http 協(xié)議的底層原理

從默認的 dubbo 協(xié)議改為 http 協(xié)議是非常簡單的一件事,上面便是使用者視角所看到的全部的內(nèi)容了,接下來我們將會探討其底層實現(xiàn)原理。

翻看 Dubbo 的源碼,找到 HttpProtocol 的實現(xiàn),你可能會吃驚,基本就依靠 HttpProtocol 一個類,就實現(xiàn)了 http 協(xié)議

一文詳解 Dubbo 中的 http 協(xié)議

要知道實現(xiàn)自定義的 dubbo 協(xié)議,有近 30 個類!http 協(xié)議實現(xiàn)的如此簡單,背后主要原因有兩點:

  1. remoting 層使用 http 通信,不需要自定義編解碼
  2. 借助了 Spring 提供的 HttpInvoker 封裝了 refer 和 exporter 的邏輯

Spring 提供的 HttpInvoker 是何方神圣呢?的確是一個比較生僻的概念,但并不復雜,簡單來說,就是使用 Java 序列化將對象轉(zhuǎn)換成字節(jié),通過 http 發(fā)送出去,在 server 端,Spring 能根據(jù) url 映射,找到容器中對應的 Bean 反射調(diào)用的過程,沒見識過它也不要緊,可以通過下面的示例快速掌握這一概念。

Spring HttpInvoker

本節(jié)內(nèi)容可參見 Spring 文檔:https://docs.spring.io/spring/docs/4.3.24.RELEASE/spring-framework-reference/htmlsingle/#remoting-httpinvoker-server

下面的示例將會展示如何使用 Spring 原生的 HttpInvoker 實現(xiàn)遠程調(diào)用。

創(chuàng)建服務提供者

public class AccountServiceImpl implements AccountService {
    @Override
    public Account findById(int id) {
        Account account = new Account(id, new Date().toString());
        return account;
    }
}
@Bean
AccountService accountService(){
    return new AccountServiceImpl();
}

@Bean("/AccountService")
public HttpInvokerServiceExporter accountServiceExporter(AccountService accountService){
    HttpInvokerServiceExporter exporter = new HttpInvokerServiceExporter();
    exporter.setService(accountService);
    exporter.setServiceInterface(AccountService.class);
    return exporter;
}

暴露服務的代碼相當簡單,需要注意兩點:

  1. org.springframework.remoting.httpinvoker.HttpInvokerServiceExporter 是 Spring 封裝的一個服務暴露器,它會以 serviceInterface 為公共接口,以 service 為實現(xiàn)類向外提供服務。
  2. @Bean("/AccountService") 不僅僅指定了 IOC 容器中 bean 的名字,還充當了路徑映射的功能,如果本地服務器暴露在 8080 端口,則示例服務的訪問路徑為 http://localhost:8080/AccountService

創(chuàng)建服務消費者

@Configuration
public class HttpProxyConfig {
    @Bean("accountServiceProxy")
    public HttpInvokerProxyFactoryBean accountServiceProxy(){
        HttpInvokerProxyFactoryBean accountService = new HttpInvokerProxyFactoryBean();
        accountService.setServiceInterface(AccountService.class);
        accountService.setServiceUrl("http://localhost:8080/AccountService");
        return accountService;
    }
}
@SpringBootApplication
public class HttpClientApp {
    public static void main(String[] args) {
        ConfigurableApplicationContext applicationContext = SpringApplication.run(HttpClientApp.class, args);
        AccountService accountService = applicationContext.getBean(AccountService.class);
        System.out.println(accountService.findById(10086));
    }
}

消費者端引用服務同樣有兩個注意點:

  1. org.springframework.remoting.httpinvoker.HttpInvokerProxyFactoryBean 是 Spring 封裝的一個服務引用器,serviceInterface 指定了生成代理的接口,serviceUrl 指定了服務所在的地址,與之前配置的服務暴露者的路徑需要對應。
  2. HttpInvokerProxyFactoryBean 注冊到容器之中時,會同時生成一個 AccountService 接口的代理類,由 Spring 封裝遠程調(diào)用的邏輯。

調(diào)用細節(jié)分析

對于 Spring HttpInvoker 的底層實現(xiàn),就沒必要深究了,但大家肯定還是會好奇一些細節(jié):dubbo 中的 http 報文體是怎么組織的?如何序列化對象的?

我們使用 wireshark 可以抓取到客戶端發(fā)送的請求以及服務端響應的報文。

一文詳解 Dubbo 中的 http 協(xié)議

追蹤報文流,可以看到詳細的請求和響應內(nèi)容

一文詳解 Dubbo 中的 http 協(xié)議

ContentType: application/x-java-serialized-object 和報文 Body 部分的 ASCII 碼可以看出,使用的是 Java Serialize 序列化。我們將 Body 部分導出成文件,使用 Java Serialize 反序列化響應,來驗證一下它的廬山真面目:

一文詳解 Dubbo 中的 http 協(xié)議

使用 Java Serialize 可以正常反序列化報文,得到結(jié)果是 Spring 內(nèi)置的包裝類 RemoteInvocationResult,里面裝飾著實際的業(yè)務返回結(jié)果。

http 協(xié)議的意義

Dubbo 提供的眾多協(xié)議有各自適用的場景,例如

  • dubbo://,dubbo 協(xié)議是默認的協(xié)議,自定義二進制協(xié)議;單個長連接節(jié)省資源;基于 tcp,架構(gòu)于 netty 之上,性能還算可以;協(xié)議設計上沒有足夠的前瞻性,不適合做 service-mesh 談不上多么優(yōu)雅,但是好歹風風雨雨用了這么多年,周邊也有不少配套組件例如 dubbo2.js, dubbo-go, dubbo-cpp,一定程度解決了多語言的問題。
  • webservice://,hession://,thrift:// 等協(xié)議,基本是為了適配已有協(xié)議的服務端/客戶端,借助于 dubbo 框架的 api,可以使用其功能特性,意義不是特別大。
  • redis://,memcached:// 等協(xié)議,并非是暴露給用戶配置的協(xié)議,一般是 dubbo 自用,在注冊中心模塊中會使用到相應的擴展

所有協(xié)議的具體使用場景和其特性,我可能會單獨寫文章來分析,而如今我們要思考的是 dubbo 提供 http 協(xié)議到底解決什么問題,什么場景下用戶會考慮使用 dubbo 的 http 協(xié)議。

我個人認為 dubbo 現(xiàn)如今的 http 協(xié)議比較雞肋,原生 http 通信的優(yōu)勢在于其通用性,基本所有語言都有配套的 http 客戶端和服務端支持,但是 dubbo 的 http 協(xié)議卻使用了 application/x-java-serialized-object 的格式來做為默認的 payload,使得其喪失了跨語言的優(yōu)勢??赡苡凶x者會反駁:HttpInvoker 支持配置序列化格式,不能這么草率的詬病它。但其實我們所關注的恰恰是默認實現(xiàn),正如 dubbo:// 協(xié)議也可以配置 fastjson 作為序列化方案,但是我們同樣不認為 dubbo:// 協(xié)議是一個優(yōu)秀的跨語言方案,理由是一樣的。當然,評價一個應用層協(xié)議是否是優(yōu)秀的,是否適合做 mesh 等等,需要多種方向去分析,這些我不在本文去分析。

說到底,本文花了一定的篇幅向大家介紹了 dubbo 的 http 協(xié)議,到頭來卻是想告訴你:這是一個比較雞肋的協(xié)議,是不是有些失望呢?不要失望,dubbo 可能在 2.7.4 版本廢棄現(xiàn)有的 http 協(xié)議,轉(zhuǎn)而使用 jsonrpc 協(xié)議替代,其實也就是將 jsonrpc 協(xié)議換了個名字而已,而關于 jsonrpc 的細節(jié),我將會在下一篇文章中介紹,屆時,我也會分析,為什么 jsonrpc 比現(xiàn)有的 http 協(xié)議更適合戴上 http 協(xié)議的帽子,至于現(xiàn)有的 http 協(xié)議,我更傾向于稱之為:spring-httpinvoker 協(xié)議。

總結(jié),dubbo 現(xiàn)有 http 協(xié)議的意義是什么?如果你習慣于使用 Spring HttpInvoker,那或許現(xiàn)有的 http 協(xié)議還有一定的用處,但從 dubbo 交流群和 Spring 文檔介紹其所花費的篇幅來看,它還是非常小眾的。同時也可以讓我們更好地認識協(xié)議發(fā)展的歷史,知道一個協(xié)議為什么存在,為什么會被淘汰。

當然,我說了不算,最終還是要看 dubbo 社區(qū)的決策,如果你對這個遷移方案感興趣,想要參與討論,歡迎大家在 dubbo 社區(qū)的郵件列表中發(fā)表你的見解

Topic:[Proposal] replace the protocol="http" with protocol="jsonrpc"

歡迎關注我的微信公眾號:「Kirito的技術分享」,關于文章的任何疑問都會得到回復,帶來更多 Java 相關的技術分享。

向AI問一下細節(jié)

免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI