RestTemplate Nacos OpenFeign初学

黑马教学地址

‬​‬​‌‌​​⁠‍⁠​‬​​‬‍​‌​‬‬‌​​⁠⁠‍⁠‍​‌​​‍​​‍​​​​⁠‬day03-微服务01 - 飞书云文档 (feishu.cn)

RestTemplate 

一  问题引入

问题一:

在拆分黑马商城项目的时候,我们发现一个问题:就是cart-service (购物车业务)中需要查询商品信息,但商品信息查询的逻辑全部迁移到了item-service(商品服务)。由于这是两个model,所以导致我们无法查询到商品信息。

就是说,一个微服务无法访问另外一个微服务的信息。

最终结果就是查询到的购物车数据不完整,导致一个微服务内某个功能的信息不完全。

因此要想解决这个问题,我们就必须改造其中的代码,把原本 本地 方法调用,改造成跨微服务的远程调用(RPC,即Remote Produce Call)

因此,现在查询购物车列表的流程变成了这样,要实现一个微服务用远程调用技术来访问另外一个微服务内的内容:

问题二:

问题来了:我们该如何跨服务调用,准确的说,如何在cart-service中获取item-service服务中的提供的商品数据呢?

大家思考一下,我们以前有没有实现过类似的远程查询的功能呢?

答案是肯定的,我们前端向服务端查询数据,其实就是从浏览器远程查询服务端数据。比如我们刚才通过Swagger测试商品查询接口,就是向http://localhost:8081/items这个接口发起的请求:

而这种查询就是通过http请求的方式来完成的,不仅仅可以实现远程查询,还可以实现新增、删除等各种远程请求。

假如我们在cart-service中能模拟浏览器,发送http请求到item-service,是不是就实现了跨微服务的远程调用了呢?

那么:我们该如何用Java代码发送Http的请求呢?

答案就是:  RestTemplate!!!

二  RestTemplate 介绍

Spring给我们提供了一个RestTemplate的API,可以方便的实现Http请求的发送。

org.springframework.web.client public class RestTemplate

extends InterceptingHttpAccessor

implements RestOperations

----------------------------------------------------------------------------------------------------------------

同步客户端执行HTTP请求,在底层HTTP客户端库(如JDK HttpURLConnection、Apache HttpComponents等)上公开一个简单的模板方法API。RestTemplate通过HTTP方法为常见场景提供了模板,此外还提供了支持不太常见情况的通用交换和执行方法。 RestTemplate通常用作共享组件。然而,它的配置不支持并发修改,因此它的配置通常是在启动时准备的。如果需要,您可以在启动时创建多个不同配置的RestTemplate实例。如果这些实例需要共享HTTP客户端资源,它们可以使用相同的底层ClientHttpRequestFactory。 注意:从5.0开始,这个类处于维护模式,只有对更改和错误的小请求才会被接受。请考虑使用org.springframework.web.react .client. webclient,它有更现代的API,支持同步、异步和流场景。

----------------------------------------------------------------------------------------------------------------

自: 3.0 参见: HttpMessageConverter, RequestCallback, ResponseExtractor, ResponseErrorHandler

其中提供了大量的方法,方便我们发送Http请求,例如:

  

可以看到常见的Get、Post、Put、Delete请求都支持,如果请求参数比较复杂,还可以使用exchange方法来构造请求。

三  实战

我们在cart-service服务中定义一个配置类:

先将RestTemplate注册为一个Bean:

package com.hmall.cart.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

@Configuration
public class RemoteCallConfig {

    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

使用restTemplate,里面的参数都是场景需要,要自己按需求更改的

temIds);
    // 2.1.利用RestTemplate发起http请求,得到http的响应
    ResponseEntity<List<ItemDTO>> response = restTemplate.exchange(
            "http://localhost:8081/items?ids={ids}",
            HttpMethod.GET,
            null,
            new ParameterizedTypeReference<List<ItemDTO>>() {
            },
            Map.of("ids", CollUtil.join(itemIds, ","))
    );

可以看到,利用RestTemplate发送http请求与前端ajax发送请求非常相似,都包含四部分信息:

  • ① 请求方式

  • ② 请求路径

  • ③ 请求参数

  • ④ 返回值类型

四  总结

服务拆分之后,不可避免的会出现跨微服务的业务,此时微服务之间就需要进行远程调用。微服务之间的远程调用被称为RPC,即远程过程调用。RPC的实现方式有很多,比如:

  • 基于Http协议

  • 基于Dubbo协议

