MemoryAPI 开发者文档 MemoryAPI 开发者文档
首页
    • 概述
    • 系统设计
    • 维护升级
    • 多元 API 生态
    • 高效 SDK 集成
    • 实时流量监控
    • 资源集中治理
    • 个人信息管理
    • 内容概览
    • Ant Design Pro 脚手架
    • OpenAPI 生成接口
    • Vuepress 静态文档站点
    • 基础信息管理
    • 丰富的接口服务
    • 高校 SDK 集成
    • Maven 聚合工程
    • Nacos 注册中心
    • Gateway 网关
    • 远程服务调用
    • API 签名认证
    • JWT 认证授权
    • Dubbo 容器部署
    • 其他核心功能
    • 定制 Spring Boot 初始模板
    • 全局业务逻辑梳理
  • 简介
  • 常见问题与解答
首页
    • 概述
    • 系统设计
    • 维护升级
    • 多元 API 生态
    • 高效 SDK 集成
    • 实时流量监控
    • 资源集中治理
    • 个人信息管理
    • 内容概览
    • Ant Design Pro 脚手架
    • OpenAPI 生成接口
    • Vuepress 静态文档站点
    • 基础信息管理
    • 丰富的接口服务
    • 高校 SDK 集成
    • Maven 聚合工程
    • Nacos 注册中心
    • Gateway 网关
    • 远程服务调用
    • API 签名认证
    • JWT 认证授权
    • Dubbo 容器部署
    • 其他核心功能
    • 定制 Spring Boot 初始模板
    • 全局业务逻辑梳理
  • 简介
  • 常见问题与解答
  • 内容概览

    • 概览
  • 前端

    • Ant Design Pro 脚手架
    • OpenAPI 生成接口
    • Vuepress 静态文档站点
  • 后端

    • 基础信息管理
    • 丰富的接口服务
    • 高效 SDK 集成
    • Maven 聚合工程
    • Nacos 注册中心
    • Gateway 网关
    • 远程服务调用
      • 🍚 需求分析
      • 🍜RPC 框架
        • 核心功能
        • RPC 框架实现
      • 🥣 Dubbo
        • 介绍
        • 服务实现
        • 依赖导入
        • 配置管理
        • 服务注册
        • 服务消费
      • 🍜 OpenFeign
        • 依赖导入
        • Feign 客户端
        • 服务注册
        • 服务消费
        • 抽象服务
    • API 签名认证
    • JWT 认证授权
    • Dubbo 容器部署
    • 其他核心功能
    • 定制 Spring Boot 初始模板
    • 全局业务逻辑梳理
目录

远程服务调用

学习目标

在这里,你将系统学习了解 远程服务调用的具体代码实现

我们将以最简单直接的方式为您呈现内容!

# 🍚 需求分析

在先前的网关实现中,我们已实现基于路由配置的请求转发到后端接口。但随着新需求的发展,我们希望 memory-gateway 服务的代码量保持简洁。为此,我们考虑将核心业务逻辑,如用户身份验证、接口存在性检查等,移至其他微服务中实现,而 memory-gateway 仅作为外部服务的调用者。

这涉及到分布式服务架构下的跨服务通信问题,关键在于如何在微服务间实现跨服务方法调用。为满足这一需求,我们将引入 RPC 框架进行远程调用。通过 RPC 框架,各微服务可以相互调用方法,实现高效、可靠的跨服务通信。

# 🍜RPC 框架

# 核心功能

  • RPC(Remote Procedure Call,远程过程调用)框架是一种实现远程过程调用的技术,它允许在不同的计算机或网络中的进程或应用程序相互通信和交互。RPC 框架通常提供了一种机制,使得客户端可以像调用本地函数或方法一样调用远程服务。

# RPC 框架实现

# 🥣 Dubbo

# 介绍

  • Dubbo 是一个高性能、轻量级的 Java RPC 框架,由阿里巴巴公司开发并开源。它提供了三大核心能力:面向接口的远程方法调用,智能容错和负载均衡,以及服务自动注册和发现。
  • Dubbo 框架使得服务提供者能够轻松地暴露服务,而服务消费者能够方便地调用远程服务。它简化了分布式系统的开发,使得系统更加灵活、可扩展和高效。

