Hi,大家好,我是编程小6,很荣幸遇见你,我把这些年在开发过程中遇到的问题或想法写出来,今天说一说
stupefy咒语怎么读_反弹一切诅咒的咒语,希望能够帮助你!!!。
在分布式场景中,Retry 和 Fallback 是最常见的容灾方案。
那 Retry 和 Fallback 该怎么抉择呢?
首先,先看下 Retry 和 Fallback 都是怎么帮助流程进行自我恢复的。
现在有一个生产流程:
image
核心流程如下:
如果发生网络抖动,将导致生产失败。
image
由于上产流程太过重要,系统需尽最大努力保障用户能够完成下单操作,那针对网络抖动这个问题,可以通过 Retry 进行修复。
image
Retry 机制非常适合服务短时间不可用,或某个服务节点异常 这类场景。
一个生产验证接口,主流程如下:
image
同样,假设在访问商品服务时出现网络异常:
image
由于无法获取商品信息,从而导致整个验证流程被异常中断,用户操作被迫终止。
聪明的你估计会说那就使用 Retry 呀,是的:
image
如果是短时不可用,通过 Retry 机制便可以恢复流程。
但,如果是商品服务压力过大,响应时间过长呢?比如,商品服务流量激增,导致 DB CPU 飙升,出现大量的慢 SQL,这时触发了系统的 Retry 会是怎样?
image
通过 Retry 机制未能将流程从异常中恢复过来,也给下游的 商品服务 造成了巨大伤害。
这就是常说的“读放大”,假设用户验证是否能够购买请求的请求量为 n,那极端情况下 商品服务的请求量为 3n (其中 2n 是由 Retry 机制造成)
此时,Retry 就不是一个好的方案。我们先退回业务场景进行思考,如果无法获取商品,验证接口是否可以直接放行,先让用户完成购买?
如果,这个业务假设能够接受的话,那就到了 Fallback 上场的时候了。
image
同样是对商品服务接口(同一个接口)的调用,在不同的场景需要使用不同的策略用以恢复业务流程,通常情况下:
那面对一个远程接口被多个场景使用,我们该怎么处理呢?
项目主要依赖 spring retry 和 lego starter
首先,引入 spring-retry 依赖
<dependency> <groupId>org.springframework.retry</groupId> <artifactId>spring-retry</artifactId> </dependency>
此次,引入 lego-starter 依赖
<dependency> <groupId>com.geekhalo.lego</groupId> <artifactId>lego-starter</artifactId> <version>0.1.17</version> </dependency>
最后新建 RetryConfiguration 以开启 Retry 能力
@EnableRetry @Configuration public class RetryConfiguration { }
在完成基本配置后,需要准备一个 ActionTypeProvider 用以提供上下文信息。
ActionTypeProvider 接口定义如下:
public interface ActionTypeProvider { ActionType get(); } public enum ActionType { COMMAND, QUERY }
通常情况下,我们会使用 ThreadLocal 组件将 ActionType 存储于线程上下文,在使用时从上下中获取相关信息。
public class ActionContext { private static final ThreadLocal<ActionType> ACTION_TYPE_THREAD_LOCAL = new ThreadLocal<>(); public static void set(ActionType actionType){ ACTION_TYPE_THREAD_LOCAL.set(actionType); } public static ActionType get(){ return ACTION_TYPE_THREAD_LOCAL.get(); } public static void clear(){ ACTION_TYPE_THREAD_LOCAL.remove(); } }
有了上下文之后,ActionBasedActionTypeProvider 直接从 Context 中获取 ActionType 具体如下
@Component public class ActionBasedActionTypeProvider implements ActionTypeProvider { @Override public ActionType get() { return ActionContext.get(); } }
上下文中的 ActionType 又是怎么进行管理的呢,包括信息绑定和信息清理?
最常用的方式便是:
核心实现为:
@Target({ ElementType.METHOD, ElementType.TYPE }) @Retention(RetentionPolicy.RUNTIME) public @interface Action { ActionType type(); } @Aspect @Component @Order(Integer.MIN_VALUE) public class ActionAspect { @Pointcut("@annotation(com.geekhalo.lego.faultrecovery.smart.Action)") public void pointcut() { } @Around(value = "pointcut()") public Object action(ProceedingJoinPoint joinPoint) throws Throwable { MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); Action annotation = methodSignature.getMethod().getAnnotation(Action.class); ActionContext.set(annotation.type()); try { return joinPoint.proceed(); }finally { ActionContext.clear(); } } }
在这些组件的帮助下,我们只需在方法上基于 @Action 注解进行标记,便能够将 ActionType 绑定到上下文。
在将 ActionType 绑定到上下文之后,接下来要做的便是对 远程接口 进行配置。远程接口的配置工作主要由 @SmartFault 来完成。
其核心配置项包括:
配置项 |
含义 |
默认配置 |
recover |
fallback 方法名称 |
|
maxRetry |
最大重试次数 |
3 |
include |
触发重试的异常类型 |
|
exclude |
不需要重新的异常类型 |
接下来,看一个 demo
@Service @Slf4j @Getter public class RetryService3 { private int count = 0; private int retryCount = 0; private int fallbackCount = 0; private int recoverCount = 0; public void clean(){ this.retryCount = 0; this.fallbackCount = 0; this.recoverCount = 0; } /** * Command 请求,启动重试机制 */ @Action(type = ActionType.COMMAND) @SmartFault(recover = "recover") public Long retry(Long input) throws Throwable{ this.retryCount ++; return doSomething(input); } /** * Query 请求,启动Fallback机制 */ @Action(type = ActionType.QUERY) @SmartFault(recover = "recover") public Long fallback(Long input) throws Throwable{ this.fallbackCount ++; return doSomething(input); } @Recover public Long recover(Throwable e, Long input){ this.recoverCount ++; log.info("recover-{}", input); return input; } private Long doSomething(Long input) { // 偶数抛出异常 if (count ++ % 2 == 0){ log.info("Error-{}", input); throw new RuntimeException(); } log.info("Success-{}", input); return input; } }
测试代码如下:
@SpringBootTest(classes = DemoApplication.class) public class RetryService3Test { @Autowired private RetryService3 retryService; @BeforeEach public void setup(){ retryService.clean(); } @Test public void retry() throws Throwable{ for (int i = 0; i < 100; i++){ retryService.retry(i + 0L); } Assertions.assertTrue(retryService.getRetryCount() > 0); Assertions.assertTrue(retryService.getRecoverCount() == 0); Assertions.assertTrue(retryService.getFallbackCount() == 0); } @Test public void fallback() throws Throwable{ for (int i = 0; i < 100; i++){ retryService.fallback(i + 0L); } Assertions.assertTrue(retryService.getRetryCount() == 0); Assertions.assertTrue(retryService.getRecoverCount() > 0); Assertions.assertTrue(retryService.getFallbackCount() > 0); } }
运行 retry 测试,日志如下:
[main] c.g.l.c.f.smart.SmartFaultExecutor : action type is COMMAND [main] c.g.l.faultrecovery.smart.RetryService3 : Error-0 [main] c.g.l.c.f.smart.SmartFaultExecutor : Retry method public java.lang.Long com.geekhalo.lego.faultrecovery.smart.RetryService3.retry(java.lang.Long) throws java.lang.Throwable use [0] [main] c.g.l.faultrecovery.smart.RetryService3 : Success-0
可见,当 action type 为 COMMAND 时:
方法主动进行重试,流程从异常中恢复,处理过程和效果符合预期。
运行 fallback 测试,日志如下:
[main] c.g.l.c.f.smart.SmartFaultExecutor : action type is QUERY [main] c.g.l.faultrecovery.smart.RetryService3 : Error-0 [main] c.g.l.c.f.smart.SmartFaultExecutor : recover From ERROR for method ReflectiveMethodInvocation: public java.lang.Long com.geekhalo.lego.faultrecovery.smart.RetryService3.fallback(java.lang.Long) throws java.lang.Throwable; target is of class [com.geekhalo.lego.faultrecovery.smart.RetryService3] [main] c.g.l.faultrecovery.smart.RetryService3 : recover-0
可见,当 action type 为 QUERY 时:
异常后自动执行 fallback,将流程从异常中恢复过来,处理过程和效果符合预期。
image
整体流程如下:
项目仓库地址:https://gitee.com/litao/lego
项目文档地址:https://gitee.com/litao/lego/wikis/support/smart-fault
今天的分享到此就结束了,感谢您的阅读,如果确实帮到您,您可以动动手指转发给其他人。
上一篇
已是最后文章
下一篇
已是最新文章