首页 > 其他分享 >线上问题排查方法

线上问题排查方法

时间:2024-11-28 19:12:45浏览次数:9  
标签:return 索引 OOM 问题 排查 线程 线上 方法 内存

线上问题排查方法

1 OOM问题
1.1 堆内存OOM
1.2 栈内存OOM
1.3 栈内存溢出
1.4 GC OOM
1.5 元空间OOM
2 CPU100%问题
3 接口超时问题
4 索引失效问题
5 死锁问题
6 磁盘问题
7 MQ消息积压问题
8 调用接口报错
8.1 返回401
8.2 返回403
8.3 返回404
8.4 返回405
8.5 返回500
8.6 返回502
8.7 返回504

优化的方向是:
1.优化索引
2.优化sql语句
3.异步处理
4.批量处理
5.远程调用
6.避免大事务
7.锁粒度
8.分页处理
9.加缓存
10.分库分表
11.辅助功能
11.1 开启慢查询日志
11.2 加监控
11.3 链路跟踪

1 OOM问题
OOM问题在生产环境中,一旦出现,一般会是非常严重的问题,服务可能会挂掉。
但是OOM问题有多种情况,不同的情况,出现问题的原因不一样。

1.1 堆内存OOM
服务器的日志一般会打印下面的内容:
java.lang.OutOfMemoryError: Java heap space
这种是出现最多的OOM问题。
在Java服务启动时,可以增加下面的参数:
-XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=heapdump.hprof
在发生OOM时,程序会自动把当时的内存使用情况,dump保存到指定的文件。
然后使用MAT(Memory Analyzer Tool),或者使用JDK自带的 Java visualvm,来分析dump 文件,找出导致OOM的代码。

link: ElasticSearch服务Java内存异常分析和排查解决
https://www.cnblogs.com/oktokeep/p/18205278

1.2 栈内存OOM
出现栈内存OOM问题的异常信息如下:
java.lang.OutOfMemoryError: unable to create new native thread
如果实际工作中,出现这个问题,一般是由于创建的线程太多,或者设置的单个线程占用内存空间太大导致的。
这个时候需要排查服务的线程数量。
推荐使用线程池,可以减少线程的创建,有效控制服务中的线程数量。【关键,避免无效创建线程,得不到控制】

1.3 栈内存溢出
出现栈内存溢出问题的异常信息如下:
java.lang.StackOverflowError
该问题一般是由于业务代码中写的一些递归调用,递归的深度超过了JVM允许的最大深度,可能会出现栈内存溢出问题。
如果生产环境中,出现了这个问题,可以排查一下递归调用是否正常,有可能出现了无限递归的情况。 【程序中尽量避免使用递归算法】

1.4 GC OOM
出现GC OOM问题时异常信息如下:
java.lang.OutOfMemoryError: GC overhead limit exceeded
GC OOM一般是由于JVM在GC时,对象过多,导致内存溢出,建议调整GC的策略。
在老代80%时就是开始GC,并且将-XX:SurvivorRatio(-XX:SurvivorRatio=8)和-XX:NewRatio(-XX:NewRatio=4)设置的更合理。

1.5 元空间OOM
出现元空间OOM问题时异常信息如下:
java.lang.OutOfMemoryError: Metaspace
JDK8之后使用Metaspace来代替永久代,Metaspace是方法区在HotSpot中的实现。
这个问题一般是由于加载到内存中的类太多,或者类的体积太大导致的。
如果生产环境中出现了这个问题,可以通过下面的命令修改元空间大小:
-XX:MetaspaceSize=10m -XX:MaxMetaspaceSize=10m

link: OOM异常的4种可能分析及常见的OOM异常演示
https://www.cnblogs.com/oktokeep/p/18205280

2 CPU100%问题

3 接口超时问题

4 索引失效问题
我们可以通过explain关键字,查看sql的执行计划,可以确认索引是否失效。

5 死锁问题
死锁是指两个或多个事务在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,这些事务将无法继续向前推进。
在Java中,使用MySQL数据库时,如果遇到MySQLTransactionRollbackException: Deadlock found when trying to get lock; try restarting transaction异常,意味着数据库检测到了死锁。

