15种线上Bug梳理,这坑我算是踩全了

news2025/2/25 0:29:11

日常开发过程中大家肯定或多或少都会遇到一些偶现的问题,最常见的一句话就是:在我本地运行的时候都是好的呀?在测试环境跑的时候都是好的呀?在预发布环境都是正常的呀?在灰度阶段都是没问题的呀?

怎么到生产上就时不时爆出几个预警来呢?

图片

一般来说,这种情况多半是遇到了在特定的条件下、多种因素叠加在一起的时候才会触发的“偶现问题”。

以前我碰到这类问题的时候,我都是调侃到:不能稳定复现的 BUG 都不算是 BUG。

后来也因为这样天真的、抱有幻想的想法,吃过了好几次亏,才开始越来越意识到比起能稳定复现的 BUG 来说,“偶现问题”往往才是发出致命一击的刺客。

最近在网上看到一篇相关的总结类的文章,觉得写的不错,很多坑我也结结实实的踩到过,分享给大家,希望能引起警惕。

链接:https://juejin.cn/post/7294844864020430902

总结过去几年遇到的一些偶现问题。

偶现问题有一定隐秘性,要有刨根问底的精神,偶现的问题也是问题。

如果上线前不把偶现的问题刨根问底弄清楚,到了线上将会更难排查。

客户所在的上下文环境可能会和我们不同,常常导致我们不能模拟重现问题,在过去的几年中也遇到过不少这样的场景,最近便梳理了一些。

本文结构安排:第一部分罗列场景;第二部分列举案例。

一、场景罗列

偶现问题可以是概率高的,也可以是概率低的; 甚至是出现一次的;或者是一开始是没有,运行一段时间出现的。

大多数问题都是编码不严谨导致,甚至是一些低级错误。

第一类:并发访问、异步编程、资源竞争

图片

第二类:缓存相关,缓存一致性

图片

数据库、本地缓存,分布式缓存数据是常见问题,编码时没有考虑周全,给业务带来麻烦。

缓存不一致性持续的时间极短,往往会忽略缓存一致性这个因素,导致排查方向走偏,增加排查时长,警惕!

第三类:脏数据、数据倾斜

脏数据常常会引起异常现象,也是偶发性问题高发区,此处换成现脏数据易发的场景。

图片

脏数据出现触发异常。常见的情况:selectOne,但是查询出来两条。

第四类:边界值、超时、限流

图片

上游的服务链路很长;异常被转换;日志被吞掉的情况会大大增加排查的难度

第五类:服务器、硬件

图片

第六类:程序代码

图片

程序未做好兼容发布,比如数据结构不兼容,请求参数不兼容,方法不兼容等等;未做好优雅关闭,正在处理的任务被中断。这样的发布都是灾难。

第七类:网络等其他

图片

二、案例描述

2.1 非线程安全集合类

并行流里面使用了非线程安全集合类,集合对象返回结果可能不正确。

当数据量小的时候,不容易察觉;当数据量多的时候,容易暴露问题。

List<XXXDO> dataList = 从 DB 中获取结果集合

// 非线程安全集合了
List<XXXDO> successList = new ArrayList();
List<XXXDO> failList = new ArrayList();

// parallelStream 并行流中使用了不安全的集合
dataList.parallelStream().forEach(
    vo -> {
        .......
        if(执行成功) {
            successList.add(vo);
        } else {
            failList.add(vo);
        }
   }
);

开始为 stream,没有任何问题。

当数据量大的时候,做了一个优化,将 stream 修改成 parallelStream,测试时,数据量较小,未察觉,线上数据量多的时候,发现了这个问题。

2.2 ThreadLocal

当使用  ThreadLocal  时,未正确执行 remove 方法;有可能是因为抛出异常导致。线程在特殊情况下被复用;导致 ThreadLocal 中的数据符合预期。

注:这是编码不严谨导致。

// 正常情况能够执行 remove
try {
    ...
} finally {
    threadLocalUser.remove();
}

不严谨,导致 remove 未执行

// 错误使用
try {
    ...
    // 业务异常, 未能执行 remove
    threadLocalUser.remove();
} catch(Exception exception) {
   ...
}

ThreadLocal 其实应用场景很多,但一定要记得移除用完 remove 掉。由于具有线程复用,比较难排查。

2.3 修改成员变量

从配置中心读取配置信息,该数据作为模板,带有占位符;在执行实例时,通过上下文参数,解析占位符。比如发送短信、卡片等。

图片

