写点什么

【程序大侠传】异步架构应用回调数据接收接口偶发 NPE

作者:Disaster
  • 2024-07-01
    上海
  • 本文字数:3521 字

    阅读完需:约 12 分钟

前序

在这片浩瀚的代码江湖中,各大门派林立,各自修炼独门绝技,江湖中的侠士们分别担任着开发、测试、产品和运维的角色,共同守护着这片数字化的疆域。


开发门派:代码剑宗代码剑宗的弟子们精通各种编程语言,擅长写出优雅而高效的代码。每一个函数、每一行代码都如同剑招,精准无比,直击要害。他们不断钻研新的技术,追求极致的性能和用户体验。


  • 绝技:算法剑法通过优化算法,提高系统的响应速度和处理能力。

  • 绝技:架构心法设计出高扩展性和高可维护性的系统架构。


测试门派:断点神教断点神教的弟子们以严谨和细致著称,他们通过各种测试手段,确保每一行代码的质量。他们的绝技如同内力,能够发现隐藏的漏洞和瑕疵,保障系统的稳定性和可靠性。


  • 绝技:白盒测试术通过了解代码内部结构,进行全面的测试。

  • 绝技:黑盒测试术从用户视角出发,进行功能和性能测试。


产品门派:需求派需求派的弟子们擅长从用户需求出发,设计出符合市场需求的产品。他们如同江湖中的智者,洞察用户心理,预见市场趋势,为开发门派提供明确的方向。


  • 绝技:用户画像术分析用户行为,构建用户画像,指导产品设计。

  • 绝技:需求拆解法将复杂的需求拆解成可执行的任务,确保开发顺利进行。


运维门派:守护盟守护盟的弟子们负责系统的维护和保障,如同江湖中的护法,确保系统的稳定运行。他们精通各种运维工具和技术,能够迅速应对突发问题,保障系统的高可用性。


  • 绝技:自动化部署术通过自动化工具,实现高效的系统部署和更新。

  • 绝技:监控预警法实时监控系统运行状态,及时发现并解决问题。


项目管理门派:天工阁在这片浩瀚的代码江湖中,天工阁是一个专注于项目管理的门派。天工阁的弟子们以严谨的流程和科学的方法论著称,他们如同江湖中的策划大师,统筹全局,确保每一个项目都能顺利完成。


  • 绝技:立项心法准备项目背景、项目目标(含 OKR 目标)、项目周期、投入产出分析和产品/技术方案。

  • 绝技:目标牵引术通过明确项目目标,牵引运营、产品和研发各相关方达成共识。


江湖现状在这片江湖中,各大门派互相竞争又互相协作,共同面对来自外部的挑战。开发门派、测试门派、产品门派和运维门派的弟子们通过不断修炼和切磋,提升自身的技艺,为江湖的繁荣和稳定贡献力量。


21 世纪的某一天,代码剑宗的阿强正坐在洞府的蒲团上沉浸式库库思考需求派小汝提出来的一个棘手的需求落地方案而眉头紧锁,此时断点神教小美突如其来的传音“阿强,快来看看你们门派中的弟子 A 在参与“异步回调接口处理数据”的任务中时候偶发出现 npe 异常了”把阿强从精神世界中拉入现实。阿强有点无奈地摇摇了头地心里想道:“npe 这种小问题,看来又是需要本座略微施展实力的时候到了”.......

第一章:小问题而已

洞府外,阿强见到了阿美,阿美见到阿强就迫不及待地描述了一下受伤弟子 A 的内伤情况,10 分钟之后,阿强已经知道基本情况,大致就是:业务系统 A 雇佣了弟子 A 去保护其安全和提供发展助力,弟子 A 在开发业务系统 A 地一次任务时偶发的发生 npe 异常(A 系统是此次弟子所保护的系统),此任务是需要三方系统进行交互的,其中与三方系统交互是采用的异步方式。从数据层面看,A 系统去请求三方系统的接口会返回一个唯一 Id,A 系统会根据这个唯一 id 记录此次的请求,此时的请求的记录状态是一个中间态,而三方处理完业务逻辑之后会通过回调 A 系统暴露给三方的接口去更新 A 系统里面的对应的那条请求记录状态至终态。大致流程图如下:



阿强很熟练地从兜里拿出了门派每个人都有的法器 IDEA(此法器能够收录所有的系统发布的任务过程),并通过法器天书(能够记录出问题的堆栈信息)分析了整个过程,阿强最终定位到是 repository 获取数据为空导致:


//回调接口处理核心逻辑public Map<String, Object> callBack(Map<String, Object> params) {      //do something......      //thirdId 跟 innerId 都是三方系统维护的字段            AEntity aEntity = aRepository.selectDataByThirdIdAndInnerId(thirdId, innerId);            //根据堆栈,下面这行代码是异常抛出行           String status = aEntity.getStatus();            //do something......                  return resultMap;    }
复制代码


