spring 定时任务与 nacos 动态配置导致的线程 park

379 阅读3分钟

  新版本加了动态业务标识配置从 nacos 取的功能后,发现新增业务标识后创建任务的请求发不进去了,大概代码如下:

代码

controller

@Slf4j
@RestController
@RequestMapping({"/split"})
@Validated
public class PdfSplitController {
    @Resource
    private SplitPdfCommandApplication commandApplication;

        @PostMapping("/createPdfJob")
        public Result<String> createPdfJob(@Validated @RequestBody PdfSplitRequest pdfSplitRequest) {
            return commandApplication.create(pdfSplitRequest);
        }
    }

SplitTask 定时任务

@Component
public class PdfSplitJobTask {
    @Resource
    private SplitPdfCommandServiceImpl splitPdfCommandService;

    /**
     * 拆分定时任务
     */
    @Scheduled(fixedDelay = 1000)
    public void splitPdfCommand() {
        splitPdfCommandService.splitPdfCommand();
    }
}

SplitPdfCommandApplication

@Service
public class SplitPdfCommandApplication {
    @Resource
    private SplitPdfCommandService splitPdfCommandService;
    // 业务标识的集合,里面是个集合,nacos 动态刷新
    @Resource
    private BusinessProperties businessProperties;

    /**
     * 保存拆分请求
     */
    public Result<String> create(PdfSplitRequest pdfSplitRequest) {
        // 构建领域模型
        CreatPdfJob creatPdfJob = new CreatPdfJob(
                pdfSplitRequest.getBusinessId(), 
                pdfSplitRequest.getFileUrl()
        );
        // 校验,其实就是判断业务标识集合是否存在于list里
        creatPdfJob.check(businessProperties.getBusinessList());

        // 调用领域层
        splitPdfCommandService.createCommand(creatPdfJob);

        return Result.succeed(creatPdfJob.getPdfSplitJobId().getJobId());
    }
}

SplitPdfCommandServiceImpl

@Service
@Slf4j
@RefreshScope
public class SplitPdfCommandServiceImpl implements SplitPdfCommandService {
    /**
     * 最大页码限制
     */
    @Value("${split.maxPageNumber}")
    private Integer maxPageNumber;
    /**
     * 最大文件大小
     */
    @Value("${split.maxFileSize}")
    private Long maxFileSize;


    @Override
    public void createCommand(CreatPdfJob creatPdfJob) {
        try {
            pdfContext.getSplitPdfRepository().save(creatPdfJob);
        } catch (Exception e) {
            throw new BizException(ErrorEnum.BIZ_ADD_JOB_ERROR.getCode(), ErrorEnum.BIZ_ADD_JOB_ERROR.getMessage());
        }
    }

BusinessProperties

@Configuration
@ConfigurationProperties(prefix = "business")
public class BusinessProperties {
    public List<String> businessList;

    public List<String> getBusinessList() {
        return businessList;
    }

    public void setBusinessList(List<String> businessList) {
        this.businessList = businessList;
    }
}

  第一时间重启,拆分服务恢复正常,但是后续发现按以下步骤操作,总会导致线程 park(但是这块代码新建一个项目也无法实现线程 park,不太清楚为什么)

  1. 正常请求,没什么问题
  2. 修改 nacos 配置,新增一个业务标识,发布,BusinessProperties 收到新集合,生效
  3. 用新增的业务标识请求,卡死

排查过程

  1. 找到 Java 进程的 PID
  2. jstack 打印线程信息,信息如下:
"http-nio-8080-exec-1@11566" daemon prio=5 tid=0x62 nid=NA waiting
  java.lang.Thread.State: WAITING
	  at sun.misc.Unsafe.park(Unsafe.java:-1)
	  at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
	  at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:836)
	  at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireShared(AbstractQueuedSynchronizer.java:967)
	  at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireShared(AbstractQueuedSynchronizer.java:1283)
	  at java.util.concurrent.locks.ReentrantReadWriteLock$ReadLock.lock(ReentrantReadWriteLock.java:727)
	  at org.springframework.cloud.context.scope.GenericScope$LockedScopedProxyFactoryBean.invoke(GenericScope.java:494)
	  at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
	  at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:688)
	  at com.rongda.split.domain.service.pdf.split.impl.SplitPdfCommandServiceImpl$$EnhancerBySpringCGLIB$$cf626698.createCommand(<generated>:-1)
	  at com.rongda.split.application.service.pdf.split.SplitPdfCommandApplication.create(SplitPdfCommandApplication.java:57)
	  at com.rongda.split.web.api.controller.pdf.PdfSplitController.createPdfJob(PdfSplitController.java:39)
	  at com.rongda.split.web.api.controller.pdf.PdfSplitController$$FastClassBySpringCGLIB$$2962b742.invoke(<generated>:-1)
  1. 在百度输入:LockedScopedProxyFactoryBean park,相关的文章较多,并且没找到相关的问题,但是大部分回答都指向一个注解:@RefreshScope
  2. 由于 Service 里的 @Value 注解一直都有,并且能动态刷新,所以猜测是因为这次加入了动态业务标识导致,也就是 Application 里的 BusinessProperties
  3. 由于动态添加业务标识的情况不多,所以时间比较多,既然有动态添加业务标识这个需求,那就想办法实现,目前有两种方式:
    1. 动态获取改成从数据库或 redis 获取,但是这样的话需要编写新增业务标识的接口
    2. 想办法解决这个问题
  4. 因为刚才百度查的结果都是 @RefreshScope 相关的文章,所以就怀疑是 @RefreshScope + @Value 的使用在目前项目里执行 lock 方法时和什么冲突了
  5. 所以尝试:新建一个 Properties 文件,service 中引用该 Properties,动态刷新的配置统一都用 Properties 文件,不再使用 @RefreshScope + @Value 实现动态刷新,最终解决

总结

最终也没有发现具体是什么问题,但是大概就是加锁的时候冲突了,哪里没释放锁。
这里只是记一下临时解决问题的思路。
以后使用 nacos 动态刷新都是用 Properties 方式来实现,避免未知的异常问题