{
    "authorized":{
        "themeHeader":"交付授权协议",
        "contentDesc":"交付工程师: ($userName$) 向您申请交付权限",
        "keyNote":"特别提醒:如果不签署,交付工程师将无法进行交付",
        "redirectLinkText":"立即前往授权",
        "tenantId":"您所在的组织“$tenantId$”有以下权限申请需要授权"
    }
}

这是一份模板数据,占位符通过上下文替换。

// 从配置中心读取配置,用成员变量保存
public class CardSceneParamConfig implements XXXDataCallback {

    // 从 nacos 配置读取初始化模板数据
    private Map<String, AuthorizedCardParamVO> cardParamVO = new HashMap<>();
    ......
    // 获取配置模板
    public AuthorizedCardParamVO getAuthorizedCardParamVO(String sceneCode) {
        return cardParamVO.get(sceneCode);
    }
}

// 获取模板对象,修改了模板里面的占位符。
private AuthorizedCardParamVO xxx(AuthorizedCardParamVO stable, Map<String, String> params) {
    ......
    final String contentDescStr = Optional.ofNullable(stable.getContentDesc())
            .map(contentDesc -> contentDesc.replace("$userName$", params.get("userName")))
            .orElse(stable.getContentDesc());

    // 更改了成员变量
    stable.setContentDesc(contentDescStr);
    .......
    return dynamic;
}

stable.setContentDesc(contentDescStr); 修改了成员变量,导致 "contentDesc":"交付工程师: ($userName$) 向您申请交付权限"

被修改成具体值 "contentDesc":"交付工程师: (XXXX) 向您申请交付权限"

如果 userName 是同一个人,或者第一次请求到不同机器;都不会有问题;否则有问题。

需要特别注意成员变量被修改的情况。修改成员变量的案例遇到过很多次。需要警惕。

2.4 异步依赖

使用线程池执行,但是将结果添加到 list 这个操作是异步的。有可能代码执行完毕,但是 list 结果集合没有任何的数据。异步依赖。

List<XXXDO> dataList = 从 DB 中获取结果集合

// 非线程安全集合
List<XXXDO> successList = new ArrayList();
List<XXXDO> failList = new ArrayList();

for (XXXDO vo : dataList) {
   ThreadUtil.execute(() -> {
        // API 操作 vo
        .......
        if(执行成功) {
            successList.add(vo);
        } else {
            failList.add(vo);
        }  
   });
}
// 可能未获取运行结果就返回了

这是个低级错误,需要异步等待,但是因为数据量小,未察觉这个问题。数据大的时候非常容易暴露。

很久以前,接手了一位离职伙伴的代码,现在想来都觉得很坑。

  • 上传 excel 数据,到服务端解析,将解析结果上再传到 redis,redis 设置 1min 过期;解析这个过程也是一个异步行为。

  • 客户端上传完成再点击提交数据,从刚才的 redis 取数据再保存到 DB 中。

图片

当数据大的时候,发现一条数据都没有插入到 DB 里面。

原因大致有二:

  • 未解析完成,提交时 redis 还没有数据

  • 提交按钮迟了,redis 解析的数据过期

图片

数据量小的时候不易察觉,因为功能不常用,等数据量大的时候,就暴露了。

2.5 并发性修改

下面案例,由于 counter++ 操作不是原子的,同时并发修改。循环的次数偏小,可能不会出现问题。

循环次数多 counter 不符合预期

public class UnsafeConcurrencyExample {
    private static int counter = 0;

    public static void main(String[] args) {
        Thread thread1 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                counter++;
            }
        });

        Thread thread2 = new Thread(() -> {
            for (int i = 0; i < 1000; i++) {
                counter++;
            }
        });

        thread1.start();
        thread2.start();

        try {
            thread1.join();
            thread2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("Counter: " + counter);
    }
}

2.6 数据不一致

当第一次运行这段代码时,会从数据库中获取数据,并将数据放入缓存中。

10 分钟内再次运行代码时,将直接从缓存中获取数据,而不会再次访问数据库。只有当缓存过期后,才会再次从数据库获取新的数据。

public class CacheExample {
    // 创建缓存
    private static Cache<String, Object> cache = CacheBuilder.newBuilder()
            .expireAfterWrite(10, TimeUnit.MINUTES) // 设置缓存过期时间为10分钟
            .build();

    public static void main(String[] args) {
        String key = "data"; // 缓存的键

        // 从缓存中获取数据,如果缓存中不存在,则从数据库获取
        Object data = cache.get(key, () -> fetchDataFromDB());

        System.out.println("Data: " + data);
    }

