首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >线上问题排查方法

线上问题排查方法

作者头像
oktokeep
发布2024-11-29 07:55:53
发布2024-11-29 07:55:53
8580
举报
文章被收录于专栏:第三方工具第三方工具

线上问题排查方法

1 OOM问题

代码语言:txt
复制
1.1 堆内存OOM
代码语言:txt
复制
1.2 栈内存OOM
代码语言:txt
复制
1.3 栈内存溢出
代码语言:txt
复制
1.4 GC OOM
代码语言:txt
复制
1.5 元空间OOM

2 CPU100%问题

3 接口超时问题

4 索引失效问题

5 死锁问题

6 磁盘问题

7 MQ消息积压问题

8 调用接口报错

代码语言:txt
复制
8.1 返回401
代码语言:txt
复制
8.2 返回403
代码语言:txt
复制
8.3 返回404
代码语言:txt
复制
8.4 返回405
代码语言:txt
复制
8.5 返回500
代码语言:txt
复制
8.6 返回502
代码语言:txt
复制
8.7 返回504

优化的方向是:

1.优化索引

2.优化sql语句

3.异步处理

4.批量处理

5.远程调用

6.避免大事务

7.锁粒度

8.分页处理

9.加缓存

10.分库分表

11.辅助功能

代码语言:txt
复制
11.1 开启慢查询日志
代码语言:txt
复制
11.2 加监控
代码语言:txt
复制
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://cloud.tencent.com/developer/article/2455857

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://cloud.tencent.com/developer/article/2455859

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://cloud.tencent.com/developer/article/2468652

================================= 优化的方向是:=================================

1.优化索引

代码语言:txt
复制
1.1 没加索引 sql语句中where条件的关键字段,或者order by后面的排序字段,忘了加索引,这个问题在项目中很常见。
代码语言:txt
复制
1.2 索引没生效 可以使用explain命令,查看mysql的执行计划,它会显示索引的使用情况。
代码语言:txt
复制
1.3 选错索引  必要时可以使用force index来强制查询sql走某个索引。

2.优化sql语句

3.异步处理 在这里有个原则就是:核心逻辑可以同步执行,同步写库。非核心逻辑,可以异步执行,异步写库。

代码语言:txt
复制
通常异步主要有两种:多线程 和 mq。

5.1 线程池

5.2 mq

4.批量处理

5.远程调用 串行调用远程接口性能是非常不好的,调用远程接口总的耗时为所有的远程接口耗时之和。

代码语言:txt
复制
5.1 并行调用  在java8之前可以通过实现Callable接口,获取线程返回结果。java8以后通过CompleteFuture类实现该功能。 + 线程池方案。
代码语言:txt
复制
public UserInfo getUserInfo(Long id) throws InterruptedException, ExecutionException {
代码语言:txt
复制
	final UserInfo userInfo = new UserInfo();
代码语言:txt
复制
	CompletableFuture userFuture = CompletableFuture.supplyAsync(() -> {
代码语言:txt
复制
		getRemoteUserAndFill(id, userInfo);
代码语言:txt
复制
		return Boolean.TRUE;
代码语言:txt
复制
	}, executor);
代码语言:txt
复制
	CompletableFuture bonusFuture = CompletableFuture.supplyAsync(() -> {
代码语言:txt
复制
		getRemoteBonusAndFill(id, userInfo);
代码语言:txt
复制
		return Boolean.TRUE;
代码语言:txt
复制
	}, executor);
代码语言:txt
复制
	CompletableFuture growthFuture = CompletableFuture.supplyAsync(() -> {
代码语言:txt
复制
		getRemoteGrowthAndFill(id, userInfo);
代码语言:txt
复制
		return Boolean.TRUE;
代码语言:txt
复制
	}, executor);
代码语言:txt
复制
	CompletableFuture.allOf(userFuture, bonusFuture, growthFuture).join();
代码语言:txt
复制
	userFuture.get();
代码语言:txt
复制
	bonusFuture.get();
代码语言:txt
复制
	growthFuture.get();
代码语言:txt
复制
	return userInfo;
代码语言:txt
复制
}
代码语言:txt
复制
5.2 数据异构  我们能不能把数据冗余一下,把用户信息、积分和成长值的数据统一存储到一个地方,比如:redis,存的数据结构就是用户信息查询接口所需要的内容。然后通过用户id,直接从redis中查询数据出来
代码语言:txt
复制
	如果使用了数据异构方案,就可能会出现数据一致性问题。
  1. 重复调用 4.1 循环查数据库 这里有个需要注意的地方是:id集合的大小要做限制,最好一次不要请求太多的数据。要根据实际情况而定,建议控制每次请求的记录条数在500以内。 批量查询一个大的集合,然后在在该集合中根据ID来过滤,而避免了单次根据ID来查询数据库。 4.2 死循环 出现死循环,大概率是开发人员人为的bug导致的,不过这种情况很容易被测出来。 还有一种隐藏的比较深的死循环,是由于代码写的不太严谨导致的。如果用正常数据,可能测不出问题,但一旦出现异常数据,就会立即出现死循环。 4.3 无限递归 建议写递归方法时,设定一个递归的深度,比如:分类最大等级有4级,则深度可以设置为4。然后在递归方法中做判断,如果深度大于4时,则自动返回,这样就能避免无限循环的情况。
  2. 避免大事务 link:
  3. 锁粒度 为了解决并发场景下,多个线程同时修改数据,造成数据不一致的情况。通常情况下,我们会:加锁。 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); }undefined return false; } 改进后: public void doSave(String path,String fileUrl) { if(this.tryLock()) { mkdir(path); } uploadFile(fileUrl); sendMessage(fileUrl); }

