后端通识 - 远程服务调用RPC

一、rpc调用是什么(定义)?为了解决什么问题出现的?什么场景可以使用?为什么要使用rpc?

定义

RPC:远程过程调用(Remote Procedure Call),允许本地程序像调用本地函数一样,调用另一台服务器上的函数,屏蔽网络通信细节。

解决的问题

解决分布式场景下不同服务跨机器网络调用的通信问题,屏蔽 socket 连接、报文编解码、网络收发等底层网络复杂逻辑。

使用场景

微服务多服务之间跨进程、跨服务器方法调用。

为什么使用 RPC

  1. 调用体验接近本地方法,开发简洁;
  2. 封装底层网络通信逻辑,开发者无需处理原生网络编程;
  3. 具备序列化、超时、负载均衡、重试等分布式通信基础能力。

伪代码示例

java

复制代码
// 客户端:像调用本地方法一样远程调用
UserRpcService rpcClient = RpcFramework.getClient(UserRpcService.class);
User user = rpcClient.getUserById(1001); // 远程调用另一台机器服务方法

// 服务端提供实现
public class UserRpcServiceImpl implements UserRpcService {
    @Override
    public User getUserById(Long userId) {
        return userMapper.selectById(userId);
    }
}

二、进行后端开发时我们常说的调用远程服务包一层RPC是什么意思?

含义

远程服务没有开放 HTTP 接口,仅提供 RPC 服务;

在自身项目新建一层 RPC 客户端封装模块,隔离底层 RPC 通信逻辑,统一参数组装、异常处理、超时配置,上层业务不再直接操作原生 RPC 客户端,这就是包一层 RPC。

真实例子

项目需要调用商品微服务的 Dubbo RPC 接口

  1. 引入商品 dubbo 依赖,原生可直接注入接口调用

java

复制代码
@Reference
private GoodsDubboApi goodsDubboApi;
  1. 封装一层 RPC 包装类

java

复制代码
@Component
public class GoodsRpcClient {

    @Reference
    private GoodsDubboApi goodsDubboApi;

    // 统一入参参数组装goodsId和统一出参格式为GoodsDTO
    public GoodsDTO getGoodsInfo(Long goodsId) { 
        GoodsQuery query = new GoodsQuery();
        query.setGoodsId(goodsId);
        GoodsResp resp = goodsDubboApi.getGoods(query);
        // 统一转换DTO、捕获RPC异常、日志打印,还可以封装超时重试等逻辑
        if (!resp.getSuccess()) {
            throw new BusinessException("查询商品失败:"+resp.getMsg());
        }
        return convert(resp.getData());
    }

    private GoodsDTO convert(GoodsModel model){
        // 对象转换
    }
}
  1. 业务 service 只调用封装好的 RpcClient,不直接使用原生GoodsDubboApi

java

复制代码
@Service
public class OrderService {
    @Autowired
    private GoodsRpcClient goodsRpcClient;

    public void createOrder(Long goodsId){
        GoodsDTO goods = goodsRpcClient.getGoodsInfo(goodsId);
        // 下单逻辑
    }
}

总的来说,包一层rpc也就是包掉原生的底层RPC(goodsDubboApi),将原生RPC封装进统一的自定义客户端GoodsRpcClient用来进行远程调用,达到客户端进行远程调用实现统一入参、出参、超时重试、异常处理等逻辑,对我们远程调用的调用前入参和调用后结果处理(成功返回统一结果格式,失败进行统一异常处理和重试逻辑)达到统一,便于我们统一管理和改造远程调用的逻辑。

三、OpenFeign远程调用是什么(定义)?为了解决什么问题出现的?什么场景可以使用?为什么要使用OpenFeign ?

定义

OpenFeign 是基于 HTTP 协议的声明式远程调用组件,能够通过定义接口 + 注解的方式发起 HTTP 请求,封装 HTTP 请求构建、参数组装、响应解析流程。

解决的问题

简化 Spring 项目中 HTTP 远程调用的编码,消除手动拼接 url、组装请求参数、解析返回结果的重复代码。

使用场景

微服务之间基于 HTTP 接口进行服务调用。

为什么要使用 OpenFeign

  1. 声明式接口编码,可读性高,风格类似本地方法调用;
  2. 内置集成负载均衡(结合 LoadBalancer);
  3. 统一管理请求配置、拦截器、异常处理。

伪代码实例

java

复制代码
// 1.定义Feign接口
@FeignClient(name = "goods-service")
public interface GoodsFeignClient {
    @GetMapping("/goods/get")
    GoodsResp getGoods(@RequestParam("goodsId") Long goodsId);
}

