2016-02-14 25 views
-1

看看redis client list的輸出,我發現目前有600個活動客戶端,並且它不斷增長。下面是輸出的一個片段:將JedisPool與Tomcat配合使用,資源不會返回池

id=285316 addr=x.x.x.x:55699 fd=14131 name= age=53055 idle=53029 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=0 events=r cmd=sismember id=285317 addr=x.x.x.x:55700 fd=14132 name= age=53055 idle=53050 flags=N db=0 sub=0 psub=0 multi=-1 qbuf=0 qbuf-free=0 obl=0 oll=0 omem=0 events=r cmd=sismember

這裏是我的代碼:

Listener.java

import com.sun.jersey.api.model.AbstractResourceModelContext; 
import com.sun.jersey.api.model.AbstractResourceModelListener; 

import javax.ws.rs.ext.Provider; 

@Provider 
public class Listener implements AbstractResourceModelListener { 

    @Override 
    public void onLoaded(AbstractResourceModelContext modelContext) { 
     RedisManager.getInstance().connect(); 
    } 

} 

RedisManager.java

import redis.clients.jedis.Jedis; 
import redis.clients.jedis.JedisPool; 
import redis.clients.jedis.JedisPoolConfig; 

public class RedisManager { 
    private static final RedisManager instance = new RedisManager(); 
    private static JedisPool pool; 

    private RedisManager() { 
    } 

    public final static RedisManager getInstance() { 
     return instance; 
    } 

    public void connect() { 
     JedisPoolConfig poolConfig = new JedisPoolConfig(); 
     poolConfig.setMaxTotal(5000); 
     poolConfig.setTestOnBorrow(true); 
     poolConfig.setTestOnReturn(true); 
     poolConfig.setMaxIdle(50); 
     poolConfig.setMinIdle(1); 
     poolConfig.setTestWhileIdle(true); 
     poolConfig.setNumTestsPerEvictionRun(10); 
     poolConfig.setTimeBetweenEvictionRunsMillis(60000); 
     pool = new JedisPool(poolConfig, "redis_hostname"); 
    } 

    public void release() { 
     pool.destroy(); 
    } 

    public Jedis getJedis() { 
     return pool.getResource(); 
    } 

    public void returnJedis(Jedis jedis) { 
     pool.returnResourceObject(jedis); 
    } 
} 

APIServlet.java

@Path("/") 
public class APIService { 

    @GET 
    @Path("/lookup") 
    @Produces(MediaType.APPLICATION_JSON) 
    public Response getMsg(@QueryParam("email") String email, 
          @QueryParam("pretty") String pretty 
    ) throws JSONException { 
     Jedis jedis = RedisManager.getInstance().getJedis(); 
     if (jedis.sismember("inprocess", email)) { 
      RedisManager.getInstance().returnJedis(jedis); 
      return Response.status(202).entity("{\"status\":202, " + 
        "\"processing\":{\"type\":\"Lookup performed\", " + 
        "\"message\":\"We're performing analysis on this " + 
        "record. Result should be ready in a few minutes" + 
        ".\"}}").build(); 
     } 

     Person person = new Person(); 
     person.lookup(person); 
     ObjectMapper mapper = new ObjectMapper(); 
     String jsonString = mapper.writeValueAsString(person); 
     JSONObject jsonObj = new JSONObject(jsonString);   
     jsonObj.remove("objectID"); 
     jsonObj.remove("data_quality"); 
     jsonObj.put("status", 200); 

     RedisManager.getInstance().returnJedis(jedis); 

     if (!jsonObj.isNull("name") && !jsonObj.get("name").equals("")) { 
      if (hasPretty) { 
       return Response.status(200).entity(jsonObj.toString(4)) 
        .build(); 
      } 
      return Response.status(200).entity(jsonObj.toString()).build(); 
     } 

     return Response.status(404).entity("{\"status\":404, " + 
       "\"error\":{\"type\":\"Data Not Found.\", " + 
       "\"message\":\"We were not able to find data " + 
       "on this email.\"}}").build(); 
    } 
} 

Maven依賴

