2026/1/6 23:51:16
网站建设
项目流程
设计美观网站有哪些,非专业人士可以做网站编辑的工作吗,网站重复页面,湖南建设银行官网网站首页日常开发过程中大家肯定或多或少都会遇到一些偶现的问题#xff0c;最常见的一句话就是#xff1a;在我本地运行的时候都是好的呀#xff1f;在测试环境跑的时候都是好的呀#xff1f;在预发布环境都是正常的呀#xff1f;在灰度阶段都是没问题的呀#xff1f;怎么到生产…日常开发过程中大家肯定或多或少都会遇到一些偶现的问题最常见的一句话就是在我本地运行的时候都是好的呀在测试环境跑的时候都是好的呀在预发布环境都是正常的呀在灰度阶段都是没问题的呀怎么到生产上就时不时爆出几个预警来呢一般来说这种情况多半是遇到了在特定的条件下、多种因素叠加在一起的时候才会触发的“偶现问题”。后来也因为这样天真的、抱有幻想的想法吃过了好几次亏才开始越来越意识到比起能稳定复现的 BUG 来说“偶现问题”往往才是发出致命一击的刺客。在网上看到一篇相关的总结类的文章觉得写的不错很多坑我也结结实实的踩到过分享给大家希望能引起警惕。总结过去几年遇到的一些偶现问题。偶现问题有一定隐秘性要有刨根问底的精神偶现的问题也是问题。如果上线前不把偶现的问题刨根问底弄清楚到了线上将会更难排查。客户所在的上下文环境可能会和我们不同常常导致我们不能模拟重现问题在过去的几年中也遇到过不少这样的场景最近便梳理了一些。本文结构安排第一部分罗列场景第二部分列举案例。一 场景罗列偶现问题可以是概率高的也可以是概率低的; 甚至是出现一次的或者是一开始是没有运行一段时间出现的。大多数问题都是编码不严谨导致甚至是一些低级错误。第一类并发访问、异步编程、资源竞争第二类缓存相关缓存一致性数据库、本地缓存分布式缓存数据是常见问题编码时没有考虑周全给业务带来麻烦。缓存不一致性持续的时间极短往往会忽略缓存一致性这个因素导致排查方向走偏增加排查时长警惕第三类脏数据、数据倾斜脏数据常常会引起异常现象也是偶发性问题高发区此处换成现脏数据易发的场景。脏数据出现触发异常。常见的情况selectOne但是查询出来两条。第四类边界值、超时、限流上游的服务链路很长异常被转换日志被吞掉的情况会大大增加排查的难度第五类服务器、硬件第六类程序代码程序未做好兼容发布比如数据结构不兼容请求参数不兼容方法不兼容等等未做好优雅关闭正在处理的任务被中断。这样的发布都是灾难。第七类网络等其他二 案例描述非线程安全集合类并行流里面使用了非线程安全集合类集合对象返回结果可能不正确。当数据量小的时候不容易察觉当数据量多的时候容易暴露问题。ListXXXDO dataList 从 DB 中获取结果集合 // 非线程安全集合了 ListXXXDO successList new ArrayList(); ListXXXDO failList new ArrayList(); // parallelStream 并行流中使用了不安全的集合 dataList.parallelStream().forEach( vo - { ....... if(执行成功) { successList.add(vo); } else { failList.add(vo); } } );开始为 stream没有任何问题。当数据量大的时候做了一个优化将 stream 修改成 parallelStream测试时数据量较小未察觉线上数据量多的时候发现了这个问题。ThreadLocal当使用 ThreadLocal 时未正确执行 remove 方法有可能是因为抛出异常导致。线程在特殊情况下被复用导致 ThreadLocal 中的数据符合预期。注这是编码不严谨导致。// 正常情况能够执行 remove try { ... } finally { threadLocalUser.remove(); }不严谨导致 remove 未执行。// 错误使用 try { ... // 业务异常, 未能执行 remove threadLocalUser.remove(); } catch(Exception exception) { ... }ThreadLocal 其实应用场景很多但一定要记得移除用完 remove 掉。由于具有线程复用比较难排查。修改成员变量从配置中心读取配置信息该数据作为模板带有占位符在执行实例时通过上下文参数解析占位符。比如发送短信、卡片等。{ authorized:{ themeHeader:交付授权协议, contentDesc:交付工程师: $userName$) 向您申请交付权限, keyNote:特别提醒如果不签署交付工程师将无法进行交付, redirectLinkText:立即前往授权, tenantId:您所在的组织“$tenantId$”有以下权限申请需要授权 } }这是一份模板数据占位符通过上下文替换。// 从配置中心读取配置用成员变量保存 public class CardSceneParamConfig implements XXXDataCallback { // 从 nacos 配置读取初始化模板数据 private MapString, AuthorizedCardParamVO cardParamVO new HashMap(); ...... // 获取配置模板 public AuthorizedCardParamVO getAuthorizedCardParamVO(String sceneCode) { return cardParamVO.get(sceneCode); } } // 获取模板对象修改了模板里面的占位符。 private AuthorizedCardParamVO xxx(AuthorizedCardParamVO stable, MapString, 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”:“交付工程师: userNameuserNameuserName) 向您申请交付权限”被修改成具体值 “contentDesc”:“交付工程师: XXXX) 向您申请交付权限”。如果 userName 是同一个人或者第一次请求到不同机器都不会有问题否则有问题。需要特别注意成员变量被修改的情况。修改成员变量的案例遇到过很多次。需要警惕。异步依赖使用线程池执行但是将结果添加到 list 这个操作是异步的。有可能代码执行完毕但是 list 结果集合没有任何的数据。异步依赖。ListXXXDO dataList 从 DB 中获取结果集合 // 非线程安全集合 ListXXXDO successList new ArrayList(); ListXXXDO failList new ArrayList(); for (XXXDO vo : dataList) { ThreadUtil.execute(() - { // API 操作 vo ....... if(执行成功) { successList.add(vo); } else { failList.add(vo); } }); } // 可能未获取运行结果就返回了这是个低级错误需要异步等待但是因为数据量小未察觉这个问题。数据大的时候非常容易暴露。很久以前接手了一位离职伙伴的代码现在想来都觉得很坑。上传 excel 数据到服务端解析将解析结果上再传到 redisredis 设置 1min 过期解析这个过程也是一个异步行为。客户端上传完成再点击提交数据从刚才的 redis 取数据再保存到 DB 中。当数据大的时候发现一条数据都没有插入到 DB 里面。原因大致有二未解析完成提交时 redis 还没有数据提交按钮迟了redis 解析的数据过期数据量小的时候不易察觉因为功能不常用等数据量大的时候就暴露了。并发性修改下面案例由于 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); } }数据不一致当第一次运行这段代码时会从数据库中获取数据并将数据放入缓存中。10 分钟内再次运行代码时将直接从缓存中获取数据而不会再次访问数据库。只有当缓存过期后才会再次从数据库获取新的数据。public class CacheExample { // 创建缓存 private static CacheString, 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; } }缓存偏长有部分已经更新有部分还是旧的导致数据表现不一致。数据一致性问题导致请求到不同服务器节点出现不一样的效果未考虑优雅关闭如果提交到线程池的任务没有考虑优雅关闭极端情况出现了脏数据导致偶发性问题。下面举一个简单的例子线程池的使用但是下面线程池未考虑优雅关闭。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 执行任务的时候重新发布重启、异常中断等等。导致正在执行的任务中断产生了脏数据脏数据导致查询结果多条使用 selectOne 方法查询数据库中的数据但查询出来多条com.baomidou.mybatisplus.core.exceptions.MybatisPlusException: One record is expected, but the query result is multiple records] with root cause边界值触发限流发生在很多年前的一个事情。需求场景是往一个 IM 群批量发送卡片由于特定场景满足场景的卡片数据量较大大约300触发了限流。由于经过了多个服务导致原始报错被转换成一个通用异常也增加了排查的成本。限流异常错误未考虑在切面层面统一处理转换成系统异常。边界值会导致偶发问题特别是不能模拟客户真实场景加上原始错误信息丢失时会增加排查难度。数据量引发的限流问题较多原始错误异常在链路上被转换其他异常也很普遍因此在系统里面要多考虑这种场景增强系统的健壮性。机器中存在机器异常分批发布时没有做好机器的优雅下线节点异常没有剔除该 IP由此可能引发以下问题下游 RPC 请求异常该服务的依赖方异常本机器请求异常mq 消费异常…集群健康非常重要因为磁盘打满而出现机器挂了服务挂掉了No space left on device因为集群中的一台机器磁盘满了hang 住不能继续服务路由到这台机器超时异常。其他机器正常可以正常访问。需要做好集群的检活异常时及时下掉机器。数据不在同一个事务内比如 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形成了死循环。对象被无限创建导致集群机器内存全部飙高。历历在目的例子…三 总结场景还远远不止上面罗列的这些但根据这些场景也总结了一些经验合理的代码编写很多问题都是编码导致甚至还有很多低级错误多考虑边界值边界值常常因为不会发生而被忽略合理的日志方便排查没有日志的异常增加排查难度别随便转换异常做好异常处理压测数据大会提前暴露并发相关问题别吞掉异常否则出现错误时不容易排查偶发性问题就变成灵异事件了机器一定要有完善的监控。包括上下游的监控否则其中 1 个节点出现问题整个链路都会因为这个节点出现偶发性的问题。做好优雅关闭等很多偶现的问题排查也十分困难遇到了就是一个很好的训练机会当排查问题多了经验就足了再遇到相似问题就能轻轻搞定了。像网络问题排查比较麻烦平时多学习工具技多不压身。总结感谢每一个认真阅读我文章的人作为一位过来人也是希望大家少走一些弯路如果你不想再体验一次学习时找不到资料没人解答问题坚持几天便放弃的感受的话在这里我给大家分享一些自动化测试的学习资源希望能给你前进的路上带来帮助。软件测试面试文档我们学习必然是为了找到高薪的工作下面这些面试题是来自阿里、腾讯、字节等一线互联网大厂最新的面试资料并且有字节大佬给出了权威的解答刷完这一套面试资料相信大家都能找到满意的工作。视频文档获取方式这份文档和视频资料对于想从事【软件测试】的朋友来说应该是最全面最完整的备战仓库这个仓库也陪伴我走过了最艰难的路程希望也能帮助到你以上均可以分享点下方小卡片即可自行领取。