RPC简述

RPC 全称是 Remote Procedure Call ,即远程过程调用,其对应的是我们的本地调用。RPC 的目的是:让我们调用远程方法像调用本地方法一样。

RPC调用过程

我们使用RPC远程框架的话只需要关心客户端和服务端即可,不需要去关注RPC调用过程,那一块会由框架来为我们实现。

框架实现过程:

1、Client远程调用的话,会根据对象进行远程通信并进行编码,然后传输给服务端,服务端收到后进行解码后通过反射进行本地调用。

2、服务器调用本地方法后查询到结果后,按照第一步的流程返回给客户端。

Spring Cloud Feign

1)什么是Feign

Feign是Netflix开发的声明式、模板化的HTTP客户端,Feign可帮助我们更加便捷、优雅地调用HTTP API。

Feign可以做到使用 HTTP 请求远程服务时就像调用本地方法一样的体验,开发者完全感知不到这是远程方法,更感知不到这是个 HTTP 请求。不需要通过常规的 Http Client 构造请求再解析返回数据。

它解决了让开发者调用远程接口就跟调用本地方法一样,无需关注与远程的交互细节,更无需关注分布式环境开发。但实际上,Feign 底层也是基于HTTP协议进行请求访问的,只是框架帮我们都封装好了。

2)Feign和Open Feign的区别

Spring Cloud openfeign对Feign进行了增强,使其支持Spring MVC注解并可以携带很多参数,另外还整合了Ribbon和Eureka,让open Feign支持负载均衡。

3)Feign架构设计

Feign逻辑架构

4)Spring Cloud Alibaba 整合Feign

1.引入Open Feign依赖

1
2
3
4
5
<!-- openfeign 远程调用 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

2.订单服务controller

订单服务controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.javaxing.xingmallorder.contrller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RequestMapping("/order")
@RestController
public class OrderContrller {
@GetMapping("/queryOrder")
public String queryOrder(@RequestParam("orderId") Integer orderId){
System.out.println("成功请求到该接口");
return "Query Success"+orderId;
}
}

3.用户服务端编写feign接口

1
2
3
4
5
6
7
8
9
10
11
package com.javaxing.springbootnacos.api;

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;

@FeignClient(value = "xingmall-order",path = "/order")
public interface OrderFeignService {
@GetMapping("/queryOrder")
public String queryOrder(@RequestParam("orderId") Integer orderId);
}

4.在服务端启动上类添加注解

1
2
3
4
5
6
7
8
@SpringBootApplication
@EnableFeignClients //扫描和注册feign客户端的beanDefinition
public class MallUserApplication {

public static void main(String[] args) {
SpringApplication.run(MallUserApplication.class, args);
}
}

5.用户服务controller(调用方)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.javaxing.springbootnacos.contrller;

import com.javaxing.springbootnacos.api.OrderFeignService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class UserContrller {
// 注入feign
@Autowired
private OrderFeignService orderFeignService;

@RequestMapping(value = "/testRibbon")
public String testRibbon() {
// 通过feign进行调用
String result = orderFeignService.queryOrder(10010);
return result;
}
}

6.调用测试

image-20230402154338646

Spring Cloud Feign扩展

Feign 提供了很多的扩展机制,可以让我们灵活的使用,如日志配置、拦截器扩展等。

1)日志配置

我们可以通过日志扩展,让每次的Feign请求信息都打印出来,便于我们在开发和生产环境的过程中更好的去定位BUG。

  • NONE【性能最佳,适用于生产】:不记录任何日志(默认值)。
  • BASIC【适用于生产环境追踪问题】:仅记录请求方法、URL、响应状态代码以及执行时间。
  • HEADERS:记录BASIC级别的基础上,记录请求和响应的header。
  • FULL【比较适用于开发及测试环境定位问题】:记录请求和响应的header、body和元数据。

本次实验源码地址:https://files.javaxing.com/nacos/NacosServer.zip

1.1 代码全局配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.javaxing.springbootnacos.config;

import feign.Logger;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

// 注意: 此处配置@Configuration注解就会全局生效,如果想指定对应微服务生效,就不能配置@Configuration
@Configuration
public class FeignConfig {
/**
* 日志级别
*
* @return
*/
@Bean
public Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
}

