【noob-rpc】⑪扩展版RPC-容错机制
【noob-rpc】⑪扩展版RPC-容错机制
核心构建说明
【1】容错机制概念、不同容错策略
【2】容错机制实现:定义容错策略接口、扩展不同的容错策略
【3】引入SPI机制和工厂模式:支持配置和自定义容错策略扩展
需求分析
基于上述内容给RPC框架增加了重试机制,提升了服务消费端的可靠性和健壮性。但如果重试超过了一定次数仍然失败,又该怎么处理呢?或者说当调用出现失败时一定要重试么?有没有其他的策略呢?
此处引入一个提高服务消费端可靠性和健壮性的机制 -- 容错机制
设计方案
容错机制
容错是指系统在出现异常情况时,可以通过一定的策略保证系统仍然稳定运行,从而提高系统的可靠性和健壮性。
在分布式系统中,容错机制尤为重要,因为分布式系统中的各个组件都可能存在网络故障、节点故障等各种异常情况。要顾全大局,尽可能消除偶发/单点故障对系统带来的整体影响。
打个比方,将分布式系统类比为一家公司,如果公司某个优秀员工请假了,需要“触发容错”,让另一个普通员工顶上,这本质上是容错机制的一种降级策略。
容错机制一般都是在系统出现错误时才触发的,需要重点学习的是容错策略和容错实现方式
容错策略
容错策略有很多种,常用的容错策略主要是以下几个:
【1】Fail-Over 故障转移:一次调用失败后,切换一个其他节点再次进行调用,也算是一种重试
【2】Fail-Back 失败自动恢复:系统的某个功能出现调用失败或错误时,通过其他的方法,恢复该功能的正常。可以理解为降级,比如重试、调用其他服务等
【3】Fail-Safe 静默处理:系统出现部分非重要功能的异常时,直接忽略掉,不做任何处理,就像错误没有发生过一样
【4】Fail-Fast 快速失败:系统出现调用错误时,立刻报错,交给外层调用方处理
容错实现方式
容错其实是个比较广泛的概念,除了上面几种策略外,很多技术都可以起到容错的作用。
【1】重试:重试本质上也是一种容错的降级策略,系统错误后再试一次
【2】限流:当系统压力过大、已经出现部分错误时,通过限制执行操作(接受请求)的频率或数量,对系统进行保护
【3】降级:系统出现错误后,改为执行其他更稳定可用的操作。也可以叫做“兜底”或“有损服务”,这种方式的本质是:即使牺牲一定的服务质量,也要保证系统的部分功能可用,保证基本的功能需求得到满足
【4】熔断:系统出现故障或异常时,暂时中断对该服务的请求,而是执行其他操作,以避免连锁故障
【5】超时控制:如果请求或操作长时间没处理完成,就进行中断,防止阻塞和资源占用 注意,在实际项目中,根据对系统可靠性的需求,通常会结合多种策略或方法实现容错机制。
容错方案设计
回归到RPC框架,之前已经给系统增加重试机制,算是实现了一部分的容错能力。现在,正式引入容错机制,通过更多策略来进一步增加系统可靠性
此处提供 2 种方案
【1】先容错再重试
当系统发生异常时,首先会触发容错机制,比如记录日志、进行告警等,然后可以选择是否进行重试。这种方案其实是把重试当做容错机制的一种可选方案
【2】先重试再容错
在发生错误后,首先尝试重试操作,如果重试多次仍然失败,则触发容错机制,比如记录日志、进行告警等。
但其实这2种方案其实完全可以结合使用
系统错误时,先通过重试操作解决一些临时性的异常,比如网络波动、服务端临时不可用等;如果重试多次后仍然失败,说明可能存在更严重的问题,这时可以触发其他的容错策略,比如调用降级服务、熔断、限流、快速失败等,来减少异常的影响,保障系统的稳定性和可靠性。
举个具体的例子:
(1)系统调用服务 A 出现网络错误,使用容错策略-重试
(2)重试 3 次失败后,使用其他容错策略-降级
(3)系统改为调用不依赖网络的服务 B,完成操作
基于上述构建思路完成容错机制的引入
实现步骤
核心构建说明(参考重试机制的引入)
【1】构建fault.tolerant包:存放容错相关的代码
【2】编写容错策略通用接口,实现不同的容错策略
【3】引入SPI机制和工厂模式支持配置和自定义容错策略扩展
# noob-rpc-core
fault.tolerant:
- TolerantStrategy
- FailFastTolerantStrategy
- FailBackTolerantStrategy
- FailOverTolerantStrategy
- FailSafeTolerantStrategy
- TolerantStrategyKeys
- TolerantStrategyFactory
config:
- RpcConfig
proxy:
- ServiceProxy
# sample-consumer
application.properties
容错策略接口
容错策略通用接口:提供一个容错方法,使用 Map 类型的参数接受上下文信息(可用于灵活地传递容错处理需要用到的数据),并且接受一个具体的异常类参数。
由于容错是应用到发送请求操作的,所以容错方法的返回值是 RpcResponse(响应)
/**
* 容错策略
*/
public interface TolerantStrategy {
/**
* 容错
*
* @param context 上下文,用于传递数据
* @param e 异常
* @return
*/
RpcResponse doTolerant(Map<String, Object> context, Exception e);
}
不同容错策略介入
FailFastTolerantStrategy:快速失败容错策略
遇到异常后,将异常再次抛出,交给外层处理
/**
* 快速失败 - 容错策略(立刻通知外层调用方)
*/
public class FailFastTolerantStrategy implements TolerantStrategy {
@Override
public RpcResponse doTolerant(Map<String, Object> context, Exception e) {
throw new RuntimeException("服务报错", e);
}
}
FailSafeTolerantStrategy:静默处理容错策略
遇到异常后,记录一条日志,正常返回一个响应对象(就好像没有出现过报错)
/**
* 静默处理异常 - 容错策略
*/
@Slf4j
public class FailSafeTolerantStrategy implements TolerantStrategy {
@Override
public RpcResponse doTolerant(Map<String, Object> context, Exception e) {
log.info("静默处理异常", e);
return new RpcResponse();
}
}
FailBackTolerantStrategy:故障恢复策略
/**
* 降级到其他服务 - 容错策略
*/
@Slf4j
public class FailBackTolerantStrategy implements TolerantStrategy {
@Override
public RpcResponse doTolerant(Map<String, Object> context, Exception e) {
// todo 可自行扩展,获取降级的服务并调用
return null;
}
}
FailOverTolerantStrategy:故障转移策略
转移到其他服务节点
/**
* 转移到其他服务节点 - 容错策略
*/
@Slf4j
public class FailOverTolerantStrategy implements TolerantStrategy {
@Override
public RpcResponse doTolerant(Map<String, Object> context, Exception e) {
// todo 可自行扩展,获取其他服务节点并调用
return null;
}
}
支持配置和自定义容错策略扩展
一个成熟的 RPC 框架可能会支持多种不同的容错策略,像序列化器、注册中心、负载均衡器一样,让开发者能够填写配置来指定使用的容错策略,并且支持自定义容错策略,让框架更易用、更利于扩展。其开发方式和序列化器、注册中心、负载均衡器都是一样的,都可以使用工厂创建对象、使用 SPI动态加载自定义的注册中心。
【1】引入TolerantStrategyKeys存储容错策略键名
【2】引入TolerantStrategyFactory结合SPI机制装配不同的容错策略,编写SPI配置文件装配容错策略
【3】RpcConfig新增容错策略配置,服务消费者可通过application.properties配置相应的容错策略
TolerantStrategyKeys
/**
* 容错策略键名常量
*/
public interface TolerantStrategyKeys {
/**
* 故障恢复
*/
String FAIL_BACK = "failBack";
/**
* 快速失败
*/
String FAIL_FAST = "failFast";
/**
* 故障转移
*/
String FAIL_OVER = "failOver";
/**
* 静默处理
*/
String FAIL_SAFE = "failSafe";
}
TolerantStrategyFactory
/**
* 容错策略工厂(工厂模式,用于获取容错策略对象)
*/
public class TolerantStrategyFactory {
static {
SpiLoader.load(TolerantStrategy.class);
}
/**
* 默认容错策略
*/
private static final TolerantStrategy DEFAULT_RETRY_STRATEGY = new FailFastTolerantStrategy();
/**
* 获取实例
*
* @param key
* @return
*/
public static TolerantStrategy getInstance(String key) {
return SpiLoader.getInstance(TolerantStrategy.class, key);
}
}
RpcConfig
@Data
public class RpcConfig {
/**
* 容错策略配置
*/
private String tolerantStrategy = TolerantStrategyKeys.FAIL_FAST;
}
SPI配置文件
在noob-rpc-core的resources/META-INF/rpc/system/目录下编写容错策略接口的SPI配置文件
# SPI配置文件名称
com.noob.rpc.fault.tolerant.TolerantStrategy
# SPI配置内容
failBack=com.noob.rpc.fault.tolerant.FailBackTolerantStrategy
failFast=com.noob.rpc.fault.tolerant.FailFastTolerantStrategy
failOver=com.noob.rpc.fault.tolerant.FailOverTolerantStrategy
failSafe=com.noob.rpc.fault.tolerant.FailSafeTolerantStrategy
容错策略应用
修改ServiceProxy请求处理操作,在异常处理的时候引入容错机制
// 发送请求方式2:扩展实现:引入重试机制、容错机制发送TCP请求
RpcResponse rpcResponse;
try {
RetryStrategy retryStrategy = RetryStrategyFactory.getInstance(rpcConfig.getRetryStrategy());
rpcResponse = retryStrategy.doRetry(() ->
VertxTcpClient.doRequest(rpcRequest, selectedServiceMetaInfo)
);
} catch (Exception e) {
// 容错机制
TolerantStrategy tolerantStrategy = TolerantStrategyFactory.getInstance(rpcConfig.getTolerantStrategy());
rpcResponse = tolerantStrategy.doTolerant(null, e);
}
return rpcResponse.getData();
在服务消费者sample-consumer的application.properties文件中配置容错策略参数
rpc.tolerantStrategy=failFast
测试
测试步骤:先启动CoreProviderSample,在ServiceProxy中发起服务请求处设置断点,随后启动CoreConsumerSample,当程序执行到断点处,随后可以关闭服务,放行查看对应的重试策略、容错策略是否正常捕获
扩展说明
扩展说明
【1】实现 Fail-Back 容错机制
参考思路:可以参考 Dubbo 的 Mock 能力,让消费端指定调用失败后要执行的本地服务和方法
【2】实现 Fail-Over 容错机制
参考思路:可以利用容错方法的上下文参数传递所有的服务节点和本次调用的服务节点,选择一个其他节点再次发起调用
【3】实现更多容错方案
参考思路:比如限流、熔断、超时控制等。或者将重试机制作为容错机制的一种策略来实现。