本文记录在解决列表移动数据同步的问题的全流程,整个解决过程耗时达2周,今天终于完美解决了,在这里记录整个过程中的心理状态、解决过程以及经验教训。
背景说明
当前界面有一个列表,里面有用户定义的自选股,支持增加、删除、更改顺序、分组等操作。自选股数据需要多平台同步,通过手机号对接后台自选股数据同步接口。
问题表现
测试发现,上下移动自选股时,当快速上下移动时,最终结果与预期不符,操作慢一点表现是正常的。此问题在开发环境可以稳定复现。
问题处理过程
可以稳定复现的Bug,都应该不难解决。话是这么说,但因为是新接手这块业务,这里的数据同步流程较为复杂,刚开始,难以进入别人的代码思路,再加之有其他一些较容易处理的问题和需求,这个问题就一直拖放着。
时间就这么慢慢过着,事实证明,短暂的忽视问题并不能解决问题,该来的还是来的,就这样过了一个星期,期间,有话一个上午的时间,专门看这个问题,梳理了下整个流程,但没发现问题在哪里,只不过对代码有了初步的理解,感到不那么陌生。
教训:拖延是解决不了难题的,只不过是暂时缓一缓,该来的还是来了。快要到发版时刻了,测试在催促,重装上阵,继续啃这个硬骨头。
流程分析
为了简化对同步流程的分析,选取5只股票进行测试,代码后缀从0~4,这样,好便对比预期和实际结果。
原有的同步流程:
- 从自选股数据中心获得界面股票
- 根据移动情况,更新该局部变量
- 更新界面显示的自选股
- 发送自选股更新请求,带上界面显示的股票
- 如果是移动某只股票到最尾端,采用先删除再添加方式
- 将自选股数据中心与界面显示股票做对比,找出待删除和待增加的股票
- 先发送删除请求,在删除响应中更新自选股数据中心
- 再发送增加请求,在请求响应中更新自选股数据中心
- 批量更新自选股
- 如果是移动某只股票到最尾端,采用先删除再添加方式
- 更新完毕后,通知界面刷新
上述流程在每一次自选股操作时,都会走一趟。
在前面流程梳理基础上,增加若干断点调试输出。这里分享几个调试技巧:
利用“命中断点时”的调试功能,可动态输出程序的内部状态,支持输出变量以及stl数据结构等信息。
通过3~5个样例数据来输出一次操作过程中的重点信息
经过多次测试,发现先删除再添加时,如果添加响应先于删除响应回复,会造成界面显示错乱。这是问题原因1。
从上面的流程可知,如果快速移动,比如连续A、B、C三个操作,正常的预期是B操作是在A操作结果之上进行,C操作是在B操作结果之上进行。这只有收到A操作响应之后再进行B操作才能满足。
当用户快速操作时,就不是这样的,A、B操作都会基于原始数据进行移动,造成快速移动时,最后顺序与预期不符。
解决方案设计
通过上面的分析得知,问题定位在业务时序,具体表现为:
- 操作结果依赖多个异步操作的应答顺序,而应答顺序是不保证的,这就是为什么有时候正常,有时候不正常。
- 同步结果依赖上次操作的结果,一旦上次操作响应稍慢,会干扰本次操作,造成慢一点操作是正常,快一点操作就不正常。
解决方案推进
问题原因找到了,解决方案也有两个方向:
-
方向一:在现有流程上打补丁
- 依赖多个异步操作结果:将两个异步操作改为同步,将待删除和待添加的数据带入请求中。先发删除操作,等删除操作响应,再发送添加操作。
- 依赖上次操作结果:原始数据既从自选股中心获得,也从界面中获得,区分条件是上一次的同步操作是否结束,判断依据是相关指令是否全部完成。计划在底层增加若干计数器,记录每种指令发送和响应次数。如果指令全部响应,则下次从自选股中心获取,否则,从界面中获取。
-
方向二:简化同步逻辑,不关心多次操作中间状,只关心起始和终止态。
- 依赖多个异步结果:去掉先删除再添加的流程,采用全量更新。
- 依赖上次操作结果:
- 起始数据只从当前界面中获取,每次操作同步更改界面数据。
- 多次操作,只关心最后一次操作的结果。当最后一次操作结束时,才进行数据同步。
我一开始是朝着方向一来走的,按照过往的开发经验,在Bug修复时,尽量不要改大框架,而是打补丁的方式,采用较少的代码来解决问题。但那个异步的问题,要新增入参,我在修改时就感觉改的较多,不合适,但已经改到这里了,思路就局限在这儿了。
后续代码经过评审后,leader提出了方向二,跳出原有框架,采用更简单的流程来,很快代码改好了,相比于方向一,不用加那么多状态变量,业务流程更加简洁易懂。
后来通过之前维护的同事了解到,采用先删除再添加的方法,可减少在拖动时的同步流量,但这个流程,其结果正确性严格依赖响应顺序,并且在多次操作的情况下,这种问题会更加严重,甚至会误删除自选股,因此,为了节省这点流量而引入异步操作,是得不偿失的。采用全量更新方案,既简单又可靠,而且,这种自选股同步操作是低频操作,为了这种低频操作做流量优化是不合适的。
小结
这个问题从开始提出到最终完美解决,耗时达2周,整个处理期间,有因自己不熟悉流程而拖延修改,直到快发版时,才又着手修复。在分析并发现原有流程缺陷后,在原有流程之上继续打补丁式的修改,也有经过leader提点后的新流程修改。整个过程中,让我深刻地明白到几点:
- 不破不立,在一个旧房子上修修补补,不是解决漏雨的长久之计。要敢于重起炉灶,住进新房子。
- 面对旧代码中的优化分支以及因优化引入的问题,要敢于质疑,优化要优在点子上,要针对高频操作,满足2/8原则,过早的优化是万恶之源。
- 在业务开发中,少写对响应返回顺序有要求的代码,这类流程非常容易出错。