【记一次生产事故】

记一次生产事故--参数校验不充分

  • 事故现象
  • 事故排查
  • 事故原因
  • 启发

事故现象

我负责的一个营销系统(售楼系统),里面有个转款的场景。比如张三客户,买房交了预定金。然后将预定金转到 房款里面。正常转款耗时2-3s 可以完成。但是有个客户 反馈他每次操作都是1-2min。然后客户立马给我们部门进行投诉。

事故排查

我这边立马联系客户。远程看下客户的操作。发现确实慢。并且确定了慢的接口。即 有个接口 要70s 才返回结果。初步定位了是后台接口返回耗时长导致的问题。
然后问下 其他问题,比如最近有没有升级更新系统,这种现象有多久了?
最后得到的回复是没有更新系统。这种现象持续了5-6天了。反正客户那边的意思是 这个问题是你们的问题,赶紧给我解决。客户是上帝,我这边也没有办法。赶紧定位.

事故原因

首先客户那边没有测试环境,无法在测试环境模拟对应的场景。
其次 这个转款功能 客户那边有二开,即 有对这个功能进行了扩展。所以有可能 这个问题 是客户自己开发的bug。这个增加了排查的难度。客户并且一口咬定 这个是我们的问题。
然后我这边查看了下接口,业务代码比较复杂。有2-3 k 行代码。想了下,耗时主要应该是和数据库打交道。发现 这2-3k 行代码中,和数据库打交道的地方 至少有15 处以上。并且找到了几个看似会耗时的sql. 带入条件去数据库查询看下。发现查询速度很快。这就尴尬了。敢情 找了半天,居然不是sql的问题,那为啥会这么慢呢? 实在是分析不出来了,然后 决定 通过打印日志看分析,把所有觉得可能耗时的地方都输出下耗时。这种方法是通过硬编码的方式,然后部署到生产环境中去。

最终通过日志的方式,定位到了有问题的代码:

private void updateRoomLoan(Context ctx, SHERevBillInfo revBillInfo) throws Exception {
		RelatBizType bizType = null;
		String bizBillId = null;
		if (revBillInfo.getRelateBizType() != null && revBillInfo.getRelateBizBillId() != null) {
			bizType = revBillInfo.getRelateBizType();
			bizBillId = revBillInfo.getRelateBizBillId();
		} else {
			long one0 = System.currentTimeMillis();
			SHERevBillInfo revInfo = SHERevBillFactory.getLocalInstance(ctx).getSHERevBillInfo("select id,trsToGatherId,revBillType," +
					"state,relateBizType,isTansCreate,relateBizBillId where id = '" + revBillInfo.getId() + "'");
			long one00 = System.currentTimeMillis();
			logger.error("getSHERevBillInfo cost time " + (one00 - one0));
			bizType = revInfo.getRelateBizType();
			bizBillId = revInfo.getRelateBizBillId();
		}
		if (bizBillId != null && bizType != null) {
			long one = System.currentTimeMillis();
			if (revBillInfo.getRelateTransId() == null) {
				// 如果为空,就不用后续操作了.
				return;
			}
			TranBusinessOverViewCollection payListEntryCol = getTranBusinessOverViewCollection(ctx, revBillInfo.getRelateTransId());
			long one1 = System.currentTimeMillis();
			logger.error("updateRoomLoan getTranBusinessOverViewCollection cost time is " + (one1 - one));
			logger.error("revBillInfo.getRelateTransId() is " + revBillInfo.getRelateTransId());
			if (payListEntryCol != null && !payListEntryCol.isEmpty()) {
				logger.error("payListEntryCol 的数量是  " + payListEntryCol.size());
				for (int i = 0; i < payListEntryCol.size(); i++) {
					TranPayListEntryInfo entry = payListEntryCol.get(i);
					BigDecimal appAmount = FDCHelper.toBigDecimal(entry.getAppAmount());
					BigDecimal actLoanAmount = FDCHelper.toBigDecimal(entry.getActRevAmount());
					MoneyTypeEnum moneyType = entry.getMoneyDefine().getMoneyType();
					// 按揭、公积金款,实收等于应收,反写按揭单据为银行放款
					if (MoneyTypeEnum.AccFundAmount.equals(moneyType) || MoneyTypeEnum.LoanAmount.equals(moneyType)) {
						if (appAmount.compareTo(FDCHelper.ZERO) != 0 && actLoanAmount.compareTo(appAmount) >= 0) {
							// 更新为银行放款
							long one2 = System.currentTimeMillis();
							SHEManageHelper.checkRoomLoan(ctx, revBillInfo, entry.getMoneyDefine(), AFMortgagedStateEnum.BANKFUND);
							long one3 = System.currentTimeMillis();
							logger.error("SHEManageHelper.checkRoomLoan cost time is " + (one3 - one2));
							logger.error("entry.getMoneyDefine() is " + entry.getMoneyDefine());
							logger.error("revBillInfo is +" + revBillInfo);
						} else if (appAmount.compareTo(FDCHelper.ZERO) != 0 && actLoanAmount.compareTo(appAmount) < 0) {
							// 更新为未办理
							// 收款单退款时不影响按揭单的状态
							/*SHEManageHelper.checkRoomLoan(ctx, revBillInfo, entry.getMoneyDefine(), AFMortgagedStateEnum.UNTRANSACT);*/
						}
					}
				}
			}
		}
	}

错误的原因是 revBillInfo.getRelateTransId() 的值为null时,通过sql xx=null 能查询到1500条记录。然后每个记录再进行循环 操作。所以很耗时。一个循环我这边打印耗时90ms 左右。 这里有个循环,循环了1500多次,如果不是打印日志,根本不可能找到这个问题。 解决办法就是在方法的前面加个判断,如果 revBillInfo.getRelateTransId() 为null.就直接返回。
也就是需要对入参进行Null判断。

启发

现在很多时候写代码都很随意,没有按照规范来写。只是为 了满足业务需求。如果出现了客户改数据,就会出现各种各样的问题,用常规的思维还根本不能解决这个问题。

所以切记 调用方法的时候,最好对入参进行校验。这是一个很好的习惯,谁都无法保证传入的参数是啥,自己校验是最靠谱的。

你可能感兴趣的:(java基础,安全管理,数据库,mysql,sql)