该文章适用于比较重的操作的服务端。
我们经常会遇到远程调用超时的情况,一般这种情况客户端会有一个超时时间,超过该时间,那客户端抛异常,客户端可以进行重试等操作,那此时此刻,服务端呢?
最近遇到了一个线上问题,我的一个服务是文件处理,所有的接口都会进行文件转换相关的操作(word转pdf、word转html等操作),文件操作是比较重的操作,遇到比较大的文件,服务可能要处理很长很长时间。某用户上传了一个不算大的excel文件,只有437.64KB,但是这个文件有一百多万行,这个文件请求到服务端,服务端会一直处理(处理了一天多还没有处理完),占用了大量的cpu、内存资源,此时客户端可能还会重试,那么其他的文件转换操作都会变得很慢。
百度了很久,发现大家都是作为客户端设置超时时间,那我们来一点不一样的,想一想服务端遇到这种情况要怎么自我保护,防止超过自己能力的请求将自己拉垮。
我们如何定义超过自己能力的请求?文件大小?文件复杂程度?
首先我们排除文件大小,上面说过,用户上传的excel只有437.64KB
那文件复杂程度?稍微想一想也可以排除,要得到文件的复杂程度是要解析这个文件的,解析这个文件本身就很慢。
最终采取了转换时间的方案,如果转换时间超过某个阈值,那就放弃这次处理。
因为文件转换服务都是文件操作,都比较重,我直接在所有的controller上加了切面(项目是spring boot),不多说了,直接上代码
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Component
@Aspect
public class TimeLimitAspect {
@Around("(execution(* com.kuaishou.is.controller..*.*(..)))")
public Object around(ProceedingJoinPoint pjp) {
TimeLimitThread timeLimitThread = new TimeLimitThread();
timeLimitThread.setPjp(pjp);
timeLimitThread.setFather(Thread.currentThread());
timeLimitThread.start();
try {
Thread.sleep(5 * 60 * 1000);
timeLimitThread.interrupt();
} catch (InterruptedException e) {
log.info("执行完成");
}
return timeLimitThread.getRes();
}
private static class TimeLimitThread extends Thread {
@Setter
private ProceedingJoinPoint pjp;
@Setter
private Thread father;
@Getter
private Object res;
@Override
public void run() {
Object r = null;
try {
r = pjp.proceed();
} catch (Throwable throwable) {
log.error("aspect error: ", throwable);
r = ResponseEntity.unprocessableEntity().build();
}
this.res = r;
this.father.interrupt();
}
}
}
|