4.SynchronousMethodHandler
前言
经过前面几篇文章的铺垫,这里终于到了正餐, 本篇我们将介绍SynchronousMethodHandler
, 它作为同步请求的核心组件, 起到承上启下的功能
在介绍SynchronousMethodHandler
, 之前我们先来看一下MethodHandler
类
结构图
MethodHandler
它定义在InvocationHandlerFactory
内部, 仅提供了一个invoke方法用来执行目标方式, 同时在它内部还定义了一个工厂接口Factory
, 用来构建MethodHandler
对象
interface MethodHandler {Object invoke(Object[] argv) throws Throwable;interface Factory<C> {MethodHandler create(Target<?> target,MethodMetadata md,C requestContext);}}
SynchronousMethodHandler
主要是用来同步请求的
final class SynchronousMethodHandler implements MethodHandler {static class Factory implements MethodHandler.Factory<Object> {public MethodHandler create(Target<?> target,MethodMetadata md,Object requestContext) {final RequestTemplate.Factory buildTemplateFromArgs =requestTemplateFactoryResolver.resolve(target, md);return new SynchronousMethodHandler(target, client, retryer, requestInterceptors,logger, logLevel, md, buildTemplateFromArgs, options,responseHandler, propagationPolicy);}}private SynchronousMethodHandler(Target<?> target,Client client,Retryer retryer,List<RequestInterceptor> requestInterceptors,Logger logger,Logger.Level logLevel,MethodMetadata metadata,RequestTemplate.Factory buildTemplateFromArgs,Options options,ResponseHandler responseHandler,ExceptionPropagationPolicy propagationPolicy) {this.target = checkNotNull(target, "target");this.client = checkNotNull(client, "client for %s", target);this.retryer = checkNotNull(retryer, "retryer for %s", target);this.requestInterceptors =checkNotNull(requestInterceptors, "requestInterceptors for %s", target);this.logger = checkNotNull(logger, "logger for %s", target);this.logLevel = checkNotNull(logLevel, "logLevel for %s", target);this.metadata = checkNotNull(metadata, "metadata for %s", target);this.buildTemplateFromArgs = checkNotNull(buildTemplateFromArgs, "metadata for %s", target);this.options = checkNotNull(options, "options for %s", target);this.propagationPolicy = propagationPolicy;this.responseHandler = responseHandler;}public Object invoke(Object[] argv) throws Throwable {...}
}
SynchronousMethodHandler实现了MethodHandler
接口, 重了invoke
方法, 并且定义了Factory
的静态内部类.SynchronousMethodHandler
实现MethodHandler
, SynchronousMethodHandler.Factory
实现MethodHandler.Factory
, 这个内聚被feign玩的明明白边。
SynchronousMethodHandler.Factory也比较简单, 仅提供了一个创建SynchronousMethodHandler
的方法
static class Factory implements MethodHandler.Factory<Object> {public MethodHandler create(Target<?> target,MethodMetadata md,Object requestContext) {final RequestTemplate.Factory buildTemplateFromArgs =requestTemplateFactoryResolver.resolve(target, md);return new SynchronousMethodHandler(target, client, retryer, requestInterceptors,logger, logLevel, md, buildTemplateFromArgs, options,responseHandler, propagationPolicy);}
}
其中的RequestTemplate.Factory
在之前的文章中也有介绍, 以及一些其它比较重要的依赖项, 例如Client
, ResponseHandler
也都介绍过
我们看到聚焦于核心方法invoke
invoke
@Override
public Object invoke(Object[] argv) throws Throwable {// 构建模版参数RequestTemplate template = buildTemplateFromArgs.create(argv);Options options = findOptions(argv);// 浅复制, 因为retryer是全局的, 不能每次都修改, 所以得复制一份Retryer retryer = this.retryer.clone();while (true) {try {// 使用Client执行请求, 并用ResponseHandler处理返回结果return executeAndDecode(template, options);} catch (RetryableException e) {try {// 处理重试; 重试等待成功后会继续重新走while循环retryer.continueOrPropagate(e);} catch (RetryableException th) {Throwable cause = th.getCause();// 异常传播机制, 抛出原始异常; 默认是NONEif (propagationPolicy == UNWRAP && cause != null) {throw cause;} else {// 抛出包装后的异常throw th;}}// 打印重试日志if (logLevel != Logger.Level.NONE) {logger.logRetry(metadata.configKey(), logLevel);}}}}/*** 默认当前线程上下文中的Options是空的*/
Options findOptions(Object[] argv) {// 参数为空的话, 从当前线程上下文中获取当前方法的Options们if (argv == null || argv.length == 0) {return this.options.getMethodOptions(metadata.method().getName());}// 1.如果有Options参数, 则返回第一个Options, 否则从当前线程上下文中获取当前方法的Options们return Stream.of(argv).filter(Options.class::isInstance).map(Options.class::cast).findFirst().orElse(this.options.getMethodOptions(metadata.method().getName()));}
小结一下
- 通过
RequestTemplate.Factory
解析方法构建请求模板 - 获取当前方法的
Options
(http请求用) - 使用Client执行请求, 并用ResponseHandler处理返回结果
- 如果返回重试异常
RetryableException
, 那么使用Retryer.Default
进行重试 - 如果重试发生异常, 如果异常传播机制是
ExceptionPropagationPolicy#UNWRAP
, 那么抛出原始异常, 否则抛出包装后的异常 - 非
Logger.Level.NONE
等级的日志级别要打印重试日志
executeAndDecode
Object executeAndDecode(RequestTemplate template, Options options) throws Throwable {// 执行拦截器并构建参数Request request = targetRequest(template);// 打印日志; 默认是Logger.Level.NONEif (logLevel != Logger.Level.NONE) {logger.logRequest(metadata.configKey(), logLevel, request);}Response response;long start = System.nanoTime();try {// 执行调用response = client.execute(request, options);// 构建响应结果; 也就多加了template参数response = response.toBuilder().request(request).requestTemplate(template).build();} catch (IOException e) {// 异常之后打印日志if (logLevel != Logger.Level.NONE) {logger.logIOException(metadata.configKey(), logLevel, e, elapsedTime(start));}// 直接抛重试异常throw errorExecuting(request, e);}// 计算请求耗时long elapsedTime = TimeUnit.NANOSECONDS.toMillis(System.nanoTime() - start);// 交给相应结果处理器ResponseHandler处理返回值return responseHandler.handleResponse(metadata.configKey(), response, metadata.returnType(), elapsedTime);}Request targetRequest(RequestTemplate template) {// 调真实调用前执行请求拦截器for (RequestInterceptor interceptor : requestInterceptors) {interceptor.apply(template);}// 这里target默认是HardCodedTarget// 用@QueryMap表示的参数给url的占位符赋值return target.apply(template);
}
这里看一下target.apply方法
public interface Target<T> {public static class HardCodedTarget<T> implements Target<T> {@Overridepublic Request apply(RequestTemplate input) {// http请求if (input.url().indexOf("http") != 0) {// 设置请求地址; 这里会将url中的占位符替换为参数值input.target(url());}// 构建请求对象return input.request();}}
}
这里有个input.target(url());
, 在前面的RequestTemplate.Factory文章中提到过, @QueryMap
标识的参数在解析后直接放到了RequestTemplate
中, 并没有使用到, 到了这里就可以给url上的占位符设置值了
对executeAndDecode
方法小结一下
- 执行请求拦截器, 处理请求模板
RequestTemplate
- 调用
target.apply
方法, 给url占位符设置值 - 使用
Client
执行请求 - 如果请求失败, 直接抛重试异常
- 使用
ResponseHandler
处理请求结果
下面看看重试逻辑
Retryer#Default
class Default implements Retryer {/*** 最大重试次数; 默认5次*/private final int maxAttempts;/*** 重试间隔起始时间,单位是毫秒; 默认100ms*/private final long period;/*** 重试最大间隔时间; 默认1s*/private final long maxPeriod;/*** 当前重试次数*/int attempt;/*** 重试等待的总时长;单位是毫秒*/long sleptForMillis;public Default() {this(100, SECONDS.toMillis(1), 5);}public Default(long period, long maxPeriod, int maxAttempts) {this.period = period;this.maxPeriod = maxPeriod;this.maxAttempts = maxAttempts;this.attempt = 1;}public void continueOrPropagate(RetryableException e) {// 超过最大重试次数, 直接抛异常if (attempt++ >= maxAttempts) {throw e;}// 重试间隔long interval;// 服务端有返回重试时间点if (e.retryAfter() != null) {// 重试间隔 = 服务端返回的重试时间点 - 当前时间interval = e.retryAfter() - currentTimeMillis();// 超过最大重试间隔; 默认1sif (interval > maxPeriod) {interval = maxPeriod;}// 重试间隔小于0, 直接return, 也就是立刻重试if (interval < 0) {return;}} else {// 服务器没有指定重试时间点, feign客户端自动计算重试间隔时间interval = nextMaxInterval();}try {// 直接用sleep暂停线程; sleep函数会释放cpu资源Thread.sleep(interval);} catch (InterruptedException ignored) {// 当前线程打上被中断标识Thread.currentThread().interrupt();throw e;}// 重试等待总时长sleptForMillis += interval;}/*** 计算重试时间间隔; 时间间隔是递增的, 每次乘以1.5* 例如重试初始间隔是1000ms, 第一次重试就是1x1.5^0=1s,第二次重试间隔是1x1.5^1=1.5s, 第三次重试间隔是1x1.5^2=2.25s, 以此类推.但不会大于最大间隔时间**/long nextMaxInterval() {long interval = (long) (period * Math.pow(1.5, attempt - 1));return Math.min(interval, maxPeriod);}@Overridepublic Retryer clone() {return new Default(period, maxPeriod, maxAttempts);}}
方法小结
- 重试器的默认是先是
Retryer#Default
- 如果服务端有返回重试时间点, 得出
重试间隔 = 服务端返回的重试时间点 - 当前时间
- 如果重试间隔大于最大的重试间隔, 也就是1s, 那么客户端等待1s后重试
- 如果重试间隔小于0, 那么直接return, 也就是like重试
- 如果服务器没有指定重试时间点, feign客户端自动计算重试间隔时间, 计算规则如下
- 时间间隔是递增的, 每次乘以1.5; 例如重试初始间隔是1000ms, 第一次重试就是1x1.5^0 = 1s,第二次重试间隔是1x1.5^1=1.5s, 第三次重试间隔是1x1.5^2=2.25s, 以此类推.但不会大于最大间隔时间
不仅是feign使用sleep做重试间隔等待, Spring的重试框架spring-retry也是如此, 以后要是你的同事不让你在项目中用sleep, 你就甩源码给他看
总结
SynchronousMethodHandler
是同步请求的核心类, 与SynchronousMethodHandler.Factory
内聚在同一个类中, 实现的接口也是都内聚在MethodHandler
和MethodHandler.Factory
上SynchronousMethodHandler
实现MethodHandler
接口的invoke
方法, 依赖RequestTemplate.Factory
组件构建请求模板RequestTemplate
并填充参数- 通过依赖的
Client
组件进行接口请求, 在请求前会先执行请求拦截器, 并对http请求的url进行@QueryMap
参数的填充 - 通过依赖的
ResponseHandler
组件处理返回后的结果, 用户可以在ResponseHandler
的响应处理中添加额外的责任链节点(响应拦截器)做特殊的处理 - 当请求失败后, 直接重试, 当处理响应结果失败后, 如果抛出重试异常, 也会进入重试
- 重试可以由服务端返回的响应头
Retry-After
控制, 如果响应头中有返回具体的重试时间点, 计算重试间隔重试间隔 = 服务端返回的重试时间点 - 当前时间
, 如果重试间隔大于最大间隔数(也就是1s), 那么sleep(1s)后重试, 如果重试间隔小于0, 那么立即重试, 否则按计算的重试间隔sleep - 如果服务端没有返回响应头
Retry-After
, 那么默认按照 第一次重试间隔100ms, 第二次150, 第n次 100 x 1.5^n-1次方的时间间隔重试