接下来我们将在本项目中引入 Dubbo 框架进行远程调用。通过 Dubbo 框架,各微服务可以相互调用方法,实现高效可靠的跨服务通信。

# 服务实现

在 memory-backend-core-service 服务中,分别实现以下核心功能:

  • 内部接口服务,判断请求的模拟接口是否存在。
/**
 * 内部接口服务实现类
 *
 * @author memory
 */
@DubboService
public class InnerInterfaceInfoServiceImpl implements InterfaceInfoDubboService {

    @Resource
    private InterfaceInfoMapper interfaceInfoMapper;

    /**
     * 请求的模拟接口是否存在
     *
     * @param url    请求 url
     * @param method 请求 方法
     * @return 接口信息
     */
    @Override
    public InterfaceInfo getInterfaceInfo(String url, String method) {
        if (StringUtils.isAnyBlank(url, method)) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        QueryWrapper<InterfaceInfo> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("url", url);
        queryWrapper.eq("method", method);

        // 获取接口信息
        return interfaceInfoMapper.selectOne(queryWrapper);
    }
}
  • 内部用户服务,判断用户调用接口所使用的 accessKey 和 secretKey 是否存在。
/**
 * 内部用户服务实现类
 *
 * @author memory
 */
@DubboService
public class UserDubboServiceImpl implements UserDubboService {
    @Resource
    private UserMapper userMapper;

    /**
     * 数据库中查是否已分配给用户秘钥(accessKey)
     * @param accessKey
     * @return
     */
    @Override
    public User getInvokeUser(String accessKey) {
        if (StringUtils.isAnyBlank(accessKey)) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("accessKey", accessKey);

        return userMapper.selectOne(queryWrapper);
    }
}
  • 内部用户接口服务,实现接口调用次数统计。
/**
 * 内部用户接口信息服务实现类
 *
 * @author memory
 */
@DubboService
public class UserInterfaceInfoDubboServiceImpl implements UserInterfaceInfoDubboService {

    @Resource
    private UserInterfaceInfoService userInterfaceInfoService;

    /**
     * 接口调用统计
     *
     * @param interfaceInfoId 接口 id
     * @param userId          用户 id
     * @return 是否调用成功
     */
    @Override
    public boolean invokeCount(long interfaceInfoId, long userId) {
        return userInterfaceInfoService.invokeCount(interfaceInfoId, userId);
    }
}

我们将使用 Nacos 作为注册中心,权限校验相关代码逻辑全部统一放到 Gateway 网关过滤器中完成,并使用 Dubbo 实现内部服务调用。

在保证 memory-backend-core-service 服务的代码量简洁的同时,实现分布式服务架构下的跨服务通信功能。

请确保 该项目的 Nacos 正常启动并已经能够投入使用。

# 依赖导入

在 memory-backend-core-service和memory-backend-gateway 服务中均导入以下依赖坐标;

  <dependency>
      <groupId>org.apache.dubbo</groupId>
      <artifactId>dubbo</artifactId>
      <version>3.1.8</version>
  </dependency>

# 配置管理

memory-backend-core-service 服务作为服务提供者,在其项目配置文件 appication.yml 中作如下配置:

# Dubbo
dubbo:
  application:
    name: api-invoke-server
  protocol:
    name: dubbo
    port: -1
  registry:
    id: api-invoke
    address: nacos://localhost:8848

memory-backend-gateway 服务作为服务消费者,在其项目配置文件 appication.yml 中作如下配置:

# Dubbo
dubbo:
  application:
    name: api-invoke-customer
  protocol:
    name: dubbo
    port: -1
  registry:
    id: api-invoke
    address: nacos://localhost:8848

并分别在memory-backend-core-service 服务和memory-backend-gateway 服务启动类上添加 @EnableDubbo 注解,如下:

