性能调优
背景:最近手里的两个系统相继出现了问题,肝了一个多月,算是告一段落了,总结一下近阶段的问题和解决方案
服务假死问题
Q1: A系统出现的现象如下:Eureka显示注册成功,前端用户访问正常,但是B用Feign方法调用该系统出现Read timeout问题
A: 看服务器发现,在那个时间段CPU爆了,服务处于不可访问状态,服务没有报错信息,之后也再也没有请求信息,仅有定时任务的日志。
方法:打GC日志,关注日志信息发现,内部有大量http请求Lock住了
解决方案:
1 设置三方请求超时时间
/**
* 执行post请求获取响应(请求体为JOSN数据)
*
* @param url 请求地址
* @param headers 请求头参数
* @param json 请求的JSON数据
* @return 响应内容
*/
public static String postJson(String url, Map<String, String> headers, String json) {
HttpPost post = new HttpPost(url);
post.setHeader("Content-type", "application/json");
post.setEntity(new StringEntity(json, UTF8));
RequestConfig requestConfig = RequestConfig.custom()
.setConnectTimeout(5000)
.setConnectionRequestTimeout(5000)
.setSocketTimeout(5000)
.build();
post.setConfig(requestConfig);
return getRespString(post, headers);
}
2 设置异步线程
@SneakyThrows
@Async("asyncPoolTaskExecutor")
public LxTobOrder pushMessageToLX(LxTobOrder orderResult, int i) {
}
/**
* 描述:异步配置
* 创建人:HuangTuL
*/
@Slf4j
@Configuration
@EnableAsync // 可放在启动类上或单独的配置类
public class AsyncConfiguration implements AsyncConfigurer {
@Bean(name = "asyncPoolTaskExecutor")
public ThreadPoolTaskExecutor executor() {
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
//核心线程数
taskExecutor.setCorePoolSize(5);
//线程池维护线程的最大数量,只有在缓冲队列满了之后才会申请超过核心线程数的线程
taskExecutor.setMaxPoolSize(10);
//缓存队列
taskExecutor.setQueueCapacity(500);
//设置线程的空闲时间,当超过了核心线程出之外的线程在空闲时间到达之后会被销毁
// taskExecutor.setKeepAliveSeconds(200);
//异步方法内部线程名称
taskExecutor.setThreadNamePrefix("async-");
/**
* 当线程池的任务缓存队列已满并且线程池中的线程数目达到maximumPoolSize,如果还有任务到来就会采取任务拒绝策略
* 通常有以下四种策略:
* ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。
* ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。
* ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程)
* ThreadPoolExecutor.CallerRunsPolicy:重试添加当前的任务,自动重复调用 execute() 方法,直到成功
*/
taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());
taskExecutor.initialize();
return taskExecutor;
}
/**
* 指定默认线程池
* The {@link Executor} instance to be used when processing async method invocations.
*/
@Override
public Executor getAsyncExecutor() {
return executor();
}
/**
* The {@link AsyncUncaughtExceptionHandler} instance to be used
* when an exception is thrown during an asynchronous method execution
* with {@code void} return type.
*/
@Override
public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
return (ex, method, params) -> log.error("线程池执行任务发送未知错误, 执行方法:{}", method.getName(), ex);
}
}
总结:核心问题是线程数被占满了,http请求没有设置超时时间,异步线程池的线程数未设置。优化后,未发现假死情况,持续观察
Q2 : 数据库死锁
Insert Batch order_status failed. DeadlockLoserDataAccessException:{} org.springframework.dao.DeadlockLoserDataAccessException: ### Error updating database. Cause: com.mysql.cj.jdbc.exceptions.MySQLTransactionRollbackException: Deadlock found when trying to get lock; try restarting transaction ### The error may exist in
A: 根据问题
- 猜想1 业务纠缠导致的死锁。在插入逻辑和更新逻辑造成死锁
优化:加锁,共用一个分布式锁,可解决死锁问题,但是导致性能下降 - 猜想2 http请求阻塞时间。由第三方请求导致导致线程占用,长时间没释放数据库资源,导致死锁
优化:新增kafka topic ,第三方请求作为kafka消费者,解耦超时时间问题
Stopwatch stopwatch = Stopwatch.createStarted();
logger.info("updateOrderStatusToBZ before : cost : {} MS",stopwatch.elapsed(TimeUnit.MILLISECONDS));
总结:问题的核心在于解耦,方法在于监控那个方法那个代码块是耗时部分,针对这部分进行优化