当前位置: 首页 > news >正文

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在之前的文章中也有介绍, 以及一些其它比较重要的依赖项, 例如ClientResponseHandler也都介绍过

我们看到聚焦于核心方法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()));}

小结一下

  1. 通过RequestTemplate.Factory解析方法构建请求模板
  2. 获取当前方法的Options(http请求用)
  3. 使用Client执行请求, 并用ResponseHandler处理返回结果
  4. 如果返回重试异常RetryableException, 那么使用Retryer.Default进行重试
  5. 如果重试发生异常, 如果异常传播机制是ExceptionPropagationPolicy#UNWRAP, 那么抛出原始异常, 否则抛出包装后的异常
  6. 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方法小结一下

  1. 执行请求拦截器, 处理请求模板RequestTemplate
  2. 调用target.apply方法, 给url占位符设置值
  3. 使用Client执行请求
  4. 如果请求失败, 直接抛重试异常
  5. 使用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);}}

方法小结

  1. 重试器的默认是先是Retryer#Default
  2. 如果服务端有返回重试时间点, 得出重试间隔 = 服务端返回的重试时间点 - 当前时间
  • 如果重试间隔大于最大的重试间隔, 也就是1s, 那么客户端等待1s后重试
  • 如果重试间隔小于0, 那么直接return, 也就是like重试
  1. 如果服务器没有指定重试时间点, feign客户端自动计算重试间隔时间, 计算规则如下
  • 时间间隔是递增的, 每次乘以1.5; 例如重试初始间隔是1000ms, 第一次重试就是1x1.5^0 = 1s,第二次重试间隔是1x1.5^1=1.5s, 第三次重试间隔是1x1.5^2=2.25s, 以此类推.但不会大于最大间隔时间

不仅是feign使用sleep做重试间隔等待, Spring的重试框架spring-retry也是如此, 以后要是你的同事不让你在项目中用sleep, 你就甩源码给他看

总结

  1. SynchronousMethodHandler是同步请求的核心类, 与SynchronousMethodHandler.Factory内聚在同一个类中, 实现的接口也是都内聚在MethodHandlerMethodHandler.Factory
  2. SynchronousMethodHandler实现MethodHandler接口的invoke方法, 依赖RequestTemplate.Factory组件构建请求模板RequestTemplate并填充参数
  3. 通过依赖的Client组件进行接口请求, 在请求前会先执行请求拦截器, 并对http请求的url进行@QueryMap参数的填充
  4. 通过依赖的ResponseHandler组件处理返回后的结果, 用户可以在ResponseHandler的响应处理中添加额外的责任链节点(响应拦截器)做特殊的处理
  5. 当请求失败后, 直接重试, 当处理响应结果失败后, 如果抛出重试异常, 也会进入重试
  6. 重试可以由服务端返回的响应头Retry-After控制, 如果响应头中有返回具体的重试时间点, 计算重试间隔重试间隔 = 服务端返回的重试时间点 - 当前时间, 如果重试间隔大于最大间隔数(也就是1s), 那么sleep(1s)后重试, 如果重试间隔小于0, 那么立即重试, 否则按计算的重试间隔sleep
  7. 如果服务端没有返回响应头Retry-After, 那么默认按照 第一次重试间隔100ms, 第二次150, 第n次 100 x 1.5^n-1次方的时间间隔重试

http://www.mrgr.cn/news/77962.html

相关文章:

  • 传奇故事杂志传奇故事杂志社传奇故事编辑部2024年第28期目录
  • 2025年01月09日Github流行趋势
  • Mysql快速列出来所有列信息
  • YARN 架构组件及原理
  • av1学习笔记(二):sequence_header_obu
  • 基于 Apache Commons Pool 实现的 gRPC 连接池管理类 GrpcChannelPool 性能分析与优化
  • Spring Boot 动态数据源切换
  • 十一、排他思想、window、延时定时器、间歇函数、时间戳、location、navigator、history、本地存储localStorage
  • C++设计模式-享元模式
  • 安装 Docker(使用国内源)
  • 从0开始学PHP面向对象内容之常用设计模式(适配器,桥接,装饰器)
  • 大模型系列11-ray
  • 疑难Tips:NextCloud域名访问登录时卡住,显示违反内容安全策略
  • k8s网络服务
  • C#设计模式——抽象工厂模式(重点)
  • Vue3响应式原理
  • Springboot项目搭建-Maven打包编译
  • 演示如何使用 `nn.CrossEntropyLoss` 来计算交叉熵损失,计算损失值的演示代码,和讲解 ,CrossEntropyLoss 损失数值等于零的原因
  • hugo文章支持数学公式
  • oracle 12c查看执行过的sql及当前正在执行的sql
  • 【计算机网络】多路转接之select
  • 新华三嵌入式面试题及参考答案
  • 海信Java后端开发面试题及参考答案
  • 第三十九篇 ShuffleNet V1、V2模型解析
  • Optional类
  • Leetcode 51 N Queens