@EnableDubbo
public class CoreApplication {
public static void main(String[] args) {
        ConfigurableApplicationContext ctx = SpringApplication.run(CoreApplication.class, args);
        long start = System.currentTimeMillis();

        long time = (System.currentTimeMillis() - start) / 1000;
        String info = "启动完成,耗时%d秒,swagger访问链接:http://%s:%s%s/doc.html";
        String address = ctx.getEnvironment().getProperty("server.address");
        String port = ctx.getEnvironment().getProperty("server.port");
        String path = ctx.getEnvironment().getProperty("server.servlet.context-path");
        log.info(String.format(info, time, address, port, path));
    }
}
@EnableDubbo
public class GatewayApplication {
    public static void main(String[] args) {
        SpringApplication.run(GatewayApplication.class, args);
    }
}

# 服务注册

分别在memory-backend-core-service 服务实现的内部接口服务,内部用户服务和内部用户接口服务中,添加 DubboService注解,如下:

/**
 * 内部接口服务实现类
 *
 * @author memory
 */
@DubboService
public class InnerInterfaceInfoServiceImpl implements InterfaceInfoDubboService {
    ................................
}

/**
 * 内部用户服务实现类
 *
 * @author memory
 */
@DubboService
public class UserDubboServiceImpl implements UserDubboService {
    ................................
}
/**
 * 内部用户接口信息服务实现类
 *
 * @author memory
 */
@DubboService
public class UserInterfaceInfoDubboServiceImpl implements UserInterfaceInfoDubboService {
    ................................
}

用最简洁的代码成功实现了服务注册,服务注册完成后即可在 Gateway 网关处实现服务消费。

# 服务消费

在 Gateway 全局过滤器 CustomGlobalFilter 下,使用 Spring 提供的 IOC 功能,实现远程服务注入:

/**
 * GateWay 全局过滤器
 */
@Slf4j
@Component
public class CustomGlobalFilter implements GlobalFilter, Ordered {
/**
     * 使用 Dubbo 实现接口远程调用
     */
    @DubboReference
    private UserDubboService;

    @DubboReference
    private InterfaceInfoDubboService;

    @DubboReference
    private UserInterfaceInfoDubboService;

    ................................
}

在 Gateway 全局过滤器 CustomGlobalFilter 中,分别实现用户身份校验,接口信息校验和接口调用次数统计功能:

// 3.2.校验accessKey
// todo 从数据库中查询, accessKey是否分配给该用户
if (accessKey == null || !accessKey.equals("memory")) {
    return handleNoAuth(response);
}
// accessKey 是否分配给该用户
User invokeUser = userDubboService.getInvokeUser(accessKey);
// 4. 请求的模拟接口是否存在
InterfaceInfo interfaceinfo = interfaceInfoDubboService.getInterfaceInfo(path, method);
// 6. 接口调用统计,接口调用次数 + 1, 用户积分 - 1
try {
    // userInterfaceInfoDubboService.invokeCount(interfaceInfoId, userId);
    userInterfaceInfoFeignClient.invokeCount(interfaceInfoId, userId);
} catch (Exception e) {
    log.error("invokeCount error", e);
}

启动 Nacos,可以看到这样的页面,说明服务生产者和服务消费者成功连接至 本地启动的 Nacos 注册中心:

image-20240610203202683

至此,服务业务逻辑实现,服务生产者以及服务消费者已全部配置完毕,成功使用 Dubbo 实现分布式服务架构下的跨服务通信功能。

# 🍜 OpenFeign

经过前面的学习,我们基本了解在实际项目中,如何正确使用 Dubbo 框架进行远程服务调用,实现跨服务间通信。

与之相比,使用 OpenFeign 实现远程服务调用,二者之间还存在些许差距。

# 依赖导入

在 memory-backend-core-service和memory-backend-gateway 服务中均导入以下依赖坐标;

<!-- 添加 openfeign 框架依赖 -->
<dependency>
    <groupId>org.springframework.cloud</groupId>
    <artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>

# Feign 客户端

接下来,完成内部间调用服务(用户身份校验,接口信息校验和接口调用次数统计)的相关接口编写,即完善 Feign 客户端:

  • 内部接口服务,判断请求的模拟接口是否存在:
/**
 * 内部接口信息服务
 */
