ITPub博客

首页 > 自动化运维 > 大规模网络运维 > 软件测试七大偶现问题BUG排查思路与工具推荐

软件测试七大偶现问题BUG排查思路与工具推荐

原创 大规模网络运维 作者:Atstudy技术社区 时间:2024-12-26 11:49:56 0 删除 编辑

身为一个软件测试员,碰到线上问题是常有的事儿。而线上问题只要牵扯到了核心业务,那便是重大事故。像之前我们钉钉文档崩了、各大券商APP无法正常使用等等。

  今天我们就以大家最习以为常,但却最为致命的“偶现问题”进行一次BUG场景归类,并对线上BUG的排查进行思路总结以及工具推荐!希望对你有所启发!


七大“偶现问题”场景罗列

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

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

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

  例如:非线程安全集合类

  并行流里面使用了非线程安全集合类,集合对象返回结果可能不正确。当数据量小的时候,不容易察觉;当数据量多的时候,容易暴露问题。

  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,测试时,数据量较小,未察觉,线上数据量多的时候,发现了这个问题。

   二、缓存相关,缓存一致性

  数据库、本地缓存,分布式缓存数据是常见问题,编码时没有考虑周全,给业务带来麻烦。缓存不一致性持续的时间极短,往往会忽略缓存一致性这个因素,导致排查方向走偏,增加排查时长,警惕!

   例如:数据不一致

  当第一次运行这段代码时,会从数据库中获取数据,并将数据放入缓存中。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";
 }
}

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

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

   三、脏数据、数据倾斜

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

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

  例如:未考虑优雅关闭

        可以到我的个人号:atstudy-js,可以免费领取一份10G软件测试工程师面试宝典文档资料。同时我邀请你进入我们的软件测试学习交流平台,大家可以一起探讨交流软件测试,共同学习软件测试技术、面试等软件测试方方面面,了解测试行业的最新趋势,助你快速进阶Python自动化测试/测试开发,稳住当前职位同时走向高薪之路。

  如果提交到线程池的任务,没有考虑优雅关闭,极端情况出现了脏数据,导致偶发性问题。下面举一个简单的例子,线程池的使用,但是下面线程池未考虑优雅关闭。

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 执行任务的时候,重新发布,重启、异常中断等等。导致正在执行的任务中断,产生了脏数据。

   四、边界值、超时、限流

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

  例如:边界值触发限流

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

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

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

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

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

   五、服务器、硬件

  六、程序代码

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

  例如:内存泄漏

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

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

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

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

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

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

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

   七、网络等其他

   线上BUG排查的4大排查思路汇总

  线上故障主要会包括CPU、磁盘、内存以及网络问题。而通常情况下都不是单一问题的出现。所以排查尽量4个方面都排查一遍。

   CPU

  一般来讲我们首先会排查 CPU 方面的问题。

  可能问题:

  · 业务逻辑问题(死循环);(最常见问题)

  ·  频繁gc(gc,为垃圾回收);

  ·  上下文切换过多;

  处理方法:

  ·  业务逻辑问题,可以使用jstack来分析对应的堆栈情况;

  ·  频繁gc,我们还是会使用 jstack 来分析问题,但有时候我们可以先确定下 gc 是不是太频繁,使用jstat -gc pid 1000命令来对 gc 分代变化情况进行观察,1000 表示采样间隔(ms),S0C/S1C、S0U/S1U、EC/EU、OC/OU、MC/MU 分别代表两个 Survivor 区、Eden 区、老年代、元数据区的容量和使用量。

  ·  上下文问题,我们可以使用vmstat命令来进行查看。

   磁盘

  可能出现的问题:

  ·  磁盘空间问题;

  ·  磁盘性能问题;

  处理办法:

  ·  磁盘空间,我们直接使用df -hl来查看文件系统状态;

  ·  磁盘性能,我们可以通过?iostat -d -k -x来进行分析;

  内存

  内存问题排查起来相对比 CPU 麻烦一些,场景也比较多。

  可能问题:

  ·  堆内内存,主要分为OOM和 Stack Overflow;

  ·  GC 问题;

  ·  堆外内存。

   处理方法:

  ·  OOM 和 Stack Overflow 的代码排查方面,我们一般使用 JMAPjmap -dump:format=b,file=filename pid来导出 dump 文件。

  ·  GC 问题,一般先使用 jstat 来查看分代变化情况,比如 youngGC 或者 fullGC 次数是不是太多呀;EU、OU 等指标增长是不是异常呀等。

  ·  如果碰到堆外内存溢出,那可真是太不幸了。首先堆外内存溢出表现就是物理常驻内存增长快,报错的话视使用方式都不确定,如果由于使用 Netty 导致的,那错误日志里可能会出现OutOfDirectMemoryError错误,如果直接是 DirectByteBuffer,那会报OutOfMemoryError: Direct buffer memory。