private boolean tryLock() {

代码语言:txt
复制
try {
代码语言:txt
复制
String result = jedis.set(lockKey, requestId, "NX", "PX", expireTime);
代码语言:txt
复制
if ("OK".equals(result)) {
代码语言:txt
复制
  return true;
代码语言:txt
复制
}

} finally{

代码语言:txt
复制
  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(() -> {

代码语言:txt
复制
    result.addAll(remoteCallUser(batchIds));
代码语言:txt
复制
    return Boolean.TRUE;
代码语言:txt
复制
}, 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>

代码语言:txt
复制
<groupId>org.springframework.boot</groupId>
代码语言:txt
复制
<artifactId>spring-boot-starter-cache</artifactId>

</dependency>

<dependency>

代码语言:txt
复制
<groupId>com.github.ben-manes.caffeine</groupId>
代码语言:txt
复制
<artifactId>caffeine</artifactId>
代码语言:txt
复制
<version>2.6.0</version>

</dependency>

2.配置CacheManager,开启EnableCaching

@Configuration

@EnableCaching

public class CacheConfig {

代码语言:txt
复制
@Bean
代码语言:txt
复制
public CacheManager cacheManager(){
代码语言:txt
复制
    CaffeineCacheManager cacheManager = new CaffeineCacheManager();
代码语言:txt
复制
    //Caffeine配置
代码语言:txt
复制
    Caffeine<Object, Object> caffeine = Caffeine.newBuilder()
代码语言:txt
复制
            //最后一次写入后经过固定时间过期
代码语言:txt
复制
            .expireAfterWrite(10, TimeUnit.SECONDS)
代码语言:txt
复制
            //缓存的最大条数
代码语言:txt
复制
            .maximumSize(1000);
代码语言:txt
复制
    cacheManager.setCaffeine(caffeine);
代码语言:txt
复制
    return cacheManager;
代码语言:txt
复制
}

}

3.使用Cacheable注解获取数据

@Service

public class CategoryService {

@Cacheable(value = "category", key = "#categoryKey")

public CategoryModel getCategory(String categoryKey) {

代码语言:txt
复制
  String json = jedis.get(categoryKey);
代码语言:txt
复制
  if(StringUtils.isNotEmpty(json)) {
代码语言:txt
复制
     CategoryTree categoryTree = JsonUtil.toObject(json);
代码语言:txt
复制
     return categoryTree;
代码语言:txt
复制
  }
代码语言:txt
复制
  return queryCategoryTreeFromDb();

}

}

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

  1. 分库分表 用户库拆分成了三个库,每个库都包含了四张用户表。 先根据用户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资源等问题。

  1. 辅助功能 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/

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2024-11-28,如有侵权请联系 cloudcommunity@tencent.com 删除
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档