предисловие
Я считаю, что благодаря моей предыдущей серии статей о Tomcat студенты, читающие мой блог, должны иметь более четкое представление о Tomcat. для разработки и того, как обрабатываются запросы, давайте поговорим об асинхронных сервлетах Tomcat в этом блоге, о том, как Tomcat реализует асинхронные сервлеты, и о сценариях использования асинхронных сервлетов.
Вручную создать асинхронный сервлет
Мы напрямую используем инфраструктуру SpringBoot для реализации сервлета, здесь показан только код сервлета:
@WebServlet(urlPatterns = "/async",asyncSupported = true)
@Slf4j
public class AsyncServlet extends HttpServlet {
ExecutorService executorService =Executors.newSingleThreadExecutor();
@Override
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
//开启异步,获取异步上下文
final AsyncContext ctx = req.startAsync();
// 提交线程池异步执行
executorService.execute(new Runnable() {
@Override
public void run() {
try {
log.info("async Service 准备执行了");
//模拟耗时任务
Thread.sleep(10000L);
ctx.getResponse().getWriter().print("async servlet");
log.info("async Service 执行了");
} catch (IOException e) {
e.printStackTrace();
} catch (InterruptedException e) {
e.printStackTrace();
}
//最后执行完成后完成回调。
ctx.complete();
}
});
}
Приведенный выше код реализует асинхронный сервлет, который реализуетdoGet
Метод обращает внимание на необходимость повторного запуска класса при использовании в SpringBoot@ServletComponentScan
Аннотация для сканирования сервлетов. Теперь, когда код написан, давайте взглянем на реальный эффект запуска.
После того, как мы отправили запрос, мы видим, что на странице есть ответ, и в то же время мы видим, что время запроса заняло 10,05 с, тогда наш сервлет можно считать работающим нормально. Некоторые студенты обязательно спросят, а не асинхронный ли это сервлет? Ваше время отклика не становится быстрее, какой в этом смысл? Да, наше время отклика не может быть ускорено, оно все еще зависит от нашей бизнес-логики, но после нашего асинхронного запроса сервлета, в зависимости от асинхронного выполнения бизнеса, мы можем вернуться немедленно, то есть поток Tomcat может быть переработан. немедленно. По умолчанию основной поток Tomcat10, максимальное количество потоков200, Мы можем вовремя перезапускать потоки, а это значит, что мы можем обрабатывать больше запросов и увеличивать нашу пропускную способность, что также является основной ролью асинхронного сервлета.
Внутреннее устройство асинхронных сервлетов
После того, как вы поняли роль асинхронного сервлета, давайте посмотрим, как Tomcat является первым асинхронным сервлетом. Фактически, приведенный выше код, основная основная логика состоит из двух частей,final AsyncContext ctx = req.startAsync()
иctx.complete()
Итак, давайте посмотрим, что именно они сделали?
public AsyncContext startAsync(ServletRequest request,
ServletResponse response) {
if (!isAsyncSupported()) {
IllegalStateException ise =
new IllegalStateException(sm.getString("request.asyncNotSupported"));
log.warn(sm.getString("coyoteRequest.noAsync",
StringUtils.join(getNonAsyncClassNames())), ise);
throw ise;
}
if (asyncContext == null) {
asyncContext = new AsyncContextImpl(this);
}
asyncContext.setStarted(getContext(), request, response,
request==getRequest() && response==getResponse().getResponse());
asyncContext.setTimeout(getConnector().getAsyncTimeout());
return asyncContext;
}
мы обнаруживаемreq.startAsync()
Он просто сохраняет асинхронный контекст и устанавливает некоторую базовую информацию, такую какTimeout
, кстати, тайм-аут по умолчанию здесь установлен30S, если ваша логика асинхронной обработки превышает30S, затем выполнитеctx.complete()
Я создам исключение IllegalStateException.
Посмотримctx.complete()
логика
public void complete() {
if (log.isDebugEnabled()) {
logDebug("complete ");
}
check();
request.getCoyoteRequest().action(ActionCode.ASYNC_COMPLETE, null);
}
//类:AbstractProcessor
public final void action(ActionCode actionCode, Object param) {
case ASYNC_COMPLETE: {
clearDispatches();
if (asyncStateMachine.asyncComplete()) {
processSocketEvent(SocketEvent.OPEN_READ, true);
}
break;
}
}
//类:AbstractProcessor
protected void processSocketEvent(SocketEvent event, boolean dispatch) {
SocketWrapperBase<?> socketWrapper = getSocketWrapper();
if (socketWrapper != null) {
socketWrapper.processSocket(event, dispatch);
}
}
//类:AbstractEndpoint
public boolean processSocket(SocketWrapperBase<S> socketWrapper,
SocketEvent event, boolean dispatch) {
//省略部分代码
SocketProcessorBase<S> sc = null;
if (processorCache != null) {
sc = processorCache.pop();
}
if (sc == null) {
sc = createSocketProcessor(socketWrapper, event);
} else {
sc.reset(socketWrapper, event);
}
Executor executor = getExecutor();
if (dispatch && executor != null) {
executor.execute(sc);
} else {
sc.run();
}
return true;
}
Итак, здесь в конечном итоге позвонятAbstractEndpoint
изprocessSocket
Метод, у студентов, которые читали мой предыдущий блог, должно сложиться впечатление,EndPoint
Он используется для приема и обработки запросов и будет передан следующемуProcessor
Для выполнения обработки протокола.
类:AbstractProcessorLight
public SocketState process(SocketWrapperBase<?> socketWrapper, SocketEvent status)
throws IOException {
//省略部分diam
SocketState state = SocketState.CLOSED;
Iterator<DispatchType> dispatches = null;
do {
if (dispatches != null) {
DispatchType nextDispatch = dispatches.next();
state = dispatch(nextDispatch.getSocketStatus());
} else if (status == SocketEvent.DISCONNECT) {
} else if (isAsync() || isUpgrade() || state == SocketState.ASYNC_END) {
state = dispatch(status);
if (state == SocketState.OPEN) {
state = service(socketWrapper);
}
} else if (status == SocketEvent.OPEN_WRITE) {
state = SocketState.LONG;
} else if (status == SocketEvent.OPEN_READ){
state = service(socketWrapper);
} else {
state = SocketState.CLOSED;
}
} while (state == SocketState.ASYNC_END ||
dispatches != null && state != SocketState.CLOSED);
return state;
}
Эта часть это точка,AbstractProcessorLight
будет основываться наSocketEvent
статус, чтобы определить, следует ли звонитьservice(socketWrapper)
, Этот метод в конечном итоге вызовет контейнер для завершения вызова бизнес-логики.Наш запрос вызывается после завершения выполнения, и он не должен входить в контейнер, иначе это будет бесконечный цикл.isAsync()
судить, войтиdispatch(status)
, который в конечном итоге вызоветCoyoteAdapter
изasyncDispatch
метод
public boolean asyncDispatch(org.apache.coyote.Request req, org.apache.coyote.Response res,
SocketEvent status) throws Exception {
//省略部分代码
Request request = (Request) req.getNote(ADAPTER_NOTES);
Response response = (Response) res.getNote(ADAPTER_NOTES);
boolean success = true;
AsyncContextImpl asyncConImpl = request.getAsyncContextInternal();
try {
if (!request.isAsync()) {
response.setSuspended(false);
}
if (status==SocketEvent.TIMEOUT) {
if (!asyncConImpl.timeout()) {
asyncConImpl.setErrorState(null, false);
}
} else if (status==SocketEvent.ERROR) {
}
if (!request.isAsyncDispatching() && request.isAsync()) {
WriteListener writeListener = res.getWriteListener();
ReadListener readListener = req.getReadListener();
if (writeListener != null && status == SocketEvent.OPEN_WRITE) {
ClassLoader oldCL = null;
try {
oldCL = request.getContext().bind(false, null);
res.onWritePossible();//这里执行浏览器响应,写入数据
if (request.isFinished() && req.sendAllDataReadEvent() &&
readListener != null) {
readListener.onAllDataRead();
}
} catch (Throwable t) {
} finally {
request.getContext().unbind(false, oldCL);
}
}
}
}
//这里判断异步正在进行,说明这不是一个完成方法的回调,是一个正常异步请求,继续调用容器。
if (request.isAsyncDispatching()) {
connector.getService().getContainer().getPipeline().getFirst().invoke(
request, response);
Throwable t = (Throwable) request.getAttribute(RequestDispatcher.ERROR_EXCEPTION);
if (t != null) {
asyncConImpl.setErrorState(t, true);
}
}
//注意,这里,如果超时或者出错,request.isAsync()会返回false,这里是为了尽快的输出错误给客户端。
if (!request.isAsync()) {
//这里也是输出逻辑
request.finishRequest();
response.finishResponse();
}
//销毁request和response
if (!success || !request.isAsync()) {
updateWrapperErrorCount(request, response);
request.recycle();
response.recycle();
}
}
return success;
}
Код вышеctx.complete()
Выполняется последний метод (разумеется, многие детали опущены), завершается вывод данных и, наконец, вывод в браузер.
Некоторые студенты здесь могут сказать: я знаю, что после асинхронного выполнения вызовctx.complete()
Он будет выведен в браузер, но откуда Tomcat знает, что нельзя возвращаться к клиенту после выполнения первого запроса doGet? Код ключа находится вCoyoteAdapter
серединаservice
Метод, часть кода выглядит следующим образом:
postParseSuccess = postParseRequest(req, request, res, response);
//省略部分代码
if (postParseSuccess) {
request.setAsyncSupported(
connector.getService().getContainer().getPipeline().isAsyncSupported());
connector.getService().getContainer().getPipeline().getFirst().invoke(
request, response);
}
if (request.isAsync()) {
async = true;
} else {
//输出数据到客户端
request.finishRequest();
response.finishResponse();
if (!async) {
updateWrapperErrorCount(request, response);
//销毁request和response
request.recycle();
response.recycle();
}
Эта часть кода вызывается послеServlet
, пройдетrequest.isAsync()
Чтобы определить, является ли это асинхронным запросом, если это асинхронный запрос, установите егоasync = true
. Если это неасинхронный запрос, выполните выходные данные для клиентской логики и одновременно уничтожьте их.request
иresponse
. Это завершает операцию отсутствия ответа клиенту после завершения запроса.
Почему аннотация Spring Boot @EnableAsync не является асинхронным сервлетом?
Поскольку я запросил много информации, когда собирался написать эту статью раньше, я обнаружил, что много информации для написания асинхронного программирования SpringBoot зависит от@EnableAsync
аннотации, а затем вController
Используйте несколько потоков для завершения бизнес-логики, окончательного суммирования результатов и завершения возвращаемого вывода. Вот пример статьи босса Nuggets»Руководство по асинхронному программированию SpringBoot, понятное новичкам", эта статья очень проста для понимания, очень хороша, с уровня бизнеса, это действительно асинхронное программирование, но есть проблема, помимо параллельной обработки бизнеса, для всего запроса, это не асинхронно, что то есть поток Tomcat не может быть немедленно освобожден, поэтому эффект асинхронного сервлета не может быть достигнут. Здесь я также написал демонстрацию, ссылаясь на вышеизложенное, давайте проверим, почему она не асинхронна.
@RestController
@Slf4j
public class TestController {
@Autowired
private TestService service;
@GetMapping("/hello")
public String test() {
try {
log.info("testAsynch Start");
CompletableFuture<String> test1 = service.test1();
CompletableFuture<String> test2 = service.test2();
CompletableFuture<String> test3 = service.test3();
CompletableFuture.allOf(test1, test2, test3);
log.info("test1=====" + test1.get());
log.info("test2=====" + test2.get());
log.info("test3=====" + test3.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
return "hello";
}
@Service
public class TestService {
@Async("asyncExecutor")
public CompletableFuture<String> test1() throws InterruptedException {
Thread.sleep(3000L);
return CompletableFuture.completedFuture("test1");
}
@Async("asyncExecutor")
public CompletableFuture<String> test2() throws InterruptedException {
Thread.sleep(3000L);
return CompletableFuture.completedFuture("test2");
}
@Async("asyncExecutor")
public CompletableFuture<String> test3() throws InterruptedException {
Thread.sleep(3000L);
return CompletableFuture.completedFuture("test3");
}
}
@SpringBootApplication
@EnableAsync
public class TomcatdebugApplication {
public static void main(String[] args) {
SpringApplication.run(TomcatdebugApplication.class, args);
}
@Bean(name = "asyncExecutor")
public Executor asyncExecutor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
executor.setCorePoolSize(3);
executor.setMaxPoolSize(3);
executor.setQueueCapacity(100);
executor.setThreadNamePrefix("AsynchThread-");
executor.initialize();
return executor;
}
Вот запускаю и вижу эффект
Здесь после моего запроса я сделал точку останова перед вызовом контейнера для выполнения бизнес-логики, а затем я также наткнулся на точку останова после возврата.Controller
После выполнения запрос возвращаетсяCoyoteAdapter
в, и судитьrequest.isAsync()
, судя по рисунку, этоfalse
, то следующее выполнениеrequest.finishRequest()
иresponse.finishResponse()
выполнить завершение ответа и уничтожить тело запроса и ответа. Самое интересное, что когда я экспериментировал, я обнаружил, что при выполненииrequest.isAsync()
Раньше на странице браузера появлялось тело ответа, которое прошел фреймворк SpringBootStringHttpMessageConverter
в классеwriteInternal
Метод имеет вывод.
Основная логика приведенного выше анализа заключается в следующем., выполняется поток TomcatCoyoteAdapter
После вызова контейнера вы должны дождаться возврата запроса, а затем оценить, является ли он асинхронным запросом, затем обработать запрос, а затем поток может быть перезапущен после завершения выполнения. И мой первый пример асинхронного сервлета, после выполнения метода doGet, он вернётся сразу, то есть перейдёт сразу кrequest.isAsync()
После выполнения логики всего потока поток перезапускается.
Расскажите о сценариях использования асинхронных сервлетов.
Проанализируйте так много, а затем сценарии использования асинхронных сервлетов, что делает? На самом деле, пока мы можем проводить некоторый анализ, то есть асинхронный сервлет увеличивает пропускную способность системы, может принимать больше запросов. Предполагая, что потока веб-системы Tomcat недостаточно, большое количество запросов ожидает времени, когда оптимизация системы на уровне веб-приложений больше не может быть оптимизирована, то есть не для сокращения времени отклика бизнес-логики, и на этот раз, если вы хотите сократить время ожидания пользователя и повысить пропускную способность, попробуйте меньшее использование асинхронного сервлета.
Приведите практический пример: Например, чтобы сделать систему коротких сообщений, система коротких сообщений предъявляет высокие требования к производительности в реальном времени, поэтому время ожидания должно быть как можно короче, а функция отправки фактически возложена на оператора для отправки, то есть нам нужно вызвать интерфейс, предполагая, что объем параллелизма очень высок. Высокий, тогда, когда бизнес-система вызывает нашу функцию отправки SMS, можно использовать наш пул потоков Tomcat, а оставшиеся запросы будут ждать в очереди. В это время задержка SMS будет увеличиваться. Чтобы решить эту проблему, мы можем ввести асинхронный сервлет, чтобы принимать больше запросов на отправку SMS, тем самым уменьшая задержку SMS.
Суммировать
В этой статье я начну с написания асинхронного сервлета, проанализирую функцию асинхронного сервлета и то, как реализовать асинхронный сервлет внутри Tomcat, а затем я также объясню это в соответствии с популярным в Интернете асинхронным программированием SpringBoot, которого нет внутри Tomcat. , Асинхронный сервлет. Наконец, я рассказал о сценариях использования асинхронных сервлетов и проанализировал ситуации, в которых можно опробовать асинхронные сервлеты.