completefuture造成的rpc重试事故

前言

最近经历了一个由于 completefuture 的使用,导致RPC重试机制触发而引起的重复写入异常的生产bug。复盘下来,并非是错误的使用了completefuture,而是一些开发时很难意识到的坑。

背景

用户反馈通过应用A使用ota批量升级设备时存在概率性失败的可能;

功能的运行流程如下:

  1. 应用A调用应用B的rpc接口
  2. 应用B将请求发布至mqtt
  3. 设备订阅接收,开始进行ota升级

通过复盘设备端以及后台的调用日志得知,设备端在相同时间戳或毫秒级相差的时间戳内收到了两条相同的指令,后台日志中也可以找到对应的消息发送日志。

那么这就是一个消息被重复发送的问题,一般有两种情况:

  1. rpc接口被多次调用
  2. 发布消息时出现重复发送

考虑到mqtt的qos特殊性,短暂的将qos=0,即不存在mqtt重发机制,依然会出现重复发送问题;

结合后台的接口调用日志后,可以确认是应用A重复调用了rpc接口。

复盘

在定位到是后台重复调用rpc接口问题后,解决与排查方式也就变得透彻了。

首先是查看代码:经过排查以及debug,应用A只是简单的业务方调用接口,并且由于app上有防触和后台接口限流处理,排除应用A的功能开发问题;

问题只可能出现在 调用rpc应用B接收与返回 两个动作上;

熟悉远程调用服务的同学应该明白,rpc接口调用,特别是基于dubbo-注册中心这样的传统调用方式,是存在默认的失败熔断、降级,以及造成这次事故的罪魁祸首 异常重试机制

重试机制:

在分布式接口调用场景中,上游方调用接口,为保证其接口的高可用性,会配置无感的重试时间以及重试策略用来抵御当网络波动,请求丢失,异常等问题时的接口可用性

由于应用B是中心类应用,是很多服务的下游应用,所以针对接口的高可用的设计都将其考虑到了正常调用的范畴中。

因此未防止应用A发起请求后,出现由于应用B的网络波动或业务内部的长链路导致的超时而出现重试调用的问题,业务B中采用了以下的执行器方式进行具体消息的发布:

    public static void main(String[] args) {
		System.out.println("我已经收到了:"+t());
    }

	public static void rpcInterface(){
        Executor executor = new ThreadPoolExecutor(1, 2, 1L,
                TimeUnit.SECONDS, new LinkedBlockingDeque<>(2), Executors.defaultThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy());
        CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> {
            System.out.println("业务开始,时间:"+System.currentTimeMillis());
            try {
                Thread.sleep(1000L);
            } catch (InterruptedException e) {}
            System.out.println("开启供应链头,组装消息");
            System.out.println("组装");
            String message = "message";
            return message;
        }, executor);
		
        completableFuture.whenComplete((message, exception) -> {
            System.out.println("消息发送:" + message);
        });
        return "ok";
    }

当应用B的接口判断为处理时间不可控、非查询、消息发布等特殊接口时,会通过以上处理,将实际处理动作线程与rpc接口调用的返回分割开。

比如以上代码执行结果为:

出现问题

在排查过程中,猜测一定是CompletableFuture运行中出现了阻塞,导致返回 ok 的时间超过配置的超时时间而发生重试;

往这个方向考虑结果就很清晰了:

CompletableFuture发生阻塞,再次请求rpc接口,这时CompletableFuture运行,第一次与第二次请求同时进行了消息发布动作;

这里先提应用B在此处线程池的设计与使用了:超时时间3S,有边界队列,拒绝策略为线程等待或主线程执行;

在经过压测后并未发现问题,于是在次接口处理中同样使用了该线程池;

但是,批量ota升级这个动作有些业务上的特殊,会导致任务入队到执行的时间比预想中的要长;

因此这里出现阻塞的原因通过一步步排查得出结论为:

1、多个地方使用同一线程池,而最大线程数未扩容;
2、业务内部设计不合理,出现预料外的慢业务链路,导致占满

结论

这就像是一个陌生的同事接手了一个业务,然后模仿其他相识接口的开发copy 了相同的线程池执行器,然后一股脑的进行套用;

最终出现了这种在测试环境很难出现的问题,因为本地网络加上测试环境线程充足的原因,并且因为相同的线程执行器所以也未考虑到经过压测;

不过回顾这次事故本身,问题与解决很简单,可以算是不熟悉系统导致的bug。但是从另一个角度上看,其实完全可以从源头上避免掉这种重复调用rpc接口的bug出现。

接口幂等

处理重复调用,即对接口进行幂等性;

并非所有的rpc接口都需要对接口做幂等处理,对于非订单操作,db生成的功能,仅查询是无所谓重复调用的。

不过还是需要结合实际考虑,因为本次事故的接口中也是考虑到线程的分离也就没注意对接口进行幂等;

