前言
1.为什么需要RPC框架?
微服务的服务提供者和服务消费者解耦合之后,虽然可以借助restTemplate这样的HTTP客户端,向微服务的服务提供者发起远程调用;
但是这样的代码有2大缺陷:
//使用RestTemplate发起远程调用 @Autowired private RestTemplate restTemplate; public Order findById(Long orderId) { // 1.查询订单 Order order = orderMapper.selectById(orderId); //2.调用user-service服务查询当前订单的用户信息 //String url = "http://127.0.0.1:8081/user/" + order.getUserId(); //使用RestTemplate发起远程调用 String url = "http://user-service/user/" + order.getUserId(); User user = restTemplate.getForObject(url, User.class); //3.user封装到order对象 order.setUser(user); // 4.返回 return order; }
所以需要使用成熟的RPC框架实现微服务之间的调用;
2.RPC协议是什么?
RPC协议英文全称,Remote Procedure Call Protocol既远程过程调用协议,而不是通信协议,旨在像调用本地函数一样获取远程服务器上程序的执行结果;
RPC协议里面定义的与远程服务端通信环节,可以基于TCP协议,也可以基于HTTP协议实现;
3.RPC框架是什么?
实现了RPC协议的成熟RPC框架有:
- gRPC:Google公司基于HTTP/2协议和ProtoBuf序列化工具,支持多种开发语言;
- Dubbo:阿里巴巴开发的,专注于Java语言,与Spring框架无缝集成。
4.Protobuf和json的区别?
Protobuf(Protocol Buffers)是一种由Google开发的二进制数据序列化协议;
它可以将结构化数据转换成紧凑且高效的二进制格式,从而实现更小、更快、更高效的数据传输和存储;
Protobuf占用字节更小
与其他文本格式的数据交换协议如JSON和XML相比,Protobuf生成的二进制数据更小。
这是因为Protobuf使用了可变长度编码,即对于不同大小的整数或浮点数使用不同长度的字节来表示,从而使得数据可以更加紧凑;
此外,由于Protobuf使用了预定义的消息架构,因此不需要在每个报文中重复定义Key(字段名称),也可以进一步减少数据的大小;
Protobuf传输速度更快
由于Protobuf生成的二进制数据更小,因此数据传输速度更快;
此外,Protobuf还提供了一些优化技术,如内存映射和缓存等,可以进一步提高数据的读写速度;
相比于其他文本格式的数据交换协议,Protobuf在大规模数据传输场景下表现更出色;
Protobuf更高效
Protobuf支持多语言,包括C++、Java、Python、Go等,并提供了各种语言的API和库。这使得开发者可以方便地在不同平台和语言之间交换数据,并且不需要手动编写各种转换代码。此外,Protobuf还支持消息版本控制和兼容性升级等功能,从而使得应用程序更加灵活和可靠。
5.RPC与HTTP协议的区别?
由于RPC协议中调用方与被调用方之间的通信环节,可以基于TCP协议,也可以基于HTTP协议实现,所以经常造成RPC和HTTP协议的混淆;
面向的2端不同
HTTP协议主要用于Web浏览器与Web服务器之间的超文本数据传输;
RPC协议主要用于分布式架构中,服务端与服务端之间的数据传输;
序列化方式不同
gRPC可以基于Protobuf序列协议进行数据的序列化和反序列化,字节占用少,更快;
HTTP通常使用传统的json序列化技术,体积较大,传输效率较低;
负载均衡策略实现不同
HTTP协议需要再Web客户端和Web服务端之间增加Nginx、HAProxy等负载均衡中间件;
RPC协议的实现框架在客户端( 发起远程调用的一方)自带负载均衡功能;
一、SpringCloud项目搭建
鉴于微服务架构中职责单一性原则,2个微服务使用单独的数据库;
如果用户微服务需要查询订单微服务的数据,就需要使用Feign发起远程调用;
在远程调用过程中user-service是消费者order-service是生产者;
1.搭建流程图
2.搭建父项目
2.1.父项目依赖版本锁定
<?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> <groupId>com.zhanggen</groupId> <artifactId>spring-cloud-demo</artifactId> <version>1.0-SNAPSHOT</version> <!--在打包的时候被打成jar,会被作为管理模块去编译--> <packaging>pom</packaging> <!-- 父项目中引入spring-boot-starter-parent继承Spring-boot的核心包 子项目的spring-starter才可以被启动 --> <parent> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-parent</artifactId> <version>2.3.9.RELEASE</version> <relativePath/> </parent> <!--dependencyManagement进行版本锁定,不会真正引入依赖包--> <dependencyManagement> <dependencies> <!-- 给spring-cloud提供的Feign、Gateway、Eureka等提供版本锁定 --> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-dependencies</artifactId> <version>Hoxton.SR10</version> <type>pom</type> <scope>import</scope> </dependency> <!-- 给spring-cloud-alibaba提供的Nacos、Sentinel、Dubbo等提供版本锁定 --> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-alibaba-dependencies</artifactId> <version>2.2.6.RELEASE</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <modules> <module>user-service</module> <module>order-service</module> </modules> </project>
3.搭建子项目
3.1.子项目依赖导入
<?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"> <parent> <artifactId>spring-cloud-demo</artifactId> <groupId>com.zhanggen</groupId> <version>1.0-SNAPSHOT</version> </parent> <modelVersion>4.0.0</modelVersion> <artifactId>order-service</artifactId> <dependencies> <!--在子项中引入spring-boot-starter-web依赖--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <!--在子项中引入nacos依赖--> <dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency> <!--在子项中引入feign基本依赖--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <!--在子项中引入httpclient依赖,添加后feign支持连接池--> <dependency> <groupId>io.github.openfeign</groupId> <artifactId>feign-httpclient</artifactId> </dependency> </dependencies> </project>
3.2.application配置文件
server:
port: 9090
spring:
application:
name: order-service
cloud:
nacos:
discovery:
server-addr: 127.0.0.1:8848
其作用就是帮助我们优雅的实现HTTP请求的发送,解决上面提到的问题。
<!--feign的依赖--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency>
@EnableFeignClients
@MapperScan("com.zhanggen.order.mapper") @SpringBootApplication @EnableDiscoveryClient @EnableFeignClients //开启Feign public class OrderApplication { public static void main(String[] args) { SpringApplication.run(OrderApplication.class, args); }
@Bean @LoadBalanced public RestTemplate restTemplate() { return new RestTemplate(); } }
com.zhanggen.order.mapper
//FeignClient必须和@EnableFeignClients标注的类在同一个包下 //该接口用于向user-service微服务发起远程调用 //Feign底层默认使用HTTP协议发起远程调用 //@FeignClient("user-service")+@GetMapping("/user/{id}")确定服务提供者的URL(http://user-service/user/{id}) @FeignClient("user-service") public interface UserClient { //根据userId查询用户信息 @GetMapping("/user/{id}") User findById(@PathVariable Long id); }
这样,Feign就可以帮助我们发送http请求,无需自己使用RestTemplate来发送了。
//使用Feign发起远程调用 @Autowired private UserClient userClient; public Order findById(Long orderId) { // 1.查询订单 Order order = orderMapper.selectById(orderId); //2.调用user-service服务查询当前订单的用户信息 //String url = "http://127.0.0.1:8081/user/" + order.getUserId(); //使用RestTemplate发起远程调用 //String url = "http://user-service/user/" + order.getUserId(); //User user = restTemplate.getForObject(url, User.class); User user = userClient.findById(order.getUserId()); //3.user封装到order对象 order.setUser(user); // 4.返回 return order; }
四、
作用 | 说明 | |
---|---|---|
feign.Logger.Level | 修改日志级别 | 包含四种不同的级别:NONE(默认)、BASIC、HEADERS、FULL NONE ,不记录。默认选项 BASIC ,仅记录请求方法和URL以及响应状态代码和执行时间。 HEADERS ,记录基本信息以及请求和响应标头。 FULL ,记录请求和响应的标题,正文和元数据。 |
feign.codec.Decoder | 响应结果的解析器 | http远程调用的结果做解析,例如解析json字符串为java对象 |
feign.codec.Encoder | 请求参数编码 | 将请求参数编码,便于通过http请求发送,例如POST请求,将请求参数编码到请求体中 |
feign.Contract | 支持的注解格式 | 默认是SpringMVC的注解 |
feign.Retryer | 失败重试机制 |
feign: client: config: user-service: # 针对某个微服务的配置,也可以针对所有服务,这个位置换成default loggerLevel: FULL # 日志级别
package com.zhanggen.order.config; import feign.Logger; import org.springframework.context.annotation.Bean; public class FeignDefaultConfiguration { @Bean public Logger.Level feignLogLevel(){ return Logger.Level.FULL; // 日志级别为FULL } }
2.1.配置局部
如果我想让以上日志配置在调用某1个服务提供者时生效,在FeigClien接口中修改如下;
@FeignClient(value = "user-service",configuration = FeignDefaultConfiguration.class) public interface UserClient { //根据userId查询用户信息 @GetMapping("/user/{id}") User findById(@PathVariable Long id); }
2.2.配置全局
@EnableFeignClients(defaultConfiguration = FeignDefaultConfiguration.class) public class OrderApplication { public static void main(String[] args) { SpringApplication.run(OrderApplication.class, args); }
五、Feign连接池
-
URLConnection:默认实现,不支持连接池,每次请求都是新建连接
-
Apache HttpClient :支持连接池
-
OKHttp:支持连接池
因此提高Feign的性能主要手段就是使用连接池
<!--httpClient的依赖内置连接池 --> <dependency> <groupId>io.github.openfeign</groupId> <artifactId>feign-httpclient</artifactId> </dependency>
max-connections-per-route:再调用某1个服务提供者时,最多从连接池中拿出50个连接发起远程调用,避免服务雪崩;
feign:
httpclient:
enabled: true # 开启feign对HttpClient的支持
max-connections: 200 # 设置最大的连接数
max-connections-per-route: 50 # 并行接收一个服务的请求数量
1.创建feign-api公共模块
把之前在order-service中编写的UserClient、User、FeignDefaultConfiguration剪切到feign-api项目中
在fein-api模块中添加spring-cloud-starter-openfeign依赖
<dependencies> <!--openfeign依赖--> <dependency> <groupId>org.springframework.cloud</groupId> <artifactId>spring-cloud-starter-openfeign</artifactId> </dependency> <!--mybatis-plus 实体类注解要用到--> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-core</artifactId> <version>3.4.0</version> </dependency> <!--httpclient基于连接池的调用--> <dependency> <groupId>io.github.openfeign</groupId> <artifactId>feign-httpclient</artifactId> </dependency> </dependencies>
2.服务消费者(调用方)
2.1.引入公共模块
服务消费者pom文件引入feign-api公共模块的依赖;
<dependency> <groupId>com.zhanggen</groupId> <artifactId>fein-api</artifactId> <version>1.0-SNAPSHOT</version> </dependency>
2.2.启动类
服务消费者启动类设置@EnableFeignClients注解一定要设置Feign公共模块中的配置类和Feign公共模块的包;
@EnableFeignClients(defaultConfiguration = FeignDefaultConfiguration.class, basePackages = "com.zhanggen.feign") public class OrderApplication { public static void main(String[] args) { SpringApplication.run(OrderApplication.class, args); } }
服务消费者配置文件
如果遇到调用超时问题,可以在服务消费者的配置文件中设置一下超时时间
feign:
client:
config:
default:
#不设置connectTimeout会导致readTimeout设置不生效
connectTimeout: 3000
readTimeout: 6000
2.3.Feign常见报错
Did you forget to include spring-cloud-starter-netflix-ribbon?
<dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId> </dependency>
3.服务提供者(被调用方)
创建1个client包专门对内面向Feign远程调用,而controller包专门对外面向前端调用;