// 2.业务层调用
@Service
public class OrderService {
    @Autowired
    private GoodsFeignClient goodsFeignClient;

    public void createOrder(Long goodsId){
        GoodsResp resp = goodsFeignClient.getGoods(goodsId);
    }
}

四、OpenFeign和RPC的区别是什么?

区别:

  1. 底层协议不同 RPC(Dubbo/GRPC)大多使用自定义 TCP 二进制协议 ;OpenFeign 底层是标准HTTP 1.1

  2. 定位本质不同 RPC 是一类远程调用技术统称;OpenFeign只是 HTTP 调用的封装框架,不属于 RPC 实现

  3. 数据序列化Dubbo/GRPC 默认二进制序列化(Hessian、Protobuf);OpenFeign 默认 JSON 序列化。

  4. 性能差异TCP 二进制 RPC 通常性能更高;HTTP+JSON 开销相对更大。

  5. 互通性OpenFeign 调用的服务只要提供标准 HTTP 接口,语言无关;Dubbo 等 RPC 服务需要对应客户端 SDK 才能通信。

  6. 使用风格相似但载体不一样二者都做到「像调用本地方法一样发起远程请求」,

  • OpenFeign:伪装本地方法,实际发 HTTP
  • Dubbo RPC:伪装本地方法,实际走 TCP 私有协议

最简代码对比

Dubbo RPC

java

复制代码
@Reference
GoodsDubboApi api;

OpenFeign

java

复制代码
@FeignClient("goods-service")
public interface GoodsFeignClient {}

场景简单区分

  • 内部微服务追求高性能:Dubbo/GRPC
  • 调用第三方 http 接口、异构服务、SpringCloud HTTP 微服务:OpenFeign

补充关键概念:Spring Cloud 里常说的「Feign 远程调用」是 HTTP 调用,不是 RPC 调用

五、OpenFeign远程调用的降级策略是什么(定义)?使用场景是什么?如何实现?

定义

OpenFeign 降级策略:依托 Sentinel/Hystrix,远程调用失败、超时、异常时,不持续等待或抛出异常,执行预设兜底逻辑返回容错结果,避免级联故障。

使用场景

微服务远程调用依赖服务不可用、响应超时、接口报错,防止雪崩,保障自身业务正常运行。

实现方式

OpenFeign 整合熔断组件(Sentinel 主流),定义接口降级实现类,发生异常自动路由到降级方法。

伪代码

java

复制代码
// Feign接口
@FeignClient(value = "goods-service", fallback = GoodsFeignFallback.class)
public interface GoodsFeignClient {
    GoodsVO getGoods(Long goodsId);
}

// 降级兜底实现类
@Component
public class GoodsFeignFallback implements GoodsFeignClient{
    @Override
    public GoodsVO getGoods(Long goodsId) {
        // 降级兜底数据
        return new GoodsVO(null,"商品服务暂时不可用");
    }
}

真实 SpringBoot 代码

  1. 开启 feign sentinel 支持(application.yml)

yaml

复制代码
feign:
  sentinel:
    enabled: true
  1. Feign 接口

java

复制代码
@FeignClient(value = "goods-service", fallback = GoodsFeignFallback.class)
public interface GoodsFeignClient {
    @GetMapping("/goods/query")
    GoodsVO getGoods(@RequestParam("goodsId") Long goodsId);
}
  1. 降级实现类

java

复制代码
@Component
public class GoodsFeignFallback implements GoodsFeignClient {
    @Override
    public GoodsVO getGoods(Long goodsId) {
        GoodsVO vo = new GoodsVO();
        vo.setGoodsName("服务繁忙,暂时无法查询商品");
        return vo;
    }
}
  1. 业务调用

java

复制代码
@Service
public class OrderService {
    @Autowired
    private GoodsFeignClient goodsFeignClient;

    public void createOrder(Long goodsId){
        GoodsVO goodsVO = goodsFeignClient.getGoods(goodsId);
    }
}

六、RPC远程调用的降级策略是什么(定义)?使用场景是什么?

定义

RPC 降级策略:RPC 远程调用出现超时、异常、服务不可用时,放弃正常远程请求,执行预先编写的兜底逻辑返回结果,阻断故障传播,防止服务雪崩。

使用场景

调用下游 RPC 服务宕机、网络波动、调用超时、限流触发,避免大量请求阻塞耗尽自身线程资源。

如何实现

