likes
comments
collection
share

015-从零搭建微服务-远程调用(一)

作者站长头像
站长
· 阅读数 22

写在最前

如果这个项目让你有所收获,记得 Star 关注哦,这对我是非常不错的鼓励与支持。

源码地址(后端):gitee.com/csps/mingyu…

源码地址(前端):gitee.com/csps/mingyu…

文档地址:gitee.com/csps/mingyu…

远程调用

远程调用为分布式系统提供了一种方便、高效和可靠的通信方式,使得不同部分的组件能够协同工作,并实现代码的模块化、解耦和重用。它是构建可扩展、灵活和可维护的分布式系统的重要工具和技术。

为什么要使用远程调用?

  1. 分布式系统:在分布式系统中,不同的组件或服务可能分布在不同的机器或进程中。使用远程调用可以使这些组件能够通过网络相互通信,进行数据交换和协作,实现系统的功能和业务逻辑。
  2. 模块化和解耦:远程调用使得系统的各个模块能够独立开发和部署,通过定义清晰的接口和协议,模块之间可以解耦并独立演化。这样,系统的不同部分可以独立进行修改、升级或替换,而不会对其他部分产生影响。
  3. 代码重用性:通过远程调用,可以将某些功能或服务封装为可复用的模块,供多个应用程序或系统共享和调用。这样可以避免重复开发相同的功能,提高代码的重用性和开发效率。
  4. 扩展性和弹性:远程调用可以实现系统的弹性扩展和水平扩展。当系统负载增加时,可以通过部署更多的服务实例,并使用负载均衡器进行流量分发,以应对更高的并发请求。
  5. 服务治理和监控:使用远程调用框架可以集成服务治理和监控功能,例如服务注册与发现、负载均衡、熔断器、日志记录等。这些功能可以提供更好的可用性、性能和可维护性,同时也可以帮助开发人员进行系统的监控和故障排查。

技术选型

OpenFeign 与 Dobbo 3 都是优秀的微服务架构下的远程调用框架,各有千秋。

维度OpenFeignDobbo 3
类型基于 Java 的声明式 Web 服务客户端RPC(远程过程调用)框架
协议支持支持 RESTful API,通常基于 HTTP 协议进行通信支持多种远程通信协议,不仅包括 Dubbo 自有的二进制协议,还支持 HTTP、gRPC等,这使得 Dubbo 3 在不同场景下更加灵活
编程模型声明式的 API 定义和调用方式,通过 Java 接口和注解来描述服务接口和方法,使用起来更加简洁和直观声明式的 API,但更多情况下使用 XML 配置或注解配置,同时支持传统的编程式调用方式。
服务治理集成了 Netflix 的负载均衡器 Ribbon,但相较 Dubbo 3在服务治理方面较为简单。提供了更多功能,如负载均衡、服务注册与发现、熔断器等,适用于大规模分布式系统的构建。
生态和社区2019年 Netflix 公司宣布 Feign 组件正式进入停更维护状态,于是 Spring 官方便推出了一个名为 OpenFeign 的组件作为 Feign 的替代方案。2011年开源,2012年发布2.5.3版本后停止更新,2017年阿里重启 dubbo 项目,现在由 Apache 软件基金会进行维护和发展,具有稳定的社区支持。

OpenFeign + OkHttp3

本架构将采用 OpenFeign + OkHttp3 作为远程调用工具。

  • 声明式 API 定义:OpenFeign 提供了声明式的 API 定义方式,使用接口和注解来描述服务接口和方法。这使得代码更加简洁、可读性更强,并且开发人员可以更专注于业务逻辑而不是底层的 HTTP 请求细节。
  • 强大的 HTTP 功能:OkHttp3 是一个功能强大的 HTTP 客户端库,提供了丰富的功能和特性,如连接池管理、请求和响应拦截器、请求重试、缓存等。通过将 OpenFeign 与 OkHttp3 结合使用,可以利用 OkHttp3 的这些功能来处理HTTP 请求和响应,提高性能和可靠性。
  • 高性能和可扩展性:OkHttp3 被广泛认为是一个高性能的 HTTP 客户端,具有优秀的性能和效率。它支持并发请求、连接复用和异步操作等,能够满足大规模和高并发的需求。同时,OkHttp3 也具备很好的可扩展性,可以通过自定义拦截器和插件来扩展和定制其功能。
  • 网络层配置:OkHttp3 提供了丰富的网络层配置选项,如连接超时、读写超时、代理设置等。通过使用 OkHttp3 作为OpenFeign 的底层 HTTP 客户端,可以灵活地配置和管理网络层的行为,以满足特定的需求。
  • 生态和社区支持:OpenFeign 和 OkHttp3 都是广受欢迎的开源项目,拥有活跃的社区和稳定的维护。这意味着开发人员可以从社区中获得支持、文档和更新的功能。