MySQL死锁通常由以下原因造成:
1.资源竞争:多个事务同时竞争相同的资源,比如都试图获取对方持有的锁。
2.循环等待:事务之间形成了一种互相等待对方释放资源的循环关系。
3.不当的事务设计:事务执行顺序不合理、执行时间过长等。
4.并发操作冲突:在高并发环境下,多个事务对同一组数据进行操作,容易引发锁冲突导致死锁。
5.索引使用不当:如果索引设计不合理,可能导致事务在获取锁时出现问题。

如何减少死锁问题?
1.设置合理的事务隔离级别。
2.避免大事务的业务代码。
3.优化sql性能。
4.增加锁等待超时处理。
5.增加监控和分析

6 磁盘问题
磁盘问题一般有两种:

磁盘坏了
磁盘空间不足

如果是磁盘空间不足。
一般需要登录到那台服务器,
使用命令:
df -hl
查看当前服务器的磁盘使用情况。
总大小 已使用多少 可用多少
Filesystem Size Used Avail Use% Mounted on
devtmpfs 31G 0 31G 0% /dev
tmpfs 31G 0 31G 0% /dev/shm
tmpfs 31G 2.0M 31G 1% /run
tmpfs 31G 0 31G 0% /sys/fs/cgroup

最快的解决办法是:
1.将/tmp文件夹中的文件删除,可以释放一些磁盘空间。
2.然后找到日志文件,删除7天以前的日志。
这两种方式,一般会释放不少磁盘空间,暂时解决磁盘空间不足的问题。

从常用来看,我们需要对服务器的磁盘使用情况做监控,如果超过阀值有预警。
同时需要需要规范业务系统,哪些场景需要打印日志,哪些场景不需要,不应该所有的场景,都打印日志。
特别是有些业务查询接口调用非常频繁,一次性返回的数据很多,这种情况下,会导致服务器上的日志迅速膨胀,占用过多的磁盘空间。

7 MQ消息积压问题
可能是下面的原因导致的:
MQ生产者批量发送消息。随着数据越来越多,MQ消费者的在处理业务逻辑时,mysql索引失效或者选错索引,导致处理消息的速度变慢。
如果生产环境出现MQ消息积压问题,先确认MQ生产者有没有批量发送消息。
如果有,则可以把MQ消费者中线程池的核心线程数和最大线程数调大一些,让更多的线程去处理业务逻辑,提升消费能力。
这套方案的前提是MQ消费者中,已经使用了线程池消费消息。
如果没有使用线程池,则只能临时增加服务器节点了。
如果MQ生产者没有批量发送消息,则需要排查MQ消费者的业务逻辑中,哪些地方出现了性能问题,需要做代码优化。

8 调用接口报错
link: http响应码简介
https://www.cnblogs.com/oktokeep/p/18559028

================================= 优化的方向是:=================================
1.优化索引
1.1 没加索引 sql语句中where条件的关键字段,或者order by后面的排序字段,忘了加索引,这个问题在项目中很常见。
1.2 索引没生效 可以使用explain命令,查看mysql的执行计划,它会显示索引的使用情况。
1.3 选错索引 必要时可以使用force index来强制查询sql走某个索引。
2.优化sql语句
3.异步处理 在这里有个原则就是:核心逻辑可以同步执行,同步写库。非核心逻辑,可以异步执行,异步写库。
通常异步主要有两种:多线程 和 mq。
5.1 线程池
5.2 mq
4.批量处理
5.远程调用 串行调用远程接口性能是非常不好的,调用远程接口总的耗时为所有的远程接口耗时之和。
5.1 并行调用 在java8之前可以通过实现Callable接口,获取线程返回结果。java8以后通过CompleteFuture类实现该功能。 + 线程池方案。

