线上问题整理
JVM 案例
案例一:服务器内存不足,影响Java
应用
问题: 收到报警,某Java
应用集群中一台服务器可用内存不足,超过报警阈值。
排查过程: 首先,通过Hickwall
查看该应用各项指标,发现无论是请求数量、CPU
使用率、还是JMX
的各项指标均未发现异常。仅系统内存占用很高,但是从JMX
指标中看,Java
应用的Heap Memory
、GC
等都是正常的,在合理大小和范围内,未发现内存泄漏等问题。故怀疑不是Java
应用本身的问题,而是系统上其他组件出了问题,但是从Hickwall
等工具上又看不出其他组件的信息。然后,直接ssh
登录到该服务器,由于是内存问题,故直接使用简单的top
命令,根据内存占用排序后发现,是logagent
进程占用了接近一半的系统内存。
由于账号权限限制,无法进一步处理,诱因找到后,随即反馈给网站运营中心的同事,帮忙临时将logagent
进程杀死,系统恢复正常。后续经网站运营中心同事排查,发现是logagent
内部bug
,导致处理格式异常的日志文件时发生内存泄漏,后续打上补丁进行修复。
问题原因及思考: 目前公司各项监控工具已经比较完善,开发人员应熟练掌握并了解其中各项指标的含义,能够在分析具体问题时灵活运用各个工具,快速定位解决问题。
同时本案例虽然不是业务系统的问题,但这一案例也提醒了开发人员:线上实际问题可能是各方各面的,除了具备Java
技术栈的相关的排障技能外,同时也要有基本的Linux
操作能力,在已有工具无法帮助解决问题时,多一种途径快速定位问题,毕竟运营中心的同事人力有限,可能无法及时提供支持。
扩展
Java Full GC
频繁: 可通过Hickwall
中的JMX Full gc time/count
指标观察Full GC
情况,正常情况下不应有Full GC
出现,Full GC
意味着 STW
,JVM
会阻塞其他所有线程来进行垃圾回收,频繁的Full GC
会严重影响应用的性能。如果出现Full GC
通常意味着Java
堆内存大小无法满足需求,如果不是代码缺陷导致(可通过以上OOM
中JVM Sampler
工具相关方法排查)则需要增加堆内存大小。
大数据量处理
案例一:大循环引起的 cpu 负载过高的问题
问题: x
应用在一次发布时,cpu
出现负载过高,其负载率突破200%
,并且响应时间也大幅度超时。
代码:
List<CityDataModel> cities = cityDomainService.allCities();
for (CityDataModel city : cities) {if (city.getCityCode().equalsIgnoreCase(flight.getDepartCity())) {dCountry = city.getCountryCode();}if (city.getCityCode().equalsIgnoreCase(flight.getArriveCity())) {aCountry = city.getCountryCode();}
}
通过在测试环境尝试调用一次服务请求,发现其循环的数据是城市列表。该列表的长度达到12000
,而且发现该循环本身被执行了11
次,String::equalsIgnoreCase
方法执行了18
万次,也就是说这是一个典型的大循环的代码。 并且通过记录日志发现,在生产中该块代码平均每次请求都会调用24
万次左右,这导致很多cpu
资源都集中在该方法上,使得cpu load
大幅度提高。
问题解决: 由于代码中的大循环非常耗费cpu
资源,通过分析,这里的Strings::equalsIgnoreCase
方法的主要作用在于遍历判断取数据。 根据这种查找数据的情况,优先选择使用HashMap
替代,用空间换时间,经过修改后重新发布,其cpu
利用率明显下降,恢复正常。
思考总结:
【1】使用循环时需要特别注意大循环,优先使用O(1)
的HashMap
,大循环对于cpu
性能的压榨在这个问题上表现地淋漓尽致。
【2】镜像机器由于是使用生产流量转发访问,所以镜像发布高度贴近实际生产发布。在每次发布前,先使用镜像机器预发布,可以尽可能地将潜在的问题暴露出来。另外性能实验室中提供了cpu
热点、内存分配热点和锁竞争热点的Flamegraph
,在预发布中遇到问题时也可以更加直观地帮忙解决问题,并且不会对实际生产机器造成影响。
【3】此次发布前,虽然在测试环境进行了压测,但是并没有复现出该问题,分析原因,其与特定的压测的请求相关。由于在压测时使用的请求没有经过某些代码分支,使得循环的次数相比较少,故而在测试环境压测时没有暴露该问题。
【4】以上总结,除了代码层面的使用注意外,可以进行测试环境多种请求压力测试,以及生产镜像机器预发布等手段,来检测和杜绝这种潜在的问题发生。
案例二:多层嵌套 map
问题: 某日某查询服务器开始不断拉出集群,造成线上订单下跌。
遇到问题需咨询解决。。。
缓存
案例一:篡改缓存
问题: 查询接口下发错误数据故障
故障描述: 包含故障开始时间,发现时间,控制措施,故障排除细节
Time | Event |
---|---|
15:00 | 接到产品邮件告知下单调用查询接口的结果与前端的数据不一致,要求紧急对问题进行排查。 |
15:05 | 开始对问题进行排查,同时了解问题大概影响范围。 |
16:00 | 定位到是因为代码bug 导致的接口在下发时,输出的结果不正确。3月1日接口由.net 切换到了java 版查询接口。 |
16:20 | 开始着手对bug 进行修复,并进行紧急发布。 |
16:20 | 与产品沟通影响范围以及问题订单的处理办法。 |
16:30 | 确定影响的单量:10万 |
16:50 | 着手准备修复问题订单SQL |
18:00 | 完成紧急修复的上线 |
故障分析:
【1】为什么接口会下发错误?
.Net
接口转Java
过程中引发的代码bug
,修改了本地缓存对象。
【2】为什么这个错误在代码review
中没有被发现?
代码review
不充分。虽然接口的逻辑并不复杂,但是代码量较多(40
个文件,2000 additions and 1000 deletions
),在review
过程中遗漏了该错误。
【3】为什么在测试过程中没有发现该bug
?
测试不充分,同时这个bug
的触发存在一定概率性,当多个订单引用同一个基础服务对象时,在对礼盒进行遍历计算时,最后一个订单的计算结果会覆盖前面所有引用了该基础服务对象的订单。如果测试时选择的订单没有触发该·bug·,·.Net·和·Java·版本的对比结果是一致的。
【4】为什么影响的单量达·10W·以上规模?
该bug
从2
月12
日发布直到3
月3
日才发现,持续了22
天。
【5】为什么从2
月12
日起该问题直到3
月3
日才发现?
目前对于这类问题缺乏有效的检测机制,只能被动的等待客户投诉发生后才会反馈到开发团队。
分析总结:
【1】通过以上故障示例,我们可以发现缓存被修改带来的影响通常具有以下特性:
■ 不容易发现,因为数据可能只在特定条件下被修改。
■ 影响面非常广,因为数据本身是被频繁使用才会被加入缓存。
■ 不确定性,因为数据被修改具有“随机性”,该特性导致影响范围难以确定,数据也难以清洗。
【2】缓存篡改通常如何发生:
■ 从缓冲获取一个对象(引用),后续过程中修改了该对象的内部成员。
public class CityCache {private static final CityCache INSTANCE = new CityCache();private final Map<String, City> cityMap = new HashMap<>();public static CityCache getInstance() {return INSTANCE;}private CityCache() {// 此处为了简便,没有写定时刷新loadDataFromDB();}public City getCityByCode(String cityCode) {return cityMap.get(cityCode);}private void loadDataFromDB() {// load cities from database and put them into cityMap}
}@Data
public class City {public City() {}public City(String code, int id) {this.code = code;this.id = id;}private String code;private int id;
}@Test
public void errorTest() {// 通过SHA获取到缓存实体,该实体的三字码与SHA相同City city1 = CityCache.getInstance().getCityByCode("SHA");assertTrue("SHA".equals(city1.getCode()));// 业务代码直接修改了city1的三字码(CityCache中的实体被修改)city1.setCode("BJS");// ...// 再次通过SHA获取到缓存实体,该实体的三字码与SHA不相同了(非期望值)City city2 = CityCache.getInstance().getCityByCode("SHA");assertFalse("SHA".equals(city2.getCode()));
}@Test
public void correctTest() {// 通过SHA获取到缓存实体,该实体的三字码与SHA相同City city1 = CityCache.getInstance().getCityByCode("SHA");assertTrue("SHA".equals(city1.getCode()));// 业务代码不能直接修改缓存实体,正确做法是先Copy一个对象,修改Copy对象的属性,后续业务使用该Copy对象City cityCopy = new City(city1.getCode(), city1.getId());cityCopy.setCode("BJS");// ...// 通过SHA获取到缓存实体,该实体的三字码与SHA相同City city2 = CityCache.getInstance().getCityByCode("SHA");assertTrue("SHA".equals(city2.getCode()));
}
■ 从缓冲获取一个集合(引用),后续过程中往该集合中添加/删除了元素。
public class CityCache {private static final CityCache INSTANCE = new CityCache();@Getterprivate final Map<String, City> cityMap = new HashMap<>();public static CityCache getInstance() {return INSTANCE;}private CityCache() {// // 此处为了简便,没有写定时刷新loadDataFromDB();}private void loadDataFromDB() {// load cities from database and put them into cityMap}
}@Data
public class City {public City() {}public City(String code, int id) {this.code = code;this.id = id;}private String code;private int id;
}@Test
public void errorTest() {Map<String, City> cityMap = CityCache.getInstance().getCityMap();// 通过SHA获取到缓存实体,该实体的三字码与SHA相同City city1 = cityMap.get("SHA");assertTrue("SHA".equals(city1.getCode()));// 业务代码直接修改缓存集合cityMap.put("SHA", new City("BJS", 2));// cityMap.remove("SHA");// 再次通过SHA获取到缓存实体,该实体的三字码与SHA不相同了(非期望值)City city2 = CityCache.getInstance().getCityMap().get("SHA");assertFalse("SHA".equals(city2.getCode()));
}
■ 缓存实体被修改
public class CityCache {private static final CityCache INSTANCE = new CityCache();@Getterprivate final Map<String, City> cityMap = new HashMap<>();public static CityCache getInstance() {return INSTANCE;}private CityCache() {// // 此处为了简便,没有写定时刷新loadDataFromDB();}private void loadDataFromDB() {// load cities from database and put them into cityMap}
}@Data
public class City {public City() {}public City(String code, int id) {this.code = code;this.id = id;}private String code;private int id;
}@Test
public void errorTest() {Map<String, City> cityMap = CityCache.getInstance().getCityMap();// 通过SHA获取到缓存实体,该实体的三字码与SHA相同City city1 = cityMap.get("SHA");assertTrue("SHA".equals(city1.getCode()));// 运行期间非预期的修改了缓存集合中的对象cityMap.forEach((k, v) -> {if (!"SHA".equals(k)) {return;}// ...v.setCode("BJS");});// 再次通过SHA获取到缓存实体,该实体的三字码与SHA不相同了(非期望值)City city2 = CityCache.getInstance().getCityMap().get("SHA");assertFalse("SHA".equals(city2.getCode()));
}
【3】如何避免缓存篡改:
■ 在可能需要修改数据的场景,从缓存获取一个深拷贝对象/集合。
■ 将缓存对象设计为只读状态,确保一旦构建就不可再修改其内部数据。
多线程
"多线程"这个话题想必开发人员或多或少都会接触到。 使用多线程最主要的原因是提高系统的资源利用率。 但在使用的过程中可能会遇到各种各样的问题,"死循环"便是其中比较棘手的一类。 下文分析了多线程环境下的死循环场景,希望对大家有所帮助。
死循环危害
程序进入假死状态: 当某个请求导致死循环,该请求会在很大一段时间内,都无法获取接口的返回。
CPU 使用率飙升: 代码出现死循环后,由于没有休眠,一直不断抢占cpu
资源,导致cpu
长时间处于繁忙状态,必定会使cpu
使用率飙升。
内存使用率飙升: 代码出现死循环时,循环体内有大量创建对象的逻辑,垃圾回收器无法及时回收,会导致内存使用率飙升。同时,如果垃圾回收器频繁回收对象,也会造成cpu
使用率飙升。
StackOverflowError
栈溢出: 在一些递归调用的场景,如果出现死循环,多次循环后,最终会报 StackOverflowError 栈溢出,程序直接挂掉。
案例一:多线程环境下的死循环案例
问题: 循环条件不正确
案例: 这里以二分查找为例
int search(List<Integer> nums, int target) {int l = 0, r = nums.size() - 1;while (l < r) {int mid = (l + r) / 2;if (nums.get(mid) > target)r = mid - 1;else// 可能出问题位置l = mid;}if (nums.get(l) == target)return l;elsereturn -1;
}
首先,会不会产生死循环的关键是l
和r
是否在每次循环后至少有一个的值发生了改变, 而while
循环体中,若走入了else
语句,l
的值有可能不发生变化,就会导致死循环的产生。 可以对循环体做以下调整:
while (l < r) {int mid = (l + r) / 2;if (nums.get(mid) >= target)r = mid;elsel = mid + 1;
}
案例二:flag 线程间不可见
有时候我们的代码需要一直做某件事情,直到某个条件达到,有个状态告诉它,要终止任务了,它就会自动退出。 这时候,很多人都会想到用while(flag)
实现这个功能:
public class FlagTest {private boolean flag = true;public void setFlag(boolean flag) {this.flag = flag;}public void fun() {while (flag) {}System.out.println("done");}public static void main(String[] args) throws InterruptedException {final FlagTest flagTest = new FlagTest();new Thread(() -> flagTest.fun()).start();Thread.sleep(200);flagTest.setFlag(false);}
}
这段代码在子线程中执行无限循环,当主线程休眠200
毫秒后,将flag
变成false
,这时子线程就会自动退出了。想法是好的,但是实际上这段代码进入了死循环,不会因为flag
变成false
而自动退出。 为什么会这样? 线程间flag
是不可见的,这时如果flag
加上了volatile
关键字,变成:
private volatile boolean flag = true;
会强制把共享内存中的值刷新到主内存中,让多个线程间可见,程序可以正常退出。
案例三:HashMap JDK7/8 死循环
问题: JDK7 rehash
(扩容)时和JDK8
链表更改为红黑树时。链接
案例四:自己手动写死循环
定时任务比如有个需求要求每隔5
分钟,从远程拉取数据,覆盖本地数据。 这时候,如果你不想用其他的定时任务框架,可以实现一个简单的定时任务,具体代码如下:
public static void sync() {new Thread(() -> {while (true) {try {System.out.println("sync data");Thread.sleep(1000 * 60 * 5);} catch (Exception e) {log.error(e);}}}).start();
}
其实很多JDK
中的定时任务,比如:Timer
类的底层,也是用了while(true)
的无限循环(也就是死循环)来实现的。
相关文章:

线上问题整理
JVM 案例 案例一:服务器内存不足,影响Java应用 问题: 收到报警,某Java应用集群中一台服务器可用内存不足,超过报警阈值。 排查过程: 首先,通过Hickwall查看该应用各项指标,发现无论…...

【elastic search】详解elastic search集群
目录 1.与集群有关的一些概念 2.集群搭建 3.集群搭建 4.kibana链接集群 5.选举流程 6.请求流程 7.master的作用 1.与集群有关的一些概念 数据分片: 数据分片(shard),单台服务器的存储容量是有限的,把一份数据…...

近红外光谱分析技术与基于深度学习的化学计量学方法
郁磊【副教授】:主要从事AI人工智能与大数据分析等相关研究,长期致力于人工智能与近红外生物医学工程等领域融合,主持并完成多项科研课题。著有《神经网络43个案例分析》等书籍。 // 讲座内容 1、近红外光谱基本理论、近红外光谱仪基本原理…...

Elasticsearch windows开箱即用【记录】
一、准备工作 安装ES之前要在本机安装好JDK,对应的兼容性见官网链接:https://www.elastic.co/cn/support/matrix ES官网链接:https://www.elastic.co/cn/, 我本机安装的是JDK8,测试使用的是7.3.0版本的ES和Kibana。 1、首先去…...

第 3 课 ROS 常用术语及命令说明
1.ROS文件系统的组成 ROS 文件是由 Packages 和 Manifests ( package.xml )组成。 Packages:功能包,是 ROS 软件中的基本单元,包含节点源码、配置文件、数据定义等。 Manifest( package xml &#x…...

基于AidLux的智慧教育版面分析应用
基于AidLux的智慧教育版面分析应用 1. Aidlux平台介绍 融合架构操作系统AidLux,可以为单一ARM设备同时提供Android和Linux运行环境,双系统既能独立使用又能相互通信。 非虚拟机方式实现双系统融合并行 同时拥有两个系统的完整用户体验无需重启即可在两个系统之间…...

Spring | Spring框架最基本核心的jar包、Spring的入门程序、依赖注入
目录: 1.Spring框架最基本、最核心的jar包2.Spring的入门程序3.依赖注入3.1 依赖注入的概念3.2 依赖注入的实现方式 1.Spring框架最基本、最核心的jar包 Spring是一个轻量级框架,Spring最基本、最核心的的jar包括 : beans、context、core、expression。 …...

[晓理紫]每日论文推送(有中文摘要,源码或项目地址)--大模型相关、扩散模型、视觉导航
专属领域论文订阅 VX关注{晓理紫|小李子},每日更新论文,如感兴趣,请转发给有需要的同学,谢谢支持 分类: 大语言模型LLM视觉模型VLM扩散模型视觉导航具身智能,机器人强化学习开放词汇,检测分割 LLM 标题:…...

【软件项目管理_软件工程】软件项目管理课后相关习题
(1)需求分析是回答系统必须(A)的问题。 A.做什么 B.怎么做 C.何时做 D.为谁做(2)WBS非常重要,下列哪项不是其很重要的原因(D)。 A.帮助组织工作 B.防止遗漏工作 C.为项目估算提供…...

Docker-Compose:教你入门到精通
引言 在当今的软件开发和运维领域,容器技术已经成为了一种非常重要的技术。而Docker-Compose作为Docker的重要工具之一,可以帮助我们轻松地管理和运行容器化的应用程序。本文将详细介绍Docker-Compose的入门到精通知识,帮助你更好地掌握这项技…...

软件设计不是CRUD(10):低耦合模块设计理论——业务抽象:从需求中提取业务维度
接上文《软件设计不是CRUD(9):低耦合模块设计理论——设计落地所面临的挑战》 2、什么是业务抽象 业务抽象是一种将需求落地成模块功能的设计思想,是对业务需求和技术设计进行转换、隔离的一种分析方法。经过业务抽象后的业务模块一般具有较高的业务屈服度,能更大程度满…...

S1-08 流和消息缓冲区
流缓冲区 流缓冲区一般用在不同设备或者不同进程间的通讯,为了提高数据处理效率和性能,设置的一定大小的缓冲区,流缓冲区可以用来存储程序中需要处理的数据、对象、报文等信息,使程序对可以对这些信息进行预处理、排序、过滤、拆…...

Java重修第五天—面向对象3
通过学习本篇文章可以掌握如下知识 1、多态; 2、抽象类; 3、接口。 之前已经学过了继承,static等基础知识,这篇文章我们就开始深入了解面向对象多态、抽象类和接口的学习。 多态 多态是在继承/实现情况下的一种现象…...

【征稿进行时|见刊、检索快速稳定】2024年经济发展与旅游管理国际学术会议(ICEDTM 2024)
【征稿进行时|见刊、检索快速稳定】2024年经济发展与旅游管理国际学术会议(ICEDTM 2024) 2024 International Conference Economic Development and Tourism Management(ICEDTM 2024) 一、【会议简介】 ICEDTM 2024将围绕"旅游管理”“经济发展”的最新研究领域ÿ…...

瑞_Java开发手册_(四)安全规约
🙊前言:本文章为瑞_系列专栏之《Java开发手册》的安全规约篇。由于博主是从阿里的《Java开发手册》学习到Java的编程规约,所以本系列专栏主要以这本书进行讲解和拓展,有需要的小伙伴可以点击链接下载。本文仅供大家交流、学习及研…...

Docker 安全必知:最佳实践、漏洞管理与监控策略
容器安全是实施和管理像 Docker 这样的容器技术的关键方面。它包括一组实践、工具和技术,旨在保护容器化应用程序及其运行的基础架构。在本节中,我们将讨论一些关键的容器安全考虑因素、最佳实践和建议。 容器隔离 隔离对于确保容器化环境的强大性和安全…...

【Flutter】多线程
Flutter 作为一个跨平台的UI库,前面的Flutter 架构有涉及到,Flutter 架构中的运行的多个线程。那么最为一个Flutter开发者,我们如何创建线程呢 多线程 上述我们提及到了,架构层涉及的多线程问题。比如说 主线程, 平台线程&#x…...

STM32-实时时钟RTC-1
...

node(express.js创建项目)+连接mysql数据库
1.node npm的安装 2.express的安装 全局安装:npm install express -gnpm install -g express-generator// ps: 4.0版本把generator分离出来了,需要单独安装3.创建express项目 express 项目名称 cd 项目名称 npm install npm start4.项目中安装数据库 npm install…...

【FLV】记录 H.264的解析
参考 FLV 文件格式分析 知乎大神的FLV文件格式分析。 FLV 首先下发9个字节的FLV 头 -2024-01-08 11:38:29.698 INFO [32196] [evplayer_client_main@153] player clinet run … 2024-01-08 11:38:29.702 INFO [2276] [evplayer_client_main::<lambda_1>::operator ()@14…...

nodejs+vue+ElementUi音乐分享社交网站77l8j
本文介绍的系统主要分为两个部分:一是前台界面:用户通过注册登录可以实现音乐播放、新闻浏览、留言评论等功能;另一个是后台界面:音乐网站管理员对用户信息进行管理,上传更新音乐资源,发布最新音乐资讯等功…...

进销存+小程序商城:实现批发零售企业的互联网转型与管理升级
在当今互联网高速发展的时代,越来越多的批发零售企业开始开始考虑转型。在这个行业中,企业要想取得更好的发展,就要积极地拥抱互联网。专属的订货商城小程序是企业转型的第一步。通过将进销存与订货商城一体化,企业可以更好地满足…...

Tomcat解压打包文件和并部署
一、文件压缩和上传解压 1.本地打包好dist.tar.gz文件 2.通过xftp拖拽上传到知道文件夹下,或者通过命令: cp dist.tar.gz /path/to/destination/folder注:将dist.tar.gz复制到 /path/to/destination/folder文件夹下,该文件夹只是举个例子怎么复制和解压! 3.进入/path/…...

JDK17
JDK 17是Java开发工具包(Java Development Kit)的一个版本。JDK是用于开发和运行Java应用程序的软件包,它包含了编译器、调试器、运行时环境和其他一些实用工具。JDK 17是Java的最新版本,它提供了许多新的功能、增强和改进。 使用…...

使用Docker运行SRS Stack
SRS Stack | SRS (ossrs.net) Docker 推荐使用Docker运行SRS Stack: docker run --restart always -d -it --name srs-stack -v $HOME/data:/data \-p 2022:2022 -p 2443:2443 -p 1935:1935 -p 8000:8000/udp -p 10080:10080/udp \registry.cn-hangzhou.aliyun…...

git安装教程 Windows 附安装包链接
Git是一款分布式源代码管理工具(版本控制工具) 。 git的作用 当你需要做一个大工程的时候,文件的管理无疑是非常庞大的工作,因为你需要不断的修改更新文件内容,同时可能还要保留旧版本保证可以复原,这样就需要备份多个版本的文件…...

TensorRT(C++)基础代码解析
TensorRT(C)基础代码解析 文章目录 TensorRT(C)基础代码解析前言一、TensorRT工作流程二、C API2.1 构建阶段2.1.1 创建builder2.1.2 创建网络定义2.1.3 定义网络结构2.1.4 定义网络输入输出2.1.5 配置参数2.1.6 生成Engine2.1.7 保存为模型文件2.1.8 释放资源 2.2 运行期2.2.1…...

如何查询MySQL中的树型表
在 MySQL 中查询树型表(即具有层级结构的表)可以使用递归查询或者使用嵌套集模型。下面分别介绍这两种方法: 递归查询:递归查询是通过自连接来实现的,可以使用 WITH RECURSIVE 关键字进行递归查询。假设有一个 catego…...

Programming Abstractions in C阅读笔记:p246-p247
《Programming Abstractions in C》学习第68天,p246-p247总结,总计2页。 一、技术总结 本章通过“the game of nim(尼姆游戏)”,这类以现实生活中事物作为例子进行讲解的情况,往往对学习者要求比较高,需要学习者具备…...

智能寻迹避障清障机器人设计(电路图附件+代码)
附 录 智能小车原理图 智能小车拓展板原理图 智能小车拓展板PCB 智能小车底板PCB Arduino UNO原理图 Arduino UNO PCB 程序部分 void Robot_Traction() //机器人循迹子程序{//有信号为LOW 没有信号为HIGHSR digitalRead(SensorRight);//有信号表明在白…...