背景:
项目UI与数据存储系统交互时遇到断连(例如:返回503 Service Unavailable),导致控件响应失灵。为解决处理503以及加强重试机制,引入Guava Retrying。
参考:
https://www.jianshu.com/p/2e3cfc509d56
https://www.jianshu.com/p/a289dde63043
https://www.jianshu.com/p/557eb67bb3d8
对于重试是有场景限制的,不是什么场景都适合重试,比如参数校验不合法、写操作等(要考虑写是否幂等)都不适合重试。
远程调用超时、网络突然中断可以重试。在微服务治理框架中,通常都有自己的重试与超时配置,比如dubbo可以设置retries=1,timeout=500调用失败只重试1次,超过500ms调用仍未返回则调用失败。
比如外部 RPC 调用,或者数据入库等操作,如果一次操作失败,可以进行多次重试,提高调用成功的可能性。
在很多业务场景中,为了排除系统中的各种不稳定因素,以及逻辑上的错误,并最大概率保证获得预期的结果,重试机制都是必不可少的。尤其是调用远程服务,在高并发场景下,很可能因为服务器响应延迟或者网络原因,造成我们得不到想要的结果,或者根本得不到响应。这个时候,一个优雅的重试调用机制,可以让我们更大概率保证得到预期的响应。
Spring Retry 为 Spring 应用程序提供了声明性重试支持。 它用于Spring批处理、Spring集成、Apache Hadoop(等等)的Spring。
在分布式系统中,为了保证数据分布式事务的强一致性,大家在调用RPC接口或者发送MQ时,针对可能会出现网络抖动请求超时情况采取一下重试操作。 大家用的最多的重试方式就是MQ了,但是如果你的项目中没有引入MQ,那就不方便了。
RuntimeException
recover()
方法。
guava-retrying
是Google Guava库的一个扩展包,可以为任意函数调用创建可配置的重试机制。该扩展包比较简单,大约包含了10个方法和类。
guava-retrying 模块提供了一种通用方法, 可以使用Guava谓词匹配增强的特定停止、重试和异常处理功能来重试任意Java代码。
Guava retryer有更优的策略定义,在支持重试次数和重试频度控制基础上,能够兼容支持多个异常或者自定义实体对象的重试源定义,让重试功能有更多的灵活性。
Guava Retryer也是线程安全的,入口调用逻辑采用的是 java.util.concurrent.Callable
的 call()
方法。
guava-retrying默认的阻塞策咯是通过Thread.sleep
来实现的,也就是说通过让当前线程休眠来实现阻塞功能,这或许不是一种很好的选择;
guava-retrying功能强大,基本能满足我们常用的操作;如果不满足当前各种已有的策咯,可以选择分别继承WaitStrategy
,StopStrategy
,BlockStrategy
来自定义自己的实现;
1 Retryer<Boolean> retryer = RetryerBuilder.<Boolean>newBuilder() 2 .retryIfException() 3 .retryIfResult(aBoolean -> Objects.equals(aBoolean, false)) 4 .withAttemptTimeLimiter(AttemptTimeLimiters.fixedTimeLimit(10, TimeUnit.SECONDS, Executors.newCachedThreadPool())) 5 .withWaitStrategy(WaitStrategies.fixedWait(5, TimeUnit.SECONDS)) 6 .withStopStrategy(StopStrategies.stopAfterAttempt(5)) 7 .withRetryListener(new RetryListener() { 8 @Override 9 public <V> void onRetry(Attempt<V> attempt) { 10 System.out.print("retry time=" + attempt.getAttemptNumber()); 11 } 12 }).build(); 13 try { 14 retryer.call(() -> { 15 // 逻辑处理 16 return null; 17 }); 18 } catch (Exception e) { 19 System.out.println("exception:" + e); 20 }
具体接口及相应的策咯:
- newBuilder:创建RetryerBuilder对象,通过该类进行构建各种重试策咯;
- retryIfException:抛出异常时重试,但抛出error不会重试;另外该方法还包含一个重载的方法,可以自定义针对异常的实现;
- retryIfRuntimeException:见名知义,抛出RuntimeException时重试;
- retryIfExceptionOfType:抛出指定异常类型时重试;
- retryIfResult:根据具体的返回值选择重试;
- withRetryListener:在重试的时候进行事件监听,这中间我们可以记录下错误日志什么的;可以注册多个事件监听器,会按照注册顺序依次调用;
- withWaitStrategy:重试等待策略,核心策咯之一;
- withStopStrategy:重试停止策略,核心策咯之一;
- withBlockStrategy:重试阻塞策略,也就是两次重试的时间间隔的实现方式;
- withAttemptTimeLimiter:单次任务执行时长限制(如果单次任务执行超时,则终止执行当前任务);
- build:通过newBuilder构建了各种重试策咯,构建完成,还需要通过build方法借助Retryer来执行;
序号 | 接口 | 描述 | 备注 |
---|---|---|---|
1 | Attempt | 一次执行任务 | |
2 | AttemptTimeLimiter | 单次任务执行时间限制 | 如果单次任务执行超时,则终止执行当前任务。NoAttemptTimeLimit,FixedAttemptTimeLimit。 |
3 | BlockStrategies | 任务阻塞策略 | 通俗的讲就是当前任务执行完,下次任务还没开始这段时间做什么),默认策略为:BlockStrategies.THREAD_SLEEP_STRATEGY |
4 | RetryException | 重试异常 | |
5 | RetryListener | 自定义重试监听器 | 可以用于异步记录错误日志 |
6 | StopStrategy | 停止重试策略 | StopAfterDelayStrategy, NeverStopStrategy, StopAfterAttemptStrategy |
7 | WaitStrategy | 等待时长策略 | (控制时间间隔),返回结果为下次执行时长。FixedWaitStrategy,RandomWaitStrategy,IncrementingWaitStrategy,ExponentialWaitStrategy,FibonacciWaitStrategy,ExceptionWaitStrategy,CompositeWaitStrategy。 |
8 | Attempt | 一次执行任务 | |
9 | Attempt | 一次执行任务 |
一个完备的重试实现,要很好地解决如下问题:
并且,为了更好地封装性,重试的实现一般分为两步:
一个完整的重试流程可以简单示意为:
graph LR
A((Start)) -->|build| B(Retryer)
B --> C{need call?}
C -->|continue| D[call]
D --> Z[call count++]
Z --> C
C -->|finished| E[result]
E --> F((success))
E --> G((failed ))
<!-- https://mvnrepository.com/artifact/com.github.rholder/guava-retrying -->
<dependency>
<groupId>com.github.rholder</groupId>
<artifactId>guava-retrying</artifactId>
<version>2.0.0</version>
</dependency>
compile("com.github.rholder:guava-retrying:2.0.0") {
exclude group: ‘com.google.guava‘, module: ‘guava‘
}
Callable<Boolean> callable = new Callable<Boolean>() {
public Boolean call() throws Exception {
return true; // do something useful here
}
};
Retryer<Boolean> retryer = RetryerBuilder.<Boolean>newBuilder()
.retryIfResult(Predicates.<Boolean>isNull())
.retryIfExceptionOfType(IOException.class)
.retryIfRuntimeException()
.withStopStrategy(StopStrategies.stopAfterAttempt(3))
.build();
retryer.call(callable);
下面是完整的参考实现。
1 public Boolean test() throws Exception { 2 //定义重试机制 3 Retryer<Boolean> retryer = RetryerBuilder.<Boolean>newBuilder() 4 //retryIf 重试条件 5 .retryIfException() 6 .retryIfRuntimeException() 7 .retryIfExceptionOfType(Exception.class) 8 .retryIfException(Predicates.equalTo(new Exception())) 9 .retryIfResult(Predicates.equalTo(false)) 10 11 //等待策略:每次请求间隔1s 12 .withWaitStrategy(WaitStrategies.fixedWait(1, TimeUnit.SECONDS)) 13 14 //停止策略 : 尝试请求6次 15 .withStopStrategy(StopStrategies.stopAfterAttempt(6)) 16 17 //时间限制 : 某次请求不得超过2s , 类似: TimeLimiter timeLimiter = new SimpleTimeLimiter(); 18 .withAttemptTimeLimiter(AttemptTimeLimiters.fixedTimeLimit(2, TimeUnit.SECONDS)) 19 20 .build(); 21 22 //定义请求实现 23 Callable<Boolean> callable = new Callable<Boolean>() { 24 int times = 1; 25 26 @Override 27 public Boolean call() throws Exception { 28 log.info("call times={}", times); 29 times++; 30 31 if (times == 2) { 32 throw new NullPointerException(); 33 } else if (times == 3) { 34 throw new Exception(); 35 } else if (times == 4) { 36 throw new RuntimeException(); 37 } else if (times == 5) { 38 return false; 39 } else { 40 return true; 41 } 42 43 } 44 }; 45 //利用重试器调用请求 46 return retryer.call(callable); 47 }
web服务器不能处理HTTP请求,可能是临时超载或者是服务器进行维护。这意味着你需要忍耐一下,等待服务器的临时处理。在这种状态下,一些服务器可以简单的拒绝socket连接,否则会发生内容不一致的错误。
1、从站点获得IP地址;
2、通过IP地址打开socket连接;
3、通过socket连接写入HTTP数据流;
4、等待响应,返回的数据流。该数据流包含由HTTP协议决定的状态代码值。然后解析数据流状态代码和其它信息。
1、站点遭到攻击,在超过限制时报503错误,待攻击停止就可以恢复了;
2、站点规模较大,并发请求过多,这种建议修改优化程序或需要升级更高类型主机;
3、程序有错误,在短时间内产生多次工作进程崩溃,会因IIS7的快速故障防护功能而关闭程序池;
4、站点提供下载,当带宽超过限制时会报错,需停止下载功能,或者升级主机解决。
原文:https://www.cnblogs.com/cathygx/p/13540415.html