application.yaml

1
2
3
logging:
level:
com.javaxing.springbootnacos.api.OrderFeignService: debug

不论是全局还是局部,在yml配置文件中配置 Client 的日志级别才能正常输出日志

1.2 yaml配置方式

局部配置有2种,1种是在feignClass文件里面配置,1种是在application.yaml文件里面配置。

1.2.1 class文件配置

feignConfig文件配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
package com.javaxing.springbootnacos.config;

import feign.Logger;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

// 注意: 此处配置@Configuration注解就会全局生效,如果想指定对应微服务生效,就不能配置@Configuration
//@Configuration
public class FeignConfig {
/**
* 日志级别
*
* @return
*/
@Bean
public Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
}

如果需要局部配置,这里的@Configuration 就不可以加,如果加了就会变成全局日志

Feign接口

1
2
3
4
5
@FeignClient(value = "xingmall-order",path = "/order",configuration = FeignConfig.class)
public interface OrderFeignService {
@GetMapping("/queryOrder")
public String queryOrder(@RequestParam("orderId") Integer orderId);
}

在feignClient注解之上增加feignConfig.class的路径,设置这个feign接口使用的配置

application.yaml 开放日志

1
2
3
logging:
level:
com.javaxing.springbootnacos.api.OrderFeignService: debug

不论是全局还是局部,在yml配置文件中配置 Client 的日志级别才能正常输出日志

1.2.2 application.yaml配置

1
2
3
4
5
6
7
8
logging:
level:
com.javaxing.springbootnacos.api.OrderFeignService: debug
feign:
client:
config:
xingmall-order: #对应微服务
loggerLevel: FULL

1、不论是全局还是局部,在yml配置文件中配置 Client 的日志级别才能正常输出日志

2、feign.client.config 配置对应的微服务的日志输出,模式为FULL 全部输出

Feign 接口

1
2
3
4
5
6
7
8
9
10
11
package com.javaxing.springbootnacos.api;

import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;

@FeignClient(value = "xingmall-order",path = "/order")
public interface OrderFeignService {
@GetMapping("/queryOrder")
public String queryOrder(@RequestParam("orderId") Integer orderId);
}

1.3 日志配置测试

image-20230403114416117

2)通过拦截器实现参数传递

通常情况下,我们的接口都是有做权限校验的,而我们在通过open feign去调用微服务时,可以通过拦截器来实现参数传递。

2.1 Feign是如何实现拦截的

每次 feign 发起http请求调用之前,会去执行拦截器中的逻辑。

1
2
3
4
5
6
7
8
9
10
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//

package feign;

public interface RequestInterceptor {
void apply(RequestTemplate var1);
}

2.2 Feign调用时配置Basic 认证

1
2
3
4
5
6
7
@Configuration  // 全局配置
public class FeignConfig {
@Bean
public BasicAuthRequestInterceptor basicAuthRequestInterceptor() {
return new BasicAuthRequestInterceptor("admin", "123456");
}
}

官方源码实现

image-20230403120631411

image-20230403120743123

2.3 自定义拦截器实现认证逻辑

2.3.1 全局拦截器

自定义Interceptor拦截器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.javaxing.springbootnacos.Interceptor;

import feign.RequestInterceptor;
import feign.RequestTemplate;

import java.util.UUID;

public class FeignAuthRequestInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate template) {
// 业务逻辑,应用场景:我们可以自定义添加header头做一些认证和携带参数
String access_token = UUID.randomUUID().toString();
template.header("Authorization",access_token);
template.header("AppVersion","1");
}
}

FeignConfig配置文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.javaxing.springbootnacos.config;

import com.javaxing.springbootnacos.Interceptor.FeignAuthRequestInterceptor;
import feign.Logger;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration // 全局配置
public class FeignConfig {
@Bean
public Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}
/**
* 自定义拦截器
* @return
*/
@Bean
public FeignAuthRequestInterceptor feignAuthRequestInterceptor(){
return new FeignAuthRequestInterceptor();
}
}

验证拦截实验

我们在user上去通过open feign调用order上的服务,可以看到user微服务打印的日志中,携带了header头。

image-20230403121825696

2.3.2 yaml拦截(微服务拦截)

