使用sdn查询时,当数据量较大并且需要做全库扫描时,很容易出现超时问题。抛出如下异常:
Exception in thread "main" com.sun.jersey.api.client.ClientHandlerException: java.net.SocketTimeoutException: Read timed out
at com.sun.jersey.client.urlconnection.URLConnectionClientHandler.handle(URLConnectionClientHandler.java:149)
at org.neo4j.rest.graphdb.UserAgent$1.handle(UserAgent.java:68)
at com.sun.jersey.api.client.filter.HTTPBasicAuthFilter.handle(HTTPBasicAuthFilter.java:81)
at com.sun.jersey.api.client.Client.handle(Client.java:648)
at com.sun.jersey.api.client.WebResource.handle(WebResource.java:670)
at com.sun.jersey.api.client.WebResource.access$200(WebResource.java:74)
at com.sun.jersey.api.client.WebResource$Builder.post(WebResource.java:553)
at org.neo4j.rest.graphdb.ExecutingRestRequest.post(ExecutingRestRequest.java:145)
at org.neo4j.rest.graphdb.RestAPIImpl.query(RestAPIImpl.java:486)
于是考虑加入超时时间,形式如:
System.setProperty("org.neo4j.rest.read_timeout", "80");
但是此时,查询sql调用的形式是,Repository+注解,该System的属性设置对于Spring托管的client并没有生效,此时查询仍然会出现超时问题,接下来会对该问题从源码上做分析。如果此时设置超时时间后,新new出一个client,这是超时时间设置就生效了,具体代码如:
System.setProperty("org.neo4j.rest.read_timeout", "80");
RestAPI restAPIFacade1 = buildClient(address1, userName1, password1);
CypherResult cr = restAPIFacade1.query("xxx", new HashMap<String, Object>());
System.out.println("cost time: " + (System.currentTimeMillis() - ss));
System.out.println(cr.asMap().get("data"));
为何这样设置就会生效,我们可以跟进看一下源码:
RestAPI restAPIFacade1 = buildClient(address1, userName1, password1) 其实调用的是:
restAPIFacade = new RestAPIImpl(address, userName, password);
接下来:
public RestAPIImpl(String uri, String user, String password) {
this.entityRefetchTimeInMillis = TimeUnit.SECONDS.toMillis(1000L);
this.entityCache = new RestEntityCache(this);
this.restEntityExtractor = new RestEntityExtractor(this);
this.restRequest = this.createRestRequest(uri, user, password);
this.restIndexAPI = new RestAPIIndexImpl(this);
}
继续到 this.createRestRequest(uri, user, password):
protected RestRequest createRestRequest(String uri, String user, String password) {
return new ExecutingRestRequest(uri, user, password);
}
继续:
public ExecutingRestRequest(String baseUri, String username, String password) {
this.userAgent = new UserAgent();
this.baseUri = this.uriWithoutSlash(baseUri);
this.client = this.createClient();
this.addAuthFilter(username, password);
}
继续createClient内部为:
protected Client createClient() {
Client client = Client.create();
client.setConnectTimeout(Integer.valueOf(Config.getConnectTimeout()));
client.setReadTimeout(Integer.valueOf(Config.getReadTimeout()));
client.setChunkedEncodingSize(Integer.valueOf(8192));
this.userAgent.install(client);
if(Config.useLoggingFilter()) {
client.addFilter(new LoggingFilter());
}
return client;
}
我们注意到,此时client在构造过程中,会有 设置timeout的步骤,继续查看Config.getReadTimeout(),整个Config类不长,贴上全部代码:
package org.neo4j.rest.graphdb.util;
import java.util.concurrent.TimeUnit;
public class Config {
public static final String CONFIG_PREFIX = "org.neo4j.rest.";
public static final String CONFIG_STREAM = "org.neo4j.rest.stream";
public static final String CONFIG_BATCH_TRANSACTION = "org.neo4j.rest.batch_transaction";
public static final String CONFIG_LOG_REQUESTS = "org.neo4j.rest.logging_filter";
public static final String WRITE_THREADS = "write_threads";
public Config() {
}
public static int getConnectTimeout() {
return getTimeout("connect_timeout", 30);
}
public static int getReadTimeout() {
return getTimeout("read_timeout", 30);
}
public static boolean streamingIsEnabled() {
return Boolean.parseBoolean(System.getProperty("org.neo4j.rest.stream", "true"));
}
public static boolean useBatchTransactions() {
return System.getProperty("org.neo4j.rest.batch_transaction", "false").equalsIgnoreCase("true");
}
public static boolean useLoggingFilter() {
return System.getProperty("org.neo4j.rest.logging_filter", "false").equalsIgnoreCase("true");
}
private static int getTimeout(String param, int defaultValue) {
return (int)TimeUnit.SECONDS.toMillis((long)Integer.parseInt(System.getProperty("org.neo4j.rest." + param, "" + defaultValue)));
}
public static int getWriterThreads() {
return Integer.parseInt(System.getProperty("org.neo4j.rest.write_threads", "10"));
}
}
很简单,在这里我们看到client生成时,超时时间设置的整个过程,在未设置系统参数时,超时时间取的缺省值30s(从代码中也可观察到为什么我们设置运行环境变量的key为 org.neo4j.rest.read_timeout)。那么接下来我们如何设置超时时间呢?这个也很简单,jvm可选参数中做如下设置:
-Dorg.neo4j.rest.read_timeout=150
通常该参数在idea或eclipse中的VM options中。
在有些情况下,我们可能需要直接在tomcat中设置,比如在beta或者生产环境的服务器上。我们可以tomcat的启动参数脚本中设置,通常在tomcat/bin下的catalina.cmd,或catalina.sh,以及startenv.sh等脚本中设置即可。
如果有多台服务器,并且可能部署在虚拟容器上,此时如果生成环境时,就需要额外在tomcat上做上述设置,也是相当的繁琐的。我们上面遇到的问题是,通过spring容器生成的neo4j client 超时时间设置不生效。其实问题在于spring生成实例时,该系统属性并未设置。所以,可以借助spring提供的工具来完成环境参数的设置。
在springContext.xml中做如下配置:
<bean class="org.springframework.beans.factory.config.MethodInvokingFactoryBean">
<property name="targetClass" value="java.lang.System" />
<property name="targetMethod" value="setProperty" />
<property name="arguments">
<list>
<value>org.neo4j.rest.read_timeout</value>
<value>${neo4j.read.timeout}</value>
</list>
</property>
</bean>
注意,根据spring bean的生成顺序,该bean必须在添加在neo4j client 的bean 配置之前方能生效。该方法是较为优雅的解决方法了。
另外关于org.springframework.beans.factory.config.MethodInvokingFactoryBean 的使用,可以另外再做整理和总结了。