我们课堂中使用的是Http方式,这种方式不关心服务提供者的具体技术实现,只要对外暴露Http接口即可,更符合微服务的需要。

Java发送http请求可以使用Spring提供的RestTemplate,使用的基本步骤如下:

  • 注册RestTemplate到Spring容器

  • 调用RestTemplate的API发送请求,常见方法有:

    • getForObject:发送Get请求并返回指定类型对象

    • PostForObject:发送Post请求并返回指定类型对象

    • put:发送PUT请求

    • delete:发送Delete请求

    • exchange:发送任意类型请求,返回ResponseEntity

Nacos 

一  问题引入:

利用RestTemplate通过Http请求实现了跨微服务的远程调用存在一些问题。

试想一下,假如商品微服务被调用较多,为了应对更高的并发,我们进行了多实例部署

此时,每个item-service的实例其IP或端口不同,问题来了:

  • item-service这么多实例,cart-service如何知道每一个实例的地址?

  • http请求要写url地址,cart-service服务到底该调用哪个实例呢?

  • 如果在运行过程中,某一个item-service实例宕机,cart-service依然在调用该怎么办?

  • 如果并发太高,item-service临时多部署了N台实例,cart-service如何知道新实例的地址?

为了解决上述问题,就必须引入注册中心的概念了,接下来我们就一起来分析下注册中心的原理。

二  注册中心原理

在微服务远程调用的过程中,包括两个角色:

  • 服务提供者:提供接口供其它微服务访问,比如item-service

  • 服务消费者:调用其它微服务提供的接口,比如cart-service

在大型微服务项目中,服务提供者的数量会非常多,为了管理这些服务就引入了注册中心的概念。注册中心、服务提供者、服务消费者三者间关系如下:

注册中心流程如下:

  • 服务启动时就会注册自己的服务信息(服务名、IP、端口)到注册中心

  • 调用者可以从注册中心订阅想要的服务,获取服务对应的实例列表(1个服务可能多实例部署)

  • 调用者自己对实例列表负载均衡,挑选一个实例

  • 调用者向该实例发起远程调用

当服务提供者的实例宕机或者启动新实例时,调用者如何得知呢?

  • 服务提供者会定期向注册中心发送请求,报告自己的健康状态(心跳请求)

  • 当注册中心长时间收不到提供者的心跳时,会认为该实例宕机,将其从服务的实例列表中剔除

  • 当服务有新实例启动时,会发送注册服务请求,其信息会被记录在注册中心的服务实例列表

  • 当注册中心服务列表变更时,会主动通知微服务,更新本地服务列表

我们使用的注册中心是nacos

目前开源的注册中心框架有很多,国内比较常见的有:

  • Eureka:Netflix公司出品,目前被集成在SpringCloud当中,一般用于Java应用

  • Nacos:Alibaba公司出品,目前被集成在SpringCloudAlibaba中,一般用于Java应用

  • Consul:HashiCorp公司出品,目前集成在SpringCloud中,不限制微服务语言

以上几种注册中心都遵循SpringCloud中的API规范,因此在业务开发使用上没有太大差异。由于Nacos是国内产品,中文文档比较丰富,而且同时具备配置管理功能(后面会学习),因此在国内使用较多

三  安装nacos

前置步骤,安装等请看教学文档。

上面将nacos端口号设置为8848。

a few moment later......

启动完成后,访问下面地址:http://192.168.150.101:8848/nacos/,注意将192.168.150.101替换为你自己的虚拟机IP地址。首次访问会跳转到登录页,账号密码都是nacos

登陆成功后的页面

四 快速入门

 服务注册

添加依赖
 <!--nacos 服务注册发现-->
        <dependency>
            <groupId>com.alibaba.cloud</groupId>
            <artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
        </dependency>

配置nacos路径

按道理来说是可以在application.yml下面配置的,但是我这里一直出错。

-Dspring.cloud.nacos.server-addr=192.168.6.100:8848

配置完之后服务会自动注册,无序手动添加。

可以看到nacos上更新了服务

服务发现

服务的消费者要去nacos订阅服务,这个过程就是服务发现,步骤如下:

  • 1 引入依赖

  • 2 配置Nacos地址

  • 3 发现并调用服务

前两部和服务注册一样,添加依赖和配置nacos路径

 发现并调用服务
private final DiscoveryClient discoveryClient;
 
