java远程调用之RPC协议与HTTP协议对比分析

目录

前言

RPC与HTTP简介

什么是RPC协议

什么是HTTP协议

分析异同

RPC在java中的实现方案

RPC调用流程

RPC实现方案之Dubbo

Dubbo简介

Dubbo总体架构

Dubbo的特点

HTTP远程调用在java中的实现方案

原生API实现

HttpClient

简介

使用步骤

RestTemplate

简介

特点和功能

使用步骤

1.引入依赖

2.创建RestTemplate对象,交由spring容器进行管理

3.接口调用

3.1 简单Get\Post请求

3.2 Post提交常规表单

3.3 Post上传文件

3.4 添加 Header 和 Cookie

Feign

简介

Feign远程调用基本流程

使用步骤

1. 引入依赖

2. 添加注解

3.编写Feign的客户端

RestTemplate与Feign对比

如何在两者间做出选择

使用场景

实际应用

原理

性能

总结


前言

讲一下写这篇文章的目的吧。在学习RPC远程调用时,有讲到微服务之间的相互调用是采用RPC协议的,可是我之前学习SpringCloud中又有讲到远程调用是使用的Feign远程调用,Feign的底层是基于HTTP协议的,那这两种远程调用方式有什么区别呢?又有什么不同的应用场景呢?带着这些问题我写了这篇文章。

RPC与HTTP简介

什么是RPC协议

RPC(Remote Procedure Call Protocol)远程过程调用协议。一个通俗的描述是:客户端在不知道调用细节的情况下,调用存在于远程计算机上的某个对象,就像调用本地应用程序中的对象一样。

比较正式的描述是:一种通过网络从远程计算机程序上请求服务,而不需要了解底层网络技术的协议。

那么我们至少从这样的描述中挖掘出几个要点:

  • RPC是协议:既然是协议就只是一套规范,那么就需要有人遵循这套规范来进行实现。目前典型的RPC实现包括:Dubbo、Thrift、GRPC、Hetty等。
  • 网络协议和网络IO模型对其透明:既然RPC的客户端认为自己是在调用本地对象。那么传输层使用的是TCP/UDP还是HTTP协议,又或者是一些其他的网络协议它就不需要关心了。由此可以看出RPC一定要对调用的过程进行封装。
  • RPC采用的网络通讯协议:现在比较流行的RPC框架,都会采用TCP作为底层传输协议。也有少数是基于UDP的。
  • 信息格式对其透明:我们知道在本地应用程序中,对于某个对象的调用需要传递一些参数,并且会返回一个调用结果。至于被调用的对象内部是如何使用这些参数,并计算出处理结果的,调用方是不需要关心的。那么对于远程调用来说,这些参数会以某种信息格式传递给网络上的另外一台计算机,这个信息格式是怎样构成的,调用方是不需要关心的。
  • 应该有跨语言能力:为什么这样说呢?因为调用方实际上也不清楚远程服务器的应用程序是使用什么语言运行的。那么对于调用方来说,无论服务器方使用的是什么语言,本次调用都应该成功,并且返回值也应该按照调用方程序语言所能理解的形式进行描述。

利用RPC协议进行远程调用需要自定义数据格式,基于原生TCP通信,速度快,效率高。

什么是HTTP协议

HTTP协议即超文本传输协议,是一种应用层协议。规定了网络传输的请求格式、响应格式、资源定位和操作的方式等。但是底层采用什么网络传输协议,并没有规定,不过现在都是采用TCP协议作为底层传输协议。


说到这里大家不难看出RPC可以看成是比HTTP更高一层的抽象RPC(远程过程调用)是一种编程模型,它允许程序调用另一个地址空间(通常是共享网络的另一台机器上)的过程或函数,就像调用本地的过程或函数一样。RPC并没有规定网络通讯协议,因此RPC的网络通讯协议可以根据自己的需求自行选择,包括HTTP。HTTP是一种应用层协议,它可以作为RPC的传输协议来使用。从这我们不难看出HTTP 和 RPC 并不是一个并行概念,可以理解为RPC是一种概念,http是rpc实现的一种方式用http交互其实就已经属于rpc了。

分析异同

这里我们可以打一个形象的比喻,RPC就是HTTP的一个父类,HTTP就是继承了父类属性和方法的子类,该子类实现了父类中定义的抽象方法