综上所述,将 OpenFeign 与 OkHttp3 结合使用可以享受到 OpenFeign 声明式 API 定义的便利性,并利用 OkHttp3 提供的强大的 HTTP 功能和性能优势。这样的组合可以简化代码、提高性能,并为开发人员提供更好的可扩展性和配置灵活性。

添加 mingyue-common-feign

引入依赖

<dependencies>
  <dependency>
    <groupId>com.csp.mingyue</groupId>
    <artifactId>mingyue-common-core</artifactId>
  </dependency>

  <!-- SpringCloud Openfeign -->
  <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
  </dependency>
  <!-- okhttp 扩展 -->
  <dependency>
    <groupId>io.github.openfeign</groupId>
    <artifactId>feign-okhttp</artifactId>
  </dependency>
  <!-- LB 扩展 -->
  <dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-loadbalancer</artifactId>
  </dependency>
</dependencies>

自定义注解

覆盖 @EnableFeignClients 注解,默认 basePackages,客户端直接使用 @EnableMingYueFeignClients 注解即可,无须再指定 basePackages。

import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.cloud.openfeign.FeignClientsConfiguration;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 开启 Feign Client
 *
 * @author Strive
 * @date 2023/7/4
 */
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
@EnableFeignClients
public @interface EnableMingYueFeignClients {

  /**
   * Alias for the {@link #basePackages()} attribute. Allows for more concise annotation
   * declarations e.g.: {@code @ComponentScan("org.my.pkg")} instead of
   * {@code @ComponentScan(basePackages="org.my.pkg")}.
   * @return the array of 'basePackages'.
   */
  String[] value() default {};

  /**
   * Base packages to scan for annotated components.
   * <p>
   * {@link #value()} is an alias for (and mutually exclusive with) this attribute.
   * <p>
   * Use {@link #basePackageClasses()} for a type-safe alternative to String-based
   * package names.
   * @return the array of 'basePackages'.
   */
  String[] basePackages() default { "com.csp.mingyue" };

  /**
   * Type-safe alternative to {@link #basePackages()} for specifying the packages to
   * scan for annotated components. The package of each class specified will be scanned.
   * <p>
   * Consider creating a special no-op marker class or interface in each package that
   * serves no purpose other than being referenced by this attribute.
   * @return the array of 'basePackageClasses'.
   */
  Class<?>[] basePackageClasses() default {};

  /**
   * A custom <code>@Configuration</code> for all feign clients. Can contain override
   * <code>@Bean</code> definition for the pieces that make up the client, for instance
   * {@link feign.codec.Decoder}, {@link feign.codec.Encoder}, {@link feign.Contract}.
   *
   * @see FeignClientsConfiguration for the defaults
   */
  Class<?>[] defaultConfiguration() default {};

  /**
   * List of classes annotated with @FeignClient. If not empty, disables classpath
   * scanning.
   * @return
   */
  Class<?>[] clients() default {};

}

Nacos 开启配置

application-common.yml

# feign 配置
feign:
  # 启用 okhttp 作为网络请求框架      
  okhttp:
    enabled: true
  # 关闭 httpclient 作为网络请求框架
  httpclient:
    enabled: false
  client:
    config:
      # 将调用的微服务名称改成 default 就配置成全局的了
      default:
        # 相当于 Request.Optionsn 连接超时时间
        connectTimeout: 10000
        # 相当于 Request.Options 读取超时时间
        readTimeout: 10000
  compression:
    request:
      # 配置请求 GZIP 压缩
      enabled: true
    response:
      # 配置响应 GZIP 压缩
      enabled: true

