目录
一.业务背景
1.概要
我们的系统是HIS(Hospital information system)系统,也就是医院信息化系统。HIS系统由很多子系统组成,比如:面向医生使用的医生站系统、面向收费使用的收费系统、面像护士使用的护士站系统;按照场景又分门诊医生站、住院医生站。由于我们是微服务架构,所以每个微服务基本上也就是一个子系统。今天的主角是门诊医生站。
2.业务说明
门诊医生站主要用户为医生,医生主要的工作就是开医嘱,医嘱又分为检查、检验、药品、治疗、手术等等多种类型的医嘱。医生开医嘱的过程是一个个开,然后保存,保存的过程称为草稿,可以开很多的草稿,然后一块提交,提交也就是正式生效
二.具体实现
因为担心医生开立很多草稿一块提交,导致数据量过大,系统响应慢,所以此处使用的多线程提交,多线程的维度是有几种类型的医嘱就开几个线程,同时多线程提交还要考虑事务一致性的问题,所以多线程使用countdownlatch来控制控制线程统一提交或者回滚,系统又使用ThreadLocal存储线程级的共享变量,需要将ThreadLocal传递给子线程
if(sdClinicTypeSet.contains(Constants.SD_CLINIC_TYPE_1)){ futureList.add(executor.submit(new AsynCallable(requestAttributes, transFlag, transDrug))); } if(sdClinicTypeSet.contains(Constants.SD_CLINIC_TYPE_2)){ futureList.add(executor.submit(new AsynCallable(requestAttributes,transFlag,transExam))); } if(sdClinicTypeSet.contains(Constants.SD_CLINIC_TYPE_3)){ futureList.add(executor.submit(new AsynCallable(requestAttributes,transFlag,transInspect))); } if(sdClinicTypeSet.contains(Constants.SD_CLINIC_TYPE_4)){ futureList.add(executor.submit(new AsynCallable(requestAttributes,transFlag,transTreat))); } if(sdClinicTypeSet.contains(Constants.SD_CLINIC_TYPE_11)){ futureList.add(executor.submit(new AsynCallable(requestAttributes,transFlag,transPathology))); } if(sdClinicTypeSet.contains(Constants.SD_CLINIC_TYPE_9)){ futureList.add(executor.submit(new AsynCallable(requestAttributes,transFlag,transConsultation))); }
AsynCallable为自定义线程,trans开头为具体实现类
public class AsynCallable implements Callable<Object>, IThreadCacheService { //事物执行体,也就是具体医嘱类型 private AbstractTransService service; //事物标志 private TransFlag transFlag; private RequestAttributes requestAttributes; public AsynCallable(RequestAttributes parentRequestAttributes, TransFlag transFlag, AbstractTransService service){ this.service = service; this.transFlag = transFlag; this.requestAttributes = parentRequestAttributes; } @Override public Object call() throws Exception { try { RequestContextHolder.setRequestAttributes(this.requestAttributes); putDtSign(transFlag.getDtSign()); Object o = service.lockExec(transFlag,this.service.getClass()); return o; } finally { drugItemsTl.remove(); dtSignThread.remove(); serialNoThread.remove(); orderGroupThread.remove(); RequestContextHolder.resetRequestAttributes(); } } }
/** * @Author * @Date 2020-1-9 17:14 * 多线程提交事物保证抽象类 */ public abstract class AbstractTransService<T> { @Transactional(rollbackFor = Exception.class) public T lockExec(TransFlag transFlag,Class clazz) throws ExecutionException, InterruptedException { T t = null; try { t = doExec(transFlag.getIdVisit(),transFlag.getIdDeptSign(),transFlag.getSignDeptName()); } catch (Exception e) { LogUtil.error("-------当前医嘱提交失败---thread:",e); transFlag.setAllSuccess(false); transFlag.getCountDownLatch().countDown(); transFlag.getSdClinicTypeClass().add(clazz); if(e instanceof ExecutionException){ ExecutionException ee = (ExecutionException)e; if(ee.getCause() instanceof CustomVerify){ CustomVerify cv = (CustomVerify)ee.getCause(); transFlag.setMsg(cv.getMsg()); }else{ transFlag.setMsg(ee.getMessage()); } }else{ transFlag.setMsg(e.getMessage()); } LogUtil.error("-------当前医嘱提交失败---thread:["+Thread.currentThread().getName()+"]:"+e.getMessage()); throw e; } try { transFlag.getCountDownLatch().countDown(); transFlag.getSdClinicTypeClass().add(clazz); transFlag.getCountDownLatch().await(); if(!transFlag.isAllSuccess()){ LogUtil.error("--------有未成功提交的医嘱,回滚......"); throw new CustomVerify(GlobalReturnCode.SYSTEM_PARA_ERROR.getCode(),"提交失败"); } } catch (InterruptedException e) { transFlag.setAllSuccess(false); LogUtil.error("线程间通讯失败:",e); throw new CustomVerify(GlobalReturnCode.SYSTEM_PARA_ERROR.getCode(),"提交失败"); } return t; } /** * 具体业务逻辑,必须实现 * @param idVisit * @param idDeptSign * @param signDeptName * @return */ public abstract T doExec(String idVisit,String idDeptSign,String signDeptName) throws ExecutionException, InterruptedException; }
三.问题现象
在开启多线程的情况偶尔会出现线程不动的情况,之所以说“出现线程不动”,是因为至今也没有找到具体的原因,猜测是数据库死锁导致,但是多线程操作时每个线程只会更新一种类型的医嘱,不会出现多个线程更新同一个记录的情况,这也是至今想不明白的地方,而且测试也无法复现
图中 58号线程已经不动了,只打出一条update语句,更新就卡住了......
四.后续
因为这个原因被领导要求反思,最后,只能先由多线程提交改为单线程提交,问题不再出现,但是速度......单线程优化吧!