以下是我这么比喻的具体原因

  • RPC并没有规定数据传输格式,这个格式可以任意指定,不同的RPC协议,数据格式不一定相同。也就是说在使用RPC远程调用协议时需要自定义数据传输格式,这就好比是定义了一个抽象方法。HTTP协议中就规定了数据传输格式,如HTML、JSON、XML、Form Data、Multipart Form Data等,这就好比子类重写了父类的抽象方法
  • Http中定义了资源定位的路径,RPC中没有,这就相当与子类的属性补充
  • 最重要的一点:RPC需要满足像调用本地服务一样调用远程服务,那么传输层使用的是TCP还是UDP协议,又或者是一些其他的网络协议它就不需要关心了(父类定义抽象方法)。而HTTP协议则规定了传输层使用的是TCP协议(子类重写抽象方法),这也是HTTP是RPC的一种实现方案的体现。由此我们还可以看出RPC一定要对调用的过程进行封装。也就是对调用过程在API层面进行封装。Http协议没有这样的要求,因此请求、响应等细节需要我们自己去实现。
    • 优点:RPC方式更加透明,对用户更方便。Http方式更灵活,没有规定API和语言,跨语言、跨平台
    • 缺点:RPC方式需要在API层面进行封装,限制了开发的语言环境。

有些读者可能对网络七层模型中不同的网络层级不够熟悉,小编特在此奉上网络分层模型图,以供大家参考。

从上面的网络模型也可以看出 HTTP 和 RPC 并不是一个并行概念。这里也再次验证了RPC是一种概念,http也是rpc实现的一种方式。用http交互其实就已经属于rpc了。

接下介绍一下在java中HTTP和RPC实现远程调用的具体实现方案。

RPC在java中的实现方案

RPC在java中的实现方案有很多种,例如gRPC、Thrift 、Dubbo这里主要介绍一下Dubbo。后续会继续完善关于gRPC、Thrift 的内容。

RPC调用流程

首先不管是哪种实现方案,都是基于RPC这个模型实现的,它们的基本的调用流程的框架是相似的,所以首先介绍一下RPC调用流程,先对RPC调用流程有个大致的了解,然后再去了解各个实现框架的细节。

  1. 客户端调用本地Stub:客户端应用程序通过本地Stub(客户端代理)调用远程服务的方法,Stub负责将调用请求封装成网络消息发送给远程服务器。

  2. 参数序列化:当客户端调用远程服务时,需要将参数序列化为二进制数据,以便在网络上传输。常见的序列化方式有JSON、XML、Protobuf等。

  3. 网络传输:Stub将封装好的请求通过网络发送给服务器。这里需要考虑如何保证网络传输的可靠性和安全性,常见的方式包括TCP、HTTP等协议和加密技术。

  4. 服务端接收并解码请求:服务器接收到请求后,需要根据请求的协议格式进行解码,还原出调用请求所需的参数信息。

  5. 调用服务方法:服务端通过反射等方式调用具体的方法,并将参数传入方法中。

  6. 返回值序列化:服务端将调用方法返回的结果序列化为二进制数据,以便在网络上传输。

  7. 网络传输:服务端将序列化好的返回值通过网络发送给客户端。

  8. 参数解码:客户端接收到返回值后,需要将二进制数据反序列化为需要的数据类型,以便进行后续处理。

  9. 返回值返回:客户端将解码后的返回值返回给调用方。

下面是RPC调用流程图

RPC实现方案之Dubbo

Dubbo简介

Dubbo 是一个由阿里巴巴开源出来的分布式服务框架,致力于提供高性能和透明化的 RPC 远程服务调用方案,以及 SOA 服务治理方案。简单的说,Dubbo 就是个服务框架,说白了就是个远程服务调用的分布式框架