有时候我们不需要全局拦截,我们只针对某个微服务的feign调用进行自定义拦截,就可以使用 局部拦截。

application.yaml

1
2
3
4
5
6
feign:
client:
config:
xingmall-order: #对应微服务
requestInterceptors[0]: #配置拦截器
com.javaxing.springbootnacos.Interceptor.FeignAuthRequestInterceptor

Interceptor自定义拦截器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.javaxing.springbootnacos.Interceptor;

import feign.RequestInterceptor;
import feign.RequestTemplate;

import java.util.UUID;

public class FeignAuthRequestInterceptor implements RequestInterceptor {
@Override
public void apply(RequestTemplate template) {
// 业务逻辑,应用场景:我们可以自定义添加header头做一些认证和携带参数
String access_token = UUID.randomUUID().toString();
template.header("Authorization",access_token);
template.header("AppVersion","1");
}
}

验证结果

image-20230403131320207

3)超时时间配置

通过 Options模块可以配置Feign调用时的连接超时时间和读取超时时间。

配置的方式有2种:1、全局配置 2、针对单独微服务配置

2.1 代码全局配置方式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.javaxing.springbootnacos.config;

import feign.Logger;
import feign.Request;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration // 全局配置
public class FeignConfig {
@Bean
public Logger.Level feignLoggerLevel() {
return Logger.Level.FULL;
}

@Bean
public Request.Options options() {
// 连接超时时间 设置为5秒,请求处理超时时间设置为10秒
return new Request.Options(5000, 10000);
}
}

第一个参数:连接的超时时间(ms),默认值是 2s

第二个参数:请求处理的超时时间(ms),默认值是 5s

2.2 yaml配置

1
2
3
4
5
6
7
8
feign:
client:
config:
xingmall-order: #对应微服务
# 连接超时时间,默认2s
connectTimeout: 5000
# 请求处理超时时间,默认5s
readTimeout: 10000

局部配置的话,需要指定要用到的微服务名称和超时时间

2.3 验证方案

订单服务controller

订单服务模拟休眠20秒,这样用户服务来调用订单服务时,肯定会出现 超时的情况。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
package com.javaxing.xingmallorder.contrller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RequestMapping("/order")
@RestController
public class OrderContrller {
@GetMapping("/queryOrder")
public String queryOrder(@RequestParam("orderId") Integer orderId) throws InterruptedException {
// 休眠20秒
Thread.sleep(20000);
System.out.println("成功请求到该接口");
return "Query Success"+orderId;
}
}

用户服务controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.javaxing.springbootnacos.contrller;

import com.javaxing.springbootnacos.api.OrderFeignService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class UserContrller {
// 注入feign
@Autowired
private OrderFeignService orderFeignService;

@RequestMapping(value = "/testRibbon")
public String testRibbon() {
// 通过feign进行调用
String result = orderFeignService.queryOrder(10010);
return result;
}
}

因为我们设定的超时时间为10秒,而我们让订单服务休眠20秒,用户服务在调用的过程中肯定会超时的。

在生产环境中,如果是重要的数据请求,如订单下单,如果请求超时了,一定要做一个降级熔断处理。

image-20230403132719831

4)优化 - Feign请求组件

Feign 中默认使用 JDK 原生的 URLConnection 发送 HTTP 请求,无法复用连接池等机制,为此我们可以替换成其他的组件。如,Apache HttpClient,OkHttp。

4.1 Apache HttpClient

4.1.1 引入依赖

1
2
3
4
5
6
7
8
9
10
11
<!-- Apache HttpClient -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.7</version>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
<version>10.1.0</version>
</dependency>

4.1.2 启用Apache 组件

1
2
3
4
feign:
#feign 使用 Apache HttpClient 可以忽略,默认开启
httpclient:
enabled: true

使用 Apache HttpClient 可以忽略,默认开启

4.2 OKHttp

4.2.1 引入依赖

1
2
3
4
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-okhttp</artifactId>
</dependency>

4.2.2 启用OKHttp组件

1
2
3
4
5
feign:
httpclient:
enabled: false
okhttp:
enabled: true

在配置文件里面设置:禁用httpClient,启用okHttp

5)优化 - GZIP压缩Feign请求

开启GZIP压缩后可以有效节约网络资源,提升接口性能。

开启方法