    // 模拟从数据库获取数据
    private static Object fetchDataFromDB() {
        // 从数据库获取数据的逻辑
        System.out.println("Fetching data from DB...");
        return "Data from DB";
    }
}

缓存偏长,有部分已经更新,有部分还是旧的,导致数据表现不一致。

图片

数据一致性问题,导致请求到不同服务器节点出现不一样的效果

2.7 未考虑优雅关闭

如果提交到线程池的任务,没有考虑优雅关闭,极端情况出现了脏数据,导致偶发性问题。

下面举一个简单的例子,线程池的使用,但是下面线程池未考虑优雅关闭。

public class SimpleThreadPool {
    private ExecutorService executor;

    public SimpleThreadPool(int threads) {
        executor = Executors.newFixedThreadPool(threads);
    }

    public void execute(Runnable task) {
        executor.execute(task);
    }

    public void shutdown() {
        executor.shutdown();
    }
}

正在使用 execute 执行任务的时候,重新发布,重启、异常中断等等。导致正在执行的任务中断,产生了脏数据

2.8 脏数据导致查询结果多条

使用 selectOne 方法查询数据库中的数据,但查询出来多条

图片

com.baomidou.mybatisplus.core.exceptions.MybatisPlusException: One record is expected, but the query result is multiple records] with root cause

2.9 边界值触发限流

发生在很多年前的一个事情。

需求场景是往一个 IM 群批量发送卡片,由于特定场景,满足场景的卡片数据量较大,大约300,触发了限流。

由于经过了多个服务,导致原始报错,被转换成一个通用异常,也增加了排查的成本。

限流异常错误未考虑,在切面层面统一处理转换成系统异常。

图片

边界值会导致偶发问题,特别是不能模拟客户真实场景,加上原始错误信息丢失时,会增加排查难度。

数据量引发的限流问题较多;原始错误异常在链路上被转换其他异常也很普遍;因此在系统里面要多考虑这种场景,增强系统的健壮性。

2.10 机器中存在机器异常

图片

  • 分批发布时,没有做好机器的优雅下线

  • 节点异常,没有剔除该 IP

由此可能引发以下问题:

  • 下游 RPC 请求异常;该服务的依赖方异常

  • 本机器请求异常

  • mq 消费异常

  • .......

集群健康非常重要!!!!!

2.11 因为磁盘打满而出现机器挂了

图片

服务挂掉了:No space left on device

因为集群中的一台机器磁盘满了,hang 住,不能继续服务,路由到这台机器超时异常。其他机器正常可以正常访问。

需要做好集群的检活,异常时及时下掉机器。

2.12 数据不在同一个事务内

比如 updateBalance 是独立事务,在执行时可能出现问题 A 账户余额不够了,导致异常。

// 假设这是一个转账操作,从账户A向账户B转账
updateBalance(connection, "B", 100); // 向账户B添加100元
// A账户钱不够了
updateBalance(connection, "A", -100); // 从账户A扣除100元

2.13 网络入口带宽不足

发生在小作坊的故事;在开发阶段,购买了阿里云的服务器,当时网络带宽 1M,测试阶段没有问题,但未压测上线;

等用户量上来时,发现一些客户请求总是出现超时,最后排查为网络带宽不足导致。

压测、网络监控非常重要

2.14 DDos攻击等导致正常用户异常

存在正常用户异常。带宽资源被抢占了。

2.15 rpc 超时

假设客户端发送一个获取用户信息的请求给服务器端,并设置一个超时时间为5秒。

客户端期望在5秒内接收到服务器端返回的用户信息。但是由于网络延迟的原因,在某些情况下,服务器端的响应可能会在超时时间之后才到达客户端。

也有可能是因为运行了很长时间,服务端性能出现问题。

2.16 内存泄漏

故事发生在多年前,至今印象深刻;是一个 16 台线上机器内存全部飙高的案例。

业务是通过计件算工资;程序是输入表达式运算结果。

服务刚上线, 测试边界值,因为输入一个很大的值,导致类型溢出;是计算工资的方法,程序设置了出错重试。

  • 本来是单例的对象,但是却在每次执行方法时被创建

  • 因为错误发生,这个方法被发送到 mq 进行重试

  • 但是 mq 未设置最大重试次数

  • 因为集群机器都监听这个 mq,导致错误被不断地发送到 mq,形成了死循环。对象被无限创建,导致集群机器内存全部飙高。

历历在目的例子......

三、总结

