一、rpc调用是什么(定义)?为了解决什么问题出现的?什么场景可以使用?为什么要使用rpc?
定义
RPC:远程过程调用(Remote Procedure Call),允许本地程序像调用本地函数一样,调用另一台服务器上的函数,屏蔽网络通信细节。
解决的问题
解决分布式场景下不同服务跨机器网络调用的通信问题,屏蔽 socket 连接、报文编解码、网络收发等底层网络复杂逻辑。
使用场景
微服务多服务之间跨进程、跨服务器方法调用。
为什么使用 RPC
- 调用体验接近本地方法,开发简洁;
- 封装底层网络通信逻辑,开发者无需处理原生网络编程;
- 具备序列化、超时、负载均衡、重试等分布式通信基础能力。
伪代码示例
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 接口
- 引入商品 dubbo 依赖,原生可直接注入接口调用
java
@Reference
private GoodsDubboApi goodsDubboApi;
- 封装一层 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){
// 对象转换
}
}
- 业务 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
- 声明式接口编码,可读性高,风格类似本地方法调用;
- 内置集成负载均衡(结合 LoadBalancer);
- 统一管理请求配置、拦截器、异常处理。
伪代码实例
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的区别是什么?
区别:
-
底层协议不同 RPC(Dubbo/GRPC)大多使用自定义 TCP 二进制协议 ;OpenFeign 底层是标准HTTP 1.1。
-
定位本质不同 RPC 是一类远程调用技术统称;OpenFeign只是 HTTP 调用的封装框架,不属于 RPC 实现。
-
数据序列化Dubbo/GRPC 默认二进制序列化(Hessian、Protobuf);OpenFeign 默认 JSON 序列化。
-
性能差异TCP 二进制 RPC 通常性能更高;HTTP+JSON 开销相对更大。
-
互通性OpenFeign 调用的服务只要提供标准 HTTP 接口,语言无关;Dubbo 等 RPC 服务需要对应客户端 SDK 才能通信。
-
使用风格相似但载体不一样二者都做到「像调用本地方法一样发起远程请求」,
- 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 代码
- 开启 feign sentinel 支持(application.yml)
yaml
feign:
sentinel:
enabled: true
- Feign 接口
java
@FeignClient(value = "goods-service", fallback = GoodsFeignFallback.class)
public interface GoodsFeignClient {
@GetMapping("/goods/query")
GoodsVO getGoods(@RequestParam("goodsId") Long goodsId);
}
- 降级实现类
java
@Component
public class GoodsFeignFallback implements GoodsFeignClient {
@Override
public GoodsVO getGoods(Long goodsId) {
GoodsVO vo = new GoodsVO();
vo.setGoodsName("服务繁忙,暂时无法查询商品");
return vo;
}
}
- 业务调用
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)
- application.yml 配置
yaml
dubbo:
consumer:
# Dubbo 消费者启动默认会去检测注册中心是否存在对应提供者服务;设为 false,项目启动不阻塞,
# 即使提供者未启动,消费者也正常启动,运行期调用时报错。
check: false
sentinel:
dubbo:
# 含义:开启Sentinel对Dubbo RPC调用的适配支持,实现RPC限流、熔断降级。
# 开启 Sentinel Dubbo 适配器,自动对所有 Dubbo 消费接口埋点,能够针对
# RPC 接口配置限流、熔断、降级规则。
enabled: true
- RPC 接口
java+
public interface GoodsDubboApi {
GoodsDTO getGoods(Long goodsId);
}
- 降级实现类
java
@Component
public class GoodsDubboFallback implements GoodsDubboApi {
@Override
public GoodsDTO getGoods(Long goodsId) {
GoodsDTO dto = new GoodsDTO();
dto.setMsg("商品服务调用降级,请稍后重试");
return dto;
}
}
- 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的值取得是降级策略方法名还是降级策略类名?
取值为降级方法名称(方法名字符串)。
硬性约束:
- 该方法必须和被 @SentinelResource 修饰的主方法处于同一个类中;
- 方法入参、返回值类型需要与主方法保持一致。
- 访问权限使用 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 调用频繁降级、评估下游稳定性;动态调整阈值、开关降级规则。
如何实现
- 指标采集:收集调用量、失败率、超时次数、降级触发次数;
- 可视化面板展示监控数据;
- 控制台动态推送降级 / 熔断规则至应用;主流方案: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();
}
真实代码 & 配套管控操作
- 引入依赖
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>
- 启动参数(连接 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;
}
}
控制台管控操作
- 登录 Sentinel Dashboard;
- 找到资源
goods#getGoods; - 【熔断规则】配置异常比例、熔断时长;
- 实时监控:QPS、异常数、熔断状态、降级触发记录;
- 支持动态新增 / 修改 / 删除规则,无需重启服务。
监控核心观测指标
- 成功请求数、失败请求数
- 请求平均耗时
- 熔断开启 / 关闭状态
- 降级触发总次数
八、真实生产环境rpc + sentinel实现降级熔断,我们必须将客户端包装的RpcClient中进行sentinel注解埋点的方法和降级策略方法写在一个类内吗?
**注意!这个是生产环境最佳实践!**必须将降级策略解耦,否则维护起来特别冗余。总的思想就是
我们降级策略方法和RpcClient中包装的RPC调用同名方法还是在同一个类内,但是降级策略方法我们直接调用外部解耦维护的降级策略类,这样能同时实现降级策略使用(遵循同类内规范)以及对降级策略的统一维护。
是的,基于原生@SentinelResource注解规范:fallback 指定的降级方法必须与主方法在同一个类中。
硬性约束
- fallback 函数和业务方法同类;
- 参数列表、返回值类型保持一致;
- 访问权限使用 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进行提供者和调用者的双方依赖吗?
- 存放位置:项目仓库独立 Maven Module,模块名称 goods-api。
- 可见实现:将 goods-api 打包为 Jar 上传至私有 Maven 仓库(Nexus)。
- 依赖方式:服务提供者、消费客户端分别在各自 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进行模块化依赖提供给其它项目使用!