而其中 repository 数据的录入到 a 系统的操作是在第一次跟三方系统进行交互的时候:


public void thirdApply(ApplyContext applyContext) {  //do something......  //通过http请求框架调用三方接口  Response response = httpClient.doPost(params);  if(response.success()){    AEntity aEntity = new AEntity();    //通过返回结果,组装状态参数    aRepository.insert(aEntity);  }  //do something......}
复制代码


此时的阿强对于处理这个 npe 还是信心满满的,因为 repository 获取问题导致 npe,无非就两种情况,要么是数据没有正常录入到数据库,要么是查询数据的条件无法在数据库中查询到数据。为了更加清晰的还原这个问题,也是为了找到偶发 npe 发生的根因,他通过自己的法器 idea 回溯当时的一次回调请求,发现并没有发生 npe 的异常。但是门派中记录的当时三方回调请求链路库中表示当时的一次请求发生了 npe 的异常。阿强此时开始在脑海里面开始头脑风暴,考虑各种造成这种现象的原因。


10 分钟之后,阿强信心满满地又重新分析了一下弟子 A 的代码,果然,他在 thirdApply 这个接口中发现了一丝猫腻,系统 A 的记录落库是在调用三方系统之后,那考虑一种场景,如果三方系统在处理完自己逻辑并回调系统 A 时,A 系统还没有将请求记录落库,这个时候在回调接口中会拿不到记录数据然后应用就会抛出 npe 的异常,与此同时,异常抛出之后,记录数完成了落库操作,三方系统再次回调就不会发生 npr 异常。阿强此时脑海中的画面如下:


第二章:小问题如何解决?

为了验证自己的想法,阿强在门派弟子 A 写的代码里面修改了一部分代码如下:


public void thirdApply(ApplyContext applyContext) {  //do something......  //通过http请求框架调用三方接口  AEntity aEntity = new AEntity();  //组装其他参数  aRepository.insert(aEntity);  Response response = httpClient.doPost(params);  if(response.success()){    //根据返回结果设置记录状态    aRepository.update(aEntity);  }  //do something......}
复制代码


修改完之后,应用 A 的回调接口再没有出现过 npe 异常,但是随之而来的一个问题是,系统 A 里面的状态会出现回溯。阿强又开始了新一轮的头脑风暴,此时他的脑海中的画面如下:



阿强此时又开始动用他的知识库去思考解决方案,他又改了一版代码:


public void thirdApply(ApplyContext applyContext) {  //do something......  //通过http请求框架调用三方接口  AEntity aEntity = new AEntity();  //组装其他参数  aRepository.insert(aEntity);  Response response = httpClient.doPost(params);  if(response.success()){        LockUtil.lock(thirdId,2000){          AEntity aEntityFromDb = aRepository.selectDataByThirdId(thirdId);          //B是更新后的状态值          if(aEntityFromDb.getStatu().equals('A')){            //根据返回结果设置记录状态          aRepository.update(aEntity);           }        }     }  //do something......}
复制代码


//回调接口处理核心逻辑public Map<String, Object> callBack(Map<String, Object> params) {      //do something......      //thirdId 跟 innerId 都是三方系统维护的字段      LockUtil.lock(thirdId,2000){           AEntity aEntity = aRepository.selectDataByThirdIdAndInnerId(thirdId, innerId);            //根据堆栈,下面这行代码是异常抛出行              String status = aEntity.getStatus();             //do something......           }eles{             //do error scene         }                 return resultMap;    }
复制代码


阿强改完这版之后,系统 A 基本上没有发生过状态问题,阿强心里也清楚,如果再出现问题,那只能是在系统负载较高的场景下了。


此时,已经过去了 2 个小时,阿强作为一个对可知错误无法容忍的人,他又开始了新的一轮头脑风暴,突然,阿强脑子的灵光一闪而过。他从思考角度上面重新对这个问题做了一番考量,如果让三方系统配合做一个回调的重试机制是不是就可以完美解决这个问题了呢?正当阿强想把这个新思路好好地梳理一遍的时候,天工阁小凯着急的传音随之而到,“阿强,小汝给你提的那个需求,目前的技术方案出来了吗?尽快给出这个需求的排期!!”。阿强此时对小凯的打扰非常不喜,语气不喜地录入让小凯乖乖地等他想完这个回调处理方案的传音,阿强正打算传过去时,突然想到了什么似的摇摇了头,最终取消了发送传音的想法。

发布于: 刚刚阅读数: 5
用户头像

Disaster

关注

talk is cheap,show me the code 2021-12-29 加入

A coder who likes open source, graduated from Jishou University with a major in computer science and technology, has worked in the field of network security and Android, and is now constantly explorin

评论

发布
暂无评论
【程序大侠传】异步架构应用回调数据接收接口偶发NPE_Disaster_InfoQ写作社区