作者:陈昌浩
1 背景
随着数据量的增长,发现零碎在与其余零碎交互时,批量接口会呈现超时景象,发现原批量接口在实现时,没有做分片解决,当数据过大时或超过其余零碎阈值时,就会呈现谬误。因为与其余零碎交互比拟多,一个一个接口做分片优化,改变量较大,所以思考通过AOP解决此问题。
2 Spring-AOP
AOP (Aspect Orient Programming),直译过去就是 面向切面编程。AOP 是一种编程思维,是面向对象编程(OOP)的一种补充。面向对象编程将程序形象成各个档次的对象,而面向切面编程是将程序形象成各个切面。
Spring 中的 AOP 是通过动静代理实现的。 Spring AOP 不能拦挡对对象字段的批改,也不反对结构器连接点,咱们无奈在 Bean 创立时利用告诉。
3 性能实现
自定义分片解决分三个局部:自定义注解(MethodPartAndRetryer)、重试器(RetryUtil)、切面实现(RetryAspectAop)。
3.1 MethodPartAndRetryer
源码
@Target(ElementType.METHOD)@Retention(RetentionPolicy.RUNTIME)@Documentedpublic @interface MethodPartAndRetryer {/*** 失败重试次数* @return*/int times() default 3;/*** 失败距离执行工夫 300毫秒* @return*/long waitTime() default 300L;/*** 分片大小* @return*/int parts() default 200;}
@interface阐明这个类是个注解。
@Target是这个注解的作用域
public enum ElementType {/** 类、接口(包含正文类型)或枚举申明 */TYPE,/** 字段申明(包含枚举常量) */FIELD,/** 办法申明 */METHOD,/** 正式的参数申明 */PARAMETER,/** 构造函数申明 */CONSTRUCTOR,/** 局部变量申明 */LOCAL_VARIABLE,/** 正文类型申明*/ANNOTATION_TYPE,/** 程序包申明 */PACKAGE,/**类型参数申明*/TYPE_PARAMETER,/**类型的应用*/TYPE_USE}
@Retention注解的生命周期
public enum RetentionPolicy {/** 编译器解决完后不存储在class中*/SOURCE,/**正文将被编译器记录在类文件中,但不须要在运行时被VM保留。 这是默认值*/CLASS,/**编译器存储在class中,能够由虚拟机读取*/RUNTIME}
- times():接口调用失败时,重试的次数。
- waitTime():接口调用失败是,距离多长时间再次调用。
- int parts():进行分片时,每个分片的大小。
3.2 RetryUtil
源码
public class RetryUtil<V> {public Retryer<V> getDefaultRetryer(int times,long waitTime) {Retryer<V> retryer = RetryerBuilder.<V>newBuilder().retryIfException().retryIfRuntimeException().retryIfExceptionOfType(Exception.class).withWaitStrategy(WaitStrategies.fixedWait(waitTime, TimeUnit.MILLISECONDS)).withStopStrategy(StopStrategies.stopAfterAttempt(times)).build();return retryer;}}
阐明
- RetryerBuilder:是用于配置和创立Retryer的构建器。
- retryIfException:抛出runtime异样、checked异样时都会重试,然而抛出error不会重试。
- retryIfRuntimeException:只会在抛runtime异样的时候才重试,checked异样和error都不重试。
- retryIfExceptionOfType:容许咱们只在产生特定异样的时候才重试。
- withWaitStrategy:期待策略,每次申请距离。
- withStopStrategy:进行策略,重试多少次后进行。
3.3 RetryAspectAop
源码:
public class RetryAspectAop {public Object around(final ProceedingJoinPoint point) throws Throwable {Object result = null;final Object[] args = point.getArgs();boolean isHandler1 = isHandler(args);if (isHandler1) {String className = point.getSignature().getDeclaringTypeName();String methodName = point.getSignature().getName();Object firstArg = args[0];List<Object> paramList = (List<Object>) firstArg;//获取办法信息Method method = getCurrentMethod(point);//获取注解信息MethodPartAndRetryer retryable = AnnotationUtils.getAnnotation(method, MethodPartAndRetryer.class);//重试机制Retryer<Object> retryer = new RetryUtil<Object>().getDefaultRetryer(retryable.times(),retryable.waitTime());//分片List<List<Object>> requestList = Lists.partition(paramList, retryable.parts());for (List<Object> partList : requestList) {args[0] = partList;Object tempResult = retryer.call(new Callable<Object>() {@Overridepublic Object call() throws Exception {try {return point.proceed(args);} catch (Throwable throwable) {log.error(String.format("分片重试报错,类%s-办法%s",className,methodName),throwable);throw new RuntimeException("分片重试出错");}}});if (null != tempResult) {if (tempResult instanceof Boolean) {if (!((Boolean) tempResult)) {log.error(String.format("分片执行报错返回类型不能转化bolean,类%s-办法%s",className,methodName));throw new RuntimeException("分片执行报错!");}result = tempResult;} else if (tempResult instanceof List) {if(result ==null){result =Lists.newArrayList();}((List) result).addAll((List) tempResult);}else {log.error(String.format("分片执行返回的类型不反对,类%s-办法%s",className,methodName));throw new RuntimeException("不反对该返回类型");}} else {log.error(String.format("分片执行返回的后果为空,类%s-办法%s",className,methodName));throw new RuntimeException("调用后果为空");}}} else {result = point.proceed(args);}return result;}private boolean isHandler(Object[] args) {boolean isHandler = false;if (null != args && args.length > 0) {Object firstArg = args[0];//如果第一个参数是list 并且数量大于1if (firstArg!=null&&firstArg instanceof List &&((List) firstArg).size()>1) {isHandler = true;}}return isHandler;}private Method getCurrentMethod(ProceedingJoinPoint point) {try {Signature sig = point.getSignature();MethodSignature msig = (MethodSignature) sig;Object target = point.getTarget();return target.getClass().getMethod(msig.getName(), msig.getParameterTypes());} catch (NoSuchMethodException e) {throw new RuntimeException(e);}}}
阐明:
getCurrentMethod:获取办法信息即要做分片的批量调用的接口。
isHandler1:判断是否要做分片解决,只有第一参数是list并且list 的值大于1时才做分片解决。
around:具体分片逻辑。
- 获取要分片办法的参数。
- 判断是否要做分片解决。
- 获取办法。
- 获取重试次数、重试间隔时间和分片大小。
- 生成重试器。
- 依据设置的分片大小,做分片解决。
- 调用批量接口并处理结果。
4 性能应用
4.1 配置文件
4.2 代码示例
@MethodPartAndRetryer(parts=100)public Boolean writeBackOfGoodsSN(List<SerialDTO> listSerial,ObCheckWorker workerData)
只有在须要做分片的批量接口办法上,加上MethodPartAndRetryer注解就能够,重试次数、重试间隔时间和分片大小能够在注解时设置,也能够应用默认值。
5 小结
通过自定义分片工具,能够疾速的对老代码进行分片解决,而且减少了重试机制,进步了程序的可用性,进步了对老代码的重构效率。