接口挂死,你咋办?

原文链接: https://juejin.im/post/5c3d2c4b6fb9a04a01649397

引子

周末原本是难得惬意的时光,尤其是晚上,陪家人看看电视啥的再好不过了。不料,晚饭过后不久,手机就传来 ”叮“ 的一声,清脆悦耳,以为有人发红包,定眼一看,不妙!—— 线上服务器告警。

连忙扒开电脑,准备 ”灭火“,根本没心思看电视。

过程

登上服务器看日志,发现每隔 2 秒钟 tomcat 日志就会打印一次 ”Broken pipe“,就是这种:

似曾相识,之前有位同事做大量数据导出功能,就出现过,很显然是接口 response 耗时过长导致的。那接口是不是有问题了?

用 postman 测试一下线上接口,点击 ”send“ 按钮后一直loading,等了老白天才有结果返回,耗时 1 min 左右。

打开手机端也无法获取数据了,一直在加载中。

嗯,事情不妙。

深吸一口气,冷静 ~

告诫自己再看一遍告警短信:redis 连接池异常。难道 redis.pool 配置太小不够用?请教了一下相关同事,他说 redis 没问题。

我打开 idea , 重新检查了一遍 redis 相关的配置,定义,使用,该检查的都检查了。

心想:既然是接口耗时,那我就把代码执行过程中,每一个步骤的执行时长先打印出来看看嘛,究竟是哪里”占坑不拉屎“?

先理一理代码逻辑:

  1. 从 redis 拿 IdList (大约2万)
  2. 遍历 IdList 从 DB 取数据 Entities,并组装为 EntityVos(组装信息较多,需从其他 rpc 接口获取)
  3. pageUtils 返回分页数据

我发现第 2 步耗时特别猛,于是先把组装的某一项信息注释掉不去 rpc 拿了,这样做的依据就是:在我刚才部署打印日志之后不久,这个调用 rpc 的服务全部报 time-out。

马上注释掉,上线。

嗯,看样子正常了,时间已经飙到10点多了,洗洗睡去。

晚上躺在床上在想,其实我可以对那个 rpc 的调用结果做一层 redis 缓存,减少请求频次应该会好很多。

第二天,兴高采烈地来到办公室,同事直接问候早安——”好像咱们的xx没数据啊,你看看?“

话音未落,我的手机”叮“地一声,清脆悦耳,这回我知道肯定不是红包了,是”炸药“ ~

连忙扒开电脑,准备”灭火“,根本没心思跟别人说早安。

这次告警短信的内容不是 redis 相关的了,而是:JVMFullGC卡顿次数频繁。

我又重读了几遍接口代码,总觉得哪里不对。哦~ 耗时的问题我是真的解决了吗?

由于打印日志并未去掉,我仔细分析了下:既然拿 2 万的实体类进行遍历组装,最后分页只返回 10 条数据,这。。。

假分页,害死人~

于是把分页逻辑放到组装信息之前来做,比如你最终是返回 10 条数据,那就是组装 10 条了,而不是像之前那样组装了 2 万条。

其实,原因也显而易见了:并发高,组装 vo 时间过长,导致用到 redis 的资源也得不到释放,new出来的大对象也多,得不到回收,一个请求未响应下一个请求已来,越叠越多,最后挂死。产生的表象就是:接口耗时长,redis 连接池异常,JVMFullGC卡顿等。

晚上复盘,拿到两张 MinorGC 和 FullGC 总次数的监控图:

总结

1、其实线上一产生问题,应该先怀疑内存或 CPU 是否占用太高,我是太小看这次问题了,不然第一时间拿到 dump 就会更顺利。 2、虽然改了一点逻辑,但同时顺便重构了不少代码,重构应该发生在每时每刻才对。

(完)

你可能感兴趣的:(接口挂死,你咋办?)