@FeignClient(name = "memory-core", path = "/api/interface/inner")
public interface InterfaceInfoFeignClient {
    /**
     * 从数据库中查询模拟接口是否存在(请求路径、请求方法、请求参数)
     *
     * @param path
     * @param method
     * @return
     */
    @GetMapping("/get/interfaceInfo")
    InterfaceInfo getInterfaceInfo(@RequestParam(value = "path") String path, @RequestParam(value = "method") String method);
}
  • 内部用户服务,判断用户调用接口所使用的 accessKey 和 secretKey 是否存在:
/**
 * 内部用户服务
 */
@FeignClient(name = "memory-core", path = "/api/user/inner")
public interface UserFeignClient {

    /**
     * 数据库中查是否已分配给用户秘钥(accessKey)
     *
     * @param accessKey
     * @return
     */
    @GetMapping("/get/invoke")
    User getInvokeUser(@RequestParam(value = "accessKey") String accessKey);
}

  • 内部用户接口服务,实现接口调用次数统计:
/**
 * 内部用户接口信息服务
 */
@FeignClient(name = "memory-core", path = "/api/user/interface/inner")
public interface UserInterfaceInfoFeignClient {

    /**
     * 调用接口统计
     *
     * @param interfaceInfoId
     * @param userId
     * @return
     */
    @GetMapping("/get/invoke/count")
    boolean invokeCount(@RequestParam(value = "interfaceInfoId") long interfaceInfoId, @RequestParam(value = "userId") long userId);
}

这里要特别注意 @FeignClient 注解的使用方式:

  • name 属性值为注册中心内所注册的服务名,与该微服务在 Nacos 中的服务配置相对应。
  • path 属性值为将来访问该 Feign 客户端时的访问路径,用来标记服务资源地址。

比方说 memory-backend-core-service 服务的 Nacos 相关配置是这样的:

# core
spring:
  application:
    name: memory-core
  # dev
  profiles:
    active: dev
  # session
  session:
    # store-type: redis
    timeout: 2592000
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848
      config:
        import-check:
          enabled: false

在 memory-backend-core-service 服务中,根据已经完善的 Feign 客户端,构造实现对应的服务接口:

  • 内部接口信息服务接口:
/**
 * 内部接口信息服务
 * 该服务仅内部调用,不是给前端的
 */
@RestController
@RequestMapping("/interface/inner")
public class InterfaceInnerController implements InterfaceInfoFeignClient {

    @Resource
    private InterfaceInfoService interfaceInfoService;

    /**
     * 从数据库中查询模拟接口是否存在(请求路径、请求方法、请求参数)
     *
     * @param path
     * @param method
     * @return
     */
    @Override
    @GetMapping("/get/interfaceInfo")
    public InterfaceInfo getInterfaceInfo(String path, String method) {
        return interfaceInfoService.getInterfaceInfo(path, method);
    }
}
  • 内部用户接口信息服务接口:
/**
 * 内部用户服务
 * 该服务仅内部调用,不是给前端的
 */
@RestController
@RequestMapping("/user/inner")
public class UserInnerController implements UserFeignClient {

    @Resource
    private UserService userService;

    /**
     * 数据库中查是否已分配给用户秘钥(accessKey)
     * @param accessKey
     * @return
     */
    @Override
    @GetMapping("/get/invoke")
    public User getInvokeUser(@RequestParam(value = "accessKey") String accessKey) {
        return userService.getInvokeUser(accessKey);
    }
}
  • 内部用户接口信息服务接口:
/**
 * 内部用户接口信息服务
 * 该服务仅内部调用,不是给前端的
 */
@RestController
@RequestMapping("/user/interface/inner")
public class UserInterfaceInnerController implements UserInterfaceInfoFeignClient {

    @Resource
    private UserInterfaceInfoService userInterfaceInfoService;

    /**
     * 接口调用统计
     *
     * @param interfaceInfoId 接口 id
     * @param userId          用户 id
     * @return 是否调用成功
     */
    @Override
    @GetMapping("/get/invoke/count")
    public boolean invokeCount(long interfaceInfoId, long userId) {
        return userInterfaceInfoService.invokeCount(interfaceInfoId, userId);
    }
}

至此,一个完整的 Feign 客户端编写完成,业务代码核心功能基本实现。

