性能文章>一个小问题,挖出了一个吃了几万美金的bug>

一个小问题,挖出了一个吃了几万美金的bug原创

1年前
6929110

接手维护的一个项目,原来的开发同事都离职了。我一个代码都没改,服务跑着跑着出bug了。

image.png

项目描述:离职的同事交给我维护后,服务一直稳定运行了大半年,且这大半年没有改动代码。接口根据客户端使用的语言下发对应语言的业务数据,各种语言(大概有17种语言)的业务数据由后台任务执行翻译。

某天接口突然没法下发中文数据。

image.png

梳理思路,数据最初是从哪里来?

后台跑定时任务,从第三方接口获取英文数据,并将数据翻译成多种语言。流程图如下:

image.png

数据缺少了,是任务没执行导致的吗?不,数据库有找到从第三方接口获取到的数据,且日志里有调用第三方接口的记录。

按照上面的流程图,问题可能出现在翻译过程、数据持久化过程。但是根据其它代码的执行情况,排除了数据持久化过程的可能。

那么只剩下翻译过程了,会不会是线程池中出现了排队情况呢?

所有的翻译任务都被提交到一个单线程池中执行,且每次执行前,线程休眠1秒钟。

private ExecutorService translateExecutorService = Executors.newSingleThreadExecutor();

public void translate(TranslateTask translateTask) {
    Runnable runnable = () -> {
        try {
            Thread.sleep(1000);
            translateTask.doTranlate();
        } catch (Exception e) {
            logger.error(e.getMessage(), e);
        }
    };
    translateExecutorService.submit(runnable);
}

掐指一算,这个翻译任务一天的吞吐量最多为60次/分钟6024分钟=86400次。按照业务数据量,一天最多也就500条数据需要翻译。照理来说,处理500条数据应该绰绰有余。

image.png

分析了一波,好像一切都是正常的。陷入了困境…

那就将JVM的堆dump出来,剖析剖析。

不看不知道,一看吓一跳。这个线程池中竟让有114258个任务在等待。

image.png

这114258个任务从何而来?照理来说一天不会超过500个任务。顺藤摸瓜,找到了一个bug。

image.png

原来第三方接口的返回数据中的时间戳为最近一周。而前同事写的代码逻辑:“从数据库加载最近一周的数据,并以时间和语言的组合值放到Set集合中。从第三方接口获取的数据用该Set集合来判断是否已存在。如果不存在,则调用谷歌翻译服务,并将翻译后的数据更新保存到数据库。上述任务每隔5分钟执行一次”。

乍一看这代码,好像没啥毛病。衰就衰在那段时间,调用第三方接口返回的数据中的时间戳出了问题(返回了2015年的时间戳),代码判断数据库不存在该数据,而任务又是五分钟执行一次,导致系统堆积了大量的翻译任务,而本该需要被及时处理的数据只能排队等待,导致缺失了部分语言的数据。最严重的是,程序疯狂地调用谷歌翻译服务,产生了一笔不小的服务费用。

image.png

分析此次事故的原因

  1. 代码未做容错处理,没有考虑到第三方数据异常的情况。

  2. 一天内多次调用第三方接口,返回数据都是不变的。很显然,任务的执行周期一天1~2次就行,但任务却被设置成5分钟执行一次。

  3. 在大量调用谷歌翻译服务时,系统未做监控告警。

分类:
标签:
请先登录,再评论

话说心真大,谷歌服务费用不是一般的贵。😄

1年前

为你推荐

字符串字面量长度是有限制的
前言 偶然在一次单元测试中写了一个非常长的字符串字面量。 正文 在一次单元测试中,我写了一个很长的字符串字面量,大概10万个字符左右,编译时,编译器给出了异常告警 `java: constant
多次字符串相加一定要用StringBuilder而不用-吗?
今天在写一个读取Java class File并进行分析的Demo时,偶然发现了下面这个场景(基于oracle jdk 1.8.0_144): ``` package test; public c
如何通过反射获得方法的真实参数名(以及扩展研究)
前段时间,在做一个小的工程时,遇到了需要通过反射获得方法真实参数名的场景,在这里我遇到了一些小小的问题,后来在部门老大的指导下,我解决了这个问题。通过解决这个问题,附带着我了解到了很多新的知识,我觉得
高吞吐、低延迟 Java 应用的 GC 优化实践
本篇原文作者是 LinkedIn 的 Swapnil Ghike,这篇文章讲述了 LinkedIn 的 Feed 产品的 GC 优化过程,虽然文章写作于 April 8, 2014,但其中的很多内容和
「每日五分钟,玩转 JVM」:久识你名,初居我心
聊聊 JVMJVM,一个熟悉又陌生的名词,从认识Java的第一天起,我们就会听到这个名字,在参加工作的前一两年,面试的时候还会经常被问到JDK,JRE,JVM这三者的区别。JVM可以说和我们是老朋友了
据说99.99%的人都会答错的类加载的问题
概述首先还是把问题抛给大家,这个问题也是我厂同学在做一个性能分析产品的时候碰到的一个问题。 同一个类加载器对象是否可以加载同一个类文件多次并且得到多个Class对象而都可以被java层使用吗请仔细注意
Java多线程——并发测试
编写并发程序时候,可以采取和串行程序相同的编程方式。唯一的难点在于,并发程序存在不确定性,这种不确定性会令程序出错的地方远比串行程序多,出现的方式也没有固定规则。那么如何在测试中,尽可能的暴露出这些问
Java多线程知识小抄集(一)
本文主要整理笔者遇到的Java多线程的相关知识点,适合速记,故命名为“小抄集”。本文没有特别重点,每一项针对一个多线程知识做一个概要性总结,也有一些会带一点例子,习题方便理解和记忆。 1.interr