SpringBoot+Hadoop项目MapReduce任务完成后无错误自动关闭问题求助
我之前在做SpringBoot整合Hadoop的批处理项目时,也碰到过一模一样的情况!结合你的环境(MacBook Pro High Sierra、Hadoop2.7.3、SpringBoot2.0.0、spring-data-hadoop-boot2.5.0),核心原因大概率是MapReduce任务是异步执行的,SpringBoot的主线程在提交任务后,发现没有其他活跃的非守护线程,就自动关闭了应用上下文。之前Jsoup爬虫的try-catch能解决,是因为爬虫任务本身是在主线程同步执行的,而MapReduce的任务提交逻辑完全不同,所以那套方法没用。
给你几个实测有效的解决方案:
1. 阻塞主线程直到MapReduce任务完成
MapReduce的Job类本身提供了waitForCompletion(true)方法,这个方法会让主线程一直阻塞,直到任务执行完成,还能输出详细的任务运行日志,一举两得。
如果是原生Hadoop API调用,代码可以这么写:
@Service public class MapReduceTaskService { public void runMapReduceJob() throws IOException, InterruptedException, ClassNotFoundException { Configuration conf = new Configuration(); // 配置Hadoop集群地址等参数 conf.set("fs.defaultFS", "hdfs://your-hadoop-host:9000"); Job job = Job.getInstance(conf, "sample-data-process-job"); job.setJarByClass(MapReduceTaskService.class); job.setMapperClass(YourMapper.class); job.setReducerClass(YourReducer.class); job.setOutputKeyClass(Text.class); job.setOutputValueClass(IntWritable.class); // 设置输入输出路径 FileInputFormat.addInputPath(job, new Path("/input")); FileOutputFormat.setOutputPath(job, new Path("/output")); // 阻塞主线程直到任务完成 boolean jobSuccess = job.waitForCompletion(true); if (!jobSuccess) { throw new RuntimeException("MapReduce任务执行失败,请查看Hadoop日志"); } } }
如果用的是spring-data-hadoop的HadoopTemplate,可以提交任务后获取Job实例再调用阻塞方法:
@Autowired private HadoopTemplate hadoopTemplate; public void runJobWithTemplate() throws Exception { Job job = hadoopTemplate.getJob("your-job-name"); // 配置job参数... hadoopTemplate.submitJob(job); // 等待任务完成 job.waitForCompletion(true); }
2. 配置SpringBoot保持应用活跃(非Web场景)
如果你的项目是批处理类(不是Web应用),SpringBoot默认会在没有活跃非守护线程时自动关闭。可以通过以下两种方式解决:
方式一:用CountDownLatch手动控制主线程
在启动类里注入一个CountDownLatch,任务完成后触发倒计时:
@SpringBootApplication public class HadoopBatchApplication { public static void main(String[] args) throws InterruptedException { ConfigurableApplicationContext context = SpringApplication.run(HadoopBatchApplication.class, args); // 等待任务完成信号 CountDownLatch latch = context.getBean(CountDownLatch.class); latch.await(); } @Bean public CountDownLatch countDownLatch() { // 初始化倒计时为1,任务完成后countDown一次 return new CountDownLatch(1); } }
然后在你的任务执行类里,任务完成后调用countDown():
@Autowired private CountDownLatch latch; public void executeTask() throws Exception { // 执行MapReduce任务... job.waitForCompletion(true); // 任务完成,释放主线程 latch.countDown(); }
方式二:修改SpringBoot启动配置
在application.properties里添加:
spring.main.web-application-type=none spring.main.exit-code-generators.enabled=false
这个配置会告诉SpringBoot不要自动关闭应用,直到手动触发退出。
3. 检查Hadoop客户端的守护线程问题
Hadoop客户端在提交任务时,会启动一些后台守护线程,SpringBoot不会将守护线程视为“活跃线程”,所以主线程还是会退出。可以通过修改Hadoop的配置,让相关线程变为非守护线程:
在Hadoop的core-site.xml里添加:
<property> <name>hadoop.client.threads.non-daemon</name> <value>true</value> </property>
不过这个配置在Hadoop2.7.x版本是有效的,你可以试试。
为什么之前的try-catch没用?
之前Jsoup爬虫的场景,爬虫任务是在主线程同步执行的,异常会直接抛出到主线程,try-catch能捕获并阻止主线程意外终止;但MapReduce任务是异步提交到Hadoop集群的,主线程提交任务后就继续执行后续代码,即使任务里有异常,也不会影响主线程的执行流程,所以主线程还是会走到结束,导致应用关闭。
内容的提问来源于stack exchange,提问作者LOGAN

