分享---rpc运维事故处理

news/2025/2/27 8:53:16

事故案例03 - Qserver RPC调用大量失败

一、事故背景

Queryserver是内部的核心服务,负责处理数据查询请求并支持分布式缓存功能。为优化缓存一致性,新增了分布式锁逻辑:在查询请求命中缓存时需先获取分布式锁(基于Tair实现),若未获取成功则等待1秒后重试。此功能上线后,在特定异常场景下(如SQL执行失败)触发了线程池资源耗尽,最终导致RPC请求被拒绝,引发服务故障。

二、事故影响

(一)业务影响

  • 线上部分数据查询失败,直接影响依赖Queryserver的业务功能(如Mustang、malldatacentor服务)。
  • 因故障快速修复(总影响时长19分钟),未造成大规模客户投诉,但存在潜在业务中断风险。
  • 某日线上出现RPC失败率突增,导致部分业务数据无法展示。

(二)技术影响

  • 告警显示上游服务调用Queryserver的RPC失败率超过50%,Queryserver的RPC线程池持续满载,触发RejectedException(服务端线程池拒绝请求),服务可用性下降。
  • 失败请求均匀分布,无明显业务流量突增或数据库负载异常,即Doris引擎负载无显著波动,故障范围集中在Queryserver自身逻辑。

三、根本原因

(一)直接原因

分布式锁逻辑在高并发场景下导致RPC线程池耗尽,RPC框架主动拒绝请求(RejectedException)。

(二)深层原因

1. 逻辑设计缺陷
  • 查询失败时未缓存结果,后续相同请求持续触发分布式锁竞争,强制串行执行,占用线程资源。
  • 分布式锁重试间隔过长(1秒),导致线程长时间阻塞,加速线程池耗尽。
2. 异常处理不足

未对失败查询的请求进行熔断或限流,异常流量持续冲击线程池。

3. 测试覆盖不全

未模拟“高并发异常SQL”场景,导致逻辑缺陷未在测试阶段暴露。

四、处理流程与数据变化

(一)处理流程

  • 16:16:收到raptor持续告警,确认Mustang/malldatacentor服务异常。
  • 16:17:通过链路追踪定位到Queryserver RPC失败率异常。
  • 16:28:尝试重启服务,但未解决问题。
  • 16:32:深入分析代码逻辑,发现分布式锁机制导致线程池满载,临时关闭缓存功能或调整锁等待时间。
  • 16:35:告警恢复,服务可用性回升。

(二)关键数据变化

指标故障前故障中恢复后
Queryserver QPS正常骤降(拒绝请求)正常
RPC失败率<1%>50%<1%
线程池使用率60%100%60%
Tair锁竞争频率高频(持续重试)

五、过程复现

(一)原因定位

queryserver添加了分布式锁的功能,如果开启了缓存的情况下,每个查询需要去拿到分布式锁之后,才能被执行,否则会等待1s时间再去尝试拿到分布式锁。代码如下:

while (true) {
    if (!cacheInfo.isUpdateCache()) {
        //1 从缓存中获取结果
        CachedResult cachedResult = cacheService.getResult(sqlMappingKey);
        if (cachedResult!= null) {
//                logInfo.setHitCache(true);
            detailLog.setUseCache(cacheInfo.isFetchFromCache());
            return ResponseContext.success(cachedResult.getData()).hitCache().withAttach(detailLog);
        }
    }

    //加锁
    if (!tairService.setNx((LOCK_PREFIX + sqlMappingKey).getBytes(), ("" + id).getBytes(), 20)) {
        Thread.sleep(1000);
        continue;
    }

    //2 从引擎中查询
    List result = queryEngineBackend.syncQuery(queryInfo);
    //3 对结果进行缓存
    if (result!= null &&!result.isEmpty()) {
        //这里最终判断传入的缓存过期时间是否合法,如果不合法就传入默认的1800s.最长时间不超过60天
        cacheService.cacheResult(sqlMappingKey, result, ((cacheInfo.getExpireMs() > 0) && (cacheInfo.getExpireMs() < MAX_EXPIRE_SEC))? cacheInfo.getExpireMs() : DEFAULT_EXPIRE_SEC);
    }

    //解锁
    if (("" + id).equals(new String(tairService.getValue((LOCK_PREFIX + sqlMappingKey).getBytes()))))
        tairService.delete((LOCK_PREFIX + sqlMappingKey).getBytes());

    return ResponseContext.success(result).withRequestId(id).withAttach(detailLog);
}

并且如果查询失败的话,不会缓存SQL结果,后续的相同请求均会提交到引擎执行,而后续的请求如果全是有问题的SQL,则会占用请求线程,并且将查询变为串行执行。

(二)复现过程

通过监控发现,RPC线程池使用率在故障期间持续100%,队列堆积后触发拒绝策略。

复现实验:构造高并发异常SQL请求,线程池在10秒内被打满,完全复现线上问题。

  1. 在Mustang上配置一个带有缓存功能的SQL,然后SQL故意写成执行会报错的SQL。
  2. 在st环境使用工具并发请求,查看queryserver报错信息。

实验结果如下:

  1. queryserver大量出现RPC请求被拒绝,出现很多com.dianping.pigeon.remoting.provider.exception.ProcessTimeoutException异常。
  2. 上游调用出现失败率较高的情况。

六、总结与改进方案

(一)深层次原因分析

1. 架构设计缺陷
  • 资源竞争模型不合理:
    • 分布式锁重试机制采用同步阻塞(Sleep),违背高并发服务的异步化设计原则。
    • 未隔离线程池:锁竞争、SQL查询、缓存操作共享同一线程池,异常SQL影响全局请求。
  • 缓存策略漏洞:
    • 仅缓存成功结果,未对失败请求做短期标记(如“异常状态缓存”),导致无效查询重复执行。
