配置
Web服务器:Nginx
应用服务器:Tomcat,默认配置为200个请求服务线程
我服务器的预计响应时间:~30秒(有很多第三方依赖项)
情景
应用程序每10秒钟需要生成一个令牌供其使用。令牌生成的预期时间约为5秒,但由于通过网络联系第三方系统,这显然不一致,最长可达10秒。
在令牌生成过程中,每秒几乎80%的传入请求都需要等待。
我相信应该发生什么
由于等待令牌生成的请求必须等待#34; long"时间,在等待令牌生成过程完成时,没有理由将这些请求服务重新用于服务其他传入请求
基本上,如果我的20%继续服务是有意义的。如果等待的线程没有被用于其他请求,那么将达到tomcat请求服务限制,服务器将基本上窒息,而不是任何开发人员想要的东西。
我做了什么
最初我期望切换到tomcat NIO连接器可以完成这项工作。但在看了this比较之后,我真的没有希望。尽管如此,我试图迫使请求等待10秒钟,但它没有用
现在我正在考虑我需要的线路,等待,搁置请求,等待并需要通知tomcat该线程可以自由重用。类似地,当请求准备好向前移动时,我将需要tomcat从其线程池中给我一个线程。但我对如何做到这一点或者即使可能这样做也是瞎了。
任何指导或帮助?
答案 0 :(得分:6)
您需要一个异步servlet,但您还需要对外部令牌生成器进行异步HTTP调用。如果您仍然在每个令牌请求的某处创建一个线程,那么通过将请求从servlet传递到带有线程池的ExecutorService,您将无法获得任何好处。您必须从HTTP请求中分离线程,以便一个线程可以处理多个HTTP请求。这可以通过Apache Asynch HttpClient或Async Http Client等异步HTTP客户端来实现。
首先,你必须创建一个像这样的异步servlet
edges = [(u,v,d) for u,v,d in G.edges(data = True) if d['agent id'] = x]
H = nx.multiDiGraph()
H.add_edges_from(edges)
此servlet使用Apache Asynch HttpClient执行异步HTTP调用。请注意,您可能希望配置每个路由的最大连接数,因为根据RFC 2616规范,HttpAsyncClient默认情况下最多只允许两个并发连接到同一主机。您还可以配置许多其他选项,如HttpAsyncClient configuration所示。 HttpAsyncClient的创建成本很高,因此您不希望在每次GET操作上创建它的实例。
一个侦听器挂钩到AsyncContext,此侦听器仅在上面的示例中用于处理超时。
public class ProxyService extends HttpServlet {
private CloseableHttpAsyncClient httpClient;
@Override
public void init() throws ServletException {
httpClient = HttpAsyncClients.custom().
setMaxConnTotal(Integer.parseInt(getInitParameter("maxtotalconnections"))).
setMaxConnPerRoute(Integer.parseInt(getInitParameter("maxconnectionsperroute"))).
build();
httpClient.start();
}
@Override
public void destroy() {
try {
httpClient.close();
} catch (IOException e) { }
}
@Override
public void doGet(HttpServletRequest request, HttpServletResponse response) {
AsyncContext asyncCtx = request.startAsync(request, response);
asyncCtx.setTimeout(ExternalServiceMock.TIMEOUT_SECONDS * ExternalServiceMock.K);
ResponseListener listener = new ResponseListener();
asyncCtx.addListener(listener);
Future<String> result = httpClient.execute(HttpAsyncMethods.createGet(getInitParameter("serviceurl")), new ResponseConsumer(asyncCtx), null);
}
}
然后您需要HTTP客户端的使用者。当HttpClient在内部执行public class ResponseListener implements AsyncListener {
@Override
public void onStartAsync(AsyncEvent event) throws IOException {
}
@Override
public void onComplete(AsyncEvent event) throws IOException {
}
@Override
public void onError(AsyncEvent event) throws IOException {
event.getAsyncContext().getResponse().getWriter().print("error:");
}
@Override
public void onTimeout(AsyncEvent event) throws IOException {
event.getAsyncContext().getResponse().getWriter().print("timeout:");
}
}
时,此消费者通过调用complete()
来通知AsyncContext,作为将buildResult()
返回给调用者Future<String>
servlet的步骤。
ProxyService
ProxyService servlet的web.xml配置可能类似于
public class ResponseConsumer extends AsyncCharConsumer<String> {
private int responseCode;
private StringBuilder responseBuffer;
private AsyncContext asyncCtx;
public ResponseConsumer(AsyncContext asyncCtx) {
this.responseBuffer = new StringBuilder();
this.asyncCtx = asyncCtx;
}
@Override
protected void releaseResources() { }
@Override
protected String buildResult(final HttpContext context) {
try {
PrintWriter responseWriter = asyncCtx.getResponse().getWriter();
switch (responseCode) {
case javax.servlet.http.HttpServletResponse.SC_OK:
responseWriter.print("success:" + responseBuffer.toString());
break;
default:
responseWriter.print("error:" + responseBuffer.toString());
}
} catch (IOException e) { }
asyncCtx.complete();
return responseBuffer.toString();
}
@Override
protected void onCharReceived(CharBuffer buffer, IOControl ioc) throws IOException {
while (buffer.hasRemaining())
responseBuffer.append(buffer.get());
}
@Override
protected void onResponseReceived(HttpResponse response) throws HttpException, IOException {
responseCode = response.getStatusLine().getStatusCode();
}
}
令牌生成器的模拟servlet延迟时间为秒,可能是:
<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
id="WebApp_ID" version="3.0" metadata-complete="true">
<display-name>asyncservlet-demo</display-name>
<servlet>
<servlet-name>External Service Mock</servlet-name>
<servlet-class>ExternalServiceMock</servlet-class>
<load-on-startup>1</load-on-startup>
</servlet>
<servlet>
<servlet-name>Proxy Service</servlet-name>
<servlet-class>ProxyService</servlet-class>
<load-on-startup>1</load-on-startup>
<async-supported>true</async-supported>
<init-param>
<param-name>maxtotalconnections</param-name>
<param-value>200</param-value>
</init-param>
<init-param>
<param-name>maxconnectionsperroute</param-name>
<param-value>4</param-value>
</init-param>
<init-param>
<param-name>serviceurl</param-name>
<param-value>http://127.0.0.1:8080/asyncservlet/externalservicemock</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>External Service Mock</servlet-name>
<url-pattern>/externalservicemock</url-pattern>
</servlet-mapping>
<servlet-mapping>
<servlet-name>Proxy Service</servlet-name>
<url-pattern>/proxyservice</url-pattern>
</servlet-mapping>
</web-app>
答案 1 :(得分:1)
这个问题基本上是存在这么多“反应性”库和工具包的原因。
通过调整或交换tomcat连接器可以解决这个问题
您基本上需要删除所有阻塞IO调用,将其替换为非阻塞IO可能需要重写应用程序的大部分内容
您的HTTP服务器需要是非阻塞的,您需要使用非阻塞API到服务器(如servlet 3.1),并且您对第三方API的调用需要非阻塞。
像Vert.x和RxJava这样的库提供了工具来帮助完成所有这些工作。
否则唯一的另一种选择是只增加线程池的大小,操作系统已经负责调度CPU,以便非活动线程不会导致太多性能损失,但总会有更多与被动方法相比的开销。
如果不了解您的应用程序,很难就特定方法提供建议。