最近,做的系统进入试运行了,第2天就出现了1个小型事故。
黑名单过期,需要给用户发送邮件。
之前,是发送给 咱们开发者自己。
试运行之后,需要发送给用户。
但是,上线的第2天,给1000个用户,发送了4000个“移除黑名单”的邮件。
1、第1个原因
试运行期间,需要删除旧的黑名单。
产品原来没有说,只是规划 线上正式运行的时候,清理一次数据。
2、按道理,不可能发送那么多邮件的。
同一天到期的黑名单,不可能有4000个。
一共才16000多个黑名单记录。
找了1整天,终于排查出了原因。
a. for 循环,处理每一个过期的黑名单。
没有用单独的线程,主要是我觉得 每天处理的数据量非常少,没必要。
b.普通更新代码是同步的,发邮件是异步的,但是异步没有生效。
spring的异步注解Async需要配置1个注解。
<task:annotation-driven proxy-target-class="true" executor="asyncExecutor"></task:annotation-driven>
之前用过,这次复制粘贴的时候,给漏了。
期望异步,确实同步,不影响正确性。
但是,当异常发生的时候,就有影响了。
c.在for循环的时候,如果前面代码抛出了异常,就退出了。
仍然是简单考虑,没有捕捉异常。
抛出异常的原因是,有的地方查询不到用户。
在出事的前1天,代码增加了“空指针判断”,所以不会报错。
因此,发送邮件全部成功,4000多封。
系统上线了3个月,每天都有黑名单产生,不断累计。系统每天1次定时任务在处理,但是每次处理了20个左右,就抛出了异常。
最终导致,黑名单一直在累计,8月份,就稳定在了4000疯上下。
d.Async注解下的类,依赖“代理生成方式”。
默认是按照标准的,基于Java接口的。
如果配置了proxy-target-class=true,就是按照cglib基于类生成代理。
发邮件等很多代码,出于简化,我只写1个实现类,不再单独定义一个接口。
e.Async异步执行,实际上就是开启线程或者重用线程池。
但是按照Spring的配置,还不如手动用Executor控制。
f.听从boss的建议,每个for循环单独开线程,每个任务都互相独立。
而不是,只让发邮件这个功能独立。
---------------------------------------------------------------------------------------------------------------------------------------------------
补充1点,线上环境,如果对性能没有极致的要求,多打点日志,方便排查问题。日志就是蛛丝马迹,这样才方便开动大脑,逻辑推理,做个好侦探呀。(*^__^*)
2,空指针判断,宁可多,也不能少。鬼知道哪个地方,突然为null了。
本地代码不抛,线上数据多了,可能就坑了。
也可能存在异常数据,比如“员工离职了,可能就突然查询不到了”。
3,控制变量法,太重要了。
31号上线,1号没问题,2号却出问题了。
就是因为,1号下午改动了代码,增加了Null判断,所以2号发邮件全部正常。
---------------------------------------------------------------------------------------------------------------------------------------------------
代码是越来越健壮,却也越来越复杂了。
技术含量是逐步提高了,只是觉得有必要么?
就这样,继续天天码代码,一边解决问题,一边提升自己。
只是觉得,这样下去,没有本质的进步额。
坐等一个机会。