一.RestTemplate
Spring给我们提供了一个RestTemplate的API,可以方便的实现Http请求的发送。
同步客户端执行HTTP请求,在底层HTTP客户端库(如JDK HttpURLConnection、Apache HttpComponents等)上公开一个简单的模板方法API。RestTemplate通过HTTP方法为常见场景提供了模板,此外还提供了支持不太常见情况的通用交换和执行方法。 RestTemplate通常用作共享组件。然而,它的配置不支持并发修改,因此它的配置通常是在启动时准备的。如果需要,您可以在启动时创建多个不同配置的RestTemplate实例。如果这些实例需要共享HTTP客户端资源,它们可以使用相同的底层ClientHttpRequestFactory。 注意:从5.0开始,这个类处于维护模式,只有对更改和错误的小请求才会被接受。请考虑使用org.springframework.web.react .client. webclient,它有更现代的API,支持同步、异步和流场景。
其中提供了大量的方法,方便我们发送Http请求,例如:
可以看到常见的Get、Post、Put、Delete请求都支持,如果请求参数比较复杂,还可以使用exchange方法来构造请求。
1.1基础配置
在springboot项目中,可以直接注入RestTemplate使用
在启动类配置(启动类也是一个简单的配置类)
@MapperScan("com.hmall.user.mapper") @SpringBootApplication public class UserApplication {public static void main(String[] args) {SpringApplication.run(UserApplication.class, args);}@Beanpublic RestTemplate restTemplate() {return new RestTemplate();} }
使用非常简单,使用 @Autowired进行注入即可
@Autowired private RestTemplate restTemplate;
但是springBoot并不推荐使用这种方式,推荐使用构造函数注入
我们可以使用 @RequiredArgsConstructor 给常量函数生成构造函数
1.2使用方式
//利用RestTemplate发起http请求,得到http的响应 ResponseEntity<List<ItemDTO>> response = restTemplate.exchange(//url路径"http://localhost:8081:items?ids={ids}",//请求方式 是一个枚举类型HttpMethod.GET,// HttpEntit类型 不需要可以个空值null,//返回值类型 以为需要的是集合,所以传递一个参数化类型的引用new ParameterizedTypeReference<List<ItemDTO>>() {},//占位符的参数,使用 CollUtils.join 把集合转换成字符串Map.of("ids", CollUtils.join(itemIds, ",")) );//判断是否成功 if (!response.getStatusCode().is2xxSuccessful()) { return; } //解析响应 List<ItemDTO> items = response.getBody();
以上就是一个简单的远程调用但是以上的操作存在一些问题,当部署多实例时应为以上代码写死的问题,做不到负载均衡。
二、服务注册于发现
为了解决上述问题,就必须引入注册中心的概念了,接下来我们就一起来分析下注册中心的原理
2.1、在微服务远程调用的过程中,包括两个角色:
- 服务提供者:提供接口供其它微服务访问
-
服务消费者:调用其它微服务提供的接口
在大型微服务项目中,服务提供者的数量会非常多,为了管理这些服务就引入了注册中心的概念。注册中心、服务提供者、服务消费者三者间关系如下:
-
服务启动时就会注册自己的服务信息(服务名、IP、端口)到注册中心
-
调用者可以从注册中心订阅想要的服务,获取服务对应的实例列表(1个服务可能多实例部署)
-
调用者自己对实例列表负载均衡,挑选一个实例
-
调用者向该实例发起远程调用
当服务提供者的实例宕机或者启动新实例时,调用者如何得知呢?
-
服务提供者会定期向注册中心发送请求,报告自己的健康状态(心跳请求)
-
当注册中心长时间收不到提供者的心跳时,会认为该实例宕机,将其从服务的实例列表中剔除
-
当服务有新实例启动时,会发送注册服务请求,其信息会被记录在注册中心的服务实例列表
-
当注册中心服务列表变更时,会主动通知微服务,更新本地服务列表
2.2、Nacos注册中心
目前开源的注册中心框架有很多,国内比较常见的有:
-
Eureka:Netflix公司出品,目前被集成在SpringCloud当中,一般用于Java应用
-
Nacos:Alibaba公司出品,目前被集成在SpringCloudAlibaba中,一般用于Java应用
-
Consul:HashiCorp公司出品,目前集成在SpringCloud中,不限制微服务语言
以上几种注册中心都遵循SpringCloud中的API规范,因此在业务开发使用上没有太大差异。由于Nacos是国内产品,中文文档比较丰富,而且同时具备配置管理功能我使用的是Nacos
我们基于Docker来部署Nacos的注册中心,首先我们要准备MySQL数据库表,用来存储Nacos的数据。
下方提供了nacos的sql语句
随后打开虚拟机在/root目录下创建 nacos/custom.env
#进入 /root目录
cd /root
#在/root目录nacos/custom.env
vim /nacos/custom.env
复制下方命令
PREFER_HOST_MODE=hostname
MODE=standalone
SPRING_DATASOURCE_PLATFORM=mysql
MYSQL_SERVICE_HOST=192.168.192.132 #自己虚拟机ip地址
MYSQL_SERVICE_DB_NAME=nacos
MYSQL_SERVICE_PORT=3307 #msyql 的端口号
MYSQL_SERVICE_USER=root #mysql 用户名
MYSQL_SERVICE_PASSWORD=root #mysql 用户密码
MYSQL_SERVICE_DB_PARAM=characterEncoding=utf8&connectTimeout=1000&socketTimeout=3000&autoReconnect=true&useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=Asia/Shanghai
拉取docker镜像
docker pull nacos/nacos-server
进入root目录,然后执行下面的docker命令:
docker run -d \
--name nacos \
--env-file ./nacos/custom.env \
-p 8848:8848 \
-p 9848:9848 \
-p 9849:9849 \
--restart=always \
nacos/nacos-server
启动完成后,访问: http://192.168.192.132:8848/nacos/, 将192.168.150.101
替换为你自己的虚拟机IP地址。
注意密码和用户名都是 :nacos
2.3、服务注册
- 引入依赖
- 配置Nacos地址
2.3.1、添加依赖
在pom.xml中添加依赖
<!--nacos 服务注册发现-->
<dependency> <groupId>com.alibaba.cloud</groupId> <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
2.3.2.配置Nacos
在application.yml
中添加nacos地址配置
spring:application:name: item-service # 服务名称cloud:nacos:server-addr: 192.168.150.101:8848 # nacos地址
访问nacos控制台,可以发现服务注册成功:
2.4.服务发现
服务的消费者要去nacos订阅服务,这个过程就是服务发现,步骤如下:
- 引入依赖
- 配置Nacos地址
- 发现并调用服务
2.4.1.引入依赖
服务发现除了要引入nacos依赖以外,由于还需要负载均衡,因此要引入SpringCloud提供的LoadBalancer依赖。
在pom.xml
中添加下面的依赖:
<!--nacos 服务注册发现-->
<dependency><groupId>com.alibaba.cloud</groupId><artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
2.4.2.配置Nacos地址
application.yml
中添加nacos地址配置:
spring:cloud:nacos:server-addr: 192.168.150.101:8848
2.5.发现并调用服务
接下来,服务调用者cart-service
就可以去订阅item-service
服务了。不过item-service有多个实例,而真正发起调用时只需要知道一个实例的地址。
因此,服务调用者必须利用负载均衡的算法,从多个实例中挑选一个去访问。常见的负载均衡算法有:
-
随机
-
轮询
-
IP的hash
-
最近最少访问
-
...
这里我们可以选择最简单的随机负载均衡。
另外,服务发现需要用到一个工具,DiscoveryClient,SpringCloud已经帮我们自动装配,我们可以直接注入使用:
接下来,我们就可以对原来的远程调用做修改了,之前调用时我们需要写死服务提供者的IP和端口:
三、 OpenFeign
我们利用Nacos实现了服务的治理,利用RestTemplate实现了服务的远程调用。但是远程调用的代码太复杂了。因此,我们必须想办法改变远程调用的开发模式,让远程调用像本地方法调用一样简单。而这就要用到OpenFeign组件了。
其实远程调用的关键点就在于四个:
-
请求方式
-
请求路径
-
请求参数
-
返回值类型
所以,OpenFeign就利用SpringMVC的相关注解来声明上述4个参数,然后基于动态代理帮我们生成远程调用的代码,而无需我们手动再编写,非常方便。
3.1、引入依赖
<!--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>
3.2.启用OpenFeign
我们在启动类上添加注解,启动OpenFeign功能:
3.3.编写OpenFeign客户端
定义一个新的接口,编写Feign客户端。其中代码如下:
这里只需要声明接口,无需实现方法。接口中的几个关键信息:
-
@FeignClient("item-service")
:声明服务名称 -
@GetMapping
:声明请求方式 -
@GetMapping("/items")
:声明请求路径 -
@RequestParam("ids") Collection<Long> ids
:声明请求参数 -
List<ItemDTO>
:返回值类型
有了上述信息,OpenFeign就可以利用动态代理帮我们实现这个方法
3.4.连接池
Feign底层发起http请求,依赖于其它的框架。其底层支持的http客户端实现包括:
-
HttpURLConnection:默认实现,不支持连接池
-
Apache HttpClient :支持连接池
-
OKHttp:支持连接池
因此我们通常会使用带有连接池的客户端来代替默认的HttpURLConnection。比如,我们使用OK Http.
4.2.1.引入依赖
在cart-service
的pom.xml
中引入依赖:
<!--OK http 的依赖 -->
<dependency><groupId>io.github.openfeign</groupId><artifactId>feign-okhttp</artifactId>
</dependency>
4.2.2.开启连接池
在cart-service
的application.yml
配置文件中开启Feign的连接池功能:
feign:okhttp:enabled: true # 开启OKHttp功能
feign: okhttp: enabled: true # 开启OKHttp功能
重启服务,连接池就生效了。