一)线上事故:
催收系统每日自动分配案件时一直正常,突然某一天(2018-3-27)以后 案件分配不均匀,一系列追踪下查到原因是责任链 有一环 未被执行(kibana上当天2018-3-27 以后 未查看相应的日志记录) ,很奇怪 ,重启服务后 第二天 结果恢复正常
(二)分析问题:
分析 具体的催收分案业务 , 有手动分案(责任链模式)和每日自动分案(责任链模式)两种情形,未被执行的一环正好是 手动分案 相比 自动分案 缺少的一环
(三)问题猜想 :
查看 日志发现 当天 确实 存在手动分案的操作记录 ,猜想是手动分案 影响了 自动分案的功能
(四)查看代码,分析问题
@Service
public 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;
}
}
@Slf4j
public abstract class AbstractHandler {
protected AbstractHandler nextHandler;
public void setNextHandler(AbstractHandler nextHandler) {
this.nextHandler = nextHandler;
}
}
@Component("distributionUserSortHandler")
@Slf4j
public 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 单例 最好无状态,责任链 的每一环必有状态(指向下一环),因为存在多条链路(多种业务情况)
所以每一环应该定义成多例(最好多例),否则就造成如上的线程间不安全的问题。