两年多的工作经验,从白丁到熟练工,不断的成长。

2013年的成长,主要在我独立负责了一个功能模块中遇到的各种问题。这些问题也促使我去思考——怎样才能设计出可用,健壮的系统呢?

都不是什么高大上,列举主要是给自己 备个份,也让跟咱一个阶段的同学们作为借鉴。

###1. 网络异常处理

公司使用SOA的架构,我所在的系统是业务系统,所以涉及到很多的基于webService的服务调用。自然也就会带来网络异常的情况。第一个版本没有进行过异常的处理,后果是,业务处理的请求已经发出,在等待接收的时候超时,直接将记录设为失败,但当服务端却将业务处理成成功时,就造成了状态的不一致,实际上反映在我的业务里就是没有收用户钱,却帮用户缴了费。

这个点上的问题,其实反映出的是对于异常的处理。异常处理,往往也是需要去领悟,有这个意识。目前除了业务特定的异常,能够想到的是网络和数据库的异常,需要考虑。但是这一类的往往也只能做些补偿的方式。

[困惑]如何合理的看待异常,为异常情况做的事情在设计里可以占多少比重?

###2. 字段设计过短:外部流水号,用户姓名

  • 我们系统存储了外部的流水号,当外部流水号发生变动时,我们的数据库就无法处理,结果就是业务失败。

  • 用户姓名设计时,只考虑了个人的用户,当时实际上是有企业用户的,企业用户的名字很长,也就导致了字段过短。

这两个事情,反映出的问题有些不同。#1系统对于外部的东西,也就是不可控的部分,需要特别的审慎,一方面是要约定好,尽早校验,不正确的就不接收,另一方面在条件允许的情况下,存储尽量放宽。#2问题在于对于业务不熟,或者说不够精细。总结来说,就是在做数据库设计的时候,思考的不够深入,没有对于每个字段去考虑用在哪些场景,有哪些变数。归根到底,思维量减少bug量。

###3.幂等性校验:重复提交的防止和处理

重复提交遇到了两个情况

  • 自己的系统没有对交易做重复提交的处理,用户在网速慢的情况下点了两下,就提交了两笔交易,都是涉及钱的事情,问题也就很严重。

  • 公司使用了负载均衡,但是这个LB本身存在bug,将同一个请求转发了两次。这种极端情况下,我们的幂等性校验将重复的记录算作失败,而失败的结果又优先返回给了用户,导致了用户状态是失败,而实际交易还在处理中。

问题在于重复提交是一类问题,需要有一个标识保证不重复,有页面可以使用每个请求不同的token解决,单纯的接口每次请求外部传入唯一的流水号,来进行校验。但是也需要注意的是,同样地请求要返回尽量相同的结果,不要粗暴的处理失败。这也是在细节上的思考。

###4. 系统关键部分的处理:流水号生成策略;状态变更(没有数据时的处理,未知状态处理)

系统的关键部分是绝对不能出错的,一旦出错业务铁定出错。

比如流水号生成策略,是基于时间的,但是一旦量大,出现并发,就会重复。而一旦重复依据此流水号做的动作就是出错了。一个解决方案是基于数据库的ID,保证不会重复。

这个方面另一个案例是状态的变更,假设业务有三个状态,[成功,失败,处理中],当出现网络异常时就将记录记成[处理中],然后去查询另一个系统,当对方的系统里没有记录时就认为是业务没有执行,回来将记录改成[失败],这个状态流转在业务执行很慢或者数据库出问题,没有插入数据的时候就会出问题了。这个问题的勉强的解决方案是多次查询,然后人工来介入决定状态。

系统的关键部分,应该在设计的初期分析出来,然后深入思考保证不出问题。当然,分析本身也是经验和功力的积累。

###5. 包版本不匹配:沟通问题,分支打facade包

  • 公司用maven作为依赖管理工具,前期经常出现的,是线下测试OK,上线,却报unmashalling的错,大部分情况是其他系统换了版本,没有及时告知。
  • 但是遇到一次奇葩的问题是,版本是一致的,把线上包拉下来反编译,发现都是一致的,却还是报unmarshalling。后来发现,是spring配置的里面有两个同样的bean的id,导致服务都没发布出去。
  • 另一个问题是,版本管理没做到引起的,同时两个分支在开发,某一个分支修改了对外的接口参数,然后合主干,发接口包,调用方集成了该接口包,之后另一个分支在合主干前发布了接口包,此接口包没有之前分支的修改,调用方再次集成此接口包,就失去了之前的接口修改,然后该分支合到主干,打包上线,就造成了本系统有之前分支的修改,而调用方没有,线上报unmarshalling错。正确做法是:先合分支到主干,然后发布接口包,保证外部集成的是主干的代码

###6. 设计不合理:垃圾费合并缴费存储的;补缴的设计

随着时间推移,我开始负责一些系统,也开始感受什么叫设计。很多时候是辨析事情的本质,找到事物的本源,照其原来的样子来设计模型。

我负责的系统里犯了两个错误。

我所在的城市规定垃圾处置费由燃气公司代收,燃气公司收费时又规定,要合并收取。合并收取,这样的规则是在系统第一版本完成后增加的。但是时间很紧的情况下没有很好的思考,而是直接在原有记录上,将合并前的多条记录的金额都存储成合并后的金额。这个失误给后续统计金额留下了个坑。后头经过思考,发现,这种合并缴费的情况改变了每次缴费和缴纳的月份之间的关系,从1:1,变为了1:n,就引入了缴费记录和缴费项的不同概念;

另一个是在缴费前期,认为一次用户缴费请求,只会有一次缴费;当pd提出补缴功能时,我在原有模型上加入了补缴的项目,与缴费表共存。导致了很多问题。后期思考过程中发现,原来用户的缴费请求和缴费执行可以作为分开的概念,一次请求可以有多次缴费。这样就合理了。

实际上, 我所犯的错误都是常识性的,如果足够的小心,加上高人的指点,可以避免。但是我还是很珍惜这些bug,掉进坑里,才能感受其中的暗无天日。

希望2014年更多成长,可以有bug,但要犯点高级的,不要重复犯错。