rpc接口幂等有三种通用方案:

方案一:

请求方请求时创建对应接口规则的分布式锁,下游方针对该锁作本次请求的一次调用

方案二:

结合重试时间对接口进行同一请求,几秒内请求n次的限制

方案三:

前两者是比较自定义式的在接口的入口处进行幂等的处理方式;

在spring项目中,我们还可以通过aop组件去实现一个基于自定义注解的接口增强;

我们可以设计一个公共的sdk包common,在其中实现接口幂等组件的装配;

实现方式也很简单:

@Aspect
@Component
public class InterfacelimitAspect {

    @Around("@annotation(limitInterface)) ")
    public Object limit(ProceedingJoinPoint point, VoiceEnter voiceEnter) throws Throwable {
        // 组成唯一的业务id point.getArgs();
        //或使用traceId
        boolean is =localCache.get(id);
        if(is)  //判断是否已经被执行 
            return;
        Object proceedResult =  point.proceed();
		
        return proceedResult;
    }
}

版权声明:本站原创文章,于2024-04-03,乐云一发表
转载请注明:https://leyunone.com/normal-notes/rpc-reload.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/682793.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

北航数据结构与程序设计第四次作业选填题复习

首先都是线性的&#xff0c;线性包括顺序和链式&#xff0c;栈和队都可以用两种方式实现。栈只能存于栈顶取于栈顶&#xff0c;队列先进先出&#xff0c;因此存取点是固定的。 函数栈帧创建原理 画图即可。 A.显然不行&#xff0c;5如果第一个出来说明5是最后一个进的&#xf…

收银系统源码-千呼新零售2.0【合作案例】

千呼新零售2.0系统是零售行业连锁店一体化收银系统&#xff0c;包括线下收银线上商城连锁店管理ERP管理商品管理供应商管理会员营销等功能为一体&#xff0c;线上线下数据全部打通。 适用于商超、便利店、水果、生鲜、母婴、服装、零食、百货等连锁店使用。 详细介绍请查看下…

解锁下载EasyRecovery2024电脑版软件 3步破解下载秘籍!

在数字时代&#xff0c;数据已成为我们生活中不可或缺的一部分。无论是工作中的重要文件&#xff0c;还是珍贵的家庭照片和视频&#xff0c;数据都承载着我们的回忆和努力。然而&#xff0c;数据的丢失也是我们常常遇到的问题。硬盘损坏、误删除、病毒攻击等都可能导致数据丢失…

echarts 仪表盘根据点击的刻度重新设置值

