Concurrency 为什么可以';t在建立httpSession后,Wildfly服务器处理并发REST请求
我遇到了一个我正试图理解的难题。我已经实现了一个在Wildfly服务器中运行的非常简单的REST服务示例。我有一个独立的多线程Java测试程序,它使用ApacheHTTP客户端库访问服务。客户端程序有两个独立的线程同时访问REST服务。当在服务器上运行测试程序而不建立httpSession时,服务器会并发处理请求(如服务器日志中所示)。如果线程是在建立httpSession之后运行的,则服务器将以顺序方式处理请求(如第二个服务器日志所示)。请注意,两个线程共享一个会话 这是故意的吗?我错过了什么?提前谢谢 以下是我的环境的基本要素:Concurrency 为什么可以';t在建立httpSession后,Wildfly服务器处理并发REST请求,concurrency,wildfly,resteasy,httpsession,undertow,Concurrency,Wildfly,Resteasy,Httpsession,Undertow,我遇到了一个我正试图理解的难题。我已经实现了一个在Wildfly服务器中运行的非常简单的REST服务示例。我有一个独立的多线程Java测试程序,它使用ApacheHTTP客户端库访问服务。客户端程序有两个独立的线程同时访问REST服务。当在服务器上运行测试程序而不建立httpSession时,服务器会并发处理请求(如服务器日志中所示)。如果线程是在建立httpSession之后运行的,则服务器将以顺序方式处理请求(如第二个服务器日志所示)。请注意,两个线程共享一个会话 这是故意的吗?我错过了什么
- Wildfly服务器信息:Wildfly-17.0.0.Final在Windows 10上运行,使用 standalone-ha.xml中指定的独立、高可用性配置
- 客户端信息:Apache http客户端4.5.5(托管版本)
以下是代表这两种情况的服务器日志:
Server log when no http session is established.
Requests run concurrently when there is no session.
13:39:13,384 INFO (default task-5) Getting widget 3
13:39:13,384 INFO (default task-6) Getting widget 4
13:39:13,384 INFO (default task-6) DELAYING widget 4
13:39:13,384 INFO (default task-5) Returning widget 3
13:39:13,404 INFO (default task-6) DELAY COMPLETE widget 4
13:39:13,404 INFO (default task-6) Returning widget 4
13:39:13,408 INFO (default task-6) Getting widget 3
13:39:13,408 INFO (default task-6) Returning widget 3
13:39:13,409 INFO (default task-5) Getting widget 4
13:39:13,409 INFO (default task-5) DELAYING widget 4
13:39:13,413 INFO (default task-6) Getting widget 3
13:39:13,413 INFO (default task-6) Returning widget 3
13:39:13,419 INFO (default task-6) Getting widget 3
13:39:13,419 INFO (default task-6) Returning widget 3
13:39:13,425 INFO (default task-6) Getting widget 3
13:39:13,425 INFO (default task-6) Returning widget 3
13:39:13,430 INFO (default task-6) Getting widget 3
13:39:13,430 INFO (default task-6) Returning widget 3
13:39:13,436 INFO (default task-5) DELAY COMPLETE widget 4
13:39:13,436 INFO (default task-5) Returning widget 4
13:39:13,436 INFO (default task-6) Getting widget 3
13:39:13,436 INFO (default task-6) Returning widget 3
13:39:13,441 INFO (default task-6) Getting widget 4
13:39:13,441 INFO (default task-5) Getting widget 3
13:39:13,442 INFO (default task-6) DELAYING widget 4
13:39:13,442 INFO (default task-5) Returning widget 3
13:39:13,447 INFO (default task-5) Getting widget 3
13:39:13,447 INFO (default task-5) Returning widget 3
...
这是REST服务的实现
@Path("concurrencyTest")
@Stateless
public class ConcurrencyTestWs extends BaseWs {
private static final Logger log = LoggerFactory
.getLogger(ConcurrencyTestWs.class);
@SuppressWarnings("static-method")
@GET
@Path("/widget")
public Response getWidget(@Context HttpServletRequest request,
@QueryParam(value = "widgetId") Long widgetId) {
try {
log.info(String.format("Getting widget %d", widgetId));
if ((widgetId % 2) == 0) {
log.info(String.format("DELAYING widget %d", widgetId));
try {
Thread.sleep(20);
} catch (Exception e) {
System.err.println(e.getMessage());
}
log.info(String.format("DELAY COMPLETE widget %d", widgetId));
}
log.info(String.format("Returning widget %d", widgetId));
return Response.ok(String.format("Widget %d retrieved", widgetId))
.build();
} catch (Exception e) {
return RestUtilities.getInternalError(request, "Widget", widgetId, e);
}
}
@SuppressWarnings("static-method")
@POST
@Path("/establishSession")
public Response establishSession(@Context HttpServletRequest request) {
try {
HttpSession session = request.getSession(true);
log.info(String.format("Session established %s", session.getId()));
Response response = Response.ok().build();
return response;
} catch (Exception e) {
return RestUtilities.getInternalError(request, e);
}
}
@SuppressWarnings("static-method")
@POST
@Path("/terminateSession")
public Response terminateSession(@Context HttpServletRequest request) {
try {
HttpSession session = request.getSession(false);
log.info(String.format("Terminating session %s",
(session == null) ? "null" : session.getId()));
Response response = Response.ok().build();
return response;
} catch (Exception e) {
return RestUtilities.getInternalError(request, e);
}
}
}
这是客户端源代码
public class WidgetFetchTest {
private final static String restServiceUrl = "http://dtp002.novodynamics.com:8081/TEST/REST/v1.0/concurrencyTest";
private final CloseableHttpClient httpClient;
private final CookieStore cookieStore;
public WidgetFetchTest() {
this.cookieStore = new BasicCookieStore();
RequestConfig requestConfig = RequestConfig.custom()
.setConnectTimeout(60000).setConnectionRequestTimeout(60000)
.setSocketTimeout(60000).setCookieSpec(CookieSpecs.STANDARD).build();
@SuppressWarnings("resource")
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager();
connectionManager.setMaxTotal(100);
connectionManager.setDefaultMaxPerRoute(40);
this.httpClient = HttpClients.custom()
.setDefaultRequestConfig(requestConfig)
.setDefaultCookieStore(this.cookieStore)
.setConnectionManager(connectionManager).build();
}
public void run() {
establishSession(this.httpClient);
DocFetchRunnable r1 = new DocFetchRunnable("4", this.httpClient);
DocFetchRunnable r2 = new DocFetchRunnable("3", this.httpClient);
ExecutorService exec = Executors.newFixedThreadPool(2);
exec.execute(r1);
exec.execute(r2);
try {
Thread.sleep(60000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
protected static void establishSession(CloseableHttpClient client) {
try {
URIBuilder ub = new URIBuilder(
String.format("%s%s", restServiceUrl, "/establishSession"));
String url = ub.toString();
HttpContext ctx = HttpClientContext.create();
try (CloseableHttpResponse response = client.execute(new HttpPost(url),
ctx)) {
// intentionally empty.
} catch (Exception e) {
e.printStackTrace();
}
} catch (URISyntaxException e) {
e.printStackTrace();
}
}
protected static void getWidget(CloseableHttpClient client, String widgetId) {
try {
URIBuilder ub = new URIBuilder(
String.format("%s%s", restServiceUrl, "/widget"));
ub.addParameter("widgetId", widgetId);
String url = ub.toString();
HttpContext ctx = HttpClientContext.create();
try (CloseableHttpResponse response = client.execute(new HttpGet(url),
ctx)) {
// intentionally empty.
} catch (Exception e) {
e.printStackTrace();
}
} catch (URISyntaxException e) {
e.printStackTrace();
}
}
private class DocFetchRunnable implements Runnable {
private String widgetId;
CloseableHttpClient client;
public DocFetchRunnable(String widgetId, CloseableHttpClient client) {
this.widgetId = widgetId;
this.client = client;
}
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println(
String.format("Fetching widget %s - %d", this.widgetId, i));
System.out.flush();
getWidget(this.client, this.widgetId);
System.out.println(
String.format("Widget fetched %s - %d", this.widgetId, i));
System.out.flush();
}
}
}
public static void main(String[] args) {
WidgetFetchTest dft = new WidgetFetchTest();
dft.run();
System.exit(0);
}
}
公共类WidgetFetchTest{
私有最终静态字符串restServiceUrl=”http://dtp002.novodynamics.com:8081/TEST/REST/v1.0/concurrencyTest";
私有最终可关闭httpClient httpClient;
私人最终CookieStore CookieStore;
公共WidgetFetchTest(){
this.cookieStore=new BasicCookieStore();
RequestConfig RequestConfig=RequestConfig.custom()
.setConnectTimeout(60000).setConnectionRequestTimeout(60000)
.setSocketTimeout(60000).setCookiesSpec(CookiesSpec.STANDARD).build();
@抑制警告(“资源”)
PoolighttpClientConnectionManager connectionManager=新的PoolighttpClientConnectionManager();
connectionManager.setMaxTotal(100);
connectionManager.setDefaultMaxPerRoute(40);
this.httpClient=HttpClients.custom()
.setDefaultRequestConfig(requestConfig)
.setDefaultCookieStore(this.cookieStore)
.setConnectionManager(connectionManager.build();
}
公开募捐{
建立会话(this.httpClient);
DocFetchRunnable r1=新的DocFetchRunnable(“4”,this.httpClient);
DocFetchRunnable r2=新的DocFetchRunnable(“3”,this.httpClient);
ExecutorService exec=Executors.newFixedThreadPool(2);
exec.execute(r1);
exec.execute(r2);
试一试{
睡眠(60000);
}捕捉(中断异常e){
e、 printStackTrace();
}
}
受保护的静态会话(CloseableHttpClient客户端){
试一试{
URIBuilder ub=新的URIBuilder(
格式(“%s%s”,restServiceUrl,“/establishSession”);
字符串url=ub.toString();
HttpContext ctx=HttpClientContext.create();
try(CloseableHttpResponse response=client.execute)(新的HttpPost(url),
(ctx){
//故意空的。
}捕获(例外e){
e、 printStackTrace();
}
}捕获(URISyntaxException e){
e、 printStackTrace();
}
}
受保护的静态void getWidget(CloseableHttpClient客户端,字符串widgetId){
试一试{
URIBuilder ub=新的URIBuilder(
格式(“%s%s”,restServiceUrl,“/widget”);
ub.addParameter(“widgetId”,widgetId);
字符串url=ub.toString();
HttpContext ctx=HttpClientContext.create();
try(CloseableHttpResponse response=client.execute)(新的HttpGet(url),
(ctx){
//故意空的。
}捕获(例外e){
e、 printStackTrace();
}
}捕获(URISyntaxException e){
e、 printStackTrace();
}
}
私有类DocFetchRunnable实现Runnable{
私有字符串widgetId;
可关闭的httpclient;
公共DocFetchRunnable(字符串widgetId,CloseableHttpClient客户端){
this.widgetId=widgetId;
this.client=client;
}
@凌驾
公开募捐{
对于(int i=0;i<50;i++){
System.out.println(
format(“获取小部件%s-%d”,this.widgetId,i));
System.out.flush();
getWidget(this.client,this.widgetId);
System.out.println(
format(“小部件获取了%s-%d”,this.widgetId,i));
System.out.flush();
}
}
}
公共静态void main(字符串[]args){
WidgetFetchTest dft=新的WidgetFetchTest();
dft.run();
系统出口(0);
}
}
在JBossDeveloper论坛上发布此问题后,提供了以下答案
请参阅文档中有关会话并发性的部分:
通过将可重复读取与关联缓存配置上提交的读取之间的隔离放宽,可以解决此问题
public class WidgetFetchTest {
private final static String restServiceUrl = "http://dtp002.novodynamics.com:8081/TEST/REST/v1.0/concurrencyTest";
private final CloseableHttpClient httpClient;
private final CookieStore cookieStore;
public WidgetFetchTest() {
this.cookieStore = new BasicCookieStore();
RequestConfig requestConfig = RequestConfig.custom()
.setConnectTimeout(60000).setConnectionRequestTimeout(60000)
.setSocketTimeout(60000).setCookieSpec(CookieSpecs.STANDARD).build();
@SuppressWarnings("resource")
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager();
connectionManager.setMaxTotal(100);
connectionManager.setDefaultMaxPerRoute(40);
this.httpClient = HttpClients.custom()
.setDefaultRequestConfig(requestConfig)
.setDefaultCookieStore(this.cookieStore)
.setConnectionManager(connectionManager).build();
}
public void run() {
establishSession(this.httpClient);
DocFetchRunnable r1 = new DocFetchRunnable("4", this.httpClient);
DocFetchRunnable r2 = new DocFetchRunnable("3", this.httpClient);
ExecutorService exec = Executors.newFixedThreadPool(2);
exec.execute(r1);
exec.execute(r2);
try {
Thread.sleep(60000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
protected static void establishSession(CloseableHttpClient client) {
try {
URIBuilder ub = new URIBuilder(
String.format("%s%s", restServiceUrl, "/establishSession"));
String url = ub.toString();
HttpContext ctx = HttpClientContext.create();
try (CloseableHttpResponse response = client.execute(new HttpPost(url),
ctx)) {
// intentionally empty.
} catch (Exception e) {
e.printStackTrace();
}
} catch (URISyntaxException e) {
e.printStackTrace();
}
}
protected static void getWidget(CloseableHttpClient client, String widgetId) {
try {
URIBuilder ub = new URIBuilder(
String.format("%s%s", restServiceUrl, "/widget"));
ub.addParameter("widgetId", widgetId);
String url = ub.toString();
HttpContext ctx = HttpClientContext.create();
try (CloseableHttpResponse response = client.execute(new HttpGet(url),
ctx)) {
// intentionally empty.
} catch (Exception e) {
e.printStackTrace();
}
} catch (URISyntaxException e) {
e.printStackTrace();
}
}
private class DocFetchRunnable implements Runnable {
private String widgetId;
CloseableHttpClient client;
public DocFetchRunnable(String widgetId, CloseableHttpClient client) {
this.widgetId = widgetId;
this.client = client;
}
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println(
String.format("Fetching widget %s - %d", this.widgetId, i));
System.out.flush();
getWidget(this.client, this.widgetId);
System.out.println(
String.format("Widget fetched %s - %d", this.widgetId, i));
System.out.flush();
}
}
}
public static void main(String[] args) {
WidgetFetchTest dft = new WidgetFetchTest();
dft.run();
System.exit(0);
}
}