当前位置: 首页 > 科技观察

如何控制方法调用的Timeout超时,主动中断调用请求

时间:2023-03-23 11:51:33 科技观察

前言在我们实际的开发过程中,经常会遇到一些场景:1、调用方法超过1秒,就应该停止调用,而它不应该一直被阻塞。避免挂掉自己的服务资源。2.对于可能出现不可预知的死锁/死循环的代码,必须加一个时间阈值,避免阻塞。许多开源框架都有超时响应设置;如果是自己开发的服务,怎么办呢?JDK的Future在jdk中有一个future类,里面有获取等待超时的方法。主要方法:cancel():取消任务get():等待任务执行完成,并获取执行结果get(longtimeout,TimeUnitunit):等待任务在指定时间内执行完毕,并抛出超时到期时出现异常。本文不重点介绍以后的方法,大家可以网上补上。Guava中的超时Google开源的Guava工具包比较强大;它包括超时控制。里面有一个。TimeLimiter是一个接口,下面有两个子类。FakeTimeLimiter,常用于调试,限时超时调试。SimpleTimeLimiter常用于形式化方法,调用方法超时,即抛出异常。SimpleTimeLimiter类有两种实现超时控制的方式,代理方式和回调方式。1、基于代理模式Guava使用JDK动态代理实现的AOP拦截,所以代理类必须实现一个接口。可以对类中的所有方法实现超时控制。pom依赖于com.google.guavaguava29.0-jre定义接口,定义一个学生服务接口;publicinterfaceStudentService{/***根据学号获取学名*@paramstudentId*@return*/StringgetStudentNameById(IntegerstudentId);/***根据学生id获取学生爱好*@paramstudentId*@return*/ListgetStudentHobbyById(IntegerstudentId);}接口实现根据id获取姓名和爱好;@ServicepublicclassStudentServiceImplimplementsStudentService{privatestaticLoggerlogger=LoggerFactory.getLogger(StudentServiceImpl.class);@OverridepublicStringgetStudentNameById(IntegerstudentId){try{TimeUnit.SECONDS.sleep(3);}catch(Exceptione){}return"张三";}@OverridepublicListgetStudentHobbyById(IntegerstudentId){try{TimeUnit.SECONDS.sleep(10);}catch(Exceptione){}returnLists.newArrayList("Basketball","Badminton");}}获取name方法需要3秒;10秒搞定爱好方法如何调用@RestControllerpublicclassTimeoutController{privatestaticLoggerlogger=LoggerFactory.getLogger(TimeoutController.class);@Autowired私人StudentServicestudentService;@GetMapping("/test/timeout")publicvoidtest01(){SimpleTimeLimitersimpleTimeLimiter=newSimpleTimeLimiter();StudentServicestudentServiceProxy=simpleTimeLimiter.newProxy(this.studentService,StudentService.class,6,TimeUnit.SECONDS);logger.info("获取学生姓名------开始");尝试{StringstudentNameById=studentServiceProxy.getStudentNameById(1);logger.info("学生姓名:{}",studentNameById);}catch(Exceptione){logger.error("获取名称调用异常:{}",e.getMessage());}logger.info("获取学生姓名------结束");logger.info("==================================");logger.info("获取学生爱好------开始");尝试{ListstudentHobbyById=studentServiceProxy.getStudentHobbyById(1);logger.info("学生爱好:{}",studentHobbyById.toString());}catch(Exceptione){logger.error("GetHobby调用异常:{}",e.getMessage());}logger.info("获取学生偏好------结束");}}以上是调用代码,核心代码如下:SimpleTimeLimitersimpleTimeLimiter=newSimpleTimeLimiter();StudentServicestudentServiceProxy=simpleTimeLimiter.newProxy(this.studentService,StudentService.class,6,TimeUnit.SECONDS);使用SimpleTimeLimiter创建一个新的代理对象studentServiceProxy,并传递6秒的超时设置。我们只需要在调用方法时捕获TimeoutException即可。执行结果如下:上述结果中,获取爱好的方法在6秒后中断,抛出异常。我们发现配置了6秒的超时后,StudentServiceProxy代理对象的所有方法都在6秒内超时了。解耦,重构代码我们发现上面的代码需要在调用方实现SimpleTimeLimiter的配置,感觉耦合度有点高。我们可以稍微修改一下代码。接口定义/***@authorgujiachun*/publicinterfaceStudentService{/***根据学生id获取学生姓名*@paramstudentId*@return*/StringgetStudentNameById(IntegerstudentId);/***根据学生id获取学生姓名---超时控制*@paramstudentId*@return*/StringgetStudentNameByIdWithTimeout(IntegerstudentId);/***根据学生id获取学生爱好*@paramstudentId*@return*/ListgetStudentHobbyById(IntegerstudentId);/***根据学生id获取学生兴趣爱好---超时控制*@paramstudentId*@return*/ListgetStudentHobbyByIdWithTimeout(IntegerstudentId);}接口实现@ServicepublicclassStudentServiceImplimplementsStudentService{privatestaticLoggerlogger=LoggerFactory.getLogger(StudentServiceImpl.class);privatestaticfinalTimeLimitertimeLimiter=newSimpleTimeLimiter();私人静态最终长TimeOutSec=6;私人学生服务学生服务代理;公共StudentServiceImpl(){studentServiceProxy=timeLimiter.newProxy(this,StudentService.class,TimeOutSec,TimeUnit.SECONDS);}@OverridepublicStringgetStudentNameById(IntegerstudentId){try{TimeUnit.SECONDS.sleep(3);}catch(Exceptione){}return"张三";}@OverridepublicStringgetStudentNameByIdWithTimeout(IntegerstudentId){returnstudentServiceProxy.getStudentNameById(studentId);}@OverridepublicListgetStudentHobbyById(IntegerstudentId){try{TimeUnit.SECONDS.sleep(10);}catch(Exceptione){}returnLists.newArrayList("篮球","羽毛球");}@OverridepublicListgetStudentHobbyByIdWithTimeout(IntegerstudentId){returnstudentServiceProxy.getStudentHobbyById(studentId);}}调用方法@RestControllerpublicclassTimeoutController{privatestaticLoggerlogger=LoggerFactory.getLogger(TimeoutController.class);@Autowired私人StudentServicestudentService;@GetMapping("/test/timeout")publicvoidtest01(){logger.info("获取学生姓名-----开始");尝试{StringstudentNameById=studentService.getStudentNameByIdWithTimeout(1);logger.info("学生姓名:{}",studentNameById);}catch(Exceptione){logger.error("获取名称调用异常:{}",e.getMessage());}logger.info("获取学生姓名------结束");logger.info("================================");logger.info("获取学生爱好------开始");尝试{ListstudentHobbyById=studentService.getStudentHobbyByIdWithTimeout(1);logger.info("学生爱好:{}",studentHobbyById.toString());}catch(Exceptione){logger.error("获取爱好调用异常:{}",e.getMessage());}logger.info("获取学生爱好------End");}}这种改造很好,调用者不需要关心具体的timeout实现,直接调用即可。一段代码。@GetMapping("/test/timeout1")publicvoidtest02(){logger.info("获取学生姓名------开始");SimpleTimeLimitersimpleTimeLimiter=newSimpleTimeLimiter();Callabletask=newCallable(){@OverridepublicStringcall()throwsException{try{TimeUnit.SECONDS.sleep(10);}catch(Exceptione){}return"张三";}};尝试{simpleTimeLimiter.callWithTimeout(task,6,TimeUnit.SECONDS,true);}catch(Exceptione){logger.error("获取名称调用异常:{}",e.getMessage());}logger.info("Getstudentname------End");}上面代码中定义了callable来使用业务代码。执行结果如下线程池定义SimpleTimeLimiteris@Bean(name="taskPool01Executor")//核心线程数taskExecutor.setCorePoolSize(10);//线程池维护最大线程数,只有当缓冲队列满了才会申请超过核心线程数的线程taskExecutor.setMaxPoolSize(100);//缓存队列taskExecutor.setQueueCapacity(50);当超出核心线程的线程在空闲时间到达后会被销毁taskExecutor.setKeepAliveSeconds(200);//异步方法内部线程名taskExecutor.setThreadNamePrefix("TaskPool-01-");/***当线程池的任务缓存队列已满,线程池中的线程数达到maximumPoolSize时。如果还有任务过来,则采用任务拒绝策略*通常有以下四种策略:*ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException。*ThreadPoolExecutor.DiscardPolicy:同样是丢弃任务,但不会抛出异常。*ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复这个过程)*ThreadPoolExecutor.CallerRunsPolicy:重新尝试添加当前任务,并自动重复调用execute()方法,直到它成功*/taskExecutor.setRejectedExecutionHandler(newThreadPoolExecutor.AbortPolicy());taskExecutor.setWaitForTasksToCompleteOnShutdown(true);taskExecutor.initialize();returntaskExecutor;}执行结果如下:总结SimpleTimeLimiter对象本质上是利用了JDK中的Future对象来实现Timeout。源码如下:由Guava封装,使用非常方便。朋友们可以自己试试。

最新推荐
猜你喜欢