java - 为什么检测 Spring REST Controller 线程等待锁定?
问题描述
我们有一个使用 REST 控制器和 JPA 和 EclipseLink 的 Spring Boot 应用程序(v2.0.7)。在其中一项测试中,我们使数据库无法访问,这在几分钟内导致应用程序根本没有响应 REST 请求。
进行了线程转储,其中显示以下内容:
1) 4 个线程在执行 OraclePreparedStatement.executeQuery() 时卡在 socket.read() 上。他们会在几分钟后解开(大概是数据库超时)。这是可以理解的。
2) ~180 线程处于 WAITING (parking) 状态。它们通过 Spring 检测代理执行 RestController 方法,这导致它们被停在等待锁上。这是一个例子:
"qtp68159840-600" #600 prio=5 os_prio=0 tid=0x00007fe54400c800 nid=0x371d waiting on condition [0x00007fe5246c8000]
java.lang.Thread.State: WAITING (parking)
at sun.misc.Unsafe.park(Native Method)
- parking to wait for <0x000000070727d808> (a java.util.concurrent.locks.ReentrantReadWriteLock$NonfairSync)
at java.util.concurrent.locks.LockSupport.park(LockSupport.java:175)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.parkAndCheckInterrupt(AbstractQueuedSynchronizer.java:836)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireShared(AbstractQueuedSynchronizer.java:967)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireShared(AbstractQueuedSynchronizer.java:1283)
at java.util.concurrent.locks.ReentrantReadWriteLock$ReadLock.lock(ReentrantReadWriteLock.java:727)
at org.springframework.cloud.context.scope.GenericScope$LockedScopedProxyFactoryBean.invoke(GenericScope.java:489)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:688)
at com.xxx.XXXApiController$$EnhancerBySpringCGLIB$$f7601a28.getSomeValue(<generated>)
...
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:209)
...
at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:873)
...
at org.eclipse.jetty.servlet.ServletHolder.handle(ServletHolder.java:865)
...
查看堆栈跟踪中的这一行:
com.xxx.XXXApiController$$EnhancerBySpringCGLIB$$f7601a28.getSomeValue(<generated>)
com.xxx.XXXApiController
我们的 REST 控制器在哪里,并且getSomeValue()
是它的方法之一,我们得出的结论是 REST 请求实际上确实到达了控制器(它的 cglib 检测版本),但是这个检测决定它应该让它停止并等待锁定. 实际上,从这个特定线程(例如0x000000070727d808
)对锁的引用只在跟踪中被提及一次,并且在线程转储中的其他任何地方都找不到。
任何人都可以帮助理解为什么它会等待锁,这个锁是什么?
Controller 有一个@Autowired
字段是 JPA Repository。可能正因为如此@Autowired
,控制器的检测代码以某种方式“知道”数据库连接存在问题并锁定了 Jetty 线程?谁能澄清这个机制是如何工作的?
更新
根据评论中的要求提供控制器代码(试图仅显示相关部分)。
@javax.annotation.Generated(value = "io.swagger.codegen.v3.generators.java.SpringCodegen", date = "2019-04-17T09:25:33.309Z[GMT]")
@RestController
@RefreshScope
public class XXXApiController implements XXXApi {
private final ObjectMapper objectMapper;
private final HttpServletRequest request;
@Autowired
private DaoService daoService;
@org.springframework.beans.factory.annotation.Autowired
public XXXApiController(ObjectMapper objectMapper, HttpServletRequest request) {
this.objectMapper = objectMapper;
this.request = request;
}
@PreAuthorize("hasAuthority('db-read')")
@Override
public ResponseEntity<Something> getSomething(
@ApiParam(value = "", required=true, allowableValues = "a,b,c,d") @PathVariable("keyType")
String keyType,
@ApiParam(value = "keyValue for a given keyType", required=true)
@PathVariable("keyValue")
String keyValue,
@ApiParam(value = "The troubleshooting optional parameter", allowableValues = "x,y,z")
@Valid @RequestParam(value = "from", required = false)
String from) {
request.setAttribute(METHOD, "getSomething");
if (keyValue == null|| keyValue.length() == 0) {
return new ResponseEntity<>(HttpStatus.BAD_REQUEST);
}
if (from != null && from.equals("cache")) {
result = daoService.getSomethingFromCache(keyValue);
} else {
result = daoService.getSomethingFromDb(keyType, keyValue);
}
ResponseEntity responseEntity;
if (routingInfo != null) responseEntity = new ResponseEntity<>(result, HttpStatus.OK);
else responseEntity = new ResponseEntity(HttpStatus.NOT_FOUND);
return responseEntity;
}
...
}
@Component
@Controller
public class DaoServiceImpl implements DaoService {
@Autowired
private SomethingRepository somethingRepository;
@Override
public Something getSomethingFromCache(String name) {
String somethingStr = getFromCache(name);
if (somethingStr != null) {
return convertSomethingFromCache(somethingStr);
}
return null;
}
@Override
public Something getSomethingFromDb(String keyType, String value) {
return createSomething(somethingRepository.findByKey(value));
}
...
}
@Repository
@Transactional
public interface SomethingRepository extends JpaRepository<Something, Long>, SomethingService {
@Query("select t from Something t where " +
"(:x is not null and t.x = :x) or " +
"(:y is not null and t.y= :y)")
public List<Something> findByFilter(
@Param("x") String x,
@Param("y") String y,
Pageable request);
}
解决方案
推荐阅读
- konvajs - 如何在 Konva 中将 globalCompositeOperation 用于具有缩放的多个形状
- javascript - 在 webpack 中编译 scss 的问题 - webpack 将旧代码与新代码混合在一起
- swift - 如果 FirebaseAuth 为空,我如何转到“登录”视图控制器?
- spring - 来自swagger yaml的春季服务器存根
- sql - SQLite 是否具有其他 SQL 风格所没有的任何特性(除了基于文件的特性)?
- mysql - MySql 时间表查询
- bash - db2“描述”控制台表输出不可读
- java - 运行 java 可运行 JAR
- javascript - 以交互方式运行 cypress 命令
- ios - 新的 UWB iPhone 可以测量它们与其他 UWB iPhone 的距离吗?