作者:陈昌浩
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)
@Documented
public @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>() {
@Override
public 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 并且数量大于1
if (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 小结
通过自定义分片工具,能够疾速的对老代码进行分片解决,而且减少了重试机制,进步了程序的可用性,进步了对老代码的重构效率。
发表回复