1 更具点击获取的坐标 event.offsetY , event.offsetX 2 通过中心点坐标差,获取角的度数,然后取180度的占比,最后✖️总值刻度值. 3 然后在赋值给data 例子 : 角的度数是30度 30/180*30 5 则刻度值指向5 角度度数怎么求? (Math.atan2(y - event.offsetY, x - event.offsetX) …

以sqlilabs靶场为例,讲解SQL注入攻击原理【42-50关】

【Less-42】 使用 or 11 -- aaa 密码&#xff0c;登陆成功。 找到注入点&#xff1a;密码输入框。 解题步骤&#xff1a; # 获取数据库名 and updatexml(1,concat(0x7e,(select database()),0x7e),1) -- aaa# 获取数据表名 and updatexml(1,concat(0x7e,(select group_conca…

Siemens-NXUG二次开发-创建倒斜角特征、边倒圆角特征、设置对象颜色、获取面信息[Python UF][20240605]

Siemens-NXUG二次开发-创建倒斜角特征、边倒圆角特征、设置对象颜色、获取面信息[Python UF][20240605] 1.python uf函数1.1 NXOpen.UF.Modeling.AskFaceData1.2 NXOpen.UF.Modeling.CreateChamfer1.3 NXOpen.UF.ModlFeatures.CreateBlend1.4 NXOpen.UF.Obj.SetColor 2.实体目标…

计算机组成原理-唐朔飞 概念总结(概论 总线 存储器部分)

计算机系统由“硬件”“软件”两大部分组成&#xff0c;软件通常存放在主存或辅存 软件分为系统软件和应用软件 1.1.2 计算机系统的层次结构 源程序&#xff1a;用户用高级语言编写的程序 目标程序&#xff1a;机器能识别的机器语言程序 实际机器&#xff1a;直接执行机器…

C++缺省参数函数重载

缺省参数 大家知道什么是备胎吗&#xff1f; C中函数的参数也可以配备胎。 3.1缺省参数概念 缺省参数是声明或定义函数时为函数的参数指定一个默认值。在调用该函数时&#xff0c;如果没有指定实参则采用该默认值&#xff0c;否则使用指定的实参。 void TestFunc(int a 0…

OPPO 文件传输 - 将文件从 OPPO 手机传输到 PC 的 5 种方法

OPPO手机以其出色的拍照功能而闻名&#xff0c;尤其是新推出的OPPO Find X2系列&#xff0c;它配备了高清前置镜头和超夜景模式&#xff0c;让您轻松拍出精彩瞬间。当您需要将这些照片或其他文件从OPPO手机传输到PC时&#xff0c;以下是五种简便的方法。 第 1 部分&#xff…

引擎:UI

一、控件介绍 Button 按钮 创建一个按钮 按钮禁用 精灵模式 颜色模式 缩放模式 绑定点击事件 EditBox 输入框 Layout 布局 支持水平排列、垂直排列、背包排列 PageView 页面视图 ProgressBar 进度条 RichText 富文本 绑定点击事件 事件可以被其它标签包裹 图文混排 Scroll…

揭秘AI 原生应用技术栈

一次性把“AI 原生应用技术栈”说明白 AI热潮持续&#xff0c;厂商努力推动有价值的应用涌现&#xff0c;并打造服务AI原始应用的平台产品。本文精简介绍业界最新的AI原生应用技术栈&#xff0c;让您迅速把握前沿科技脉搏。 整体架构 AI技术栈逻辑图精简呈现&#xff0c;多层…

nexus搭建npm前端项目的私服

一、为什么要搭建私库 节省外网带宽加速maven构建部署第三方构件&#xff08;特别是无法从公共仓库下载的构件&#xff09;提高稳定性&#xff08;内网部署&#xff0c;更少地依赖外网&#xff09;降低中央仓库的负荷 构件&#xff0c;好比我们的藏书&#xff0c;去书店或商城…

Python 全栈系列249 IO并发:异步、线程与协程

说明 很久没有关注这方面的问题了&#xff0c;平时大部分时候还是做批量操作 。在这种情况下(CPU密集),异步、协程这些意义就不大了&#xff0c;甚至可能进一步拖慢处理时间。 但是在IO这一块的零碎处理是比较重要的&#xff0c;可以更快&#xff0c;且更省资源。很早的时候&…

什么是通配符SSL证书?要怎么申请?

通配符SSL证书的作用主要是为了方便管理和加密具有多个子域名的网站。它能够保护一个主域名及其所有的同级子域名&#xff0c;无论子域名的数量多少或名称如何变化。使用一个通配符证书&#xff0c;你可以为像 *.example.com 这样的设置加密&#xff0c;这样不论是 blog.exampl…

Mysql:通过一张表里的父子级,递归查询并且分组分级

表&#xff1a;gc_jzst_single_base 需求&#xff1a;要求返回这张表里符合条件的数据&#xff0c;且有父子级关系的&#xff0c;展示为同一组且分级&#xff0c;给后续业务调用 代码 WITH RECURSIVE t1 AS (SELECTsingle_id,old_build_single_id,single_name,bulid_code,1 A…

运维开发介绍

目录 1.什么是运维开发 2.作用 3.优点 4.缺点 5.应用场景 5.1.十个应用场景 5.2.网站和Web应用程序 6.案例 7.小结 1.什么是运维开发 运维开发&#xff08;DevOps&#xff09;是一种结合软件开发&#xff08;Development&#xff09;与信息技术运维&#xff08;Opera…

从分布式训练到大模型训练

要了解大模型训练难&#xff0c;我们得先看看从传统的分布式训练&#xff0c;到大模型的出现&#xff0c;需要大规模分布式训练的原因。接着第二点去了解下大规模训练的挑战。 从分布式训练到大规模训练 常见的训练方式是单机单卡&#xff0c;也就是一台服务器配置1块AI芯片&a…

地球科学SCI期刊,中科院2区,IF=14.6,自引率低,无预警风险!

一、期刊名称 IEEE Geoscience and Remote Sensing Magazine 二、期刊简介概况 期刊类型&#xff1a;SCI 学科领域&#xff1a;地球科学 影响因子&#xff1a;14.6 中科院分区&#xff1a;2区 三、期刊征稿范围 IEEE地球科学和遥感杂志向读者介绍IEEE GRS协会及其技术委员…

谢宁DOE培训的奇妙之旅:从陌生到熟练

在充满挑战与机遇的现代社会&#xff0c;不断提升自我&#xff0c;掌握新的技能和知识&#xff0c;成为了我们追求进步的重要途径。而对于我来说&#xff0c;参加谢宁DOE培训&#xff0c;无疑是我职业生涯中的一次重要抉择。这次培训让我从对谢宁DOE陌生到熟练&#xff0c;经历…

618电视盒子哪个好?经销商总结热销电视盒子品牌排行榜

电视盒子是目前热度最高的数码产品&#xff0c;大家都在讨论电视盒子的资源问题&#xff0c;究竟电视盒子还值不值得入手&#xff1f;电视盒子哪个好&#xff1f;电视盒子的功能并没有受到影响&#xff0c;依然是不可缺少的&#xff0c;本期我要给大家盘点实体店销量最好的电视…