Dubbo总体架构

  1. 服务提供者(Provider):提供具体的业务实现,并注册到 Dubbo 注册中心。服务提供者接收来自消费者的请求并返回响应。

  2. 服务消费者(Consumer):调用远程服务的消费者应用。通过 Dubbo 客户端代理(Proxy)发起远程方法调用,并将请求发送给服务提供者。

  3. 注册中心(Registry):用于服务的注册与发现,维护服务的元数据信息。服务提供者启动时,将自己的地址、服务接口等信息注册到注册中心;服务消费者从注册中心获取服务提供者的地址列表。

  4. 监控中心(Monitor):用于监控 Dubbo 服务的调用情况、性能指标、访问日志等统计数据。可以使用 Dubbo 提供的内置监控中心,也可以集成第三方监控系统,如 Prometheus、Grafana 等。

  5. 远程通信(Remoting):负责 Dubbo 中的网络通信,基于 Netty 实现高性能的 NIO 通信。Dubbo 支持多种协议,如 Dubbo 协议、HTTP、RMI 等。

  6. 集群容错(Cluster):通过集群容错机制提高系统的可靠性和可用性。Dubbo 提供了多种集群容错策略,如 Failover 失败重试、Failfast 快速失败、Failsafe 容错返回、Failback 失败自动恢复等。

  7. 负载均衡(Load Balance):在服务消费者面对多个服务提供者时,根据负载均衡策略选择合适的提供者进行调用。Dubbo 支持多种负载均衡算法,如随机、轮询、一致性哈希等。

  8. 服务配置与路由(Service Configuration & Routing):通过配置文件或注解方式配置服务的属性,如超时时间、重试次数、路由规则等。

Dubbo的特点

  • 远程通讯: 提供对多种基于长连接的 NIO 框架抽象封装(非阻塞 I/O 的通信方式,Mina/Netty/Grizzly),包括多种线程模型,序列化(Hessian2/ProtoBuf),以及“请求-响应”模式的信息交换方式。
  • 集群容错: 提供基于接口方法的透明远程过程调用(RPC),包括多协议支持(自定义 RPC 协议),以及软负载均衡(Random/RoundRobin),失败容错(Failover/Failback),地址路由,动态配置等集群支持。
  • 自动发现: 基于注册中心目录服务,使服务消费方能动态的查找服务提供方,使地址透明,使服务提供方可以平滑增加或减少机器。

HTTP远程调用在java中的实现方案

这里主要介绍基于原生API实现、HttpClient、RestTemplate、Feign这几种实现方案

原生API实现

java中提供了多种原生的API来发送 HTTP 请求。下面我来介绍一下:

java.net.HttpURLConnectionHttpURLConnection 是 Java 提供的一个用于发送 HTTP 请求的类。它是基于 JDK 内置的 HTTP 栈实现的,可以用于发送 GET、POST 等各种类型的请求。以下是一个简单的使用例子:
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;