Set<Long> itemIds = vos.stream().map(CartVO::getItemId).collect(Collectors.toSet());
        // 2.0查询商品
        //服务发现与使用
        //2.0.1使用服务名字获取服务实例
        List<ServiceInstance> instances = discoveryClient.getInstances("item-service");
        //2.0.2  负载均衡,挑选一个实例
        ServiceInstance instance = instances.get(RandomUtil.randomInt(instances.size()));
        //2.0.3  获取uri (uri = ip + 端口号)
        URI uri = instance.getUri();
        // 2.1.利用RestTemplate发起http请求,得到http的响应
        ResponseEntity<List<ItemDTO>> response = restTemplate.exchange(
                uri + "/items?ids={ids}",
                HttpMethod.GET,
                null,
                new ParameterizedTypeReference<List<ItemDTO>>() {
                },
                Map.of("ids", CollUtil.join(itemIds, ","))
        );

OpenFeign

一  问题引入

在上面,我们利用Nacos实现了服务的治理,利用RestTemplate实现了服务的远程调用。但是远程调用的代码太复杂了

因此,我们必须想办法改变远程调用的开发模式,让远程调用像本地方法调用一样简单。而这就要用到OpenFeign组件了。

其实远程调用的关键点就在于四个:

  • 请求方式

  • 请求路径

  • 请求参数

  • 返回值类型

所以,OpenFeign就利用SpringMVC的相关注解来声明上述4个参数,然后基于动态代理帮我们生成远程调用的代码,而无需我们手动再编写,非常方便。

二  快速入门

在原来nacos服务发现与调用和restTemplate发送请求来实现跨微服务请求资料的过程中,可以总结为以下四点

openFeign会一一对应来实现

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>

2  开启openFeign

在启动类上加  @EnableFeignClients  注解

package com.hmall.cart;


import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Bean;
import org.springframework.web.client.RestTemplate;


@EnableFeignClients
@MapperScan("com.hmall.cart.mapper")
@SpringBootApplication
public class HMallCartApplication {
    public static void main(String[] args) {
        SpringApplication.run(HMallCartApplication.class, args);
    }

    //做http请求
    @Bean
    public RestTemplate createRestTemplate(){
        return new RestTemplate();
    }
}

3  设置openFeign配置接口

package com.hmall.cart.cliect;

import com.hmall.cart.domain.dto.ItemDTO;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;

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> :返回值类型

这里可以看到一一对应上面的,繁琐的代码

private final ItemClient itemClient;


private void handleCartItems(List<CartVO> vos) {
        // TODO 1.获取商品id
        Set<Long> itemIds = vos.stream().map(CartVO::getItemId).collect(Collectors.toSet());
//        // 2.0查询商品
//        //服务发现与使用
//        //2.0.1使用服务名字获取服务实例
//        List<ServiceInstance> instances = discoveryClient.getInstances("item-service");
//        //2.0.2  负载均衡,挑选一个实例
//        ServiceInstance instance = instances.get(RandomUtil.randomInt(instances.size()));
//        //2.0.3  获取uri (uri = ip + 端口号)
//        URI uri = instance.getUri();
//        // 2.1.利用RestTemplate发起http请求,得到http的响应
//        ResponseEntity<List<ItemDTO>> response = restTemplate.exchange(
//                uri + "/items?ids={ids}",
//                HttpMethod.GET,
//                null,
//                new ParameterizedTypeReference<List<ItemDTO>>() {
//                },
//                Map.of("ids", CollUtil.join(itemIds, ","))
//        );
//        // 2.2.解析响应
//        if(!response.getStatusCode().is2xxSuccessful()){
//            // 查询失败,直接结束
//            return;
//        }
//        List<ItemDTO> items = response.getBody();

        //调用接口
        List<ItemDTO> items = itemClient.queryItemByIds(itemIds);


        if (CollUtils.isEmpty(items)) {
            return;
        }
        // 3.转为 id 到 item的map
        Map<Long, ItemDTO> itemMap = items.stream().collect(Collectors.toMap(ItemDTO::getId, Function.identity()));
        // 4.写入vo
        for (CartVO v : vos) {
            ItemDTO item = itemMap.get(v.getItemId());
            if (item == null) {
                continue;
            }
            v.setNewPrice(item.getPrice());
            v.setStatus(item.getStatus());
            v.setStock(item.getStock());
        }
    }

三  连接池