堆外内存溢出往往是和 NIO的使用相关,一般我们先通过pmap 来查看下进程占用的内存情况。堆内内存泄漏总是和 GC 异常相伴。常见的问题包括:youngGC 过频繁,针对该问题我们就需要使用Jmap和MAT对dump文件进行进一步排查;还有一个是youngGC耗时过长的问题,我们需要明确?GC 日志里耗时耗在哪一块了。以 G1 日志为例,可以关注 Root Scanning、Object Copy、Ref Proc 等阶段。Ref Proc 耗时长,就要注意引用相关的对象。Root Scanning 耗时长,就要注意线程数、跨代引用。Object Copy 则需要关注对象生存周期。还有就是触发 fullGC的问题。

   网络

  涉及到网络层面的问题一般都比较复杂,场景多,定位难,成为了大多数开发的噩梦,应该是最复杂的了。

  可能出现的问题 :

  ·  超时:超时大体可以分为连接超时和读写超时,某些使用连接池的客户端框架还会存在获取连接超时和空闲连接清理超时。

  ·  TCP 队列溢出:tcp 队列溢出是个相对底层的错误,它可能会造成超时、rst 等更表层的错误。

  ·  RST 异常:RST 包表示连接重置,用于关闭一些无用的连接,通常表示异常关闭,区别于四次挥手。在实际开发中,我们往往会看到connection reset / connection reset by peer错误,这种情况就是 RST 包导致的。

  ·  端口不存在:如果像不存在的端口发出建立连接 SYN 请求,那么服务端发现自己并没有这个端口则会直接返回一个 RST 报文,用于中断连接。

  ·  主动代替 FIN 终止连接:一般来说,正常的连接关闭都是需要通过 FIN 报文实现,然而我们也可以用 RST 报文来代替 FIN,表示直接终止连接。实际开发中,可设置 SO_LINGER 数值来控制,这种往往是故意的,来跳过 TIMED_WAIT,提供交互效率,不闲就慎用。

   排查工具推荐

  在上面的介绍中我们汇总了常见的偶现问题场景,以及排查思路汇总,那针对这些BUG的排查有没有工具推荐呢?下面给大家推荐几款使用率较高的工具!

   btrace

  首当其冲的要说的是btrace。真是生产环境&预发的排查问题大杀器。

   1、查看当前谁调用了ArrayList的add方法,同时只打印当前ArrayList的size大于500的线程调用栈。

 @OnMethod(clazz = "java.util.ArrayList", method="add", location = @Location(value = Kind.CALL, clazz = "/.*/", method = "/.*/"))
public static void m(@ProbeClassName String probeClass, @ProbeMethodName String probeMethod, @TargetInstance Object instance, @TargetMethodOrField String method) { if(getInt(field("java.util.ArrayList", "size"), instance) > 479){ println("check who ArrayList.add method:" + probeClass + "#" + probeMethod + ", method:" + method + ", size:" + getInt(field("java.util.ArrayList", "size"), instance)); jstack(); println(); println("==========================="); println();
 }
}

  2、监控当前服务方法被调用时返回的值以及请求的参数

  @OnMethod(clazz = "com.taobao.sellerhome.transfer.biz.impl.C2CApplyerServiceImpl", method="nav", location = @Location(value = Kind.RETURN))
  public static void mt(long userId, int current, int relation, String check, String redirectUrl, @Return AnyType result) {
   println("parameter# userId:" + userId + ", current:" + current + ", relation:" + relation + ", check:" + check + ", redirectUrl:" + redirectUrl + ", result:" + result);
  }

   注意:

   · 经过观察,1.3.9的release输出不稳定,要多触发几次才能看到正确的结果;

   ·  正则表达式匹配trace类时范围一定要控制,否则极有可能出现跑满CPU导致应用卡死的情况;

   ·  由于是字节码注入的原理,想要应用恢复到正常情况,需要重启应用。

   Arthas

  这是阿里开源不久的故障排查利器,非常之方便,更多操作,移步
https://github.com/alibaba/arthas

   eclipseMAT

  可作为eclipse的插件,也可作为单独的程序打开。详情请移步
http://www.eclipse.org/mat/

来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/70034708/viewspace-3059124/ ,如需转载,请注明出处,否则将追究法律责任。

请登录后发表评论 登录
全部评论

注册时间:2023-10-23

  • 博文量
    99
  • 访问量
    96504