1. 程式人生 > 實用技巧 >0057、解決@Scheduled定時任務被阻塞的問題

0057、解決@Scheduled定時任務被阻塞的問題

問題描述:

@Scheduled註解定義的某個定時任務,每隔30秒通過dubbo介面去統一登入系統定時拉取使用者資訊,在測試環境定時任務執行正常,到生產環境後,從第二天開始定時任務無法被執行;
經分析後,定位問題原因為系統存在許多@Scheduled定義的其他定時任務,且生產環境資料量較大,每個定時任務任務執行時間邊長,而@Scheduled是單執行緒的,因此導致後續的
其他定時任務被阻塞;
解決辦法:拉取使用者資訊的定時任務不用@Scheduled註解定義,改用自定義執行緒池實現,因此不會再被阻塞

1、原拉取使用者資訊的定時任務方法程式碼如下:
package com.xxx.scheduled;

import com.alibaba.dubbo.config.annotation.Reference;
import com.xxx.ApplicationService;
import com.xxx.UserDTO;
import com.xxx.entity.SystemUser;
import com.xxx.SystemUserServcie;
import lombok.extern.slf4j.Slf4j;
import org.joda.time.DateTime;
import org.joda.time.format.DateTimeFormatter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import java.util.Date;
import java.util.List;
import java.util.Objects;

@Component
@Slf4j
@EnableScheduling
public class UserPullTask {


@Value("${config.application.code}")
private String code;

@Autowired
private SystemUserServcie systemUserServcie;

@Reference(version = "1.0")
private ApplicationService applicationService;

@Value("${default.role.id}")
private String roleId;
@Value("${default.user.id}")
private String userId;
@Scheduled(fixedDelay = 30000)

public void doTask() {
DateTime dateTime = DateTime.now().minusHours(1);
List<UserDTO> userList = applicationService.getUsersByModifyTimeAndAppCode(code, dateTime.toDate());
if (CollectionUtils.isEmpty(userList)) {
log.info("本次沒有查詢到新增使用者,執行結束");
return;
}
log.info("獲取到要同步的使用者,本次同步數量:{}",userList.size());
for (UserDTO userDTO : userList) {
try {
SystemUser systemUser = convertSystemUser(userDTO);
SystemUser exitUser = systemUserServcie.isExitUser(userDTO.getName());
if(Objects.isNull(exitUser)){
systemUser.setIsDelete(0);
systemUser.setLevel(3);
systemUser.setRoleId(roleId);
systemUser.setIsOnline(0);
systemUser.setCreatAt(DateTime.now().toDate());
systemUser.setAddUser(userId);
systemUserServcie.insertUser(systemUser);
}else{
systemUser.setId(exitUser.getId());
systemUserServcie.updateByPrimaryKey(systemUser);
}

} catch (Exception e) {
log.info("{}同步失敗",userDTO.getName());
}
}
}

private SystemUser convertSystemUser(UserDTO userDTO) {
SystemUser systemUser = new SystemUser();
systemUser.setUserName(userDTO.getNickName());
systemUser.setAccountNum(userDTO.getName());
systemUser.setPhone(userDTO.getPhone());
//狀態定義相反
if(userDTO.getStatus() == 0){
systemUser.setState(1);
}else{
systemUser.setState(0);
}
systemUser.setLastLoginAt(userDTO.getGmtLastLogin());
return systemUser;
}
}

2、將上述標紅的註解註釋掉,並通過自定義執行緒池的方式來呼叫該獲取使用者資訊的方法
package com.xxx.scheduled;

import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;

import java.util.Date;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;

@Component
@Slf4j
public class UserPullApplicationRunner implements ApplicationRunner {
//專案啟動結束後會立即執行該方法
@Autowired
private UserPullTask userPullTask;
@Override
public void run(ApplicationArguments args) throws Exception {
ScheduledExecutorService service = Executors.newScheduledThreadPool(1);
service.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
userPullTask.doTask();
}
}, 1, 30, TimeUnit.SECONDS);//首次延遲1秒,之後每30秒執行一次
}
}
親測,這種方式,即使其他用@Scheduled定義的方法sleep幾秒鐘,該方法也不會被阻塞。