<dependency> 
     <groupId>com.sun.jersey</groupId> 
     <artifactId>jersey-server</artifactId> 
     <version>1.8</version> 
    </dependency> 
    <dependency> 
     <groupId>com.sun.jersey</groupId> 
     <artifactId>jersey-json</artifactId> 
     <version>1.8</version> 
    </dependency> 
    <dependency> 
     <groupId>redis.clients</groupId> 
     <artifactId>jedis</artifactId> 
     <version>2.7.2</version> 
     <type>jar</type> 
     <scope>compile</scope> 
    </dependency> 
    <dependency> 
     <groupId>commons-validator</groupId> 
     <artifactId>commons-validator</artifactId> 
     <version>1.2.0</version> 
    </dependency> 

監聽器創建RedisManager的一個實例,在整個應用程序中使用 - 這應該只發生一次,在啓動(注意:我不知道如何在關機時調用destroy,這很好理解)。在整個程序中,JedisPool的這個實例在Jersey路由中使用,如APIServlet.java中所示。在路由中,我得到一個JedisPool資源,然後在返回任何路由的任何部分之前,我返回資源。

發生什麼事是資源似乎沒有被返回(或我對池的理解是錯誤的)。一段時間後,與我的Redis實例的連接增長到maxTotal爲5,000,然後我開始發生錯誤「無法從池中獲取資源」,並且Tomcat死亡。

有幾件事情我已經注意到:

  1. 似乎有大量的是堅持各地建立HTTPS連接(在此一定不要100%,但它似乎是這樣)。

  2. 所有閒置的Redis客戶端(幾乎所有的)都有一個cmd的sismember。

:我不包括全APIService.java代碼,因爲我真的不能這樣做。我包含的片段確實給出了代碼的總體要點。我返回整個APIService.java代碼(返回404,返回429等),並且在每次返回之前,我確保將資源返回到池中。

最後,這裏的堆棧跟蹤:

10-Feb-2016 08:04:23.161 SEVERE [http-nio-443-exec-14] com.sun.jersey.spi.container.ContainerResponse.mapMappableContainerException The RuntimeException could not be mapped to a response, re-throwing to the HTTP container 
redis.clients.jedis.exceptions.JedisConnectionException: Could not get a resource from the pool 
     at redis.clients.util.Pool.getResource(Pool.java:50) 
     at redis.clients.jedis.JedisPool.getResource(JedisPool.java:86) 
     at co.talentiq.api.APIService.getMsg(APIService.java:63) 
     at sun.reflect.GeneratedMethodAccessor52.invoke(Unknown Source) 
     at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) 
     at java.lang.reflect.Method.invoke(Method.java:498) 
     at com.sun.jersey.spi.container.JavaMethodInvokerFactory$1.invoke(JavaMethodInvokerFactory.java:60) 
     at com.sun.jersey.server.impl.model.method.dispatch.AbstractResourceMethodDispatchProvider$ResponseOutInvoker._dispatch(AbstractResourceMethodDispatchProvider.java:205) 
     at com.sun.jersey.server.impl.model.method.dispatch.ResourceJavaMethodDispatcher.dispatch(ResourceJavaMethodDispatcher.java:75) 
     at com.sun.jersey.server.impl.uri.rules.HttpMethodRule.accept(HttpMethodRule.java:288) 
     at com.sun.jersey.server.impl.uri.rules.RightHandPathRule.accept(RightHandPathRule.java:147) 
     at com.sun.jersey.server.impl.uri.rules.ResourceClassRule.accept(ResourceClassRule.java:108) 
     at com.sun.jersey.server.impl.uri.rules.RightHandPathRule.accept(RightHandPathRule.java:147) 
     at com.sun.jersey.server.impl.uri.rules.RootResourceClassesRule.accept(RootResourceClassesRule.java:84) 
     at com.sun.jersey.server.impl.application.WebApplicationImpl._handleRequest(WebApplicationImpl.java:1469) 
     at com.sun.jersey.server.impl.application.WebApplicationImpl._handleRequest(WebApplicationImpl.java:1400) 
     at com.sun.jersey.server.impl.application.WebApplicationImpl.handleRequest(WebApplicationImpl.java:1349) 
     at com.sun.jersey.server.impl.application.WebApplicationImpl.handleRequest(WebApplicationImpl.java:1339) 
     at com.sun.jersey.spi.container.servlet.WebComponent.service(WebComponent.java:416) 
     at com.sun.jersey.spi.container.servlet.ServletContainer.service(ServletContainer.java:537) 
     at com.sun.jersey.spi.container.servlet.ServletContainer.service(ServletContainer.java:699) 
     at javax.servlet.http.HttpServlet.service(HttpServlet.java:729) 
     at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:291) 
     at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206) 
     at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:52) 
     at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:239) 
     at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:206) 
     at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:212) 
     at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:106) 
     at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:502) 
     at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:141) 
     at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:79) 
     at org.apache.catalina.valves.AbstractAccessLogValve.invoke(AbstractAccessLogValve.java:616) 
     at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:88) 
     at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:521) 
     at org.apache.coyote.http11.AbstractHttp11Processor.process(AbstractHttp11Processor.java:1096) 
     at org.apache.coyote.AbstractProtocol$AbstractConnectionHandler.process(AbstractProtocol.java:674) 
     at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1500) 
     at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.run(NioEndpoint.java:1456) 
     at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142) 
     at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617) 
     at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) 
     at java.lang.Thread.run(Thread.java:745) 

