今天突然接到了监控平台的电话,顿时有一种不好的预感。果然是我们模块把服务器干崩溃了。
事故
服务器某个接口一小时被访问了六百多万次
,导致监控平台数据失真、瘫痪。
距离上次发版其实已经过去一周时间了,按理说这种问题应该立马爆出来才对,立即排查...
排查
这种问题定位起来比较容易,移动端只有一个地方调用了这个接口,大家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() {
}
}
就这么一段简单的代码有多少问题
- 模块被多次拉起会不会创建多个定时任务?
- 为什么5分钟请求一次,而不是下发数据更新通知?
- 定时任务什么时候结束?
- 线程池什么时候关闭?
解决措施
从上面的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%的用途了吧
- 处理到位问题,日志就毫无用处了吗?显然不是
如果在开发编译运行阶段有时时观察日志,而不是只看自己想看的,这个问题早就被发现了
希望通过这个案例能让大家今后多注意日志的正确使用,测试反馈的日志定位完问题全部扫一遍,或者搜一下这次开发需求中的关键日志,看看有没有走到异常路径,避免线上事故。