修改 mingyue-system-api

引入依赖

<dependency>
  <groupId>com.csp.mingyue</groupId>
  <artifactId>mingyue-common-feign</artifactId>
</dependency>

远程调用用户服务

import com.csp.mingyue.common.core.constant.ServiceNameConstants;
import com.csp.mingyue.common.core.vo.R;
import com.csp.mingyue.system.api.entity.SysUser;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;

/**
 * 远程调用用户服务
 *
 * @author Strive
 * @date 2023/7/3 09:48
 */
@FeignClient(contextId = "remoteUserService", value = ServiceNameConstants.SYSTEM_SERVICE)
public interface RemoteUserService {

  /**
   * 通过用户名查询用户、角色信息
   * @param username 用户名
   * @return R
   */
  @GetMapping(value = "/sysUser/getSysUserInfoByUsername")
  R<SysUser> getSysUserInfoByUsername(@RequestParam(value = "username") String username);

}

mingyue-auth 远程调用 mingyue-system

引入依赖

<!-- 系统服务 API -->
<dependency>
  <groupId>com.csp.mingyue</groupId>
  <artifactId>mingyue-system-api</artifactId>
</dependency>

修改登录

public SaTokenInfo login(PasswordLoginDto dto) {
  R<SysUser> userInfoResp = remoteUserService.getSysUserInfoByUsername(dto.getUsername());

  if (Objects.isNull(userInfoResp) || Objects.isNull(userInfoResp.getData())) {
    return null;
  }

  SysUser userInfo = userInfoResp.getData();

  if (dto.getUsername().equals(userInfo.getUsername()) && dto.getPassword().equals(userInfo.getPassword())) {
    // 第1步,先登录上
    StpUtil.login(10001);
    // 第2步,获取 Token 相关参数
    SaTokenInfo tokenInfo = StpUtil.getTokenInfo();

    return tokenInfo;
  }

  return null;
}

启动测试

curl -X 'POST' \
  'http://mingyue-gateway:9100/auth/login' \
  -H 'accept: */*' \
  -H 'Content-Type: application/json' \
  -d '{
  "username": "mingyue",
  "password": "123456"
}'

返回示例

{
  "code": 200,
  "msg": "登录成功",
  "data": "2GcAFW7UZ0XJjDe5H76CBAtj7zc7bm8S"
}

开启或关闭 OkHttp3

默认的HttpURLConnection是 JDK 自带的,并不支持连接池,如果要实现连接池的机制,还需要自己来管理连接对象。OkHttp3 自带连接池管理,提升吞吐量。

核心依赖

<!-- okhttp 扩展 -->
<dependency>
  <groupId>io.github.openfeign</groupId>
  <artifactId>feign-okhttp</artifactId>
</dependency>

使用方式

Nacos 配置 application-common.yml

# feign 配置
feign:
  # 启用 okhttp 作为网络请求框架      
  okhttp:
    # true 开启 / false 关闭
    enabled: true

验证是否开启

查看 Client 实现,实际发送请求是由 Feign 中的Client接口实现类去处理的,默认使用的是 Defalut 类,该类使用的是HttpURLConnection

015-从零搭建微服务-远程调用(一)

默认实现,关闭 okhttp 后,对此方法断点查看是否进入

@Override
public Response execute(Request request, Options options) throws IOException {
  HttpURLConnection connection = convertAndSend(request, options);
  return convertResponse(connection, request);
}

打开 OkHttpClient 类,查看 OkHttp3 源码,找到 execute 方法。开启 okhttp 后,对此方法断点查看是否进入

@Override
public feign.Response execute(feign.Request input, feign.Request.Options options)
  throws IOException {
  okhttp3.OkHttpClient requestScoped = getClient(options);
  Request request = toOkHttpRequest(input);
  Response response = requestScoped.newCall(request).execute();
  return toFeignResponse(response, input).toBuilder().request(input).build();
}

小结

至此,mingyue-auth 已经可以通过远程调用获取 mingyue-system 服务。下一节,网关增加接口拦截,引入白名单列表。