1
2
3
4
5
6
7
8
9
10
11
feign:
# 配置 GZIP 来压缩数据
compression:
request:
enabled: true
# 配置压缩的类型
mime-types: text/xml,application/xml,application/json
# 最小压缩值
min-request-size: 2048
response:
enabled: true

注意:只有在feign的Http Client不是OKHttp的情况下才会进行GZIP压缩,如果使用OKHttp的话,GZIP是不会生效的。

OKHttp无法使用的原因

image-20230403162258409

@ConditionalOnMissingBean(type = {“okhttp3.OkHttpClient”}) 表示Spring BeanFactory初始化时,匹配到这个bean条件时,不启用GZIP,换言之,你用的OKHttp 就无法使用GZIP压缩

6)优化 - 编码器解码器配置

Feign 中提供了自定义的编码解码器设置,同时也提供了多种编码器的实现,比如 Gson、Jaxb、Jackson。我们可以用不同的编码解码器来处理数据的传输。如果你想传输 XML 格式的数据,可以自定义 XML 编码解码器来实现获取使用官方提供的 Jaxb。

源码扩展点:Encoder & Decoder

1
2
3
4
5
6
public interface Encoder {
void encode(Object object, Type bodyType, RequestTemplate template) throws EncodeException;
}
public interface Decoder {
Object decode(Response response, Type type) throws IOException, DecodeException, FeignException;
}

6.1 代码全局配置方式

配置编码解码器只需要在 Feign 的配置类中注册 Decoder 和 Encoder 这两个类即可:

1
2
3
4
5
6
7
8
@Bean
public Decoder decoder() {
return new JacksonDecoder();
}
@Bean
public Encoder encoder() {
return new JacksonEncoder();
}

6.2 yaml配置

1
2
3
4
5
6
7
feign:
client:
config:
xingmall-order: #对应微服务
# 配置编解码器
encoder: feign.jackson.JacksonEncoder
decoder: feign.jackson.JacksonDecoder

Spring Cloud 整合Dubbo

本次实验源码地址:https://files.javaxing.com/nacos/NacosServer.zip

1)项目环境介绍

image-20230404093622687

本次整合的实验项目,一共有2个模块,消费者和生产者,而消费者和生产者 各自有一个api和biz模块,api模块存放的是对外开放的方法和实体类,biz则是具体的实现方法。

本次实验,消费者和生产者都要引入dubbo依赖,而生产者则在api模块开放service接口,消费者引入 生产者的api模块,并通过dubbo 调用这些service接口,完成RPC远程调用。

我们在最顶层,有一个父类工程,其中依赖里面已经引入了springboot,spring cloud alibaba的依赖:

NacosServer工程的pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.12.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>

<groupId>com.javaxing</groupId>
<artifactId>NacosServer</artifactId>
<version>1.0-SNAPSHOT</version>

<packaging>pom</packaging>


<properties>
<spring-cloud.version>Hoxton.SR12</spring-cloud.version>
<spring-cloud-alibaba.version>2.2.8.RELEASE</spring-cloud-alibaba.version>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- 引入nacos依赖-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
<version>2.1.0.RELEASE</version>
</dependency>

<!-- openfeign 远程调用 -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

<!-- Apache HttpClient -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<version>4.5.7</version>
</dependency>
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-httpclient</artifactId>
<version>10.1.0</version>
</dependency>

<!-- <dependency>-->
<!-- <groupId>io.github.openfeign</groupId>-->
<!-- <artifactId>feign-okhttp</artifactId>-->
<!-- </dependency>-->

</dependencies>

<dependencyManagement>
<dependencies>