回答

2

第一:如果你已經有池初始化不創建一個新的:

public class RedisManager { 
... 
public void connect() { 
    if(pool != null) { 
     System.out.println("Already exists"); 
     return; 
    } 
    JedisPoolConfig poolConfig = new JedisPoolConfig(); 
    ... 

二...你有你的日誌異常來自getMsg方法?

public Response getMsg(@QueryParam("email") String email, 
         @QueryParam("pretty") String pretty 

你應該把所有的工作都包含在try-catch-finally中並且總是返回finally塊中的資源。注意:確保不要返回資源(在這種情況下是jedis)來池兩次。

Jedis jedis; 
try { 
    jedis = RedisManager.getInstance().getJedis(); 
    ... 
} finally { 
    if (jedis != null) { 
     RedisManager.getInstance().returnJedis(jedis); 
     jedis = null; 
    } 
} 

順便說一句:你可以創建一個小AutoCloseable來包裹你的jedis獲取/返回代碼和使用Java嘗試與資源 - https://docs.oracle.com/javase/tutorial/essential/exceptions/tryResourceClose.html

代碼段使用try-與資源

public void release() { 
    pool.destroy(); 
} 
public static class JedisWrapper implements AutoCloseable { 
    private final JedisPoolConfig pool; 
    private final Jedis jedis; 
    public JedisWrapper(JedisPoolConfig pool, Jedis jedis) { 
     this.pool = pool; 
     this.jedis = jedis; 
    } 
    public Jedis get() { 
     return jedis; 
    } 
    @Override 
    public void close() { 
     pool.returnResourceObject(jedis); 
    } 
} 
public JedisWrapper getJedis() { 
    return new JedisWrapper(pool, pool.getResource()); 
} 
// you can delete this method 
public void returnJedis(Jedis jedis) { 
    pool.returnResourceObject(jedis); 
} 

後來在使用的地方

public Response getMsg(@QueryParam("email") String email, 
         @QueryParam("pretty") String pretty 
) throws JSONException { 
    try(JedisWrapper jw = ...) { 
     Jedis jedis = jw.get(); 
     ... 
    } 
+0

是否有區別pool.return ResourceOjbect()和pool.close()?從我一直在讀的內容來看,pool.close()是做事情的正確方法,但是從我對池的理解中,似乎你只想返回資源,以便它可以被重用。我目前正在實施您的建議,並將很快進行測試。感謝您的反饋! –

+0

@FranzKafka pool.returnObject - 將返回單個採集對象,pool.close將關閉該池以及池中註冊的所有對象。我更新了一個示例如何將代碼轉換爲autoclose方法的答案。使用記事本在3分鐘內創建示例。我認爲你可以更好地來。 –