Spring Boot使用執行緒池處理事務任務

sweetmain發表於2018-07-04

最近遇到一個小型的秒殺活動,使用 Spring Boot,沒辦法團隊沒法大規模使用 Vert.x。 好了,思路就是,將Controller裡面的方法包裝成一個 Runnable, 放到一個單執行緒的執行緒池裡進行執行,任務成功後,將結果放入一個 Map裡,前端定期輪詢這個 Map。一開始簡單粗暴的在某個Service裡建立一個 static final 的執行緒池,因為有些 service 方法裡面有 Transactional, 而且還使用了 Hibernate的懶載入,這種簡單粗暴的寫法導致 Hibernate 無法獲取當前執行緒的session,因為這個執行緒是使用自己new出來的執行緒池的執行緒,不是Spring管理的。下面的寫法是正常好用的。

建立一個介面

public interface AsyncService {

  /**
   * 執行非同步任務
   */
  void executeAsync(String orderNo, String pwd);
}
複製程式碼

實現介面

裡面寫自己的業務邏輯

@Service
public class AsyncServiceImpl implements AsyncService {

  private static final Logger logger = LoggerFactory.getLogger(AsyncServiceImpl.class);

  @Autowired
  private AppService appService;

  @Override
  @Async("asyncServiceExecutor")
  public void executeAsync(String orderNo, String pwd) {
    logger.info("start executeAsync");
    try {
      appService.payHandler(orderNo, pwd);
    } catch (PayException pex) {
      System.out.println(pex.getMessage());
      appService.putSeckillResult(orderNo, AppMessage.error(pex.getMessage()));
      appService.cancelOrder(orderNo);
    } catch (Exception e) {
      e.printStackTrace();
      appService.putSeckillResult(orderNo, AppMessage.error("購買失敗"));
      appService.cancelOrder(orderNo);
    }
    logger.info("end executeAsync");
  }
}
複製程式碼

Controller

@PostMapping("/pay2")
public AppMessage pay2Controller(@RequestBody PayDTO dto) {
    String orderNo = dto.getOrderNo();
    String pwd = dto.getPwd();
    
    if (StringUtils.isEmpty(pwd) || StringUtils.isEmpty(orderNo)) {
      return RespErrorUtils.paramsError();
    }
    asyncService.executeAsync(orderNo, pwd);
    return AppMessage.success("ok");
}
複製程式碼

任務會在http請求來的建立,但是還沒有一個執行任務的執行緒池。現在來建立一個受Spring管理的執行緒池

執行緒池

@Configuration
@EnableAsync
public class ExecutorConfig {

  private static final Logger logger = LoggerFactory.getLogger(ExecutorConfig.class);

  @Bean
  public Executor asyncServiceExecutor() {
    logger.info("start asyncServiceExecutor");
    return Executors.newSingleThreadExecutor(r -> new Thread(r, "pool-seckill"));
  }
}
複製程式碼

建立完之後,要在 任務上新增一個 @Async("asyncServiceExecutor") 註解,該註解的value要和 執行緒池的 asyncServiceExecutor() 對應上。如果你使用的 IDEA,那麼可以直接 Ctrl + 任務上的註解名字,會跳到執行緒池這裡。

相關文章