主流 Dubbo 依托 Sentinel/Hystrix 实现;定义接口降级实现类,RPC 调用失败自动执行降级方法。

伪代码

java

复制代码
// Dubbo RPC标准接口
public interface GoodsDubboApi {
    GoodsDTO getGoods(Long goodsId);
}
// 降级兜底实现
public class GoodsDubboFallback implements GoodsDubboApi {
    @Override
    public GoodsDTO getGoods(Long goodsId) {
        GoodsDTO dto = new GoodsDTO();
        dto.setMsg("商品RPC服务暂时不可用");
        return dto;
    }
}

真实代码 (SpringBoot + Dubbo + Sentinel)

  1. application.yml 配置

yaml

复制代码
dubbo:
  consumer:
    # Dubbo 消费者启动默认会去检测注册中心是否存在对应提供者服务;设为 false,项目启动不阻塞,
    # 即使提供者未启动,消费者也正常启动,运行期调用时报错。
    check: false
sentinel:
  dubbo:
    # 含义:开启Sentinel对Dubbo RPC调用的适配支持,实现RPC限流、熔断降级。
    # 开启 Sentinel Dubbo 适配器,自动对所有 Dubbo 消费接口埋点,能够针对 
    # RPC 接口配置限流、熔断、降级规则。
    enabled: true
  1. RPC 接口

java+

复制代码
public interface GoodsDubboApi {
    GoodsDTO getGoods(Long goodsId);
}
  1. 降级实现类

java

复制代码
@Component
public class GoodsDubboFallback implements GoodsDubboApi {
    @Override
    public GoodsDTO getGoods(Long goodsId) {
        GoodsDTO dto = new GoodsDTO();
        dto.setMsg("商品服务调用降级,请稍后重试");
        return dto;
    }
}
  1. RPC 封装客户端

java

复制代码
@Component
public class GoodsRpcClient {
    # @Reference 注解注入生成 Dubbo 动态代理对象,代理封装网络通信、序列化、注册中心寻址,
    # 调用代理对象方法时发起 RPC 远程请求。
    @Reference
    private GoodsDubboApi goodsDubboApi;

    @Autowired
    private GoodsDubboFallback goodsDubboFallback;

    public GoodsDTO getGoods(Long goodsId) {
        try {
            return goodsDubboApi.getGoods(goodsId);
        } catch (Exception e) {
            return goodsDubboFallback.getGoods(goodsId);
        }
    }
}

补充:生产推荐使用 Sentinel 注解方式统一拦截降级

java

复制代码
@SentinelResource(value = "getGoods", fallback = "getGoodsFallback")
public GoodsDTO getGoods(Long goodsId){
    return goodsDubboApi.getGoods(goodsId);
}
public GoodsDTO getGoodsFallback(Long goodsId){
    GoodsDTO dto = new GoodsDTO();
    dto.setMsg("降级返回");
    return dto;
}

问题:fallback = "getGoodsFallback"这段代码中fallback的值取得是降级策略方法名还是降级策略类名?

取值为降级方法名称(方法名字符串)

硬性约束:

  1. 该方法必须和被 @SentinelResource 修饰的主方法处于同一个类中
  2. 方法入参、返回值类型需要与主方法保持一致。
  3. 访问权限使用 public。

示例对照:

java

复制代码
@SentinelResource(value = "goods#getGoods", fallback = "getGoodsFallback")
public GoodsDTO getGoods(Long goodsId) {
    return goodsDubboApi.getGoods(goodsId);
}
// fallback填写的就是下方这个方法名
public GoodsDTO getGoodsFallback(Long goodsId) {
    GoodsDTO dto = new GoodsDTO();
    dto.setMsg("RPC调用触发降级");
    return dto;
}

补充区分:OpenFeign @FeignClient(fallback = XxxClass.class) 写法,填写的是Class 类,二者语法不要混淆。

七、如何监控和管理RPC远程调用的降级策略?

定义

RPC 降级监控管理:采集 RPC 调用指标、异常、熔断触发事件,可视化查看降级生效情况,并支持动态修改降级、熔断规则,无需重启服务。

使用场景

线上排查 RPC 调用频繁降级、评估下游稳定性;动态调整阈值、开关降级规则。

如何实现

  1. 指标采集:收集调用量、失败率、超时次数、降级触发次数;
  2. 可视化面板展示监控数据;
  3. 控制台动态推送降级 / 熔断规则至应用;主流方案:Dubbo + Sentinel Dashboard

伪代码

java

