Статьи

Асинхронное веб-приложение Restful

В этом блоге описывается пример веб-приложения Jetty 7, в котором используется асинхронный HTTP-клиент Jetty и предлагаемый API приостановленных сервлетов 3.0 для вызова спокойной веб-службы eBay. Этот метод объединяет асинхронный HTTP-клиент Jetty с возможностью серверов Jetty приостанавливать обработку сервлета, поэтому потоки не удерживаются в ожидании ответов покоя. Таким образом, потоки могут обрабатывать гораздо больше запросов, и веб-приложения, использующие эту технику, должны повысить производительность как минимум в десять раз

[Img_assist | NID = 6367 | название = | убывание = | ссылка = нет | ALIGN = не определено | ширина = 620 | Высота = 479]

На приведенном выше снимке экрана показаны четыре фрейма, вызывающие либо синхронный, либо асинхронный демонстрационный сервлет со следующими результатами:

Синхронный вызов, одно ключевое слово

Запрос на поиск аукционов Ebay с ключевым словом «стул» обрабатывается синхронной реализацией.
Вызов занимает 660 мс, и поток сервлета блокируется на все время. Сервер с 100 потоками в пуле сможет обрабатывать 151 запрос в секунду.
Асинхронный вызов, одно ключевое слово

Запрос на поиск аукционов Ebay с ключевым словом «стул» обрабатывается асинхронной реализацией.
Вызов занимает 669 мс, но запрос сервлета приостановлен, поэтому поток запроса удерживается только 2 мс. Сервер с 100 потоками в пуле сможет обрабатывать 5000 запросов в секунду (если не ограничен другими ограничениями)
Синхронный вызов, три ключевых слова

Запрос на поиск аукционов ebay с ключевыми словами «мышь», «пиво» ​​и «гномы» обрабатывается синхронной реализацией.
В ebay делается три последовательных вызова, каждый из которых занимает около 900 мс, общее время составляет 2706 мс, а поток сервлета блокируется на все время. Сервер с 100 потоками в пуле сможет обрабатывать только 40 запросов в секунду!
Асинхронный вызов, три ключевых слова

Запрос на поиск аукционов Ebay с ключевыми словами «мышь», «пиво» ​​и «гномы» обрабатывается асинхронной реализацией. Три вызова могут быть сделаны на ebay параллельно, каждый из которых занимает около 900 мс, общее время 906 мс, а запрос сервлета приостанавливается, поэтому поток запроса удерживается только 2 мс. Сервер с 100 потоками в пуле сможет обрабатывать 5000 запросов в секунду (если не ограничен другими ограничениями).

Из этих результатов видно, что асинхронная обработка повторных запросов может значительно улучшить пропускную способность, избегая истощения потоков.

Код для примера асинхронного сервлета доступен здесь и работает следующим образом:
сервлету передается запрос, который обнаруживается как первая отправка, поэтому запрос приостанавливается и список для накопления результатов добавляется в качестве атрибута запроса:

if (request.isInitial() || request.getAttribute(CLIENT_ATTR)==null)
{
String[] keywords=request.getParameter(ITEMS_PARAM).split(",");

final List<Map<String, String>> results =
Collections.synchronizedList(new ArrayList<Map<String, String>>());
final AtomicInteger count=new AtomicInteger(keywords.length);

request.suspend();
request.setAttribute(CLIENT_ATTR, results);

Запрос приостанавливается перед началом поиска, чтобы избежать гонок, если поиск каким-либо образом завершится до приостановки запроса.

После приостановки сервлет создает и отправляет асинхронный HTTP-обмен для каждого ключевого слова:

for (final String item:keywords)
{
ContentExchange exchange = new ContentExchange()
{
protected void onResponseComplete() throws IOException
{
// see step 4 below
}
};
exchange.setMethod("GET");
exchange.setURL("http://open.api.ebay.com/shopping?MaxEntries=5&appid=" +
_appid +
"&version=573&siteid=0&callname=FindItems&responseencoding=JSON&QueryKeywords=" +
URLEncoder.encode(item,"UTF-8"));
_client.send(exchange);
}

API для клиентских обменов Jetty Http был вдохновлен стилем обратного вызова javascript XHR.
Как только все асинхронные обмены HTTP отправлены, сервлет сохраняет некоторую информацию о времени для демонстрации и затем возвращается. Поскольку запрос приостановлен, ответ не сбрасывается в браузер, но поток возвращается в пул потоков, чтобы он мог обслуживать другие запросы:

request.setAttribute(START_ATTR, start);
request.setAttribute(DURATION_ATTR, new Long(System.currentTimeMillis() - start));
return;
}

Все остальные запросы обрабатываются параллельно серверами eBay, и когда каждый из них завершается, вызывается обратный вызов объекта обмена. Код (опущен выше, показан ниже) извлекает информацию об аукционе из ответа JSON и добавляет ее в список результатов. Затем число ожидаемых ответов уменьшается, и когда оно достигает 0, приостановленный запрос возобновляется:

protected void onResponseComplete() throws IOException
{
Map query = (Map) JSON.parse(this.getResponseContent());
Object[] auctions = (Object[]) query.get("Item");
if (auctions != null)
{
for (Object o : auctions)
results.add((Map) o);
}

if (count.decrementAndGet()<=0)
request.resume();
}

После возобновления запрос повторно отправляется сервлету. На этот раз запрос не является начальным и имеет результаты, поэтому результаты извлекаются из атрибута запроса, и для генерации ответа используется обычный код стиля сервлета:

List<Map<String, String>> results = (List<Map<String, String>>) request.getAttribute(CLIENT_ATTR);
response.setContentType("text/html");
PrintWriter out = response.getWriter();
out.println("<html><head><style type='text/css'>img:hover {height:75px}</style></head><body><small>");

for (Map<String, String> m : results)
{
out.print("<a href=\""+m.get("ViewItemURLForNaturalSearch")+"\">");
...

В этом примере показано, как асинхронный клиент Jetty можно легко объединить с приостановленными сервлетами Jetty-7 (или Continuations of Jetty-6) для создания очень масштабируемых веб-приложений. Jetty-7now содержит аналогичные примеры для асинхронных веб-сервисов SOX CXF и для вызова асинхронных релакс-сервисов из действий JSF, которые могут быть приостановлены.