设计模式与容错处理:策略模式选择容错策略、状态模式管理状态、责任链处理异常
容错处理,说白了就是系统在出问题的时候还能「扛得住」。我做了十几年系统,见过太多因为容错没做好而翻车的案例。有一次线上支付服务挂了,就因为一个第三方接口超时,结果整个订单链路都堵死了。嗯,从那以后我特别重视容错设计。
今天咱们聊聊三个设计模式在容错场景下的配合:策略模式选容错策略、状态模式管服务状态、责任链模式处理异常。这三个模式组合起来,能搭出一套很灵活的容错框架。
一、策略模式:动态选择容错策略
容错策略有很多种:重试、降级、熔断、限流、静默失败……每个场景适合的策略不一样。我个人习惯把策略定义成接口,然后让调用方按需选择。
举个例子,一个远程调用失败了,你可以选择:
- 重试策略:最多重试3次,间隔递增
- 降级策略:返回缓存数据或默认值
- 熔断策略:直接拒绝请求,快速失败
- 静默策略:吞掉异常,记录日志
我在项目中遇到过一种情况:同一个接口,白天流量大时用降级策略,晚上流量小时用重试策略。策略模式正好能解决这种动态切换的问题。
// 策略接口
public interface FaultToleranceStrategy {
void handle(Throwable t, Context ctx);
}
// 重试策略
public class RetryStrategy implements FaultToleranceStrategy {
private int maxRetries = 3;
public void handle(Throwable t, Context ctx) {
int retryCount = ctx.getRetryCount();
if (retryCount < maxRetries) {
ctx.incrementRetry();
// 执行重试逻辑
} else {
throw new RuntimeException("重试耗尽", t);
}
}
}
// 降级策略
public class DegradeStrategy implements FaultToleranceStrategy {
public void handle(Throwable t, Context ctx) {
// 返回降级数据
ctx.setResult(DegradeData.getDefault());
}
}
二、状态模式:管理容错状态
容错不是一成不变的。一个服务可能从「正常」状态,变成「熔断」状态,再变成「半开」状态,最后恢复。状态模式就是用来管理这种状态流转的。
你想想看,熔断器有三个状态:
- CLOSED(关闭):正常状态,请求正常通过
- OPEN(打开):熔断状态,直接拒绝请求
- HALF_OPEN(半开):试探状态,放少量请求测试
状态模式的好处是:每个状态的行为是独立的,状态之间的转换逻辑清晰。我曾经在一个项目中用状态模式管理数据库连接池的健康状态,效果很好。
// 状态接口
public interface CircuitBreakerState {
boolean allowRequest();
void onSuccess();
void onFailure();
}
// 关闭状态
public class ClosedState implements CircuitBreakerState {
private int failureCount = 0;
private int threshold = 5;
public boolean allowRequest() { return true; }
public void onFailure() {
failureCount++;
if (failureCount >= threshold) {
// 切换到打开状态
}
}
}
// 打开状态
public class OpenState implements CircuitBreakerState {
private long timeout;
public boolean allowRequest() {
return System.currentTimeMillis() > timeout;
}
public void onSuccess() {
// 切换到关闭状态
}
}
三、责任链模式:链式处理异常
一个异常来了,可能不止一个处理器需要处理。比如:先记录日志,再发送告警,最后执行降级。责任链模式正好适合这种场景。
责任链的核心是:每个处理器只关心自己该做的事,处理完就传给下一个。这样新增一个处理器,不需要改现有代码。
// 处理器抽象
public abstract class ExceptionHandler {
protected ExceptionHandler next;
public void setNext(ExceptionHandler next) {
this.next = next;
}
public void handle(Exception e, Context ctx) {
doHandle(e, ctx);
if (next != null) {
next.handle(e, ctx);
}
}
protected abstract void doHandle(Exception e, Context ctx);
}
// 日志处理器
public class LogHandler extends ExceptionHandler {
protected void doHandle(Exception e, Context ctx) {
Logger.log("异常发生: " + e.getMessage());
}
}
// 告警处理器
public class AlertHandler extends ExceptionHandler {
protected void doHandle(Exception e, Context ctx) {
if (e instanceof TimeoutException) {
AlertService.send("超时告警");
}
}
}
// 降级处理器
public class DegradeHandler extends ExceptionHandler {
protected void doHandle(Exception e, Context ctx) {
ctx.setResult(DegradeData.getDefault());
}
}
四、三者如何配合
这三个模式不是孤立的。在实际项目中,它们经常一起出现:
- 策略模式决定「用什么策略」——比如根据错误类型选择重试还是降级
- 状态模式管理「当前状态」——比如熔断器是开还是关
- 责任链模式处理「异常流程」——比如先日志、再告警、最后降级
举个例子:一个 RPC 调用失败了,系统先查熔断器状态(状态模式),如果是关闭状态,就按策略执行重试(策略模式),重试失败后走责任链(记录日志、发送告警、返回降级数据)。
五、SVG 框架图:容错处理核心逻辑
六、实战中的注意事项
光有模式还不够,落地时有些坑得避开:
- 策略不要太多:我见过有人搞了十几种策略,结果维护成本比收益还高。3-5种就够了。
- 状态要持久化:熔断器状态如果只存在内存里,服务重启就丢了。建议用 Redis 或数据库持久化。
- 责任链要可配置:每个异常走哪些处理器,最好能动态调整。我习惯用配置文件定义链的顺序。
- 别忘了监控:容错策略执行了多少次?熔断器打开了几次?这些指标一定要上报,不然出了问题都不知道。
好了,今天的内容就到这里。这三个模式配合起来,能帮你搭出一套灵活又稳定的容错系统。下次遇到类似需求,不妨试试这个组合。