场景还远远不止上面罗列的这些,但根据这些场景也总结了一些经验:

  • 合理的代码编写,很多问题都是编码导致,甚至还有很多低级错误

  • 多考虑边界值,边界值常常因为不会发生而被忽略

  • 合理的日志,方便排查,没有日志的异常增加排查难度

  • 别随便转换异常,做好异常处理

  • 压测,数据大会提前暴露并发相关问题

  • 别吞掉异常,否则出现错误时不容易排查,偶发性问题就变成灵异事件了

  • 机器一定要有完善的监控。包括上下游的监控,否则其中 1 个节点出现问题,整个链路都会因为这个节点出现偶发性的问题。

  • 做好优雅关闭等

很多偶现的问题排查也十分困难,遇到了就是一个很好的训练机会,当排查问题多了,经验就足了,再遇到相似问题就能轻轻搞定了。像网络问题排查比较麻烦,平时多学习工具,技多不压身。

偶发性问题往往也是由于我们细节做的不够到位!!!

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

MFC工程中无法使用cygwin64的库

文章目录 MFC工程中无法使用cygwin64的库概述在MFC中使用cygwin64的静态库在MFC中使用cygwin64的DLL进行静态包含在MFC中使用cygwin64的DLL进行动态调用唯一可以使用cygwin64的方法是进程隔离来通讯cygwin64的官方用途修正后的启动进程隐藏dos窗口的函数动态载入DLL的实现 - La…

SCENIC+:增强子和基因调控网络的单细胞多组学推理

摘要 对单个细胞中染色质可及性和基因表达的联合分析为破译增强子驱动的基因调控网络&#xff08;GRN&#xff09;提供了机会。在这里&#xff0c;我们提出了一种用于推理增强器驱动的 GRN 的方法&#xff0c;称为 SCENIC。 SCENIC 预测基因组增强子以及候选上游转录因子 (TF)…

Ubuntu及Docker 安装rabbitmq

安装ubuntu 前 先暴露端口&#xff1a; 5672 用于与mq服务器通信用 15672 管理界面使用的端口 docker命令&#xff1a;docker run -itd --name ubuntu -p 5672:5672 -p 15672:15672 ubuntu 进入docker : docker exec -it ubuntu /bin/bash 步骤&#xff1a; 1. 更新安装源…

Android开发笔记(四)

中级控件 图形定制图形Drawable形状图形Shape状态列表图形 选择按钮复选框CheckBox开关按钮Switch单选按钮RadioButton 文本输入编辑框EditText焦点变更监听器 图形定制 图形Drawable Android把所有能够显示的图形都抽象为Drawable类&#xff08;可绘制的&#xff09;&#x…

【Fastadmin】通用排序weigh不执行model模型的事件

在model模型类支持的before_delete、after_delete、before_write、after_write、before_update、after_update、before_insert、after_insert事件行为中&#xff0c;我们可以快捷的做很多操作&#xff0c;如删除缓存、逻辑判断等 但是在fastadmin的通用排序weigh拖动中无法触发…

【我与Java的成长记】之this引用和构造方法的使用详解

系列文章目录 能看懂文字就能明白系列 C语言笔记传送门 &#x1f31f; 个人主页&#xff1a;古德猫宁- &#x1f308; 信念如阳光&#xff0c;照亮前行的每一步 文章目录 系列文章目录&#x1f308; *信念如阳光&#xff0c;照亮前行的每一步* 前言一、this的使用this引用的特…

设备智能运维利器:无线振温一体式传感器

在现代工业领域中&#xff0c;设备的状态监测和维护是确保生产正常运行的关键环节。随着技术的不断进步&#xff0c;传感器在设备监测中发挥着越来越重要的作用。其中&#xff0c;无线振温一体式传感器作为设备智能运维的利器&#xff0c;具有独特的优势和潜力。本文将介绍无线…

Python+OpenCV 零基础学习笔记(6):ROI

文章目录 相关链接运行环境前言ROI颜色区域分割颜色通道合并 相关链接 【2022B站最好的OpenCV课程推荐】OpenCV从入门到实战 全套课程 CSDN标题里个括号对应视频的分P OpenCVPython CSDN专栏 Gitee 项目地址 运行环境 Python:3.11.5Anaconda:23.7.4IDE:vscode运行环境&#x…

一次性带屏电子烟解决方案,基于32位单片机开发

电子烟一般是指液态雾化电子烟&#xff0c;就是加烟油进行雾化的品类。电子烟根据烟油种类、烟雾量及使用情况有不同划分&#xff0c;如一次性小烟、换弹式电子烟、开放式注油电子烟、大烟、CBD雾化器等。一次性电子烟外形小巧&#xff0c;重量轻&#xff0c;便于随身携带&…

