WES 重构系列(六):老旧系统的持续性改造
文内的图示需要科学上网才能流畅查看,图床直接用了 GitHub。
聊聊老旧系统的奇葩问题与持续性改造心得,有些点比较小,有时间再展开一下。
我觉得系统优化,很多时候是个持续性的过程,避免眼高手低,从小的点开始做起,一步一步将系统优化成整洁干净的样子。
1、ABA 问题
在并发编程中,我们经常聊到CAS,CAS 全称是 Compare And Swap,CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。在进行并发修改的时候,会先比较A和V中取出的值是否相等,如果相等,则会把值替换成B,否则就不做任何操作。
在业务开发中,也会遇到这个问题,尤其是在老旧系统的升级改造中,真的切切实实感受到了 ABA 的伤害。
举一个简单例子,WES 系统中存在工作站下线的业务,需要做许多的逆向操作,比如:
- 回滚 WES 任务
- 回滚出库作业单
- 解锁二级库存、三级库存
- 通知搬运系统小车离站
- ……
有一次遇到一个老旧系统工作站下线后,任务状态还是待实操状态(理论上是已回滚状态),导致后续调度任务出现了级联故障。
问题根因也很简单,用了 MyBatis-Plus 封装的更新方法,没加状态乐观锁,导致原本已经回滚的任务又重新改为了待实操状态。
后面大家系统性做了自查,包括代码上以及 SQL 监控上,update 语句必须加上版本号乐观锁。
2、Spring Scheduling Task 自定义线程池问题
有一些老旧系统没接入 XXL-JOB,直接用了 @Scheduled 执行定时任务,加上自身又没有自定义线程池,导致定时任务之间其实是串行执行的(@Scheduled 默认使用单线Executors.newSingleThreadExecutor() )。
3、不要使用粒度很大的锁,尽量缩小缩范围,业务复杂了真的很容易死锁。
4、优化 for 循环 DB 调用和远程调用
5、SQL 查询只查必要的字段
6、死锁问题注意锁顺序保持一致
7、不吝啬日志,尤其是关键节点的日志,比如关键业务日志、error 日志、warn 日志、返回点日志
8、事务里面别搞多线程,我真遇到过有老旧系统里面写了多线程,导致现场出问题
9、如果运维能力一般,mq 消息的重试尽量别交由消息队列本身处理
10、做 B 端业务时,需要和许多第三方打交道,接口字段注意一下,尤其是布尔类型,is 开头的容易踩坑,能让第三方改掉就尽早改掉。
11、不要写长方法,抽取方法时,也要考虑单一职责,虽然实际工作中很难做到写单元测试,但至少在脑海里过一遍是否抽出来的这个方法可以写单元测试。
12、长逻辑不用函数式编程,不好调试,且不好发现问题
13、不信任从第三方获取的数据,要判空、提前校验数据。
14、final 关键字能让代码变得可维护性更强一些,避免上下文变量改变。
15、聊聊 BigDecimal 引起的车辆不调度问题