2. 运维与监控盲区
  • 线程池状态未监控:仅关注QPS和错误率,未实时跟踪线程池使用率、锁竞争耗时等指标。
  • 告警阈值不合理:RPC失败率告警触发阈值过高(>30%),未能提前预警。
3. 测试与流程问题
  • 异常场景覆盖不足:压力测试仅覆盖正常SQL,未模拟高并发异常SQL场景。
  • 代码评审遗漏:分布式锁逻辑的Sleep操作在评审中未被质疑,团队对阻塞风险认知不足。

(二)技术解决方案(面试重点)

1. 逻辑优化
  • 锁重试异步化:
    • Thread.sleep(1000)改为异步等待(如基于CompletableFuture的调度),释放线程资源。
    • 代码示例:
if (!tairService.setNx(...)) { 
    return CompletableFuture.delayedExecutor(100, TimeUnit.MILLISECONDS) 
          .submit(() -> retryLockAndQuery()); 
}
  • 失败结果缓存:
    • 对异常SQL结果缓存5秒,避免重复执行,代码修改:
if (result == null) { 
    cacheService.markError(sqlMappingKey, 5); // 标记异常状态 
}
2. 架构增强
  • 线程池隔离:
    • 为锁竞争、SQL查询、缓存操作分配独立线程池,避免相互影响。
  • 使用Netty或Vert.x实现异步RPC框架,提升吞吐量。
3. 熔断与限流
  • 集成Hystrix,对异常SQL请求熔断(10秒内错误率>50%则直接拒绝)。
  • 为单SQL设置QPS限流(如每秒100次),防止资源耗尽。
4. 监控与运维改进
  • 新增监控指标:
    • 线程池使用率、锁竞争耗时、Tair操作成功率。
    • 通过Prometheus + Grafana实时展示,阈值触发企业微信告警。
  • 混沌测试常态化:
    • 定期注入线程池满载、分布式锁失效等故障,验证服务自愈能力。

七、后续计划

  • 灰度发布改进后的分布式锁逻辑,验证异常场景下的线程池稳定性。
  • 完善自动化测试平台的异常注入能力(如模拟SQL失败、线程池满载)。
  • 建立服务健康度评分体系,将线程池状态、锁竞争等指标纳入服务可用性评估。

http://www.niftyadmin.cn/n/5869833.html

相关文章

Flutter系列教程之(6)——Tab导航与ListView使用

目录 Tab导航 ListView的使用 Tab导航 效果图: 与原生的Android不同,flutter中使用Tab的步骤还算简单,不用写Adapter return DefaultTabController(//必传参数1length: 2,child: Scaffold(appBar: AppBar(title:Text(tab演示, style: TextStyle(color: Colors.white, fontS…

为什么要划分vlan

划分VLAN&#xff08;Virtual Local Area Network&#xff0c;虚拟局域网&#xff09;的原因主要包括以下几个方面&#xff1a; 控制广播风暴&#xff1a; 在传统的局域网中&#xff0c;广播风暴是一个常见问题&#xff0c;即一个广播包会在整个网络中传播&#xff0c;消耗大量…

区块链仿真工具SimBlock使用

1. Environment requirements SimBlock 可以在 Windows、MacOS、Ubuntu Linux 或任何支持 Java 的 Unix 平台上运行。 它需要以下版本的 JDK 和 Gradle。 请注意&#xff0c;SimBlock 的仓库中包含 Gradle Wrapper&#xff0c;因此您也可以自动安装 Gradle&#xff08;我们稍…

【FL0091】基于SSM和微信小程序的社区二手物品交易小程序

&#x1f9d1;‍&#x1f4bb;博主介绍&#x1f9d1;‍&#x1f4bb; 全网粉丝10W,CSDN全栈领域优质创作者&#xff0c;博客之星、掘金/知乎/b站/华为云/阿里云等平台优质作者、专注于Java、小程序/APP、python、大数据等技术领域和毕业项目实战&#xff0c;以及程序定制化开发…

VQ-GAN复现

最近研究在自编码器&#xff0c;放一个复现的代码&#xff0c;移除了工程相关的代码&#xff0c;只保留了核心&#xff0c;有多卡accelerate就设置为True&#xff0c;没有就关了。 Decode 和 Encode 参考了stable diffusion的设计&#xff0c;Decode最后一层改成了方差和均值&…

unity学习55:按钮 button

目录 1 按钮 button 1.1 按钮button 其实就是一个组合体 1.2 测试按钮&#xff0c;在UI中添加1个按钮 1.3 按钮的属性 2 按钮的图片属性 3 按钮的变换 transition 3.1 按颜色变换 3.2 按图片精灵变换 3.3 按动画变换 4 按钮的导航 5 按钮的事件和脚本 1 按钮 …

软件安全性测试类型分享,第三方软件测试机构如何进行安全性测试?

在数字化时代&#xff0c;软件的安全性至关重要&#xff0c;因此软件产品安全性测试必不可少。软件安全性测试是指针对软件系统的漏洞、弱点及其他安全隐患进行评估和检测的过程。它旨在发现潜在的安全问题&#xff0c;以保护软件和用户的利益。通过系统化的测试&#xff0c;企…

JSON Schema 入门指南:如何定义和验证 JSON 数据结构

文章目录 一、引言二、什么是 JSON Schema&#xff1f;三、JSON Schema 的基本结构3.1 基本关键字3.2 对象属性3.3 数组元素3.4 字符串约束3.5 数值约束 四、示例&#xff1a;定义一个简单的 JSON Schema五、使用 JSON Schema 进行验证六、实战效果6.1 如何使用 七、总结 一、引…