目录
7.1.1.spring-cloud-starter-openfeign
7.1.2.spring-cloud-starter-loadbalancer
1.引言:
在进行项目的微服务拆分中,不同的模块功能被拆分成了许多的微服务项目,服务拆分之后,不可避免的会出现跨微服务的业务,比如在业务中查询购物车业务模块时需要查询商品信息业务模块,而商品信息不在当前服务 ,此时微服务之间就需要进行远程调用。
2.服务调用
微服务之间的远程调用被称为RPC,即远程过程调用。RPC的实现方式有很多,比如:
-
基于Http协议
-
基于Dubbo协议
使用的是Http方式,这种方式不关心服务提供者的具体技术实现,只要对外暴露Http接口即可,更符合微服务的需要。
Java发送http请求可以使用Spring提供的RestTemplate
在Spring框架中,RestTemplate默认使用Java的标准SimpleClientHttpRequestFactory作为底层的HTTP客户端实现。这个HTTP客户端实现是基于Java标准库中的HttpURLConnection来进行HTTP请求和响应的处理。RestTemplate来使用其他的HTTP客户端,比如Apache HttpClient或OkHttp等。这些客户端通常提供了更多的功能和配置选项,以满足特定的需求
RestTemplate可以方便的实现Http请求的发送,使用的基本步骤如下:
注册RestTemplate到Spring容器
调用RestTemplate的API发送请求,常见方法有:
getForObject:发送Get请求并返回指定类型对象
PostForObject:发送Post请求并返回指定类型对象
put:发送PUT请求
delete:发送Delete请求
exchange:发送任意类型请求,返回ResponseEntity
不过这种手动发送Http请求的方式存在一些问题。
试想一下,假如商品微服务被调用较多,为了应对更高的并发,我们进行了多实例部署,如图:
此时,每个item-service
的实例其IP或端口不同,问题来了:
item-service这么多实例,cart-service如何知道每一个实例的地址?
http请求要写url地址,
cart-service
服务到底该调用哪个实例呢?如果在运行过程中,某一个
item-service
实例宕机,cart-service
依然在调用该怎么办?如果并发太高,
item-service
临时多部署了N台实例,cart-service
如何知道新实例的地址?
为了解决上述问题,就必须引入注册中心的概念了,接下来我们就一起来分析下注册中心的原理。
3.注册中心原理
在微服务远程调用的过程中,包括两个角色:
-
服务提供者:提供接口供其它微服务访问,比如上面图片中
item-service(购物模块)
-
服务消费者:调用其它微服务提供的接口,比如上面图片中
cart-service(商品模块)
在大型微服务项目中,服务提供者的数量会非常多,为了管理这些服务就引入了注册中心的概念。注册中心、服务提供者、服务消费者三者间关系如下:
流程如下:
-
服务启动时就会注册自己的服务信息(服务名、IP、端口)到注册中心
-
调用者可以从注册中心订阅想要的服务,获取服务对应的实例列表(1个服务可能多实例部署)
-
调用者自己对实例列表负载均衡,挑选一个实例
-
调用者向该实例发起远程调用
当服务提供者的实例宕机或者启动新实例时,调用者如何得知呢?
-
服务提供者会定期向注册中心发送请求,报告自己的健康状态(心跳请求)
-
当注册中心长时间收不到提供者的心跳时,会认为该实例宕机,将其从服务的实例列表中剔除
-
当服务有新实例启动时,会发送注册服务请求,其信息会被记录在注册中心的服务实例列表
-
当注册中心服务列表变更时,会主动通知微服务,更新本地服务列表
简单理解就是
3.1. 服务注册者(Service Registrar)
例子:想象一个大型商场(微服务架构系统)中的商家(服务)。每个商家在开业前(服务启动时),都需要到商场的管理中心(注册中心)进行登记,告知自己的店铺名称(服务名称)、位置(IP地址和端口号)、营业时间(服务状态)等信息。这个登记的过程就是服务的注册。
作用:服务注册者的主要作用是将自己的信息(如服务名称、地址、端口等)注册到注册中心,以便其他服务(如服务消费者)能够找到并与之通信。通过注册,服务注册者成为了微服务架构中可被发现和调用的实体。
3.2. 服务消费者(Service Consumer)
例子:还是在这个大型商场中,顾客(服务消费者)想要去某家特定的餐厅(服务)用餐。他们不会直接去找餐厅的位置,而是会先查看商场的导览图(注册中心)或询问商场工作人员(服务发现机制),以获取餐厅的详细信息(如楼层、位置等)。
作用:服务消费者的主要作用是通过注册中心查找并调用所需的服务。在微服务架构中,服务消费者不需要知道服务提供者的具体地址,只需通过注册中心获取服务实例的列表,然后根据一定的策略(如负载均衡)选择一个实例进行通信。
3.3 注册中心(Service Registry)
例子:商场的管理中心(注册中心)负责维护所有商家的登记信息,并提供查询服务。当顾客需要找到某家餐厅时,管理中心会根据顾客的需求提供餐厅的详细信息。同时,管理中心还会定期检查商家的营业状态(健康检查),确保顾客能够找到并顺利使用服务。
作用:注册中心是微服务架构中的核心组件,它充当了服务注册者和服务消费者之间的中介。注册中心负责接收服务注册者的注册信息,并维护这些信息的准确性和实时性。同时,它还向服务消费者提供服务发现功能,帮助服务消费者找到并调用所需的服务。此外,注册中心还提供了健康检查、负载均衡等高级功能,以确保微服务架构的稳定性和可靠性。
4.Nacos注册中心
目前开源的注册中心框架有很多,国内比较常见的有:
-
Eureka:Netflix公司出品,目前被集成在SpringCloud当中,一般用于Java应用
-
Nacos:Alibaba公司出品,目前被集成在SpringCloudAlibaba中,一般用于Java应用
-
Consul:HashiCorp公司出品,目前集成在SpringCloud中,不限制微服务语言
以上几种注册中心都遵循SpringCloud中的API规范,因此在业务开发使用上没有太大差异。由于Nacos是国内产品,中文文档比较丰富,而且同时具备配置管理功能(配置共享,配置热更新,搭配网关gateway
的动态路由都是比较重要的),因此在国内使用较多。
我们在虚拟机上通过Dacker来部署Nacos的注册中心。完成后启动页面如下
5.服务注册
5.1.添加依赖
在item-service
的pom.xml
中添加依赖:
<!--nacos 服务注册发现-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
5.2.配置Nacos
spring:
application:
name: item-service # 服务名称
cloud:
nacos:
server-addr: 192.168.xxx.xxx:8848 # nacos地址 修改为自己虚拟机地址
5.3.启动服务实例
访问nacos控制台,可以发现服务注册成功:
后续就可以通过注册中心来远程调用需要的端口了下面就是在cart-servic项目模块中来调用item-servic
6.服务发现
服务的消费者要去nacos订阅服务,这个过程就是服务发现,步骤如下:
引入依赖
配置Nacos地址
发现并调用服务
6.1.引入依赖
服务发现除了要引入nacos依赖以外,由于还需要负载均衡,因此要引入SpringCloud提供的LoadBalancer依赖。
我们在cart-service
中的pom.xml
中添加下面的依赖:
<!--nacos 服务注册发现-->
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
这里Nacos的依赖于服务注册时一致,这个依赖中同时包含了服务注册和发现的功能。因为任何一个微服务都可以调用别人,也可以被别人调用,即可以是调用者,也可以是提供者。
6.2配置Nacos地址
在cart-service
的application.yml
中添加nacos地址配置:
spring:
cloud:
nacos:
server-addr: 192.168.xxx.xxx:8848
6.3.发现并调用服务
接下来,服务调用者cart-service
就可以去订阅item-service
服务了。不过item-service有多个实例,而真正发起调用时只需要知道一个实例的地址。我们可以利用RestTemplate实现了服务的远程调用。但是远程调用的代码太复杂了这里我们直接使用OpenFeign
让远程调用像本地方法调用一样简单。而这就要用到OpenFeign组件了。
其实远程调用的关键点就在于四个:
-
请求方式
-
请求路径
-
请求参数
-
返回值类型
所以,OpenFeign就利用SpringMVC的相关注解来声明上述4个参数,然后基于动态代理帮我们生成远程调用的代码,而无需我们手动再编写,非常方便。
7.OpenFeign
7.1.引入依赖
在cart-service
服务的pom.xml中引入OpenFeign
的依赖和loadBalancer
依赖:
<!--openFeign-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<!--负载均衡器-->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-loadbalancer</artifactId>
</dependency>
7.1.1.
spring-cloud-starter-openfeign
作用:
spring-cloud-starter-openfeign
是Spring Cloud对Netflix的Feign客户端的封装,旨在简化微服务之间的HTTP调用。通过定义服务接口并使用注解的方式,可以轻松地调用其他服务提供的接口,就像调用本地方法一样。- 它集成了Ribbon(在Spring Cloud 2020.0及之前版本)或Spring Cloud LoadBalancer(在Spring Cloud 2020.x及之后版本)作为客户端负载均衡器,以支持服务的负载均衡。
- 提供了声明式的Web服务客户端,让编写Web服务客户端变得更加简单。
- 支持多种注解,如
@FeignClient
用于指定调用的服务名,@RequestMapping
、@GetMapping
等用于定义具体的HTTP请求信息。优点:
- 简化了微服务之间的调用。
- 支持多种HTTP请求方式。
- 提供了负载均衡的支持。
- 易于集成和使用。
7.1.2.spring-cloud-starter-loadbalancer
作用:
- 从Spring Cloud 2020.0开始,
spring-cloud-starter-loadbalancer
替代了Ribbon作为客户端负载均衡的实现。这是因为Ribbon已经进入了维护模式,并且不再接收新功能。- 它提供了基于Spring Framework 5的
RestTemplate
和WebClient
的客户端负载均衡支持。spring-cloud-starter-loadbalancer
与Spring Cloud Gateway的负载均衡器集成,提供了更灵活的负载均衡策略。优点:
- 提供了与Spring Framework 5更好的集成。
- 支持
RestTemplate
和WebClient
,使得负载均衡的配置更加灵活。- 提供了与Spring Cloud Gateway等项目的无缝集成。
- 作为Ribbon的替代者,拥有更好的维护和未来支持。
7.2.启用OpenFeign
启动类上添加注解,启动OpenFeign功能:
为了接口的重利用也可以下定义一个新的module用来放这些抽取出来的公共接口,这样子避免重复编码,然后需要指定扫描公共所在的包即可
7.3.编写OpenFeign客户端
定义一个新的接口,编写Feign客户端:
其中代码如下:
package com.hmall.api.client;
import com.hmall.api.dto.ItemDTO;
import com.hmall.api.dto.OrderDetailDTO;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.*;
import java.util.Collection;
import java.util.List;
@FeignClient("item-service")
public interface ItemClient {
@GetMapping("/items")
List<ItemDTO> queryItemByIds(@RequestParam("ids") Collection<Long> ids);
}
这里只需要声明接口,无需实现方法。接口中的几个关键信息:
@FeignClient("item-service")
:声明服务名称
@GetMapping
:声明请求方式
@GetMapping("/items")
:声明请求路径
@RequestParam("ids") Collection<Long> ids
:声明请求参数
List<ItemDTO>
:返回值类型
有了上述信息,OpenFeign就可以利用动态代理帮我们实现这个方法,并且向http://item-service/items
发送一个GET
请求,携带ids为请求参数,并自动将返回值处理为List<ItemDTO>
。
我们只需要直接调用这个方法,即可实现远程调用了。item-service 就是我们模块的服务名字,根据服务名字就会从我们的注册中心中找到对应模块的端口,这样我们就可以实现远程调用了
7.4.使用FeignClient
直接调用ItemClient
的方法
feign替我们完成了服务拉取、负载均衡、发送http请求的所有工作,是不是看起来优雅多了。
而且,这里我们不再需要RestTemplate了,还省去了RestTemplate的注册。
8.连接池
Feign底层发起http请求,依赖于其它的框架。其底层支持的http客户端实现包括:
-
HttpURLConnection:默认实现,不支持连接池
-
Apache HttpClient :支持连接池
-
OKHttp:支持连接池
因此我们通常会使用带有连接池的客户端来代替默认的HttpURLConnection。比如,我们使用OK Http.
8.1.连接池的好处
性能提升:连接池通过重用现有的连接来减少创建和销毁连接的开销,这可以显著提高应用程序的性能,特别是在高并发场景下。
资源优化:频繁地创建和销毁连接会消耗大量的系统资源,如内存和CPU。使用连接池可以优化这些资源的使用,减少资源浪费。
响应时间缩短:由于连接池中的连接已经建立并准备好发送请求,因此可以更快地发送请求并接收响应,从而缩短应用程序的响应时间。
8.2.OKHttp的优势
除了支持连接池之外,OKHttp还有其他一些优势,这些优势也是选择它作为Feign底层HTTP客户端的原因之一:
现代和高效:OKHttp是一个现代的HTTP客户端,它使用最新的HTTP协议特性,如HTTP/2和SPDY(尽管现在HTTP/2已经取代了SPDY),以提供更高效的网络通信。
强大的功能:OKHttp提供了许多高级功能,如拦截器(Interceptors)、请求和响应的缓存、透明的GZIP压缩等,这些功能可以帮助开发者更轻松地处理复杂的网络请求。
广泛的社区支持:OKHttp是一个广泛使用的HTTP客户端,拥有庞大的用户社区和丰富的文档资源,这有助于开发者更快地解决问题和获取帮助。
与Retrofit的集成:OKHttp与Retrofit(另一个流行的RESTful API客户端)紧密集成,如果你的项目中已经使用了Retrofit,那么使用OKHttp作为Feign的底层HTTP客户端可能会更加自然和方便。
8.3.引入依赖
<!--OK http 的依赖 -->
<dependency>
<groupId>io.github.openfeign</groupId>
<artifactId>feign-okhttp</artifactId>
</dependency>
8.4.开启连接池
application.yml
配置文件中开启Feign的连接池功能:
feign:
okhttp:
enabled: true # 开启OKHttp功能
Debug方式启动cart-service,请求一次查询我的购物车方法,进入断点:跟踪源码:
底层就会是连接池这种模式,性能就会好很多!!!有时间还是可以多尝试跟踪学习下底层源码的实现,可以深入理解Java是如何在底层实现的。也可以提高我们的问题解决能力:当你在开发过程中遇到问题时,比如性能瓶颈、内存泄漏、难以理解的异常等,直接查看相关框架或库的源码可以帮助你快速定位问题原因。