下单后Solr库存实时刷新流程优化及问题排查咨询
下单后Solr库存实时刷新流程阻塞问题:排查与优化方案
一、现有实现的核心问题
- 订单主流程被同步阻塞:把Solr索引刷新硬塞在订单串行流程里,还用
Thread.sleep(10000)循环等待索引任务完成,直接占用订单处理线程,导致整个订单流程迟迟走不到success节点,连带卡断后续依赖的业务动作(比如订单确认邮件发送)。 - JVM级锁的局限性:用
ReentrantLock做店铺级别锁,集群部署下锁不共享,会导致多节点同时跑索引任务;单节点内所有该店铺的订单请求都会被堵在锁获取环节,直接拖垮系统吞吐量。 - 误用全量索引任务:调用的
SolrIndexerCronJob是全量索引更新,明明只需要更新下单商品的库存,却要刷新整个索引库,执行时间长,进一步放大阻塞问题。 - 低效的等待逻辑:10秒间隔的轮询不仅浪费线程资源,还会让订单流程等待时间不可控,用户体验和系统性能双重受损。
二、优化方案:复用现有Action改逻辑(不用彻底重构)
核心思路是异步解耦+增量更新+分布式锁(可选),基于现有RefreshSolrAction修改即可:
1. 异步化处理,脱离订单主流程
把索引刷新从订单串行流程中剥离,提交异步任务后直接返回,不阻塞订单流程:
@Override public Transition executeAction(OrderProcessModel orderProcessModel) throws Exception { if (Objects.nonNull(orderProcessModel.getOrder())) { final OrderModel order = orderProcessModel.getOrder(); // 提交异步任务,直接放行订单流程 solrRefreshAsyncTask.submit(() -> { try { refreshStockForOrderedProducts(order); } catch (Exception e) { // 只打日志,不影响主流程 log.error("Solr库存更新失败,订单号:{}", order.getCode(), e); } }); } return Transition.OK; }
2. 改为增量更新,避免全量索引
针对订单内的商品单独更新Solr库存字段,不用跑全量CronJob:
private void refreshStockForOrderedProducts(OrderModel order) { BaseStoreModel store = order.getStore(); // 遍历订单里的所有商品 for (OrderEntryModel entry : order.getEntries()) { ProductModel product = entry.getProduct(); // 获取该商品在当前店铺的最新库存 Long currentStock = stockService.getStockLevelForProductAndStore(product, store); // 只更新Solr中该商品的库存字段 solrService.updateProductStockField(product.getCode(), currentStock, store.getSolrCore()); } // 根据Solr配置决定是否需要提交,建议用软提交提升性能 solrService.softCommit(store.getSolrCore()); }
3. 分布式锁控制并发(集群场景必加)
如果是集群部署,把JVM级的ReentrantLock换成Redis分布式锁,避免多节点同时更新同一商品的库存:
private void refreshStockForOrderedProducts(OrderModel order) { BaseStoreModel store = order.getStore(); for (OrderEntryModel entry : order.getEntries()) { ProductModel product = entry.getProduct(); String lockKey = String.format("solr_stock_lock:%s:%s", store.getUid(), product.getCode()); // 尝试获取锁,超时5秒,防止死锁 if (redisLockService.tryLock(lockKey, 5, TimeUnit.SECONDS)) { try { Long currentStock = stockService.getStockLevelForProductAndStore(product, store); solrService.updateProductStockField(product.getCode(), currentStock, store.getSolrCore()); } finally { // 必须释放锁 redisLockService.unlock(lockKey); } } } solrService.softCommit(store.getSolrCore()); }
4. 调整订单流程配置
异步化后,不需要让refreshSolr动作阻塞订单流程,直接调整orderProcess.xml:
<action id="decrementStockOrderPlaced" bean="decrementStockOrderPlacedAction"> <!-- 库存扣减完成后直接到success,不用等Solr更新 --> <transition name="OK" to="success"/> <transition name="NOK" to="success"/> </action> <!-- 如果保留refreshSolr动作,确保它是异步的,不阻塞流程 --> <action id="refreshSolr" bean="refreshSolrAction"> <transition name="OK" to="success"/> <transition name="NOK" to="success"/> </action>
三、额外优化点
- Solr软提交配置:把Solr的
autoSoftCommit设为1秒左右,平衡实时性和性能,不用每次更新都手动提交。 - 批量合并更新:短时间内多个订单涉及同一商品时,合并更新请求,减少Solr调用次数。
- 监控告警:给异步更新任务加监控,统计成功/失败次数,失败率过高时触发告警。
内容的提问来源于stack exchange,提问作者iamrooovic
相关产品推荐
相关产品推荐

