一)线上事故:

催收系统每日自动分配案件时一直正常,突然某一天(2018-3-27)以后 案件分配不均匀,一系列追踪下查到原因是责任链 有一环 未被执行(kibana上当天2018-3-27 以后 未查看相应的日志记录) ,很奇怪 ,重启服务后 第二天 结果恢复正常

 

(二)分析问题: 

分析 具体的催收分案业务 , 有手动分案(责任链模式)和每日自动分案(责任链模式)两种情形,未被执行的一环正好是 手动分案 相比 自动分案 缺少的一环 

 

(三)问题猜想 :

查看 日志发现 当天 确实 存在手动分案的操作记录 ,猜想是手动分案 影响了 自动分案的功能

 

(四)查看代码,分析问题

  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
@Servicepublic class DistributionHandlerHolder implements IDistributionHandlerHolder {     /**     * 自动分案,逻辑最完备     */    private AbstractHandler autoDistribution;     /**     * 手动分案,不记录上次催收人以及下次催收人等     */    private AbstractHandler manualDistribution;     @Override    public AbstractHandler getAutoExecuteHandler() {        if(autoDistribution == null) {            synchronized(this) {                if(autoDistribution == null) {                    createAutoExecuteHandler();                }            }        }        return autoDistribution;    }     @Override    public AbstractHandler getManualExecuteHandler() {        if(manualDistribution == null) {            synchronized(this) {                if(manualDistribution == null) {                    createManualExecuteHandler();                }            }        }        return manualDistribution;    }     private void createAutoExecuteHandler() {        AbstractHandler autoStart = null;        AbstractHandler autoPre = null;        for(AutoDistributionEnum distributionEnum : AutoDistributionEnum.values()) {            AbstractHandler handler = ApplicationContextHolder.getBean(distributionEnum.getBeanName(), AbstractHandler.class);            if(autoPre != null) {                autoPre.setNextHandler(handler);            }            if(autoStart == null) {                autoStart = handler;            }            autoPre = handler;        }        autoDistribution = autoStart;    }     private void createManualExecuteHandler() {        AbstractHandler manualStart = null;        AbstractHandler manualPre = null;        for(ManualDistributionEnum distributionEnum : ManualDistributionEnum.values()) {            AbstractHandler handler = ApplicationContextHolder.getBean(distributionEnum.getBeanName(), AbstractHandler.class);            if(manualPre != null) {                manualPre.setNextHandler(handler);            }            if(manualStart == null) {                manualStart = handler;            }            manualPre = handler;        }        manualDistribution = manualStart;    }}

 

  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
@Slf4jpublic abstract class AbstractHandler {          protected AbstractHandler nextHandler;        public void setNextHandler(AbstractHandler nextHandler) {        this.nextHandler = nextHandler;    }}
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
@Component("distributionUserSortHandler")@Slf4jpublic class UserSortHandler extends AbstractHandler {     @Autowired    private IOrderService orderService;     @Override    public void execute(DistributionRequest request, DistributionResponse response) {                nextHandler.execute(request, response);    }}
 
 

1、大体上看没有问题,

手动分案和自动分案是两条完全不同的

责任链创建过程

 

2、仔细观察是每个责任链 是单例模式 ,没有标记@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE) 注解 ,和大脑印象中代码不符合

 

3、仔细分析 : (1)自动分案 走 1-》2-》3-》4

             (2)手动分案走 1-》2-》4

             (3)先自动分案 ,后手动分案,造成 自动分案 

                              被替换成   1-》2-》4,造成 3链缺失

 

(四) 解决方案:

每个责任链上加上@Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)注解,造成

AbstractHandler handler = 

ApplicationContextHolder.getBean(distributionEnum.getBeanName(), AbstractHandler.class);获取时获取到不同的对象 ,而不是spring单例对象

 

(五) 归纳总结:

spring 单例 最好无状态,责任链 的每一环必有状态(指向下一环),因为存在多条链路(多种业务情况)

所以每一环应该定义成多例(最好多例),否则就造成如上的线程间不安全的问题。

责任链模式 多条链路时 spring单例 造成的深坑_多线程