<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-alibaba-dependencies</artifactId>
<version>${spring-cloud-alibaba.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>

</dependencies>
</dependencyManagement>


<modules>
<module>xingmall-user</module>
<module>xingmall-order</module>
<module>xingmall-order-duboo</module>
<module>xingmall-feign-dubbo</module>
</modules>

</project>

xingmall-order-duboo pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.javaxing</groupId>
<artifactId>NacosServer</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>xingmall-order-duboo</artifactId>
<groupId>com.javaxing</groupId>
<version>1.0-SNAPSHOT</version>

<packaging>pom</packaging>

<dependencies>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-dubbo</artifactId>
<version>2.2.7.RELEASE</version>
</dependency>


</dependencies>


<modules>

<module>xingmall-order-duboo-producer</module>
<module>xingmall-order-duboo-consumer</module>

</modules>

</project>

注意:因为spring cloud alibaba 2.2.8这个版本没有整合dubbo,所以需要指定dubbo的版本,如果是springCloudAlibaba其他版本的话,具体看官网的版本对应:https://github.com/alibaba/spring-cloud-alibaba/wiki/%E7%89%88%E6%9C%AC%E8%AF%B4%E6%98%8E

https://note.youdao.com/yws/public/resource/c90bd2616b59aa073f9a1e330989ef04/xmlnote/0FC5FF6625CD4923B49DC8DCCDB91F1D/54856

2)producer

工程模块截图

image-20230404094835022

2.1 producer 父工程 xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<artifactId>xingmall-order-duboo</artifactId>
<groupId>com.javaxing</groupId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>xingmall-order-duboo-producer</artifactId>
<name>Archetype - xingmall-order-duboo-producer</name>


<modules>
<module>xingmall-order-duboo-producer-api</module>
<module>xingmall-order-duboo-producer-biz</module>
</modules>

<packaging>pom</packaging>
</project>

2.2 producer api模块

pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.javaxing</groupId>
<artifactId>xingmall-order-duboo-producer</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>xingmall-order-duboo-producer-api</artifactId>
<packaging>war</packaging>
<name>xingmall-order-duboo-producer-api Maven Webapp</name>
<url>http://maven.apache.org</url>
</project>

service接口

1
2
3
4
5
6
package com.javaxing.xingmallorderdubooproducer.service;

public interface OrderService {
public String queryOrderList();
public String queryOrderById(Integer id);
}

2.3 producer biz模块

pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<!-- 指定父类模块-->
<parent>
<groupId>com.javaxing</groupId>
<artifactId>xingmall-order-duboo-producer</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>

<groupId>com.javaxing</groupId>
<artifactId>xingmall-order-duboo-producer-biz</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>xingmall-order-duboo-producer</name>
<description>xingmall-order-duboo-producer</description>
<properties>
<java.version>1.8</java.version>
</properties>


<dependencies>
<!--引入producer api模块-->
<dependency>
<groupId>com.javaxing</groupId>
<artifactId>xingmall-order-duboo-producer-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

serviceImpl实现类

服务实现类上配置@DubboService暴露服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.javaxing.xingmallorderdubooproducer.service;
import org.apache.dubbo.config.annotation.DubboService;

// 通过dubbo注解对外开放该接口
@DubboService
public class OrderServiceImpl implements OrderService{

public String queryOrderList(){
return "query success";
}

public String queryOrderById(Integer id){
return "query success by id";
}
}

启动类

1
2
3
4
5
6
7
8
9
10
11
12
package com.javaxing.xingmallorderdubooproducer;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class XingmallOrderDubooProducerApplication {

public static void main(String[] args) {
SpringApplication.run(XingmallOrderDubooProducerApplication.class, args);
}
}

application.yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
server:
port: 8080

spring:
main:
# Spring Boot2.1及更高的版本需要设定
allow-bean-definition-overriding: true
application:
name: xingmall-producer
cloud:
nacos:
discovery:
server-addr: 10.211.55.12:8848
namespace: 37731914-6c28-4265-a090-7a9391ed843d
cluster-name: shanghai


dubbo:
scan:
# 指定 Dubbo 服务实现类的扫描基准包
base-packages: com.javaxing.xingmallorderdubooproducer.service
protocol:
# dubbo 协议
name: dubbo
# dubbo 协议端口( -1 表示自增端口,从 20880 开始)
port: -1

3)consumer

工程模块截图

image-20230404095646869

2.1 consumer 父工程xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.javaxing</groupId>
<artifactId>xingmall-order-duboo</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>

<groupId>com.javaxing</groupId>
<artifactId>xingmall-order-duboo-consumer</artifactId>
<version>1.0-SNAPSHOT</version>

<packaging>pom</packaging>


<modules>
<module>xingmall-order-duboo-consumer-biz</module>
<module>xingmall-order-duboo-consumer-api</module>
</modules>
</project>

2.2 consumer api模块

pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.javaxing</groupId>
<artifactId>xingmall-order-duboo-consumer</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>xingmall-order-duboo-consumer-api</artifactId>
<name>Archetype - xingmall-order-duboo-consumer-api</name>
<url>http://maven.apache.org</url>
</project>

2.3 consumer biz模块

pom.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.javaxing</groupId>
<artifactId>xingmall-order-duboo-consumer</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>

<groupId>com.javaxing</groupId>
<artifactId>xingmall-order-duboo-consumer-biz</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>xingmall-order-duboo-consumer</name>
<description>xingmall-order-duboo-consumer</description>
<properties>
<java.version>1.8</java.version>
</properties>


<dependencies>
<!-- 引入生产者api模块,这个模块主要是对外暴露的service接口和实体类等,通过dubbo调用该模块,可以远程调用生产者的service接口-->
<dependency>
<groupId>com.javaxing</groupId>
<artifactId>xingmall-order-duboo-producer-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

controoler

服务消费方通过@DubboReference引入服务

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
package com.javaxing.xingmallorderdubooconsumer.controller;

import com.javaxing.xingmallorderdubooproducer.service.OrderService;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class QueryOrderContrller {

// 通过注解来使用dubbo 引入该接口
@DubboReference
private OrderService orderService;


@GetMapping("/queryOrderById")
public String queryOrderById(Integer id){
String result = orderService.queryOrderById(id);
System.out.println("成功执行根据订单id查询订单详情:"+result);
return result;
}

@GetMapping("/queryOrderList")
public String queryOrderList(){
String result = orderService.queryOrderList();
System.out.println("成功执行查询订单列表:"+result);
return result;
}
}

application.yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
server:
port: 8081

spring:
main:
# Spring Boot2.1及更高的版本需要设定
allow-bean-definition-overriding: true
application:
name: xingmall-consumer
cloud:
nacos:
discovery:
server-addr: 10.211.55.12:8848
namespace: 37731914-6c28-4265-a090-7a9391ed843d
cluster-name: shanghai

dubbo:
cloud:
# 指定需要订阅的服务提供方,默认值*,会订阅所有服务,不建议使用
subscribed-services: xingmall-producer
protocol:
# dubbo 协议
name: dubbo
# dubbo 协议端口( -1 表示自增端口,从 20880 开始)
port: -1

如果不写allow-bean-definition-overriding:true,就会出现下面的错误:

4)远程调用测试

我们通过接口进行远程调用,访问consumer接口,通过consumer去调用producer service接口。

image-20230404102047928

我们找到DubboInvoker的doInvoke源码,可以看到 springboot在启动时,会去调用该方法,这个方法是dubbo的入口点。

image-20230404101747213

从Open Feign迁移到Dubbo

在生产环境中,如果一开始的时候,我们在微服务之间的调用模块之间使用的是open feign,随着功能和微服务的扩展,性能逐渐跟不上的事情,就需要把open feign方案迁移到dubbo方案,但是直接迁移的话时间成本和人工成本太高。

为此我们可以通过以下的方案,在不调整 Feign 接口以及 RestTemplate URL 的前提下,实现无缝迁移。

实现原理

  • 通过@DubboTransported 注解,让服务消费端的 Spring Cloud Open Feign 接口以及 @LoadBalanced RestTemplate Bean 底层走 Dubbo 调用(可切换 Dubbo 支持的协议)
    • 而服务提供方则只需在原有 @RestController 类上追加 Dubbo @Servce 注解(需要抽取接口)即可

1)搭建基础feign环境

项目工程图

image-20230404115448971

我们创建2个模块,一个生产者 一个消费者。消费者在controller上通过feign远程调用生产者的controller。

2)producer

2.1 producer父工程xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.javaxing</groupId>
<artifactId>xingmall-feign-dubbo</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>xingmall-feign-dubbo-producer</artifactId>
<name>Archetype - xingmall-feign-dubbo-producer</name>
<url>http://maven.apache.org</url>

<packaging>pom</packaging>

<modules>
<module>xingmall-feign-dubbo-producer-api</module>
<module>xingmall-feign-dubbo-producer-biz</module>
</modules>
</project>

2.2 producer api模块

xml

