问题一:同步操作,导致批量处理过慢,锁超时
现象:
在批量打标过程中,由于之前设计的是同步操作,一次打标的商品数过多时,导致执行时间较长,运营以为操作失败,多次重新导入和点击,导致系统出现不可预知情况。
原因:
之前为了数据的一致性,对打标过程使用了事务处理,所以当批量打标的时候,相当于对这批数据都进行了行锁,其它操作这些数据的请求都会等待事务的提交,当等待超时,就报错了,“Lock wait timeout exceeded; try restarting transaction”(排查过程)。
代码:
解决方案:
分析业务非强一致性,允许出错,故将事务去掉,将任务修改为异步任务,引入了自动重试机制,并将出错的任务加入到异常队列,用户可以看到那些是失败的任务,并主动进行重试(一般失败原因为商品主图太大,比如20~30M,导致打标失败)。
总结:
- 系统设计前缺乏整体性思考,明知道同步操作会导致业务处理缓慢,但未进行主动改进。
- 对项目编写完整的技术方案还是有必要的,有助于梳理现有业务的特性,并为之提供特定的解决方案。
问题二:异步操作,线程池滥用
现象:
运营在一次批量打标3000+商品后,前端用户进行商详查询页面缓慢或超时。
原因:
由于之前紧急将同步操作修改为异步操作时,线程池使用了公用的线程池,此线程池还用于获取其他商品信息,当打标一次占用了太多的线程的任务时,其他请求就必须排队,由于打标是一个非常耗时的操作(每一个大约200~500ms不等),所以导致队列外的大量请求因为获取不到请求线程处于等待状态。
代码:
解决方案:
定义标签管理自己的线程池。
总结:
- 深入了解线程池的使用方法,对系统中的线程池提前做好统一规划。
- 一定程度上限制线程池的使用,防止线程池滥用或者过度使用。
- 熟悉系统当前可能最大的线程数,系统支持的最大线程数,防止线程数超过外部限制。
问题三:业务范围把握不清,多系统间数据处理遗漏
现象:
最开始标签只考虑了详情页和搜索列表页,忽略了订单列表页、CMS关联的活动列表页,导致用户在活动列表页、订单列表页中看不到商品标签。
原因:
缓存清理部分未对CMS活动列表页、订单列表页中商品数据进行更新导致数据不一致。究其原因,我们在对一个已经存在的功能进行业务重构时,必须首先完全掌握之前的业务处理流程,把各个与之相关的点都梳理出来,弄清楚业务边界,如果能拿到之前的文档最好,其次最好与之前的作者做好信息沟通,单纯的通过阅读代码很容易遗漏一些细节点。
总结:
- 对老功能重构,首先必须通过一些手段完全掌握业务的边界和技术的实现手段。
- 我们在设计代码时,尽量做好抽象和代码的内聚性,减少代码的扩散和功能性逸出。
- 方法标签上必要的业务注释可以帮我们更好的理解代码。
- 当一个功能设计N个系统操作时,我们需要认真对待功能的设计,考虑数据的实时性、一致性。
问题四:事务操作中存在消息、Redis等不支持回滚的操作
现象:
去标时,详情页的标去掉了,但是通过发消息进行的搜索结果中标未去掉,导致数据不一致。
原因:
事务处理过程中,先进行了数据删除,然后通过消息通知搜索进行商品索引重构并进行了缓存清理,此时为了防止缓存被击穿,对数据进行了加载,所有完成后才会进行数据提交,这导致了,数据提交前,搜索已经收到了消息并进行了搜索重构,搜索拿到的是事务未提交前的数据,所以导致数据未更新。
代码:
解决方案:
取消事务的限制,在这样的一个处理链路中由于存在无法回滚的操作,所以嵌套事务是没有必要的,其次,嵌套事务还可能会带来更大的问题。
总结:
- 不是任何操作都是可以事务操作的,不要滥用事务处理机制。
- 在进行功能设计的时候,全局考虑数据一致性方案,强一致性、弱一致性、最终一致性。
- 在事务里面操作外部系统数据,一定要小心,需要考虑分布式事务的处理,否则请放弃使用事务。
问题五:测试不充分,导致低级bug产生
现象:
CMS活动页面过期标签未失效,搜索、详情页、订单列表页均已失效。
原因:
搜索、详情页、订单列表页中标签失效的原因是虽然标签数据未被清理,但是通过时间戳判断做了失效,但是CMS只能通过新图覆盖的方式进行数据更新,所以如果去标的定时任务未执行时一定会导致数据未更新。
代码:
在进行结束时间移除标签的SQL中,由于通过代码拷贝,字段未修改,导致业务逻辑异常,查询出来的数据为空,故去标任务等价于未执行。
解决方案:
将第二个SQL中的start_time全部替换为end_time
总结:
- 没有经过验证的点,均有可能存在问题,不要抱侥幸心理。
- 项目上线没有经过codeReview,且上线后没有做全功能的验证。
- 任何反馈出来的问题,都可能是隐藏的bug,及时排查问题,防止问题进一步扩大。
- 复制代码非常容易出错,必须格外小心,如果存在相同结构的代码,尽量通过抽象完成结构统一。