1. 未初始化
因为php语言是弱类型语言,不需要声明变量类型,也不需要初始化值,所以经常对变量直接用,也不初始化。在php中这种做法不会造成致命错误,但是不初始化的这种思路会出问题。项目中经常要写一些脚本来统计业务的增长。增量的计算是今天基于前一天的增长,如果前一天的总量数据计算失败,如何计算增量呢。如果这个程序在一台新的机器上运算,没有前一天的数据,如何基于前一天的数据计算今天的增量呢。所以在计算增量的时候,两天数据的初始化是重要的。
除此之外,有的业务讲究原子性,执行业务B必须在业务A执行完毕之后。但是如果业务A一直执行失败,B就一直等待,然后阻塞后面的C业务,D业务。比如说下了订单之后,需要支付成功才能发货。那么如果支付一直不成功,这种crash如何捕捉又如何自动恢复。有一个同事写的脚本就是业务A执行失败的时候没有报警,而业务B一直在等待业务A执行成功的状态,没有尝试过重试,没有去自动恢复。导致后续的业务都阻塞了没有执行。
上面问题的关键,需要初始化程序状态,不要只想着程序在正常预期的状态,应该考虑异常的状态,如何捕捉异常状态并且自动恢复。
问题一,我们当发现前一天的数据不存在的话,就重新初始化程序状态。或者获取到最后一次成功的数据状态作为原始的数据。
问题二,当业务B捕捉到业务A未在预期时间内未执行成功的时候,发出报警,并且自动重启重试。
2. 添加一条日志引发的蝴蝶效应
本来以为在程序中添加一条日志,没什么关系,不就加一条日志,对业务逻辑又没什么影响。后来才知道自己是too young,too simple。因为这个success日志是在业务处理成功的时候打。业务量每天都可以达到10亿,所以一下子每天多出10亿日志。
(1)首先日志量过多,而磁盘有限。所以磁盘保存的日志天数减少,之前可以保存5天,后续只能保存3天。你也许会说,日志少了就少了呗,又不影响线上业务。但是对于技术运营来说,亲你知道日志对于排查问题的重要性么,就相当于破案之保留现场的重要性。
(2)再者是日志量过多,导致日志同步过慢,每10分钟的日志10分钟都无法同步完。比如在19:00的时候将2015/12/18/19/00将日志移动到backup_log文件夹,然后进行rsync,rsync时间过长,超过了10分钟,在19:10的时候2015/12/18/19/10日志已经移动到了backup_log文件夹。原有的逻辑是rsync完之后,要清空backup_log文件夹。so第二个十分钟2015/12/18/19/10的日志还没rsync就被清空了,导致同步日志丢失。后续考虑不删除backup_log的文件夹。日志都是按照时间分片的,虽然rsync是按照增量推送,因为推送整个backup_log文件夹,导致一下子文件夹中包含了很多的分支(19点的6个分支,18点的所有分支。。。),从而导致rsync的连接数过多。最后决定指定文件夹推送,不推送backup_log整个文件夹,推送19/00的日志,就指定推送00的日志文件夹,推送19/10的日志,就指定推送10的日志文件夹。
(3)后续就是日志分析,因为每次都是10分钟日志全局搜索,由于日志量太多,写入内存也变多,搜索也变慢,日志分析也慢。
真是动一发迁全身,一步错步步错。后来将日志进行精简,解决了上述各种问题。
3. for循环,if等各个分支的考虑
很多人在写程序的时候,估计也犯过这样的错误,在for循环中有if判断,if判断只考虑某些情况,不考虑for循环或者while循环的各个分支。特别是在for循环和while循环要剔除某些情况,经常不用continue而是直接return false。但有时候也犯相反的错误。某些异常不满足执行条件,不能执行for循环以后的逻辑,所以要直接return false,却使用了continue,导致不符合要求的分支在for循环之后继续往下执行。
这个错误虽然很低级,但是大家经常犯。所以在代码review的时候,一定要含情脉脉多加凝视,多加考虑再三。否则后续出问题,排查起来问题,坑坑可不会轻易饶过你。嘿嘿~
4. linux命令的滥用(awk/sed/sort/rsync)
由于linux命令的简短,很多人喜欢在程序中使用shell命令,简短的前提有时候是以牺牲可读性为前提的。一旦程序的可读性得不到保证,就容易滋生bug。前段时间日志分析有一个地方需要对日志按照某一个字段进行拆分。比如说要按照第5个字段$5进行拆分。大家就喜欢使用 awk '{print $0 > $5}'; 这个代码确实非常的简洁,但是在处理一个大文件的时候,效率特别慢,特别是在awk还有pattern匹配判断的时候。慢的原因是awk只是单进程工作,而且需要锁住整个文件进行处理。曾经有考虑将文件split,然后拆分速度有所提高。但是终究不是解决根本问题。最后决定采用多进程读取本地文件的方式进行拆分到各个子文件,程序可读性提高,效率也提高。同时采用定时实时处理的模式,每一个小时拆分前一小时的日志,这样极大的快速完成任务。而且使用php可以实现比awk更加复杂的逻辑。之前程序rsync的时候因为rsync是增量推送,所以每小时都将日志处理结果的整个文件夹rsync过去。但是rsync还是很慢,因为rsync要比较,如何让rsync不比较,同事查手册,加了一个--append的选项,就能直接将数据附加在后面,而不需要对数据进行比较。这是一种方法提高rsync,其实我们也可以只推送新产生的数据,就是对推送的日志打上时间戳,将这个日志推送过去。
这件事也让我明白:
(1)业务程序的可读性摆在第一位
如果程序不可读,一是容易逻辑混乱,滋生bug。二是后续接手的人不好维护,无法清晰理解程序逻辑,从而引进更多bug。
(2)每种程序语言或者工具都有其使用条件和局限性
awk固然简短好用,但是在实现多进程处理和复杂业务逻辑处理的时候还是有其局限性,毕竟它只是一个工具语言,而非一个适用于实现复杂业务逻辑的高级语言。为什么会误用awk和rsync,关键还是对工具了解不深,从而无法了解工具的适用场景和使用局限。
(3)尽可能简单,少冗余(Keep it simple)
之前推送整个文件夹,完全可以按照小时推送新增加的数据。不需要推送整个文件夹,导致不必要的比较。在优化rsync的时候,添加--append的配置参数,和按照小时推送都是一种优化方式。什么是简单,多一个不多,少一个不行。