最近遇到一个小型的秒杀活动,使用 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 + 任务上的注解名字,会跳到线程池这里。