微服务——远程调用

为什么需要远程调用?

        在微服务架构中,每个服务都是独立部署和运行的,它们之间需要相互协作以完成复杂的业务逻辑。因此,远程调用成为微服务之间通信的主要方式。通过远程调用,一个服务可以请求另一个服务执行某些操作或获取所需数据,从而实现服务的解耦和复用。

        例如:在商城项目拆分中购物车业务中需要查询商品信息,但商品信息查询的逻辑全部迁移到了item-service服务,导致我们无法查询。最终结果就是查询到的购物车数据不完整,因此要想解决这个问题,我们就必须改造其中的代码,把原本本地方法调用,改造成跨微服务的远程调用(RPC,即Remote Produce Call)。

查询购物车列表的流程图:

远程调用的方式

微服务远程调用的方式多种多样,主要包括以下几种:

  1. HTTP/REST
    • 使用HTTP协议进行同步调用,通常使用JSON或XML作为数据交换格式。
    • 优点:简单、通用性强、可跨语言、跨平台。
    • 缺点:消息封装较为臃肿,对于大量数据或频繁调用的场景可能不是最优选择。
  2. RPC(远程过程调用)
    • 允许像调用本地方法一样调用远程服务的方法。
    • 优点:通信速度快、效率高、可自定义数据格式。
    • 缺点:限制于开发语言环境,跨语言和跨平台能力较弱。
  3. 消息队列
    • 通过消息队列进行消息传递,实现服务之间的解耦。
    • 优点:高可用性、高可扩展性、低耦合性。
    • 缺点:实时性较弱,可能不适用于所有场景。
  4. 事件总线
    • 发布/订阅模型,通过事件进行通信。
    • 优点:解耦程度高、灵活性好。
    • 缺点:实现复杂度较高,需要维护事件的定义和订阅关系。

远程调用的实现框架

在微服务架构中,有多种框架支持远程调用,以下是一些常见的实现框架:

  1. RestTemplate
    • Spring 提供的用于访问 Rest 服务的客户端。
    • 提供了多种便捷访问远程 HTTP 服务的方法。
    • 示例代码:通过配置类注入 RestTemplate Bean,并在 Controller 中使用它发送 HTTP 请求。
  2. Feign
    • Netflix 开发的声明式、模板化的 Http 客户端。
    • 通过处理注解相关信息生成 Request,并对调用返回的数据进行解码。
    • OpenFeign 是 Spring Cloud 在 Feign 的基础上增加了对 SpringMVC 注解的支持。
    • 示例代码:定义 Feign 客户端接口,使用 @FeignClient 注解指定要调用的服务,并在接口方法上使用 SpringMVC 注解定义请求路径和参数。
  3. gRPC
    • Google 开源的高性能、跨语言的 RPC 框架。
    • 使用 Protocol Buffers 作为接口描述语言。
    • 适用于对性能要求较高的场景。
  4. Dubbo
    • 阿里巴巴开源的 Java RPC 框架。
    • 支持多种序列化方式,适用于 Java 微服务架构。

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

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

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

先将RestTemplate注册为一个Bean:(首先需要去声明request method)

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();
    }
}

接下来,我们修改service中的服务实现类中方法,发送http请求到service

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

  • ① 请求方式(master枚举:get、post等)

  • ② 请求路径(请求的URL路径里面包含的首先就是请求的IP,然后是请求端口,最后才是真正的资源路径)

  • ③ 请求参数(查询默认情况下服务端返回都是JASON格式的,但是Rest template可以帮你转成你想要的java类型。比如查到的是个user,那就可以写成user.class。它就会返回一个user,会把你查到的JASON的字符串反序列化)

  • ④ 返回值类型(请求中如果有参数,比如说有路径占位符参数,可以使用map指定这个参数
    map里面要指定一个key和value,key要跟占位符一致,value就是你的具体的参数)

在实现类中使用Rest template

最常规的方法是autowear注入

        但是这种方式spring是不推荐的,它推荐采用构造函数注入,写一个构造函数spring也能帮你做自动注入。

但是假如成员变量很多,那构造函数的列表就会非常长。所以使用lombok可以帮助我们去自动生成构造函数。

什么是lombok库?

        Lombok是一个Java库,它可以帮助开发者自动生成getter和setter方法、构造函数、equals、hashCode和toString方法等,从而减少样板代码,提高开发效率。

  1. @AllArgsConstructor:这个注解会为类生成一个包含所有参数的构造函数。如果不希望某个变量成为构造函数的一部分,那么不应该使用这个注解。

  2. @RequiredArgsConstructor:这个注解会为需要特殊处理的字段(如final字段和被@NonNull注解的字段)生成一个构造函数。这样,只有这些“必需”的字段才会被包含在构造函数中。

  3. final字段:如果将某个字段声明为final,那么它必须在声明时或在构造函数中进行初始化。使用@RequiredArgsConstructor注解时,Lombok会自动为这些final字段生成一个构造函数,以确保它们被正确初始化。

  4. 手动初始化:如果已经在字段声明时对其进行了初始化,那么即使使用了@AllArgsConstructor@RequiredArgsConstructor注解,该字段也不会被包含在自动生成的构造函数中,因为它已经被初始化了。

实现类的方法的完整代码如下:

private void handleCartItems(List<CartVO> vos) {
    // TODO 1.获取商品id
    Set<Long> itemIds = vos.stream().map(CartVO::getItemId).collect(Collectors.toSet());
    // 2.查询商品
    // List<ItemDTO> items = itemService.queryItemByIds(itemIds);
    // 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, ","))
    );
    // 2.2.解析响应
    if(!response.getStatusCode().is2xxSuccessful()){
        // 查询失败,直接结束
        return;
    }
    List<ItemDTO> items = response.getBody();
    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());
    }
}
  • 21
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值