public UserInfo getUserInfo(Long id) throws InterruptedException, ExecutionException {
final UserInfo userInfo = new UserInfo();
CompletableFuture userFuture = CompletableFuture.supplyAsync(() -> {
getRemoteUserAndFill(id, userInfo);
return Boolean.TRUE;
}, executor);

CompletableFuture bonusFuture = CompletableFuture.supplyAsync(() -> {
getRemoteBonusAndFill(id, userInfo);
return Boolean.TRUE;
}, executor);

CompletableFuture growthFuture = CompletableFuture.supplyAsync(() -> {
getRemoteGrowthAndFill(id, userInfo);
return Boolean.TRUE;
}, executor);
CompletableFuture.allOf(userFuture, bonusFuture, growthFuture).join();

userFuture.get();
bonusFuture.get();
growthFuture.get();

return userInfo;
}

5.2 数据异构 我们能不能把数据冗余一下,把用户信息、积分和成长值的数据统一存储到一个地方,比如:redis,存的数据结构就是用户信息查询接口所需要的内容。然后通过用户id,直接从redis中查询数据出来
如果使用了数据异构方案,就可能会出现数据一致性问题。
4. 重复调用
4.1 循环查数据库 这里有个需要注意的地方是:id集合的大小要做限制,最好一次不要请求太多的数据。要根据实际情况而定,建议控制每次请求的记录条数在500以内。
批量查询一个大的集合,然后在在该集合中根据ID来过滤,而避免了单次根据ID来查询数据库。
4.2 死循环 出现死循环,大概率是开发人员人为的bug导致的,不过这种情况很容易被测出来。
还有一种隐藏的比较深的死循环,是由于代码写的不太严谨导致的。如果用正常数据,可能测不出问题,但一旦出现异常数据,就会立即出现死循环。
4.3 无限递归 建议写递归方法时,设定一个递归的深度,比如:分类最大等级有4级,则深度可以设置为4。然后在递归方法中做判断,如果深度大于4时,则自动返回,这样就能避免无限循环的情况。

6. 避免大事务
link:
7. 锁粒度
为了解决并发场景下,多个线程同时修改数据,造成数据不一致的情况。通常情况下,我们会:加锁。
7.1 synchronized 通常有两种写法:在方法上加锁 和 在代码块上加锁。
public synchronized doSave(String fileUrl) {
mkdir();
uploadFile(fileUrl);
sendMessage(fileUrl);
}
改进后:
public void doSave(String path,String fileUrl) {
synchronized(this) {
if(!exists(path)) {
mkdir(path);
}
}
uploadFile(fileUrl);
sendMessage(fileUrl);
}
synchronized只能保证一个节点加锁是有效的,但如果有多个节点如何加锁呢?
这就需要使用:分布式锁了。目前主流的分布式锁包括:redis分布式锁、zookeeper分布式锁 和 数据库分布式锁。
7.2 redis分布式锁
public void doSave(String path,String fileUrl) {
try {
String result = jedis.set(lockKey, requestId, "NX", "PX", expireTime);
if ("OK".equals(result)) {
if(!exists(path)) {
mkdir(path);
uploadFile(fileUrl);
sendMessage(fileUrl);
}
return true;
}
} finally{
unlock(lockKey,requestId);
}
return false;
}
改进后:
public void doSave(String path,String fileUrl) {
if(this.tryLock()) {
mkdir(path);
}
uploadFile(fileUrl);
sendMessage(fileUrl);
}

private boolean tryLock() {
try {
String result = jedis.set(lockKey, requestId, "NX", "PX", expireTime);
if ("OK".equals(result)) {
return true;
}
} finally{
unlock(lockKey,requestId);
}
return false;
}
7.3 数据库分布式锁

8.分页处理 将一次获取所有的数据的请求,改成分多次获取,每次只获取一部分用户的数据,最后进行合并和汇总。
8.1 同步调用
List<List<Long>> allIds = Lists.partition(ids,200);

for(List<Long> batchIds:allIds) {
List<User> users = remoteCallUser(batchIds);
}
代码中我用的google的guava工具中的Lists.partition方法,用它来做分页简直太好用了,不然要巴拉巴拉写一大堆分页的代码。
8.2 异步调用
List<List<Long>> allIds = Lists.partition(ids,200);

