背景:
最近做了一个任务:将各个业务存于MySQL的旧日志文件(业务日志而非运行日志)迁移到新的日志系统中。众所周知,在企业级应用中,因用户各种操作而产生的日志是非常多的,那么在执行迁移日志的时候,接口执行的时间肯定会很长。这次迁移大概用了8个多小时才完全迁移成功。在开发环境写迁移代码的时候,领导有这么一个需求----希望在迁移的时候,能够随时中断迁移。
解决:
接口在正常情况下,会一直执行下去,除非遇见了异常或者资源耗尽,才会中途停止。显然资源耗尽是很危险的,我们只能考虑人为的提前终止循环里的操作,或者人为的制造异常。
步骤:
- SpringBoot的每一次接口请求相当于新建一个线程或者用线程池管理未被使用的线程。
- 在循环处理旧日志的代码里面加一个信号变量,控制循环的退出。
- 在迁移接口运行的过程中,使用额外的接口改变信号变量的值,达到退出循环的目的
- 在信号变量发生变化,退出执行的时候,选择直接return 和抛出异常。
- return 的话可能会在某次循环中一部分迁移成功了,而一部分迁移失败。
- 使用抛出异常加上事务控制,可以控制每一次循环成功要么成功,要么失败,记录失败的点,方便下次执行。
package com.controller;
import java.util.UUID;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* 描述:测试用的controller
*
* @since 2019年11月06日 下午8:05:48
* @author
*/
@RestController
@RequestMapping("/inter/test")
public class TestController {
//设置的信号变量,最好使用volatile 关键字或者使用AtomicInteger原子类
private volatile static int at = 1;
/**
* 测试用
*
* @return 接口返回结果
* @throws Exception
* 异常
*/
@GetMapping(value = "/test02")
public Object test02() throws Exception {
for (int i = 0; i < 9999; i++) {
//信号变量发生改变,抛出异常,加上事务,保证每一次执行要么成功,要么失败
if (at > 1) {
throw new Exception("中断调用");
}
System.out.println("当前线程02:" + i + "个" + Thread.currentThread().getName());
//模拟执行
Thread.sleep(1500);
}
return "SUCCESS";
}
/**
* 测试用(执行加一)
*
* @return 接口返回结果
* @throws Exception
* 异常
*/
@GetMapping(value = "/test03")
public Object test03() throws Exception {
++at;
System.out.println("当前线程03:" + Thread.currentThread().getName());
return "SUCCESS";
}
/**
* 因为线程被异常中断,已经结束,即使信号变量变回原来的也不会再执行
*
* @return 接口返回结果
* @throws Exception
* 异常
*/
@GetMapping(value = "/test04")
public Object test04() throws Exception {
--at;
System.out.println("当前线程04:" + Thread.currentThread().getName());
return "SUCCESS";
}
说明:
在接口执行的过程中,通过另外的一个接口来改变at信号变量的值,使得循环抛出异常,这样达到终止接口的目的。本质上,中断接口的访问就是中断一个线程,线程中断了,即使改变信号变量也不会再次恢复到之前的执行状态。