复制代码
// 标记资源,监控埋点
@SentinelResource(value = "getGoodsRpc", fallback = "queryFallback")
public GoodsDTO queryGoods(Long goodsId){
    return goodsDubboApi.getGoods(goodsId);
}
// 降级方法
public GoodsDTO queryFallback(Long goodsId){
    return fallbackData();
}

真实代码 & 配套管控操作

  1. 引入依赖

xml

复制代码
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-dubbo-adapter</artifactId>
</dependency>
<dependency>
    <groupId>com.alibaba.csp</groupId>
    <artifactId>sentinel-transport-simple-http</artifactId>
</dependency>
  1. 启动参数(连接 Sentinel 控制台)

plaintext

复制代码
-Dcsp.sentinel.dashboard.server=127.0.0.1:8080
-Dproject.name=order-service

3.RPC 调用封装

java

复制代码
@Component
public class GoodsRpcClient {
    @Reference
    private GoodsDubboApi goodsDubboApi;

    @SentinelResource(value = "goods#getGoods", fallback = "getGoodsFallback")
    public GoodsDTO getGoods(Long goodsId) {
        return goodsDubboApi.getGoods(goodsId);
    }

    public GoodsDTO getGoodsFallback(Long goodsId) {
        GoodsDTO dto = new GoodsDTO();
        dto.setMsg("RPC调用触发降级");
        return dto;
    }
}

控制台管控操作

  1. 登录 Sentinel Dashboard;
  2. 找到资源 goods#getGoods
  3. 【熔断规则】配置异常比例、熔断时长;
  4. 实时监控:QPS、异常数、熔断状态、降级触发记录;
  5. 支持动态新增 / 修改 / 删除规则,无需重启服务。

监控核心观测指标

  • 成功请求数、失败请求数
  • 请求平均耗时
  • 熔断开启 / 关闭状态
  • 降级触发总次数

八、真实生产环境rpc + sentinel实现降级熔断,我们必须将客户端包装的RpcClient中进行sentinel注解埋点的方法和降级策略方法写在一个类内吗?

**注意!这个是生产环境最佳实践!**必须将降级策略解耦,否则维护起来特别冗余。总的思想就是

我们降级策略方法和RpcClient中包装的RPC调用同名方法还是在同一个类内,但是降级策略方法我们直接调用外部解耦维护的降级策略类,这样能同时实现降级策略使用(遵循同类内规范)以及对降级策略的统一维护。

是的,基于原生@SentinelResource注解规范:fallback 指定的降级方法必须与主方法在同一个类中

硬性约束

  1. fallback 函数和业务方法同类
  2. 参数列表、返回值类型保持一致;
  3. 访问权限使用 public。

备选方案(想要拆分类实现降级逻辑)

使用 blockHandler 区分:

  • fallback:业务异常、调用失败降级(同类)
  • blockHandler:限流触发拦截,同样要求同类。

两种标准写法参考

java

复制代码
@Component
public class GoodsRpcClient {
    @Reference
    private GoodsDubboApi goodsDubboApi;

    @SentinelResource(value = "getGoods", fallback = "getGoodsFallback")
    public GoodsDTO getGoods(Long goodsId) {
        return goodsDubboApi.getGoods(goodsId);
    }
    // 必须同类内
    public GoodsDTO getGoodsFallback(Long goodsId) {
        GoodsDTO dto = new GoodsDTO();
        dto.setMsg("RPC调用降级返回");
        return dto;
    }
}

如果你希望降级逻辑抽离到独立类:原生@SentinelResource fallback不支持。解决方案:在当前 RpcClient 的 fallback 方法内部,调用外部独立降级工具类完成逻辑,代码如下:

java

复制代码
public GoodsDTO getGoodsFallback(Long goodsId) {
    // 调用外部类,实现逻辑解耦
    return rpcFallbackService.goodsDefaultFallback(goodsId);
}

九、RPC和OpenFeign二者的远程服务端是如何定义实现客户端可以远程调用的?

一、Dubbo RPC(TCP 协议)

伪代码

java

复制代码
// 1.公共API模块(提供者、消费者共同依赖)
public interface GoodsDubboApi {
    GoodsDTO getGoods(Long goodsId);
}

// 2.服务提供者实现
@DubboService
public class GoodsDubboApiImpl implements GoodsDubboApi{
    @Override
    public GoodsDTO getGoods(Long goodsId) {
        // 查询数据库
    }
}

真实生产代码

公共接口(单独 maven 模块:goods-api)

java