final List<User> result = Lists.newArrayList();
allIds.stream().forEach((batchIds) -> {
CompletableFuture.supplyAsync(() -> {
result.addAll(remoteCallUser(batchIds));
return Boolean.TRUE;
}, executor);
})

9.加缓存
9.1 redis缓存 缓存可能是:redis和memcached。
操作redis可以使用成熟的框架,比如:jedis和redisson等
String json = jedis.get(key);
if(StringUtils.isNotEmpty(json)) {
CategoryTree categoryTree = JsonUtil.toObject(json);
return categoryTree;
}
return queryCategoryTreeFromDb();

缓存同步机制: job每隔一段时间,从数据库中查询菜单数据,更新到redis当中,这样以后每次都能直接从redis中获取菜单的数据,而无需访问数据库了。

9.2 二级缓存 使用二级缓存,即基于内存的缓存。
除了自己手写的内存缓存之后,目前使用比较多的内存缓存框架有:guava、Ehcache、caffine等。 caffeine为例,它是spring官方推荐的。
1.引入caffeine的相关jar包
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-cache</artifactId>
</dependency>
<dependency>
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
<version>2.6.0</version>
</dependency>

2.配置CacheManager,开启EnableCaching
@Configuration
@EnableCaching
public class CacheConfig {
@Bean
public CacheManager cacheManager(){
CaffeineCacheManager cacheManager = new CaffeineCacheManager();
//Caffeine配置
Caffeine<Object, Object> caffeine = Caffeine.newBuilder()
//最后一次写入后经过固定时间过期
.expireAfterWrite(10, TimeUnit.SECONDS)
//缓存的最大条数
.maximumSize(1000);
cacheManager.setCaffeine(caffeine);
return cacheManager;
}
}

3.使用Cacheable注解获取数据
@Service
public class CategoryService {

@Cacheable(value = "category", key = "#categoryKey")
public CategoryModel getCategory(String categoryKey) {
String json = jedis.get(categoryKey);
if(StringUtils.isNotEmpty(json)) {
CategoryTree categoryTree = JsonUtil.toObject(json);
return categoryTree;
}
return queryCategoryTreeFromDb();
}
}

二级缓存给我们带来性能提升的同时,也带来了数据不一致的问题。使用二级缓存一定要结合实际的业务场景,并非所有的业务场景都适用。

10. 分库分表
用户库拆分成了三个库,每个库都包含了四张用户表。
先根据用户id路由到其中一个用户库,然后再定位到某张表。
路由算法:
根据id取模,比如:id=7,有4张表,则7%4=3,模为3,路由到用户表3。
给id指定一个区间范围,比如:id的值是0-10万,则数据存在用户表0,id的值是10-20万,则数据存在用户表1。
一致性hash算法

分库:是为了解决数据库连接资源不足问题,和磁盘IO的性能瓶颈问题。
分表:是为了解决单表数据量太大,sql语句查询数据时,即使走了索引也非常耗时问题。此外还可以解决消耗cpu资源问题。
分库分表:可以解决 数据库连接资源不足、磁盘IO的性能瓶颈、检索数据耗时 和 消耗cpu资源等问题。

11. 辅助功能
11.1 开启慢查询日志
开启慢查询日志需要重点关注三个参数:
slow_query_log 慢查询开关
slow_query_log_file 慢查询日志存放的路径
long_query_time 超过多少秒才会记录日志

set global slow_query_log='ON';
set global slow_query_log_file='/usr/local/mysql/data/slow.log';
set global long_query_time=2;
也可以直接修改配置文件my.cnf 但这种方式需要重启mysql服务。
[mysqld]
slow_query_log = ON
slow_query_log_file = /usr/local/mysql/data/slow.log
long_query_time = 2

11.2 加监控
开源监控系统是:Prometheus。 提供了 监控 和 预警 的功能 Prometheus的官网:https://prometheus.io/

11.3 链路跟踪 分布式链路跟踪系统:skywalking。 https://skywalking.apache.org/

标签:return,索引,OOM,问题,排查,线程,线上,方法,内存
From: https://www.cnblogs.com/oktokeep/p/18574990

相关文章