Вопросы о Сервлете 3.1 Образец неблокирующего ввода-вывода
Ниже приведен код сервлета 3.1 Non Blocking IO demo:
UploadServlet:
@WebServlet(name = "UploadServlet", urlPatterns = {"/UploadServlet"}, asyncSupported=true)
public class UploadServlet extends HttpServlet {
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
AsyncContext context = request.startAsync();
// set up async listener
context.addListener(new AsyncListener() {
public void onComplete(AsyncEvent event) throws IOException {
event.getSuppliedResponse().getOutputStream().print("Complete");
}
public void onError(AsyncEvent event) {
System.out.println(event.getThrowable());
}
public void onStartAsync(AsyncEvent event) {
}
public void onTimeout(AsyncEvent event) {
System.out.println("my asyncListener.onTimeout");
}
});
ServletInputStream input = request.getInputStream();
ReadListener readListener = new ReadListenerImpl(input, response, context);
input.setReadListener(readListener);
}
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
}
}
RealListenerImpl:
public class ReadListenerImpl implements ReadListener{
private ServletInputStream input = null;
private HttpServletResponse res = null;
private AsyncContext ac = null;
private Queue queue = new LinkedBlockingQueue();
ReadListenerImpl(ServletInputStream in, HttpServletResponse r, AsyncContext c) {
input = in;
res = r;
ac = c;
}
public void onDataAvailable() throws IOException {
System.out.println("Data is available");
StringBuilder sb = new StringBuilder();
int len = -1;
byte b[] = new byte[1024];
while (input.isReady() && (len = input.read(b)) != -1) {
String data = new String(b, 0, len);
sb.append(data);
}
queue.add(sb.toString());
}
public void onAllDataRead() throws IOException {
System.out.println("Data is all read");
// now all data are read, set up a WriteListener to write
ServletOutputStream output = res.getOutputStream();
WriteListener writeListener = new WriteListenerImpl(output, queue, ac);
output.setWriteListener(writeListener);
}
public void onError(final Throwable t) {
ac.complete();
t.printStackTrace();
}
}
WriteListenerImpl:
public class WriteListenerImpl implements WriteListener{
private ServletOutputStream output = null;
private Queue queue = null;
private AsyncContext context = null;
WriteListenerImpl(ServletOutputStream sos, Queue q, AsyncContext c) {
output = sos;
queue = q;
context = c;
}
public void onWritePossible() throws IOException {
while (queue.peek() != null && output.isReady()) {
String data = (String) queue.poll();
output.print(data);
}
if (queue.peek() == null) {
context.complete();
}
}
public void onError(final Throwable t) {
context.complete();
t.printStackTrace();
}
}
вышеуказанные коды работают нормально, я хочу знать, каковы различия с блокировкой IO сервлета? и я хочу знать, как работает код выше.
1 ответ
Чтение входных данных:
В сценарии блокировки при чтении данных из входного потока каждое чтение блокируется, пока данные не станут доступны. Это может занять много времени для удаленного клиента, отправляющего большие данные, что означает, что поток удерживается в течение длительного времени.
Например, рассмотрим входящие данные, получаемые в течение 2 минут с регулярными интервалами в 13 кусках. При блокировании чтения вы читаете первый блок, удерживаете поток ~10 секунд, читаете следующий блок, удерживаете поток ~10 секунд и т. Д. В этом случае поток может потратить меньше секунды, фактически обрабатывая данные и почти 120 секунд блокируя жду данных. Затем, если у вас есть сервер с 10 потоками, вы можете видеть, что у вас будет пропускная способность 10 клиентов каждые 2 минуты.
В неблокирующем сценарии readListener читает данные, в то время как isReady() возвращает true (он должен проверять isReady() перед каждым вызовом для чтения данных), но когда isReady() возвращает false, readListener возвращает и поток освобождается. Затем, когда поступает больше данных, вызывается onDataAvailable(), и readListener снова считывает данные, пока isReady не станет false().
В том же примере, на этот раз поток читает данные и возвращает их, он просыпается через 10 секунд, читает следующие данные и возвращается, просыпается через 10 секунд, читает данные и возвращает и т. Д. На этот раз, пока он все еще занял 2 минут, чтобы прочитать данные, которые были необходимы для этой цепочки, которые были активны менее секунды и были доступны для другой работы. Таким образом, хотя конкретный запрос все еще занимает 2 минуты, сервер с 10 потоками теперь может обрабатывать гораздо больше запросов каждые 2 минуты.
Отправка данных ответа:
Сценарий похож на отправку данных и полезен при отправке больших ответов. Например, отправка большого ответа в 13 чанках может занять 2 минуты для отправки в сценарии блокировки, поскольку клиенту требуется 10 секунд для подтверждения получения каждого чанка, и поток удерживается во время ожидания. Однако в неблокирующем сценарии поток удерживается только во время отправки данных, а не в ожидании возможности повторной отправки. Итак, опять же для конкретного клиента ответ не отправляется быстрее, но поток удерживается в течение доли времени, и пропускная способность сервера, который обрабатывает запрос, может значительно увеличиться.
Таким образом, примеры здесь придуманы, но использованы для иллюстрации сути. Ключевым моментом является то, что неблокирующий ввод / вывод не выполняет ни одного запроса быстрее, чем при блокирующем вводе / выводе, но увеличивает пропускную способность сервера, когда приложение может читать входные данные быстрее, чем клиент может отправлять их и / или отправлять данные ответа быстрее чем клиент может получить его.