来公司有一年时间了,一直在马不停蹄的做着新的业务,但要想做好新业务就不能过多将时间消耗在其它方面。刚来的半年时间,我们消耗了30%左右的时间在时间系统维护上。我们一边做新业务,一边天天需要花时间在系统维护上,难免会心力交瘁,前半年由于刚来公司,需要大量时间熟悉现在系统,分析优点缺点,努力去发现问题,期间有很多想做的事一直没有推进。我前后经过近一年的时间,参与了开发我们组几乎所有核心模块的代码,所以对其中的问题基本把握的比较准确,半年后花了近三个月时间来重构优化系统(当然不是团队停下来专门做重构,只能在新业务中挤时间),小有成果,这里分享很大家:
我先简单介绍下我们主体业务的几个模块以及对应的问题:(有一部分问题是因为数据量大造成的,其余是系统设计问题)
- 提交合同
- 商品特批比较慢,最长的一次达到2分钟
- 批量导入商品非常慢,最惨的结果是一小时都未导完,结果是用户误以为程序崩溃强制关闭页面然后重新导入
- 批量导入商品容易产生错误的数据,数据一致性问题搞得我们比较惨
- 订单价格计算异常,这是系统致命的,订单价格是核心业务数据,会影响到下流系统的数据分析
- 审核合同
- 订单加载非常慢,最长的一次达到15分钟
- 审核错误,订单多的时候,直接报错
- 订单使用过程
- 订单使用数据计算错误,这会直接导致用户多使用了库存或者少使用了库存
- 退款
- 退款订单时搜索有点像大海捞针,订单多的时候,想要退其中一个用户的订单只能一页一页的找,非常郁闷
- 退款错误,订单多的时候直接报错
- 订单退款金额错误,这也是致命的,和订单价格一样
看到上面的问题,可以归纳出,在数据量大的时候,系统的基本功能都不能保证正常运转,那么应对的方案按优先级订为:
- 保证主体功能能够正常运转,不考虑性能
- 在保证主体功能不受影响的情况下,确保性能达到最优化
- 针对用户的操作习惯做有针对性的操作优化,提升用户体验
功能不能正常运转问题:
先来看个小故事:去年的某月提单高峰日,线上出现用户反馈提单失败,网站响应速度非常慢。因为之前未出现过同类现象,所以我们下意识的认为不应该是程序问题,是否是服务器的问题,但查看服务器的负载并未发现问题,由于半夜都未解决问题,最终我们领导刚准备睡觉最终提起脱了一半的裤子又来到公司与我们一起奋战。后来沟通才得知是一批新的用户使用了系统,他们的特点就是一次性提交的订单数据非常多(一次2000以上)。之前系统由于没有经过大量订单的考验所以没有暴露出问题来,知道情况后经过分析确认是程序面对大量订单时比较脆弱。下面是解决的一些主要问题小集合:
- 查询集合时,扩展的WhereIn方法报错。
下面的这类查询需求,想必是比较常见的,查询指定范围内的数据,扩展的目的是为了调用方便,容易理解
下面是扩展的方法以及问题描述
我并没有尝试去解决这个表达式的问题,直接改成Contains就解决问题了。
- 审核时,当订单超过一定数量时报错。
订单达到一定数量时,在更新数据库时会生成大量的SQL语句,容易形成SQL传输的网络堵塞最终产生数据库操作超时(之前提交2000个订单时,单表需要生成2000行SQL),解决方案是按批次来处理,比如500条数据一个批次操作数据库,将大批次改成小批次来处理就不会有问题。
- 当导入数据时,如用户关闭浏览器,会导致导入的数据不完整,最终导致存在错误数据
原方案:用户在导入数据的时候,显示一个滚动的图片,但这个滚动的图片不是真正的进度条,用户无法知道导入的进程到底什么时间能够完成。
新方案:
-
- 对用户给出相应提示,正确的诱导用户按照正规的流程操作,用户是善良的,如果我们的提示正确,他们基本都会按操作来执行,不会像测试人员一样找BUG的。
- 采用一定的机制实现真正的进度条控制,让用户正直的知道系统导入的准实时进度,用户心里有底了也就不会焦躁了。
- 订单的相关金额计算错误
最严重的当数这些与金额相关的数据,由于系统设计的不够严谨,导致金额数据出现负数。为了彻底堵住这个漏洞,我在实体模型上做了霸王条款:
所以我们在设计数据库以及实体模型时,一定要确保数据在可掌控范围内,决不能允许出现错误数据。
- 订单已使用数量计算错误
比如购买了一个订单,数据是100个,用户可随意使用,使用一个就在订单上记录一个已使用的数据,最终到已使用数据=原订单数量,其中用户如果做退款下线,那么这个已使用数据还需要回补回来。问题有主要有两个:
-
- 判断是否还有未使用时的逻辑不严谨。应该在更新的前一步骤做校验或者将之前的已使用数据做为条件来更新。
- 更新已使用数量的逻辑不严谨,缺少服务端的校验导致用户可以在多个页面同时打开一个订单来操作退款。
我个人并不推荐采用这种利用一个数据库字段来记录核心数据的方案,因为它总是基于一个中间状态在做运算,其中只要出现一个错误,后面的运算都会出错,最起码我们应该有数据监控,以确保这个结果值是正常的。
数据一致性问题:
- 审核通过的订单状态没有变更,或者一部分订单变更成功一部分订单变更失败
- 调用某些服务时有异常
除了从系统角度去发现问题的本质并解决,另外就是采用一种亡羊补牢的方式:数据监控以及数据的自动修复。将核心的业务数据监控起来,当发现有异常时通过一种报警机制通过相关负责人。能够确认的问题可通过一些工具实现数据的自动修改,比如某些服务临时调用失败,我们就可以自动的重新调用直到成功为止。
性能问题:
- 订单列表加载慢,最长的一次达到15分钟,影响审核效率
- 如何定位性能瓶颈
可采取一些工具或者纯人工记录日志的方式,这里我使用了dottrace,专打出头鸟:
- 如何优化
- 全面启用缓存,相同的数据只计算一次
- 去掉无用于业务的鸡肋功能。
列表页有一个批量的订单,它会包含多个订单,但列表为了显示的统一,给这个批量的订单也会显示它的价格以及状态,它的价格是下面所有子订单的价格总和,订单的状态是下面所有子订单的状态中最小的状态,所以应用了一个递归来查询,非常消耗时间。经常与业务方沟通后,确认这两个我们花了大量去计算的数据对业务方其实并没有什么实质的作用,果断去掉,性能提升感觉是从地狱到了天堂。
- 如何定位性能瓶颈
- 批量导入订单的数量很慢
- 问题
- 导入300条数据需要超过20分钟
- 导入500条数据,系统报错
- 导入的数据检测需要等操作完所有数据之后才会报出,如果数据的质量不高,会花大量时间等待,结果就是用户导300条数据可以需要尝试3次或者更多次,每次20分钟计算的话,可能就需要一个小时以上的时间,用户真实的“夸张”说法就是“导了半天也没导进去”。
- 如何优化
- 将数据检测提前做,避免用户无辜的等待:如果关键数据不合法直接提示用户。
- 原方案是一条数据一条数据的插入数据库,修改为批量插入,详情可参考:一次EF批量插入多表数据的性能优化经历
- 问题
总结:我们的目的就是为了用户能够愉快的使用系统,为此我们持续不断的进行重构优化。优化都是相对的,我们不一定要找到最完美的方案,但保证用户体验是最基本的要求。