一个被忽视的定时任务写法,直接把服务器干穿了!

61 阅读3分钟

点击上方“程序员蜗牛g”,选择“设为星标”跟蜗牛哥一起,每天进步一点点程序员蜗牛g大厂程序员一枚 跟蜗牛一起 每天进步一点点31篇原创内容**公众号

今天突然接到了监控平台的电话,顿时有一种不好的预感。

果然是我们模块把服务器干崩溃了。

事故

服务器某个接口一小时被访问了六百多万次,导致监控平台数据失真、瘫痪。

距离上次发版其实已经过去一周时间了,按理说这种问题应该立马爆出来才对,立即排查...

排查

这种问题定位起来比较容易,移动端只有一个地方调用了这个接口,大家review下代码能不能发现问题

public class Module {

    privatestaticfinal ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();

    // 模块启动
    public void moduleStart() {
        // 定时任务,延迟 0 分钟开始,每隔 5 分钟执行一次
        executor.scheduleAtFixedRate(() -> {
            // 在子线程中执行任务
            request(new Callback(){
                @Override
                public void onResponse(Data data) {
                    if (data == null) {
                        Log.w("data is null");
                        return;
                    }
                    ...
                }
            });
        }, 05, TimeUnit.MINUTES);
    }

    public void moduleStop() {
    }
    
}

就这么一段简单的代码有多少问题

  • 模块被多次拉起会不会创建多个定时任务?
  • 为什么5分钟请求一次,而不是下发数据更新通知?
  • 定时任务什么时候结束?
  • 线程池什么时候关闭?

解决措施

从上面的4个问题相信大家已经知道原因了

public class Module {

    privatefinal ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
    
    private ScheduledFuture<?> scheduledFuture;
    
    public void moduleStart() {
        // 如果已经创建就不创建了
        if (scheduledFuture == null) {
            scheduledFuture = executor.scheduleAtFixedRate(() -> {
                // 在子线程中执行任务
                request(new Callback(){
                    @Override
                    public void onResponse(Data data) {
                        if (data == null) {
                            Log.w("data is null");
                            return;
                        }
                        ...
                    }
                });
            }, 05, TimeUnit.MINUTES);
        }

    
    }
    
    public void moduleStop() {
        // 结束任务
        if (scheduledFuture != null) {
            scheduledFuture.cancel(true);
            scheduledFuture = null;
        }
        // 关闭线程池
        executor.shutdown();
    }

}

复盘

开发

开发自身素质肯定是第一责任人,前面提到的几个问题都没有考虑到

测试

1、为什么没测出来?了解原因后也是哭笑不得。

开发虽然这些简单问题没考虑到,但是容灾、逃生做的相当不错,接口异常并没有影响到正常业务(这里提出表扬)。

2、压测

虽然业务不受影响,但是压测后肯定能发现服务器异常。

压测变成了一种形式,更没有做到多端同步

review

更不用说了,国内同事深有感触,形式大于一切

日志

不知道大家注意到没有,当这个接口返回空数据后其实是有一行日志打印的,所以细心的同学完全可以在上线前避免这个问题。

日志的作用到底是什么?

  • 定位测试或线上反馈的问题,这应该是99%的用途了吧
  • 处理到位问题,日志就毫无用处了吗?显然不是。如果在开发编译运行阶段有时时观察日志,而不是只看自己想看的,这个问题早就被发现了

希望通过这个案例能让大家今后多注意日志的正确使用,测试反馈的日志定位完问题全部扫一遍,或者搜一下这次开发需求中的关键日志,看看有没有走到异常路径,避免线上事故。

如果这篇文章对您有所帮助,或者有所启发的话,求一键三连:点赞、转发、在看。

关注公众号:woniuxgg,在公众号中回复:笔记  就可以获得蜗牛为你精心准备的java实战语雀笔记,回复面试、开发手册、有超赞的粉丝福利!