春节前我们智能停车平台出现,用户支付成功通知停车能力提供平台延时较高,最大达到了5分钟,造成车场车辆积压严重,客诉很严重。
出现的问题上面描述了,先整体说明一下智能停车的线上支付整体流程;
- 1.车辆入场
- 1.1用户车辆进入车场,停车场本地系统拍照抬竿;
- 1.2停车场本地系统通知停车能力提供方线上平台,该车辆入场;
- 1.3停车能力提供方线上平台调用飞凡停车服务同步入场信息;
- 2.用户缴费
- 2.1用户开启应用查费
- 2.1.1智能停车平台调用停车能力提供方车辆信息接口,查询车辆在场和费用信息;
- 2.2 用户创建订单
- 2.2.1 智能停车平台调用停车能力提供方查费接口,查询确认当前车费;
- 2.2.2 智能停车平台调用停车能力提供方创建订单接口,创建外部订单;
- 2.2.3 智能停车平台调用交易平台接口创建本平台订单;
- 2.3 用户进入收银台进行支付
- 2.3.1 用户支付完成,交易平台获得支付完成信息,通知加入消息队列
- 2.3.2 智能停车平台监听消息队列,获得支付成功消息;
- 2.3.3 智能停车平台将支付信息同步给停车能力提供方订单通知接口;
- 2.1用户开启应用查费
- 2.3 用户离场
- 2.3.1 用户车辆驶离车场,本地岗亭识别车牌,调用停车能力提供方线上平台查询支付状态
- 2.3.2 抬竿出场
这个问题出现在2.3流程。当时消息队列消费情况如下图:
发现消费延时较大,完全来不及消费里面的支付消息。
经过排查代码发现是单线程消费消息队列,通知逻辑是串行的,效率可想而知,另外由于停车能力提供方接口响应也较慢,平均响应时间将近300毫秒,因此单线程每秒钟最多通知3笔订单。
由于临近春节,不便修改代码,一开始想到的解决方案是扩容,但分析下来由于消费支付消息队列使用的是kafka,这个topic只有4个partition,而每个partition智能由1个线程进行消费,而支付系统与消息队列耦合较大,无法进行扩容,因此该方案不具操作性。
那么只能第二条路,优化代码逻辑。优化策略是使用线程池进行能力提供方的支付通知;
由于消费partition的线程只能单一,因此消费线程暂时无法优化,而通知线程进行线程池优化。代码如下:
//线程池队列
private LinkedBlockingDeque<Runnable> linkedBlockingDeque;
//通知线程池
private ThreadPoolExecutor notifyExecutor;
//消息队列消费状态
private volatile boolean isRun;
if(null == notifyExecutor){
linkedBlockingDeque = new LinkedBlockingDeque<Runnable>(8);
notifyExecutor = new ThreadPoolExecutor(8, 32, 20L,
TimeUnit.SECONDS, linkedBlockingDeque, new ThreadPoolExecutor.CallerRunsPolicy());
isRun = true;
}
由于线上服务器cpu核数为8,因此core线程数定义为8,然而通知能力方接口延时较长,并没有完全消耗cpu资源,因此最大线程数定义为32,当线程和队列都满情况下,有新任务加入,使用CallerRunsPolicy策略,即调用线程本身执行该任务。
改造后需考虑服务重启无法处理已消费消息的情况,而应用是基于tomcat的,因此需要注册事件通知:
@Component
public class MessageConsumerLauncher implements ApplicationListener<ApplicationEvent> {
private static final Logger logger = LogManager.getLogger(MessageConsumerLauncher.class.getName());
@Autowired
private OrderStatusMessageReceiver orderStatusMessageReceiver;
@Override
public void onApplicationEvent(ApplicationEvent event) {
logger.info("MessageConsumerLauncher.onApplicationEvent ContextRefreshedEvent "
+ event.getSource().toString() + "; " + event.getClass().getName());
if(event instanceof ContextClosedEvent){
try {
logger.info("MessageConsumerLauncher.onApplicationEvent.orderStatusMessageReceiver stopping...");
orderStatusMessageReceiver.stopReceiver();
logger.info("MessageConsumerLauncher.onApplicationEvent.orderStatusMessageReceiveron.ApplicationEvent() stopping...");
} catch (Exception e) {
logger.error(e.getStackTrace());
}
}
}
}
stopReceiver的逻辑为:
public void stopReceiver(){
logger.info("stop begin OrderStatusMessageReceiver threadPool...........");
if(null != threadPool) {
isRun = false; //状态置为partition消费关闭
threadPool.shutdown(); //关闭partition消费线程池(只有1个线程)
}
logger.info("stop end OrderStatusMessageReceiver threadPool...........");
logger.info("stop begin OrderStatusMessageReceiver threadPool...........");
if(null != notifyExecutor){
notifyExecutor.shutdown(); //关闭通知线程池
}
logger.info("stop end OrderStatusMessageReceiver threadPool...........");
}
当notifyExecutor执行完剩余任务,应用关闭。
经过上面改造进在测试环境进行压测,由于测试环境使用4cpu,4G内存,生产环境使用8cpu,8G内存。测试结果如下:
效率有将近12倍的提升,完全达到系统要求。