一个定时任务是如何干爆服务器的?

1,057 阅读3分钟

今天突然接到了监控平台的电话,顿时有一种不好的预感。果然是我们模块把服务器干崩溃了。

事故

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

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

排查

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

public class Module {

    private static final 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;
                    }
                    ...
                }
            });
        }, 0, 5, TimeUnit.MINUTES);
    }

    public void moduleStop() {
    }
    
}

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

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

解决措施

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

public class Module {

    private final 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;
                        }
                        ...
                    }
                });
            }, 0, 5, TimeUnit.MINUTES);
        }

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

}

复盘

开发

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

测试

1、为什么没测出来?了解原因后也是哭笑不得。
开发虽然这些简单问题没考虑到,但是容灾、逃生做的相当不错,接口异常并没有影响到正常业务(这里提出表扬)。
2、压测
虽然业务不受影响,但是压测后肯定能发现服务器异常。
压测变成了一种形式,更没有做到多端同步

review

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

日志

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

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

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