复制代码
public interface GoodsDubboApi {
    GoodsDTO getGoods(@RequestParam("goodsId") Long goodsId);
}

提供者实现

java

复制代码
import org.apache.dubbo.config.annotation.DubboService;

@DubboService
public class GoodsDubboApiImpl implements GoodsDubboApi {
    @Override
    public GoodsDTO getGoods(Long goodsId) {
        GoodsDTO dto = new GoodsDTO();
        dto.setGoodsId(goodsId);
        dto.setGoodsName("测试商品");
        return dto;
    }
}

二、OpenFeign(HTTP 接口)

伪代码

java

复制代码
// 服务提供者Controller
@RestController
@RequestMapping("/goods")
public class GoodsController {
    @GetMapping("/query")
    public GoodsDTO getGoods(Long goodsId){
        //业务逻辑
    }
}

真实生产代码

java

复制代码
@RestController
@RequestMapping("/goods")
public class GoodsController {
    @GetMapping("/query")
    public GoodsDTO getGoods(@RequestParam Long goodsId) {
        GoodsDTO dto = new GoodsDTO();
        dto.setGoodsId(goodsId);
        dto.setGoodsName("测试商品");
        return dto;
    }
}

简要原理说明(精简)

RPC (Dubbo):提供者使用@DubboService暴露接口至注册中心,消费者通过@Reference订阅接口发起 TCP 远程调用。OpenFeign:服务端编写标准 SpringMVC Controller 提供 HTTP 接口;客户端定义@FeignClient接口发起 http 请求调用。

补充RPC客户端通过@Reference进行远程调用:

@Reference 注解注入生成 Dubbo 动态代理对象,代理封装网络通信、序列化、注册中心寻址,调用代理对象方法时发起 RPC 远程请求。

示例真实代码片段:

java

复制代码
@Component
public class GoodsRpcClient {
    @Reference
    private GoodsDubboApi goodsDubboApi;

    public GoodsDTO getGoods(Long goodsId){
        return goodsDubboApi.getGoods(goodsId);
    }
}

十、RPC远程调用服务提供者和消费者同时依赖的公共API模块公共接口(单独 maven 模块:goods-api)一般放在什么位置?如何实现二者都能看到?通过maven进行提供者和调用者的双方依赖吗?

  1. 存放位置:项目仓库独立 Maven Module,模块名称 goods-api。
  2. 可见实现:将 goods-api 打包为 Jar 上传至私有 Maven 仓库(Nexus)。
  3. 依赖方式:服务提供者、消费客户端分别在各自 pom.xml 引入 goods-api 坐标完成依赖引用。

提供者 pom 依赖示例:

xml

复制代码
<dependency>
    <groupId>com.demo</groupId>
    <artifactId>goods-api</artifactId>
    <version>1.0.0</version>
</dependency>

消费者 pom 依赖示例:

xml

复制代码
<dependency>
    <groupId>com.demo</groupId>
    <artifactId>goods-api</artifactId>
    <version>1.0.0</version>
</dependency>

下一篇文章,带你实现如何将项目打包成maven进行模块化依赖提供给其它项目使用!

相关推荐
数据知道1 小时前
网络安全实战:子域名接管实战——从 CNAME 配置错误到完全控制
网络·安全·web安全·网络安全
lf13210272 小时前
用 JSON Schema 管装修节点记录:从照片台账到可校验工程数据
网络·数据库·人工智能·经验分享·物联网·json·智能家居
华清远见成都中心3 小时前
FreeRTOS事件组(Event Group)的工作机制分析
服务器·网络·网络协议
xiaoxiangsiyan4 小时前
RHCE2026云原生路线EX188和EX288完整备考指南
运维·网络·云原生·自动化
西安景驰电子4 小时前
《PTP精确时间协议系列》第二篇:工程部署、调试与性能优化
运维·服务器·网络·数据库·windows·性能优化
tiantianuser4 小时前
NVME-oF IP 设计15 : 适于高速网络存储系统的IP设计1
网络协议·rdma·高速传输·cmac·roce v2
虹科网络安全5 小时前
KnowBe4 SAT 是什么?安全意识培训平台功能与应用详解
网络
艺杯羹5 小时前
古典密码学攻防演进全景:从凯撒移位、频率分析到一次一密与恩尼格玛机破译
网络·人工智能·安全·网络安全·密码学·密码安全
发量惊人的中年网工6 小时前
多品牌酒店门店如何集中管控网络?SD-WAN 实现住客网与业务网隔离的实践
网络·组网