K8S 日志方案

目录 一、统一日志管理的整体方案 1、基础日志 2、Node级别的日志 3、集群级别的日志架构 二、安装统一日志管理组件 1、 部署Elasticsearch 2、部署Fluentd 3、部署Kibana 三、日志数据展示 一、统一日志管理的整体方案 通过应用和系统日志可以了解Kubernetes集群内…

力扣算法-Day14

第202题. 快乐数 编写一个算法来判断一个数 n 是不是快乐数。 「快乐数」 定义为&#xff1a; 对于一个正整数&#xff0c;每一次将该数替换为它每个位置上的数字的平方和。然后重复这个过程直到这个数变为 1&#xff0c;也可能是 无限循环 但始终变不到 1。如果这个过程 结…

每日一题--------求数字的每⼀位之和

大家好今天的每日一题又来了&#xff0c;有啥不对的请在评论区留言哦 文章目录 目录 文章目录 求数字的每⼀位之和 题⽬描述&#xff1a; 输⼊⼀个整数m&#xff0c;求这个整数m的每⼀位之和&#xff0c;并打印。 一、解题思路 我们可以通过不断获取该整数的个位数&#xff0c…

Github项目推荐:KaTeX

项目地址 GitHub - KaTeX/KaTeX: Fast math typesetting for the web. 项目描述 这是一个渲染公式的JavaScript库。有时候可能网页中需要写一些公式&#xff0c;但html本身并没有提供相应的标签。这个时候这个库就能派上用场了。 项目截图

centos7安装nginx并安装部署前端

目录&#xff1a; 一、安装nginx第一种方式&#xff08;外网&#xff09;第二种方式&#xff08;内网&#xff09; 二、配置前端项目三、Nginx相关命令 好久不用再次使用生疏&#xff0c;这次记录一下 一、安装nginx 第一种方式&#xff08;外网&#xff09; 1、下载nginx ng…

【DDD领域驱动篇】如何理解领域驱动设计?

如何理解领域驱动设计? ✔️典型解析✔️扩展知识仓库✔️DDD带来的好处✔️DDD 的不足 ✔️典型解析 领域动设计(Domain-Driven Design&#xff0c;DDD)是一种软件开发方法论&#xff0c;将业务领域作为软件设计的核心&#xff0c;以便更好地满足业务需求。 DDD认为&#xff…

CodeWhisperer——轻松使用一个超级强大的工具

CodeWhisperer 简介 CodeWhisperer是亚⻢逊云科技出品的一款基于机器学习的通用代码生成器&#xff0c;可实时提供代码建议。 CodeWhisperer有以下几个主要用途&#xff1a; 解决编程问题&#xff0c;提供代码建议&#xff0c;学习编程知识等等&#xff0c;并且CodeWhisper…

什么是https证书?

HTTPS证书&#xff0c;也称为SSL&#xff08;Secure Sockets Layer&#xff09;证书或TLS&#xff08;Transport Layer Security&#xff09;证书&#xff0c;是一种数字证书&#xff0c;用于在网络上建立安全的加密连接。它的主要目的是确保在互联网上进行的数据传输的安全性和…

12.18构建哈夫曼树(优先队列),图的存储方式,一些细节(auto,pair用法,结构体指针)

为结构体自身时&#xff0c;用.调用成员变量&#xff1b;为结构体指针时&#xff0c;用->调用成员变量 所以存在结构体数组时&#xff0c;调用数组元素里的成员变量&#xff0c;就是要用. 结构体自身只有在new时才会创建出来&#xff0c;而其指针可以随意创建 在用new时&…

深入浅出图解C#堆与栈 C# Heap(ing) VS Stack(ing) 第六节 理解垃圾回收GC,提搞程序性能

深入浅出图解C#堆与栈 C# Heaping VS Stacking 第六节 理解垃圾回收GC&#xff0c;提搞程序性能 [深入浅出图解C#堆与栈 C# Heap(ing) VS Stack(ing) 第一节 理解堆与栈](https://mp.csdn.net/mdeditor/101021023)[深入浅出图解C#堆与栈 C# Heap(ing) VS Stack(ing) 第二节 栈基…

Ubuntu fcitx Install

ubuntu经常出现键盘失灵的问题 查询资料得知应该是Ibus框架的问题 于是需要安装fcitx框架和搜狗拼音 sudo apt update sudo apt install fcitx 设置fcitx开机自启动&#xff08;建议&#xff09; sudo cp /usr/share/applications/fcitx.desktop /etc/xdg/autostart/ 然后…