美文网首页
Java 接口 限制请求时间

Java 接口 限制请求时间

作者: Yellowtail | 来源:发表于2019-06-13 19:17 被阅读0次

    0x1 概述

    我们公司采取了前后端分离的模式,这是背景
    客户端调用后台接口的时候,他们对http请求设置了一个超时时间: 10
    偶尔,后端由于各种问题,请求不能在 10秒内返回,客户端其实已经报错了,但是服务器还在老老实实的继续执行,浪费了服务器和数据库性能
    特别的是,在后端代码没有写好的时候,比如查询某些数据的时候,过滤字段没有加索引,那么数据库就会全表扫描,
    当数据量比较大的时候,很容易花的时间会比较久,时间一旦超过了客户端的阈值,客户看到报错了,就会再次请求一次
    如此往复,数据库就在短短的时间内CPU飙到了100%
    进而导致整个系统全部变慢,所有接口超时,用户就会多次重试
    恶性循环。。

    0x2思路

    所以,我想了一个办法,在接口过来的时候,在 filter 里面设置一个 时间戳,也就是请求开始时间

    然后对 DAO 的每个方法就进行切入,每次进行数据库操作前,判断一下当时时间,和开始时间的时间差,
    是否已经达到了阈值,如果是,就抛异常,快速结束这个请求;如果没有就放行

    当接口提前退出的时候,其实用户还是会重试,只是比之前好点,因为之前我们有些接口可能会一直执行下去,
    最后花了4~5 分钟才执行完,这个过程其实没有必要,就算我们执行完了,客户端也收不到我们的结果了,早点止损比较好

    这个方法其实不能解决超时的问题,只是作为一种快速止损防止由点及面的故障扩散

    0x3 TimeStamp

    在思路里提到了 时间戳 TimeStamp
    如果用普通的思路,从 filter传递一个参数到 切面里,对代码侵入性太大,所以我们需要一个工具类,可以借助 ThreadLocal 来实现

    public class ThreadLocalUtils {
        
        private static ThreadLocalUtils INSTANCE = new ThreadLocalUtils();
        
        //请求开始时的时间戳
        private static ThreadLocal<Long> startTimeStamp = new ThreadLocal<Long>() {
            //涉及到 Long 到 long 的转换,所以设置一个默认值
            @Override
            protected Long initialValue() {
                return 0L;
            }
        };
        
        
        /**
         * <br>设置 时间戳
         *
         * @param value
         * @author YellowTail
         * @since 2019-06-10
         */
        public static void setTimeStamp(long value) {
            startTimeStamp.set(value);
        }
        
        /**
         * <br>得到时间戳
         *
         * @return
         * @author YellowTail
         * @since 2019-06-10
         */
        public static long getTimeStamp() {
            return startTimeStamp.get();
        }
        
        /**
         * <br>清除 时间戳
         *
         * @author YellowTail
         * @since 2019-06-10
         */
        public static void clearTimeStamp() {
            startTimeStamp.remove();
        }
    }
    

    0x4 filter

    挑第一个 filter 设置一下 时间戳

    @Override
    public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
            throws IOException, ServletException {
        
        //给当前线程设置 开始时间戳
        long start = System.currentTimeMillis();
        ThreadLocalUtils.setTimeStamp(start);
        
        LOGGER.info("request is start {}", start);
        
        chain.doFilter(request, response);
        
        //清理时间戳
        ThreadLocalUtils.clearTimeStamp();
        
    }
    

    0x5 切面

    @Component
    @Aspect
    public class TimeOutAspectjService {
    
        private static final Logger LOGGER = LoggerFactory.getLogger(UnitModifyAspectjService.class);
        
        //7秒
        public static final long LIMIT = 7 * 1000;
        
        @Pointcut("execution(* com.xxx.MongoDAOSupport.*(..) ) ")
        private void invokeDao() {}
        
        //环绕类型,可以自行决定执行方法的时机
        @Around("invokeDao()")
        public Object updateUnit(ProceedingJoinPoint joinPoint) throws Throwable {
            long timeStamp = ThreadLocalUtils.getTimeStamp();
            
            if (0 != timeStamp) {
                long gap = System.currentTimeMillis() - timeStamp;
                if (gap >= LIMIT) {
                    
                    LOGGER.error("DAO methods exceed time limit, start is {}, gap is {}", timeStamp, gap);
                    
                    //抛异常,结束当前请求
                    throw new BizExcetion(ErrorMsg.TIME_OUT);
                }
            }
            
            //有异常抛出来,这里不捕获
            return joinPoint.proceed();
        }
    }
    
    

    注意,com.xxx.MongoDAOSupport 是我们所有 DAO 类的父类方法

    至此,所有的开发完成,一旦请求时间超过7秒(留点余量,大家可以自行调整),接口就会退出

    相关文章

      网友评论

          本文标题:Java 接口 限制请求时间

          本文链接:https://www.haomeiwen.com/subject/ferefctx.html