首页 > 解决方案 > 为什么检测 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);
}

标签: javamultithreadingspring-booteclipselinkspring-restcontroller

解决方案


推荐阅读