在我们日常的开发过程中,我们都会时不时地遇到问题。最常见的句子是:
我在本地运行时是否一切正常?在预发布环境中是否正常?
为什么我有时会收到一些有关生产的早期警告?
一般来说,这种情况最常是由特定条件或多种因素引起的“偶然问题”引起的。
后来,由于这种幼稚、幻想的想法,我遭受了一些损失,但与能够稳定重现的bug相比,“偶尔出现的问题”更为致命,我开始越来越意识到,这往往可以是毁灭性的打击。
我在网上看到一篇相关的总结文章,觉得写得很好,所以分享给大家,以提醒我也遇到过的坑。
我将总结过去几年中不时出现的一些问题。
偶发问题具有一定的隐秘性,我们需要有精神,即使偶发问题也作为问题去探究。
如果在上线前不确定偶发问题的原因,上线后排查就会变得更加困难。
您的环境可能与我们不同,因此我们常常无法模拟和重现该问题,但过去几年我们遇到过很多这样的场景,最近我已经整理出来了。
这篇文章的结构安排如下。第一部分列出场景,第二部分列出案例。
一 场景罗列
偶尔出现的问题可能是高概率或低概率的问题,可能只发生一次,也可能最初不发生,可能在运行一段时间后才发生。
大多数问题都是由松散的编码甚至低级错误引起的。
第一类:并发访问、异步编程、资源争用
类别2:缓存相关,缓存一致性
数据库、本地缓存和分布式缓存数据是一个常见问题,由于编码考虑不周,它会给企业带来问题。
请注意,由于缓存不一致持续的时间很短,因此缓存一致性常常被忽略,从而导致杂散调查和更长的调查时间。
第三类:脏数据、数据倾斜
脏数据通常会导致异常行为,并且是偶发问题的常见区域。现在我们切换到脏数据可能性更大的场景。
脏数据会引发异常。常见情况:selectOne,但是查询找到两个结果。
第4 类:边界值、超时、电流限制
上游业务链路较长,异常情况需要翻译,导致故障排查难度加大。
类别5:服务器和硬件
第六类:程序代码
不兼容的数据结构、不兼容的请求参数、不兼容的方法等。程序尚未发布兼容性,尚未完全关闭,并且正在中断正在处理的任务。像这样的发布是一场灾难。
第7类:网络及其他
二 案例描述
非线程安全集合类
非线程安全的集合类用于并行流中,集合对象返回的结果可能不正确。
当数据量较小时,问题很难发现,当数据量较大时,问题更加明显。
ListXXXDO dataList=从数据库获取结果集合
//非线程安全集合
ListXXXDO successList=new ArrayList();
ListXXXDO 失败列表=new ArrayList();
//ParallelStream 在并行流中使用不安全集合
dataList.ParallelStream().forEach(
沃-{
……
if (执行成功) {
successList.add(vo);
} 除此之外{
失败列表.add(vo);
}
}
);
它以流的形式开始,没有任何问题。
当数据量较大时,我进行了优化,将流改为ParallelStream,但是当线上数据量较大时,由于数据量较小,我无法发现问题。
ThreadLocal
使用ThreadLocal 时,remove 方法无法正确执行。这可能是由于抛出异常造成的。
特殊情况下会复用线程,ThreadLocal中的数据符合预期。
注意:这是由松散的编码引起的。
//一般情况下可以执行remove。
尝试{
.
} 最后{
threadLocalUser.remove();
}
由于不严格,所以不执行删除操作。
//错误用法
尝试{
.
//业务异常。执行删除失败。
threadLocalUser.remove();
} catch(异常异常){
.
}
ThreadLocal其实有很多应用场景,但是用完后要记得去掉。由于它重用了线程,因此很难排除故障。
修改成员变量
从配置中心读取配置信息,实例运行时将数据作为带有占位符的模板,通过上下文参数解析占位符。比如发送短信、卡片等。
{
“已批准”:{
\’themeHeader\’:\’分发许可协议\’,
\’contentDesc\’:\’交付工程师: ($userName$) 正在请求您的交付许可\’,
\’keyNote\’:\’特别说明:如果您不签收,交付工程师将无法交付\’,
\’redirectLinkText\’:\’立即进行身份验证\’,
\’tenantId\’:\’您的组织\’$tenantId$\’具有以下需要批准的权限\’
}
}
这是一段模板数据,其中占位符被上下文替换。
//从配置中心读取配置并与成员变量一起保存
公共类CardSceneParamConfig 实现XXXDataCallback {;
//从nacos配置中读取初始化模板数据
私有MapString, AuthorizedCardParamVO CardParamVO=new HashMap();
.
//获取配置模板
公共AuthorizedCardParamVO getAuthorizedCardParamVO(字符串场景代码){
返回卡ParamVO.get(场景代码);
}
}
//获取模板对象并修改模板中的占位符。
私人AuthorizedCardParamVO xxx(AuthorizedCardParamVO稳定,MapString,字符串参数){
.
最终字符串contentDescStr=Optional.ofNullable(stable.getContentDesc())
.map(contentDesc – contentDesc.replace(\’$userName$\’, params.get(\’userName\’)))
.orElse(stable.getContentDesc());
//改变成员变量
stable.setContentDesc(contentDescStr);
……
动态返回。
}
stable.setContentDesc(contentDescStr); 修改成员变量,结果为\’contentDesc\’ :\’ 交付工程师: (
你
s
e
r
氮
是
仪表
e
用户名
传送权限应用于用户名)。 ”
具体值“contentDesc”: 已更改为“交付工程师: (XXXX) 将应用交付权限”。
如果用户名是同一个人或者初始请求是针对不同的机器,则没有问题。如果没有,你就有麻烦了。
修改成员变量时必须特别小心。改变成员变量的情况多次发生。你需要保持警惕。
异步依赖
它使用线程池运行,但将结果添加到列表是异步的。即使代码运行,列表结果集合也可能不包含任何数据。异步依赖。
ListXXXDO dataList=从数据库获取结果集合
//非线程安全集合
ListXXXDO successList=new ArrayList();
ListXXXDO 失败列表=new ArrayList();
for (XXXDO vo : 数据列表) {
ThreadUtil.execute(() – {
//API操作vo
……
if (执行成功) {
successList.add(vo);
} 除此之外{
失败列表.add(vo);
}
});
}
//可能没有获取到就返回执行结果。
这是一个低级错误,需要异步等待,但由于数据量很小,所以问题是不可见的。如果数据量很大,很容易泄露。
很久以前,我从前伴侣那里继承了一个密码,现在我相信这是一个陷阱。
上传Excel数据,在服务器上进行分析,并将分析结果上传到Redis。这个解析过程也是一个异步操作。
客户端完成上传后,点击“发送数据”即可从redis中检索数据并保存到DB中。
如果数据很大,您会注意到数据没有插入到数据库中。
主要有两个原因。
分析未完成。发送时,redis中没有数据发送按钮。 Redis解析的数据已经过期。
如果数据量较小,则该特征不常用,因此如果数据量较大,则很难发现。
并发性修改
在以下情况下,counter++ 上的操作不是原子的并且同时发生变化:周期太少可能不会导致问题。
循环计数计数器不符合预期。
公共类UnsafeConcurrencyExample {
私有静态int 计数器=0;
公共静态无效主(字符串[] args){
线程thread1=新线程(() – {
for (int i=0; i 1000; i++) {
计数器++;
}
});
线程thread2=新线程(() – {
for (int i=0; i 1000; i++) {
计数器++;
}
});
线程1.start();
线程2.start();
尝试{
线程1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(\’Counter:\’ + 计数器);
}
}
数据不一致
第一次运行此代码时,将从数据库检索数据并将其放入缓存中。
如果10分钟内再次运行该代码,将直接从缓存中获取数据,而无需再次访问数据库。只有当缓存过期时,才会再次从数据库中检索新数据。
公共类CacheExample {
//创建缓存
私有静态CacheString,对象缓存=CacheBuilder.newBuilder()
.expireAfterWrite(10, TimeUnit.MINUTES) //设置缓存过期时间为10分钟
。建造();
公共静态无效主(字符串[] args){
String key=\’data\’ //缓存的键
//从缓存中获取数据。如果缓存中不存在该数据,则会从数据库中检索。
对象数据=cache.get(key, () – fetchDataFromDB());
System.out.println(\’Data:\’ + data);
}
//模拟从数据库中检索数据
私有静态对象fetchDataFromDB() {
//从数据库获取数据的逻辑
System.out.println(\’正在从数据库中检索数据.\’);
返回“来自数据库的数据”。
}
}
由于缓存过长,有些部分更新了,有些部分仍然陈旧,导致数据性能不稳定。
由于数据一致性问题,对不同服务器节点的请求效果不同。
未考虑优雅关闭
如果提交到线程池的任务没有正确关闭,极端情况下可能会出现脏数据,从而出现偶发性问题。
下面是一个使用线程池的简单示例,但是下面的线程池并没有考虑到正常关闭。
公共类SimpleThreadPool {
私有ExecutorService执行器。
公共SimpleThreadPool(int 线程) {
执行器=Executors.newFixedThreadPool(线程);
}
公共voidexecute(可执行任务){
执行器.execute(任务);
}
公共无效关闭(){
执行器.shutdown();
}
}
使用execute、reissue、restart、异常中断等执行任务时任务执行中断,产生脏数据。
脏数据导致查询结果多条
我使用selectOne 方法查询数据库中的数据但找到多个条目
com.baomidou.mybatisplus.core.Exceptions.MybatisPlusException: 需要一条记录,但查询结果是多条记录] 根本原因存在
边界值触发限流
许多年前发生过一件事。
需求场景是批量向IM群发送卡片。由于特定场景,满足场景的卡数据量较大,在300条左右,触发限流。
经过多次服务将原始错误转化为一般异常,这也增加了故障排除的成本。
限流异常错误不予考虑,统一处理并转换为切面级别的系统异常。
边界值偶尔会引起问题,尤其是无法模拟真实客户场景时,原始错误信息的丢失增加了故障排除难度。
数据量带来的限流问题有很多。原始错误异常被转换为链接上的其他异常是很常见的。因此,应进一步考虑这种情况,以提高系统的鲁棒性。系统。
机器中存在机器异常
批量发布时机器未正常下线。
该IP不会因为节点不健康而被删除。
这可能会导致以下问题:
此服务的下游RPC 请求异常。
本机请求异常
MQ消费异常
……
集群健康状况非常重要。
因为磁盘打满而出现机器挂了
服务已关闭:您的设备上没有可用空间
集群中某台机器磁盘满了,挂起,路由到这台机器超时。其他机器可以正常访问它。
需要检查集群,发现异常立即删除机器。
数据不在同一个事务内
例如,updateBalance是一个独立的事务,因此帐户A可能会耗尽余额并遇到麻烦,从而导致异常。
//假设这是一个转账操作,将钱从账户A 发送到账户B。
updateBalance(connection, \’B\’, 100); //给B账户添加100元
//A账户资金不足
updateBalance(connection, \’A\’, -100); //A账户减去100元
网络入口带宽不足
这是一个小作坊里的故事。我在开发阶段购买了阿里云服务器。当时的网络带宽是1M,在测试阶段这不是问题。
随着用户数量的增加,发现部分客户请求不断超时,最终确定问题是由于网络带宽不足造成的。
压力测试和网络监控非常重要
DDos攻击等导致正常用户异常
存在正常用户异常。带宽资源被抢占。
rpc 超时
假设客户端向服务器发送请求以检索用户信息,并将超时设置为5 秒。
客户端期望在5秒内收到服务器返回的用户信息。然而,在某些情况下,由于网络延迟,服务器端响应可能要等到超时时间过后才能到达客户端。
服务器端由于运行时间较长,也可能存在性能问题。
内存泄漏
虽然这个故事发生在很多年前,但它仍然是一次所有16 台在线机器突然出现内存峰值的事件。
业务是计算计件工资,程序是输入公式计算的结果。
我刚刚启动一项服务并正在测试边界值。由于您输入了较大的值,因此类型会溢出,并且程序会设置为在发生错误时重试。
最初是一个单例对象,每次执行方法时都会创建它。
发生错误,该方法被发送到mq重试
不过,mq 并没有设置最大重试次数。
由于所有集群机器都在监控这个mq,所以错误会不断的发送到这个mq,形成死循环。对象被无限地创建,导致所有集群机器上的内存峰值。
一个生动的例子.
三 总结
场景比上面列出的还有很多,但根据以下场景总结了一些经验:
合理的代码编写。编码会导致许多问题和许多低级错误。
让我们更多地考虑边界值。边界值经常被忽视,因为它们在现实生活中从未出现过。
拥有良好的日志可以使故障排除更加容易。未记录的异常使故障排除变得更加困难。
适当地处理异常而不是太容易地转换它们
压测和数据会议提前发现并发相关问题
不要忽视异常情况。否则一旦出现错误,排查就会变得困难,偶尔的问题就会变成灵异现象。
机器需要全面监督。如果包括上游和下游监控,一个节点出现问题将导致整个链路出现零星问题。
准备一个优雅的结束。
另外,经常出现的问题是很难解决的,但是一旦解决了很多问题,以后再出现类似的问题就容易解决了。例如,解决网络问题通常需要学习更多工具,但不要让自己因太多技能而不知所措。
最后:下面完整的软件测试视频教程已经编辑上传。为自己找到您需要的朋友[100% 免费保证]。
软件测试面试文档
以下面试题是阿里巴巴、腾讯、百图等顶级互联网公司的最新面试素材,部分百图大佬做完这套后都给出了权威答案。相信大家根据面试信息都能找到满意的工作。
# 我们整理了15类线上bug,消除了所有陷阱!相关内容来源网络仅供参考。相关信息请参见官方公告。
原创文章,作者:CSDN,如若转载,请注明出处:https://www.sudun.com/ask/93940.html