最近遇到一個小型的秒殺活動,使用 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 + 任務上的註解名字
,會跳到執行緒池這裡。