我们将使用 Nacos 作为注册中心,权限校验相关代码逻辑全部统一放到 Gateway 网关过滤器中完成,并使用 OpenFeign 实现内部服务调用。

在保证 memory-backend-core-service 服务的代码量简洁的同时,实现分布式服务架构下的跨服务通信功能。

请确保 该项目的 Nacos 正常启动并已经能够投入使用。

# 服务注册

并分别在memory-backend-core-service 服务和memory-backend-gateway 服务启动类上添加 @EnableDubbo 注解,如下:

@EnableFeignClients(basePackages = "com.memory.client.feignClient")
public class CoreApplication {
    public static void main(String[] args) {
        ConfigurableApplicationContext ctx = SpringApplication.run(CoreApplication.class, args);
        long start = System.currentTimeMillis();

        long time = (System.currentTimeMillis() - start) / 1000;
        String info = "启动完成,耗时%d秒,swagger访问链接:http://%s:%s%s/doc.html";
        String address = ctx.getEnvironment().getProperty("server.address");
        String port = ctx.getEnvironment().getProperty("server.port");
        String path = ctx.getEnvironment().getProperty("server.servlet.context-path");
        log.info(String.format(info, time, address, port, path));
    }
}
@EnableFeignClients(basePackages = "com.memory.client.feignClient")
public class GatewayApplication {
    public static void main(String[] args) {
        SpringApplication.run(GatewayApplication.class, args);
    }
}

已经成功注册上述服务至 Nacos 注册中心,接下来尝试在 memory-backend-gateway服务中使用 Feign 客户端调用memory-backend-core-service服务提供的内部接口。

# 服务消费

在 Gateway 全局过滤器 CustomGlobalFilter 下,实现 Feign 客户端注入:

/**
 * GateWay请求过滤器
 */
@Slf4j
@Component
public class CustomGlobalFilter implements GlobalFilter, Ordered {
    /**
     * 我们使用OpenFeign 实现接口远程调用
     */
    @Resource
    private UserFeignClient userFeignClient;

    @Resource
    private InterfaceInfoFeignClient interfaceInfoFeignClient;

    @Resource
    private UserInterfaceInfoFeignClient userInterfaceInfoFeignClient;
}

在 Gateway 全局过滤器 CustomGlobalFilter 中,分别实现用户身份校验,接口信息校验和接口调用次数统计功能:

// accessKey 是否分配给该用户
User invokeUser = userFeignClient.getInvokeUser(accessKey);
if (invokeUser == null) {
    return handleNoAuth(response);
}
// 4. 请求的模拟接口是否存在
InterfaceInfo interfaceInfo = interfaceInfoFeignClient.getInterfaceInfo(path, method);
if (interfaceInfo == null) {
    return handleNoAuth(response);
}
// 6. 接口调用统计,接口调用次数 + 1, 用户积分 - 1
try {
    userInterfaceInfoFeignClient.invokeCount(interfaceInfoId, userId);
} catch (Exception e) {
    log.error("invokeCount error", e);
}

启动 Nacos,可以看到这样的页面,说明服务生产者和服务消费者成功连接至 本地启动的 Nacos 注册中心:

image-20240610203202683

至此,服务业务逻辑实现,服务生产者以及服务消费者已全部配置完毕,成功使用 OpenFeign 实现分布式服务架构下的跨服务通信功能。

# 抽象服务

经过考虑,我最终决定抽象远程调用服务,即抽象 Feign 客户端为该项目内的memory-backend-server-client服务,并注册服务到 Nacos 注册中心中,管理更加便捷:

spring:
  cloud:
    nacos:
      discovery:
        server-addr: 127.0.0.1:8848

想要了解更多实践 Dubbo 和 Openfeign 实现跨服务通信时的踩坑记录和经验技巧,可以尝试访问:从零开始构建分布式服务架构:用 Dubbo 和注册中心实现远程调用、服务注册与发现、配置管理 - Memory's blog (atomgit.net) (opens new window)

Gateway 网关
API 签名认证

← Gateway 网关 API 签名认证→

Theme by Vdoing | Copyright © 2023-2024 回忆如初
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式