1
2
3
4
5
6
7
8
9
10
11
12
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.javaxing</groupId>
<version>1.0-SNAPSHOT</version>
<artifactId>xingmall-feign-dubbo-producer</artifactId>
</parent>
<artifactId>xingmall-feign-dubbo-producer-api</artifactId>
<name>Archetype - xingmall-feign-dubbo-producer-api</name>
<url>http://maven.apache.org</url>
</project>

service

1
2
3
public interface OrderService {
public String queryOrderById(Integer id);
}

2.3 producer biz模块

xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.javaxing</groupId>
<version>1.0-SNAPSHOT</version>
<artifactId>xingmall-feign-dubbo-producer</artifactId>
</parent>

<groupId>com.javaxing</groupId>
<artifactId>xingmall-feign-dubbo-producer-biz</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>xingmall-feign-dubbo-producer-biz</name>
<description>xingmall-feign-dubbo-producer-biz</description>
<properties>
<java.version>1.8</java.version>
</properties>


<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

</project>

serviceImpl

1
2
3
4
5
6
7
8
9
10
11
package com.javaxing.xingmallfeigndubboproducerbiz.service;

import org.apache.dubbo.config.annotation.DubboService;

@Service
public class OrderServiceImpl implements OrderService{
public String queryOrderById(Integer id){
System.out.println("调用producer方法成功");
return "query success id:"+id;
}
}

controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.javaxing.xingmallfeigndubboproducerbiz.controller;

import com.javaxing.xingmallfeigndubboproducerbiz.service.OrderService;
import org.apache.dubbo.config.annotation.DubboService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

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

@Autowired
private OrderService orderService;

@GetMapping("/queryOrderById")
public String queryOrderById(@RequestParam("id") Integer id){
return orderService.queryOrderById(id);
}
}

application.yaml

1
2
3
4
5
6
7
8
9
10
11
server:
port: 8080
spring:
application:
name: xingmall-feign-dubbo-producer
cloud:
nacos:
discovery:
server-addr: 10.211.55.12:8848
namespace: 37731914-6c28-4265-a090-7a9391ed843d
cluster-name: shanghai

3)consumer

2.1 consumer 父工程xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.javaxing</groupId>
<artifactId>xingmall-feign-dubbo</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>

<groupId>com.javaxing</groupId>
<version>1.0-SNAPSHOT</version>
<artifactId>xingmall-feign-dubbo-consumer</artifactId>
<packaging>pom</packaging>
<name>Archetype - xingmall-feign-dubbo-consumer</name>
<url>http://maven.apache.org</url>
<modules>
<module>xingmall-feign-dubbo-consumer-api</module>
<module>xingmall-feign-dubbo-consumer-biz</module>
</modules>
</project>

2.2 consumer api模块

xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.javaxing</groupId>
<artifactId>xingmall-feign-dubbo-consumer</artifactId>
<version>1.0-SNAPSHOT</version>
</parent>
<artifactId>xingmall-feign-dubbo-consumer-api</artifactId>
<name>Archetype - xingmall-feign-dubbo-consumer-api</name>
<url>http://maven.apache.org</url>


</project>

feign接口

1
2
3
4
5
6
7
8
9
10
11
12
13
package com.javaxing.xingmallfeigndubboconsumerbiz.feign;


import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;

@FeignClient(value = "xingmall-feign-dubbo-producer",path = "/order")
public interface OrderFeign {

@GetMapping("/queryOrderById")
public String queryOrderById(@RequestParam("id") Integer id);
}

2.3 consumer biz模块

xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.javaxing</groupId>
<version>1.0-SNAPSHOT</version>
<artifactId>xingmall-feign-dubbo-consumer</artifactId>
</parent>

<groupId>com.javaxing</groupId>
<artifactId>xingmall-feign-dubbo-consumer-biz</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>xingmall-feign-dubbo-consumer-biz</name>
<description>xingmall-feign-dubbo-consumer-biz</description>
<properties>
<java.version>1.8</java.version>
</properties>

<dependencies>
<dependency>
<groupId>com.javaxing</groupId>
<artifactId>xingmall-feign-dubbo-consumer-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>
</dependencies>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>


</project>

controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
package com.javaxing.xingmallfeigndubboconsumerbiz.controller;