Feign底层发起http请求,依赖于其它的框架。其底层支持的http客户端实现包括:

  • HttpURLConnection:默认实现,不支持连接池

  • Apache HttpClient :支持连接池

  • OKHttp:支持连接池

默认底层不支持连接池,每一次调用方法都会创建一次HttpURLConnection,效率低下。

因此我们通常会使用带有连接池的客户端来代替默认的HttpURLConnection。比如,我们使用OK Http.

引入依赖

<!--OK http 的依赖 -->
<dependency>
  <groupId>io.github.openfeign</groupId>
  <artifactId>feign-okhttp</artifactId>
</dependency>

开启连接池

feign:
  okhttp:
    enabled: true # 开启OKHttp功能

四  最佳实践

由于我们每一个微服务都要去远程调用,就是说我们每一个微服务都要写上面相关的client和引入与本微服务无关的一些类,麻烦而且耦合度上升,我们需要想方案来优化这些东西。

相信大家都能想到,避免重复编码的办法就是抽取。不过这里有两种抽取思路:

  • 思路1:抽取到微服务之外的公共module

  • 思路2:每个微服务自己抽取一个module

方案1抽取更加简单,工程结构也比较清晰,但缺点是整个项目耦合度偏高。

方案2抽取相对麻烦,工程结构相对更复杂,但服务之间耦合度降低。

因此这里我们采用方案1来演示

演示部分

我们已抽取cart-service的内容为例

此微服务引入了client, dto, 和相关的依赖

注意!!!

1  将本模块的信息给cart-service添加依赖

2  由于client-service启动类扫描不到我们在外面的api接口,我们要手动加上扫描,在启动feign注解后面加上我们client的包路径

五  日志

OpenFeign只会在FeignClient所在包的日志级别为DEBUG时,才会输出日志。而且其日志级别有4级:

  • NONE:不记录任何日志信息,这是默认值。

  • BASIC:仅记录请求的方法,URL以及响应状态码和执行时间

  • HEADERS:在BASIC的基础上,额外记录了请求和响应的头信息

  • FULL:记录所有请求和响应的明细,包括头信息、请求体、元数据。

Feign默认的日志级别就是NONE,所以默认我们看不到请求日志

如果我们想设置日志级别,需要自己手动配置

先保证日志输出的环境是debug环境,我这里已经是debug环境了

步骤一   在hm-api模块下新建一个配置类,定义Feign的日志级别:

步骤二  要让日志级别生效,还需要配置这个类。

有两种方式:

  • 局部生效:在某个FeignClient中配置,只对当前FeignClient生效

@FeignClient(value = "item-service", configuration = DefaultFeignConfig.class)
  • 全局生效:在@EnableFeignClients中配置,针对所有FeignClient生效。

@EnableFeignClients(defaultConfiguration = DefaultFeignConfig.class)

  • 28
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
NacosOpenFeign、Gateway和Sentinel都是微服务架构中常用的开源工具。 Nacos(Naming and Configuration Service)是一个动态服务发现、配置管理和服务治理平台。它提供了服务注册和发现、配置管理和动态路由等功能,使得微服务架构中的各个服务能够自动注册并通过服务名进行发现。通过Nacos,我们可以方便地进行服务的注册与发现,以及实时更新配置信息。 OpenFeign是一个基于Java的声明式服务调用客户端。它内置了Ribbon和Hystrix,可以自动处理服务的负载均衡和容错。通过使用注解和接口代理,OpenFeign可以简化服务间的调用,在代码中只需要定义接口和方法的声明,而无需手动实现具体的服务调用逻辑。 Gateway是一个高性能的API网关,用于将外部客户端的请求路由到后端的不同服务。通过配置路由规则和过滤器链,Gateway可以完成请求的转发并进行相应的处理。它具有动态路由、请求限流、熔断降级、安全认证等功能,可以提高系统的可靠性和稳定性。 Sentinel是一个流量控制和流量治理框架,用于实现对微服务架构中各个服务的流量控制和实时监控。它提供了实时的监控、熔断降级、系统保护和流量统计等功能,并且具备高度可扩展性。通过使用Sentinel,我们可以对服务的流量进行实时监控和管理,保证系统的稳定性和可靠性。 综上所述,Nacos提供了服务发现和配置管理,OpenFeign简化了微服务间的调用,Gateway实现了高性能的API网关,Sentinel用于流量控制和治理。这些工具的结合使用可以帮助我们构建可靠、稳定的微服务架构,并提升系统的性能和可扩展性。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值