最近,做的系统进入试运行了,第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号发邮件全部正常。

---------------------------------------------------------------------------------------------------------------------------------------------------

代码是越来越健壮,却也越来越复杂了。

技术含量是逐步提高了,只是觉得有必要么?


就这样,继续天天码代码,一边解决问题,一边提升自己。

只是觉得,这样下去,没有本质的进步额。

坐等一个机会。