import com.javaxing.xingmallfeigndubboconsumerbiz.feign.OrderFeign;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class TestController {


@Autowired
private OrderFeign orderFeign;

@GetMapping("/testFeignToDubbo")
public String testFeignToDubbo(Integer id){
return orderFeign.queryOrderById(id);
}
}

启动类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
package com.javaxing.xingmallfeigndubboconsumerbiz;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;

@SpringBootApplication
@EnableFeignClients //扫描和注册feign客户端的beanDefinition
public class XingmallFeignDubboConsumerBizApplication {

public static void main(String[] args) {
SpringApplication.run(XingmallFeignDubboConsumerBizApplication.class, args);
}
}

application.yaml

1
2
3
4
5
6
7
8
9
10
11
server:
port: 8081
spring:
application:
name: xingmall-feign-dubbo-consumer
cloud:
nacos:
discovery:
server-addr: 10.211.55.12:8848
namespace: 37731914-6c28-4265-a090-7a9391ed843d
cluster-name: shanghai

4)feign调用测试

调用之前,我们先看一下nacos注册中心的情况

image-20230404120301873

调用测试

image-20230404120316828

producer控制台

image-20230404120342050

5)feign迁移到dubbo

5.1 producer添加dubbo参数

添加yaml参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
server:
port: 8080
spring:
main:
allow-bean-definition-overriding: true
application:
name: xingmall-feign-dubbo-producer
cloud:
nacos:
discovery:
server-addr: 10.211.55.12:8848
namespace: 37731914-6c28-4265-a090-7a9391ed843d
cluster-name: shanghai

dubbo:
scan:
# 指定 Dubbo 服务实现类的扫描基准包
base-packages: com.javaxing.xingmallfeigndubboproducerbiz.service.OrderService
protocol:
# dubbo 协议
name: dubbo
# dubbo 协议端口( -1 表示自增端口,从 20880 开始)
port: -1

service 增加dubbo注解

1
2
3
4
5
6
7
8
9
10
11
package com.javaxing.xingmallfeigndubboproducerbiz.service;

import org.apache.dubbo.config.annotation.DubboService;

@DubboService
public class OrderServiceImpl implements OrderService{
public String queryOrderById(Integer id){
System.out.println("调用producer方法成功");
return "query success id:"+id;
}
}

5.2 consumer feign添加dubbo注解

添加@DubboTransported注解

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
package com.javaxing.xingmallfeigndubboconsumerbiz.feign;


import com.alibaba.cloud.dubbo.annotation.DubboTransported;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;

@FeignClient(value = "xingmall-feign-dubbo-producer",path = "/order")
@DubboTransported(protocol = "dubbo")
public interface OrderFeign {

@GetMapping("/queryOrderById")
public String queryOrderById(@RequestParam("id") Integer id);
}

添加yaml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
server:
port: 8081
spring:
main:
allow-bean-definition-overriding: true
application:
name: xingmall-feign-dubbo-consumer
cloud:
nacos:
discovery:
server-addr: 10.211.55.12:8848
namespace: 37731914-6c28-4265-a090-7a9391ed843d
cluster-name: shanghai

dubbo:
cloud:
# 指定需要订阅的服务提供方,默认值*,会订阅所有服务,不建议使用
subscribed-services: xingmall-feign-dubbo-producer
protocol:
# dubbo 协议
name: dubbo
# dubbo 协议端口( -1 表示自增端口,从 20880 开始)
port: -1

增加xml依赖,引入producer api

1
2
3
4
5
<dependency>
<groupId>com.javaxing</groupId>
<artifactId>xingmall-feign-dubbo-producer-api</artifactId>
<version>1.0-SNAPSHOT</version>
</dependency>

controller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
package com.javaxing.xingmallfeigndubboconsumerbiz.controller;

import com.javaxing.xingmallfeigndubboconsumerbiz.feign.OrderFeign;
import com.javaxing.xingmallfeigndubboproducerbiz.service.OrderService;
import org.apache.dubbo.config.annotation.DubboReference;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class TestController {


@DubboReference
private OrderService orderService;

@GetMapping("/testFeignToDubbo")
public String testFeignToDubbo(Integer id){
return orderService.queryOrderById(id);
}
}

增加@DubboReference并且通过orderSerivce进行调用(producer api service)。