public class HttpURLConnectionExample {
    public static void main(String[] args) {
        try {
            // 创建 URL 对象
            URL url = new URL("http://example.com/api");
            
            // 打开连接
            HttpURLConnection conn = (HttpURLConnection) url.openConnection();
            
            // 设置请求方法
            conn.setRequestMethod("GET");
            
            // 获取响应码
            int responseCode = conn.getResponseCode();
            System.out.println("Response Code: " + responseCode);
            
            // 读取响应内容
            BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
            String line;
            StringBuilder response = new StringBuilder();
            while ((line = reader.readLine()) != null) {
                response.append(line);
            }
            reader.close();
            
            // 输出响应内容
            System.out.println("Response: " + response.toString());
            
            // 关闭连接
            conn.disconnect();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

java.net.URLConnectionURLConnection 是 HttpURLConnection 的父类,提供了更通用的接口。可以通过 openConnection() 方法获取 URLConnection 对象,并设置请求方法、请求头、请求体等。以下是一个简单的使用例子:

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.URL;
import java.net.URLConnection;

public class URLConnectionExample {
    public static void main(String[] args) {
        try {
            // 创建 URL 对象
            URL url = new URL("http://example.com/api");

            // 打开连接
            URLConnection conn = url.openConnection();

            // 设置请求方法
            conn.setRequestProperty("Request-Method", "GET");

            // 获取响应码
            int responseCode = ((HttpURLConnection) conn).getResponseCode();
            System.out.println("Response Code: " + responseCode);

            // 读取响应内容
            BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
            String line;
            StringBuilder response = new StringBuilder();
            while ((line = reader.readLine()) != null) {
                response.append(line);
            }
            reader.close();

            // 输出响应内容
            System.out.println("Response: " + response.toString());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

java.net.URLURL 类可以直接用于发送 GET 请求。通过调用 openStream() 方法获取请求的输入流,然后读取输入流中的数据。以下是一个简单的使用例子:

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.URL;

public class URLExample {
    public static void main(String[] args) {
        try {
            // 创建 URL 对象
            URL url = new URL("http://example.com/api");
            
            // 打开连接并获取输入流
            BufferedReader reader = new BufferedReader(new InputStreamReader(url.openStream()));
            
            // 读取响应内容
            String line;
            StringBuilder response = new StringBuilder();
            while ((line = reader.readLine()) != null) {
                response.append(line);
            }
            reader.close();
            
            // 输出响应内容
            System.out.println("Response: " + response.toString());
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

HttpClient

简介

HttpClient是一个基于Java的开源的HTTP客户端库,它提供了发送HTTP请求、处理HTTP响应的功能。HttpClient是Apache软件基金会的一个项目,具有稳定性和可靠性。与以下两种方案不同,HttpClient是一个独立的库,可以与各种Java框架进行集成,如Spring、Apache CXF等,而下面的两者方案则是集成在spring中的。

使用步骤

首先引入依赖

<dependency>
	<groupId>org.apache.httpcomponents</groupId>
	<artifactId>httpclient</artifactId>
	<version>4.5.13</version>
</dependency>

下面介绍以下HttpClient使用的基本流程

1. 创建HttpClient对象。

2. 创建请求方法的实例,并指定请求URL。如果需要发送GET请求,创建HttpGet对象;如果需要发送POST请求,创建HttpPost对象。

3. 如果需要发送请求参数,可调用HttpGet、HttpPost共同的setParams(HttpParams params)方法来添加请求参数;对于HttpPost对象而言,也可调用setEntity(HttpEntity entity)方法来设置请求参数。

4. 调用HttpClient对象的execute(HttpUriRequest request)发送请求,该方法返回一个HttpResponse。

5. 调用HttpResponse的getAllHeaders()、getHeaders(String name)等方法可获取服务器的响应头;调用HttpResponse的getEntity()方法可获取HttpEntity对象,该对象包装了服务器的响应内容。程序可通过该对象获取服务器的响应内容。

6. 释放连接。无论执行方法是否成功,都必须释放连接。

下面是一个简单的例子,演示了如何使用HttpClient发送GET请求并处理响应的过程

import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.util.EntityUtils;

public class HttpClientExample {
    public static void main(String[] args) throws Exception {
        // 创建HttpClient对象
        CloseableHttpClient httpClient = HttpClients.createDefault();

        // 创建HttpGet请求对象
        HttpGet httpGet = new HttpGet("http://www.example.com/");

        // 执行Http请求
        CloseableHttpResponse response = httpClient.execute(httpGet);

        // 处理Http响应
        int statusCode = response.getStatusLine().getStatusCode();
        String responseBody = EntityUtils.toString(response.getEntity());
        System.out.println("Status Code: " + statusCode);
        System.out.println("Response Body: " + responseBody);

        // 关闭Http连接
        response.close();
        httpClient.close();
    }
}

RestTemplate

简介

先简单介绍一下RestTemplate:

RestTemplate 是 Spring Framework 提供的一个用于发送 HTTP 请求的模板类,它简化了在 Java 应用中进行 RESTful 服务调用的过程。注意与HttpClient不同,它只能在spring框架中使用,并且主要在单体架构中使用,或者是需要更多自定义配置的场景。在SpringCloud微服务中远程调用的实现方案是下面的要介绍的Feign。

RestTemplate 提供了一组方便的方法,可以用于发送 GET、POST、PUT、DELETE 等不同类型的 HTTP 请求,并支持对请求和响应进行自定义配置和处理。

特点和功能

  1. 支持多种 HTTP 请求方法:RestTemplate 提供了多个方法,如 getForObject()、postForObject()、put()、delete() 等,用于发送不同类型的 HTTP 请求。你可以根据实际需要选择合适的方法来发送请求。

  2. 支持请求参数传递:RestTemplate 允许将请求参数作为 URL 查询参数、路径参数、请求体或者请求头的方式进行传递。你可以通过方法的参数或者使用 UriVariables、RequestEntity 等对象来设置请求参数。

  3. 支持请求和响应的自定义配置:你可以通过设置 RestTemplate 的属性、拦截器、消息转换器等来自定义请求和响应的处理。例如,你可以设置超时时间、设置请求头、添加认证信息,以及指定响应的数据类型等。

  4. 支持响应的处理和转换:RestTemplate 可以将响应的数据转换成指定的数据类型,如将 JSON 数据转换成 Java 对象、XML 数据转换成对象等。你可以使用自带的消息转换器,也可以自定义消息转换器来处理响应数据。

  5. 集成了 Spring 的异常处理机制:当发生 HTTP 请求错误时,RestTemplate 会根据 HTTP 响应状态码抛出相应的异常,例如 HttpClientErrorException、HttpServerErrorException 等,你可以捕获这些异常并进行处理。

使用步骤

1.引入依赖
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>
2.创建RestTemplate对象,交由spring容器进行管理

列举常见的创建方式

1.启动类中注入RestTemplate对象

package com.example.demo;
 
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;
 
@SpringBootApplication
public class DemoApplication {
 
    @Bean
    public RestTemplate getRestTemplate(){
        return new RestTemplate();
    }
    public static void main(String[] args) {
        SpringApplication.run(DemoApplication.class, args);
    }
 
}

 2.创建配置类,注入RestTemplate的对象

package com.czxy.ssm.config;
 
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;
 
/**
 * RestTemplate工具类,主要用来提供RestTemplate对象
 */
@Configuration//加上这个注解作用,可以被Spring扫描
public class RestTemplateConfig {
    /**
     * 创建RestTemplate对象,将RestTemplate对象的生命周期的管理交给Spring
     */
    @Bean
    public RestTemplate restTemplate(){
        return new RestTemplate();
    }
}

 3.创建配置类,注入RestTemplate的对象,并设置连接和请求的时间

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.ClientHttpRequestFactory;
import org.springframework.http.client.SimpleClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
 
/**
 * RestTemplate工具类,主要用来提供RestTemplate对象
 */
@Configuration//加上这个注解作用,可以被Spring扫描
public class RestTemplateConfig {
 
    @Bean
    public RestTemplate restTemplate(ClientHttpRequestFactory factory){
        return new RestTemplate(factory);
    }
 
    @Bean
    public ClientHttpRequestFactory simpleClientHttpRequestFactory(){
        SimpleClientHttpRequestFactory factory = new SimpleClientHttpRequestFactory();
        factory.setReadTimeout(500000);//单位为ms
        factory.setConnectTimeout(500000);//单位为ms
        return factory;
    }
}
3.接口调用
3.1 简单Get\Post请求
    @Test
    public void testGetPost(){
        RestTemplate restTemplate = new RestTemplate();
        restTemplate.getMessageConverters().add(new FastJsonHttpMessageConverter());
        String res = restTemplate.postForObject("http://test.aihe.space/", null, String.class);
        System.out.println(res);
        String res2 = restTemplate.getForObject("http://test.aihe.space/",  String.class);
        System.out.println(res2);
    }
 
3.2 Post提交常规表单
    @Test
    public void testGetPost(){
        RestTemplate restTemplate = new RestTemplate();
        restTemplate.getMessageConverters().add(new FastJsonHttpMessageConverter());
        String res = restTemplate.postForObject("http://test.aihe.space/", null, String.class);
        System.out.println(res);
        String res2 = restTemplate.getForObject("http://test.aihe.space/",  String.class);
        System.out.println(res2);
    }
 
3.3 Post上传文件
@Test
    public void testPostFile(){
 
        RestTemplate restTemplate = new RestTemplate();
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.MULTIPART_FORM_DATA);
        MultiValueMap<String, Object> map= new LinkedMultiValueMap<>();
        map.add("id", "1");
        map.add("file",new FileSystemResource("/Users/aihe/code/init/src/test/java/me/aihe/RestTemplateTest.java"));
        HttpEntity<MultiValueMap<String, Object>> request = new HttpEntity<>(map, headers);
        String fooResourceUrl = "http://test.aihe.space/";
        ResponseEntity<String> response = restTemplate.postForEntity(fooResourceUrl, request, String.class);
        System.out.println(response.getStatusCode());
        System.out.println(response.getBody());
    }
 
3.4 添加 Header 和 Cookie
 UriComponents uriComponents = UriComponentsBuilder.fromHttpUrl("127.0.0.1:8080").
                path("/test").build(true);
 URI uri = uriComponents.toUri();
 
RequestEntity<JSONObject> requestEntity = RequestEntity.post(uri).
                // 添加 cookie(这边有个问题,假如我们要设置 cookie 的生命周期,作用域等参数我们要怎么操作)
                header(HttpHeaders.COOKIE,"key1=value1").
                // 添加 header
                header(("MyRequestHeader", "MyValue")
                accept(MediaType.APPLICATION_JSON).
                contentType(MediaType.APPLICATION_JSON).
                body(requestParam);
ResponseEntity<JSONObject> responseEntity = restTemplate.exchange(requestEntity,JSONObject.class);
// 响应结果
JSONObject responseEntityBody = responseEntity.getBody();

Feign

简介

Feign是一个声明式、模板化的HTTP客户端框架,它简化了使用HTTP请求和响应的过程。它是Netflix开源的一部分,旨在帮助开发人员轻松地构建基于HTTP的客户端。

Feign远程调用基本流程

Feign远程调用,核心就是通过一系列的封装和处理,将以JAVA注解的方式定义的远程调用API接口,最终转换成HTTP的请求形式,然后将HTTP的请求的响应结果,解码成JAVA Bean,放回给调用者。Feign远程调用的基本流程,大致如下图所示

 

从上图可以看到,Feign通过处理注解,将请求模板化,当实际调用的时候,传入参数,根据参数再应用到请求上,进而转化成真正的 Request 请求。通过Feign以及JAVA的动态代理机制,使得Java 开发人员,可以不用通过HTTP框架去封装HTTP请求报文的方式,完成远程服务的HTTP调用。

使用步骤

1. 引入依赖
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
2. 添加注解

在客户端的服务模块的启动类上添加注解@EnableFeignClients开启Feign的功能

3.编写Feign的客户端

在客户端的服务模块中新建一个接口,下面举个例子

package com.kjz.order.client;

import com.kjz.order.pojo.User;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;

@FeignClient("userservice")
public interface UserClient {
    @GetMapping("/user/{id}")
    User findById(@PathVariable("id") Long id);
}

这个客户端主要是基于SpringMVC的注解来声明远程调用的信息,比如:

  • 服务名称:userservice

  • 请求方式:GET

  • 请求路径:/user/{id}

  • 请求参数:Long id

  • 返回值类型:User

这样,Feign就可以帮助我们发送http请求,无需自己使用RestTemplate来发送了。

RestTemplate与Feign对比

为什么SpringCloud中使用使用Feign进行远程调用而不使用RestTemplate呢?

我认为主要有以下原因:

  1. 声明式接口:Feign 允许你使用注解方式定义接口,将服务调用的细节隐藏在接口之后,使得代码更加简洁、易读。相比之下,RestTemplate 需要显式地构建请求和处理响应,代码量较多且容易出错,参数复杂URL难以维护

  2. 与服务注册中心的集成:Feign 可以与 Spring Cloud 提供的服务注册中心(如 Eureka、Consul)无缝集成,从而自动获取服务实例列表并基于负载均衡策略进行调用。而 RestTemplate 需要手动编写逻辑来实现服务发现和负载均衡。

  3. 支持断路器 Hystrix:Feign 在设计时就考虑到了与 Hystrix 断路器的紧密集成,可以方便地实现服务降级、熔断等容错机制。而 RestTemplate 需要额外的配置和集成工作才能与 Hystrix 整合。

  4. 更好的扩展性:Feign 的设计理念是可以轻松地与其他 Spring Cloud 组件进行集成。它支持通过编写自定义的解码器、编码器和拦截器等来扩展其功能。而 RestTemplate 在这方面的扩展性相对较差。

如何在两者间做出选择

由于HTTP与RPC不是一个并行的概念,所以也无法在两者间做选择。这里我浅谈一下RPC的一种实现方案Dubbo与Feign之间该如何做出选择。

使用场景

Feign 是一种声明式的 HTTP 客户端,用于简化对 RESTful API 的调用。它的主要使用场景是在微服务架构中,通过 HTTP 协议调用其他服务的 RESTful API。Feign 支持多种编解码器,如 Jackson、Gson、JAXB 等,可以将请求和响应转换成对象。Feign 还提供了负载均衡和服务发现功能,可以通过 Eureka、Consul 等服务注册中心来自动发现和负载均衡服务。

Dubbo 是一种基于 RPC(Remote Procedure Call)协议的远程调用框架,它可以将远程调用抽象成本地调用的方式。Dubbo 的主要使用场景是在大型分布式系统中,通过 RPC 协议调用其他服务。Dubbo 支持多种协议,如 Dubbo 协议、HTTP 协议、Hessian 协议等。Dubbo 还提供了负载均衡、服务治理、容错等功能,可以通过 ZooKeeper、Consul 等服务注册中心来实现服务发现和负载均衡。

实际应用

Feign 和 Dubbo 都是目前广泛应用于微服务架构的远程调用框架。

Feign 可以与 Spring Cloud 等微服务框架集成,通过注解方式声明远程调用接口,例如:

​
@FeignClient(name = "users-service")
public interface UsersClient {

    @GetMapping("/users/{id}")
    User getUser(@PathVariable("id") Long id);

}

​

这个接口定义了一个名为 users-service 的远程服务,通过 @GetMapping 注解来指定远程调用的路径。Feign 会根据注解生成代理对象,调用代理对象的方法就会触发远程调用。Feign 还支持 Hystrix 熔断器,可以防止服务雪崩。

Dubbo 也可以与 Spring Cloud 等微服务框架集成,通过注解方式声明远程调用接口,例如:

@Service
public interface UserService {

    User getUser(Long id);

}

这个接口定义了一个名为 UserService 的远程服务,Dubbo 会自动将这个接口暴露成远程服务,其他服务可以通过 Dubbo 的客户端调用这个接口。Dubbo 还提供了丰富的配置选项,可以配置负载均衡算法、容错策略、超时时间等。同时,Dubbo 还支持服务降级和熔断,可以在服务不可用时自动切换到备用服务。

原理

Feign 是基于动态代理和注解实现的。当应用程序调用 Feign 客户端接口时,Feign 会在运行时动态地生成一个代理对象,代理对象通过注解来获取远程服务的信息,然后将远程调用转化为 HTTP 请求,发送给远程服务。Feign 通过编解码器将请求和响应转换成对象。


Dubbo 是基于 RPC 协议实现的。当应用程序调用 Dubbo 服务时,Dubbo 会将调用信息封装成一个 RPC 请求,然后通过网络传输到远程服务,远程服务将 RPC 请求解码,执行对应的方法,然后将执行结果封装成 RPC 响应,通过网络传输回来。Dubbo 支持多种协议,可以选择最适合当前场景的协议。

性能

Feign 和 Dubbo 的性能各有优劣。Feign 的性能相对较差,因为它是基于 HTTP 协议实现的,每次远程调用都需要建立 TCP 连接,开销比较大。而 Dubbo 的性能相对较好,因为它是基于 RPC 协议实现的,每次远程调用只需要发送一个小的二进制请求,响应也是一个小的二进制数据包,开销比较小。

另外,Dubbo 还提供了多种负载均衡算法和容错策略,可以在服务负载均衡和故障恢复方面更加灵活和高效。

总结

Feign 和 Dubbo 都是优秀的微服务架构下的远程调用框架,各有特点。Feign 适合调用 RESTful API 接口,具有简单、轻量、易用等特点。Dubbo 适合大规模分布式系统,具有高性能、丰富的配置、容错机制等特点。在实际应用中,需要根据具体场景选择合适的远程调用框架。对于简单的微服务应用场景,Feign 可以轻松实现服务之间的远程调用,而且使用起来非常方便,无需过多的配置和代码编写。但是,如果要实现高性能、高可用的大规模分布式系统,Dubbo 更适合。

此外,需要注意的是,Feign 和 Dubbo 虽然都是远程调用框架,但其实现方式不同,也有着不同的使用场景和局限性。Feign 仅支持 HTTP 协议,不支持 RPC 协议,也不支持自定义协议。而 Dubbo 支持多种协议,并且提供了丰富的配置选项,可以根据实际需要进行灵活配置。

因此,在选择 Feign 和 Dubbo 时,需要根据实际情况进行综合考虑,包括应用场景、性能需求、安全性、易用性等多个方面。同时,还需要根据具体业务需求和技术栈进行权衡和选择,以便最终达到最优的性能和效果。

  • 6
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

编程小猹

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值