公司一次项目事故,项目在正常测试时没有问题,在实际生产并发场景下发生了线程锁死的状况。
后来分析原因,项目中在数据库查询时使用了多线程的写法。项目使用Spring Data JPA, 配置了
spring.jpa.open-in-view: True
该配置是为了关联模型查询方便,但是导致查询结束,当前访问会话未结算的情况下,数据库不释放连接。
后续子线程会尝试继续申请数据库连接,进而导致这种写法情况下,应用会为每次访问分步骤至少请求2个数据库连接资源。
该模型场景在并发量小的情况下,数据库连接数量充裕的时候并不会发生问题。
但是遇到高并发场景,大量前端会话在同时占用了主线程数据库连接而又未在子线程申请到连接的时候,就会发生进程锁死,进而导致整个应用假死的情况。
后来发现这个问题之后,解决思路是尝试获取到主线程jpa的当前会话,在主线程查询之后关闭会话,经过试验貌似可以解决问题。
Session session = (Session) jpaContext.getEntityManagerByManagedType(PriceTicket.class).getDelegate();
session.close();
下面是该文件完整代码
/*
* Created by Wanghw 2018/3/29.
*/
package com.example.demo;
import com.example.demo.jpa.PriceTicket;
import com.example.demo.jpa.PriceTicketRepository;
import org.hibernate.Session;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaContext;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.Callable;
import java.util.concurrent.Future;
@RestController
@RequestMapping("/mulThread")
public class TestMulThread {
@Autowired
private PriceTicketRepository priceTicketRepository;
@Autowired
private BeanFactory beanFactory;
@Resource(name="myExecutor")
private ThreadPoolTaskExecutor executor;
@Autowired
private JpaContext jpaContext;
@GetMapping()
public ResultDTO list(){
Pageable page = new PageRequest(0,1);
// 如果配置为 spring.jpa.open-in-view: True(默认是True)。主线程会占用一个数据库连接,直到该访问会话结束
priceTicketRepository.findAll(page);
Session session = (Session) jpaContext.getEntityManagerByManagedType(PriceTicket.class).getDelegate(); //尝试获取当前jpa会话,强制结束会话。
session.close();
List<Future<Integer>> tasks= new ArrayList<>();
// 这里会起多个子线程进行数据库查询,会尝试获取数据库连接
for (int i = 0; i<4 ; i++){
ProductPriceCalculateTask task = beanFactory.getBean(ProductPriceCalculateTask.class);
task.init(i);
tasks.add(executor.submit(task));
}
try {
for (Future<Integer> productPriceTask : tasks){
if(productPriceTask!=null){
Integer pt = productPriceTask.get();
}
}
} catch (Exception e) {
e.printStackTrace();
}
return ResultDTO.builder().isSuccess(true).build();
}
@Component
@Scope("prototype")
public static class ProductPriceCalculateTask implements Callable<Integer> {
@Autowired
private PriceTicketRepository priceTicketRepository;
private Integer index;
public void init(Integer index) {
this.index=index;
}
@Override
public Integer call() throws Exception {
Pageable page = new PageRequest(0,1);
priceTicketRepository.findAll(page);
return index;
}
}
}