Программирование асинхронных задач Spring / Spring boot WebAsyncTask

Spring Boot задняя часть сервер Spring
Программирование асинхронных задач Spring / Spring boot WebAsyncTask

Узнайте, как сделать асинхронное программирование в Spring сегодня. Мы все знаем, что веб-серверы обрабатывают запросыrequestПоток получается из пула потоков, что нетрудно объяснить, потому что, когда количество одновременных веб-запросов очень велико, как создать поток обработки при поступлении запроса, потому что накладные расходы на создание потока и переключение контекста потока относительно большой, веб-сервер в конечном итоге выйдет из строя. Кроме того, потоки обработки, созданные веб-сервером, по умолчанию выполняются синхронно от начала до конца, то есть если поток обработки A отвечает за обработку запроса B, то когда B не выполняетreturnРанее поток обработки A не мог уйти для обработки других запросов, что сильно ограничивало возможности параллельной обработки веб-сервера.

Таким образом, пул потоков решает проблему повторного использования потоков.Как решить проблему синхронной обработки запросов? Ответ — асинхронная обработка. Что такое асинхронная обработка? Асинхронная обработка в основном предназначена для того, чтобы позволить потоку A бездействовать для продолжения обработки других запросов до завершения обработки вышеуказанного запроса B. Затем мы можем сделать это, перезапустить поток C внутри потока A для выполнения задачи, позволить A вернуться непосредственно на веб-сервер и продолжить принимать новые входящие запросы.

Прежде чем приступить к следующему объяснению, позвольте мне выделить здесь два понятия:

  • 1. Обработка потока

    Поток обработки принадлежит веб-серверу, который отвечает за обработку пользовательских запросов и принимает управление пулом потоков.

  • 2. Асинхронный поток

    Асинхронные потоки — это определяемые пользователем потоки, которыми можно управлять с помощью пулов потоков.

Spring обеспечивает поддержку асинхронных задач, используяWebAsyncTaskКлассы могут реализовывать асинхронные задачи, и в то же время мы также можем установить соответствующую обработку обратного вызова для асинхронных задач, например, как обрабатывать, когда задача истекает и выдает исключение. Асинхронные задачи обычно очень практичны.Например, мы хотим передать операцию, которая может занять много времени, в асинхронный поток для обработки, или когда оплата заказа завершена, запускается асинхронная задача для запроса результата оплаты приказ.

1. Обычные асинхронные задачи

Для удобства демонстрации выполнение асинхронных задач принимаетThread.sleep(long)Смоделируйте, теперь предположим, что пользователь запрашивает следующий интерфейс:

http://localhost:7000/demo/getUserWithNoThing.json

Интерфейс асинхронной задачи определяется следующим образом:

/**
 * 测试没有发生任何异常的异步任务
 */
@RequestMapping(value = "getUserWithNoThing.json", method = RequestMethod.GET)
public WebAsyncTask<String> getUserWithNoThing() {
    // 打印处理线程名
    System.err.println("The main Thread name is " + Thread.currentThread().getName());
    
    // 此处模拟开启一个异步任务,超时时间为10s
    WebAsyncTask<String> task1 = new WebAsyncTask<String>(10 * 1000L, () -> {
    	System.err.println("The first Thread name is " + Thread.currentThread().getName());
    	// 任务处理时间5s,不超时
    	Thread.sleep(5 * 1000L);
    	return "任务1顺利执行成功!任何异常都没有抛出!";
    });
    
    // 任务执行完成时调用该方法
    task1.onCompletion(() -> {
    	System.err.println("任务1执行完成啦!");
    });
    
    System.err.println("task1继续处理其他事情!");
    return task1;
}

Консоль печатает следующим образом:

The main Thread name is http-nio-7000-exec-1
task1继续处理其他事情!
The first Thread name is MvcAsync1
任务1执行完成啦!

Результаты браузера следующие:

2. Генерация исключений асинхронных задач

Вызов интерфейса:http://localhost:7000/demo/getUserWithError.json

/**
 * 测试发生error的异步任务
 * @return
 */
@RequestMapping(value = "getUserWithError.json", method = RequestMethod.GET)
public WebAsyncTask<String> getUserWithError() {
	System.err.println("The main Thread name is " + Thread.currentThread().getName());

	// 此处模拟开启一个异步任务
	WebAsyncTask<String> task3 = new WebAsyncTask<String>(10 * 1000L, () -> {
		System.err.println("The second Thread name is " + Thread.currentThread().getName());
		// 此处抛出异常
		int num = 9 / 0;
		System.err.println(num);
		return "";
	});

	// 发生异常时调用该方法
	task3.onError(() -> {
		System.err.println("====================================" + Thread.currentThread().getName()
				+ "==============================");
		System.err.println("任务3发生error啦!");
		return "";
	});
	// 任务执行完成时调用该方法
	task3.onCompletion(() -> {
		System.err.println("任务3执行完成啦!");
	});

	System.err.println("task3继续处理其他事情!");
	return task3;
}

Вывод консоли выглядит следующим образом:

The main Thread name is http-nio-7000-exec-1
task3继续处理其他事情!
The second Thread name is MvcAsync1
2018-06-15 09:40:13.538 ERROR 9168 --- [nio-7000-exec-2] o.a.c.c.C.[.[.[.[dispatcherServlet]      : Servlet.service() for servlet [dispatcherServlet] threw exception

java.lang.ArithmeticException: / by zero
	at com.example.demo.controller.GetUserInfoController.lambda$5(GetUserInfoController.java:93) ~[classes/:na]
	at org.springframework.web.context.request.async.WebAsyncManager.lambda$startCallableProcessing$4(WebAsyncManager.java:317) ~[spring-web-5.0.6.RELEASE.jar:5.0.6.RELEASE]
	at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) ~[na:1.8.0_161]
	at java.util.concurrent.FutureTask.run(FutureTask.java:266) ~[na:1.8.0_161]
	at java.lang.Thread.run(Thread.java:748) [na:1.8.0_161]

2018-06-15 09:40:13.539 ERROR 9168 --- [nio-7000-exec-2] o.a.c.c.C.[.[.[.[dispatcherServlet]      : Servlet.service() for servlet [dispatcherServlet] in context with path [/demo] threw exception [Request processing failed; nested exception is java.lang.ArithmeticException: / by zero] with root cause

java.lang.ArithmeticException: / by zero
	at com.example.demo.controller.GetUserInfoController.lambda$5(GetUserInfoController.java:93) ~[classes/:na]
	at org.springframework.web.context.request.async.WebAsyncManager.lambda$startCallableProcessing$4(WebAsyncManager.java:317) ~[spring-web-5.0.6.RELEASE.jar:5.0.6.RELEASE]
	at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) ~[na:1.8.0_161]
	at java.util.concurrent.FutureTask.run(FutureTask.java:266) ~[na:1.8.0_161]
	at java.lang.Thread.run(Thread.java:748) [na:1.8.0_161]

====================================http-nio-7000-exec-2==============================
任务3发生error啦!
任务3执行完成啦!

Конечно, вы также можете выполнить некоторую обработку исключений для вышеизложенного, чтобы не показаться недружественным для пользователей.Использование унифицированной схемы обработки ошибок Spring boot/Spring

Вывод браузера:

3. Время ожидания асинхронных задач

Вызов интерфейса:http://localhost:7000/demo/getUserWithTimeOut.json

/**
 * 测试发生任务超时的异步任务
 * @return
 */
@RequestMapping(value = "getUserWithTimeOut.json", method = RequestMethod.GET)
public WebAsyncTask<String> getUserWithTimeOut() {
    System.err.println("The main Thread name is " + Thread.currentThread().getName());
    
    // 此处模拟开启一个异步任务,超时10s
    WebAsyncTask<String> task2 = new WebAsyncTask<String>(10 * 1000L, () -> {
    	System.err.println("The second Thread name is " + Thread.currentThread().getName());
    	Thread.sleep(20 * 1000L);
    	return "任务2执行超时!";
    });
    
    // 任务超时调用该方法
    task2.onTimeout(() -> {
    	System.err.println("====================================" + Thread.currentThread().getName()
    			+ "==============================");
    	return "任务2发生超时啦!";
    });
    
    // 任务执行完成时调用该方法
    task2.onCompletion(() -> {
    	System.err.println("任务2执行完成啦!");
    });
    
    System.err.println("task2继续处理其他事情!");
    return task2;
}

Результат выполнения консоли:

The main Thread name is http-nio-7000-exec-4
task2继续处理其他事情!
The second Thread name is MvcAsync2
====================================http-nio-7000-exec-5==============================
任务2执行完成啦!

Результат выполнения браузера:

В-четвертых, асинхронные задачи пула потоков

Асинхронные задачи в трех вышеприведенных случаях по умолчанию не управляются механизмом пула потоков, то есть когда приходит запрос, хотя поток обработки освобождается, система все равно создает поток асинхронной задачи для каждого запроса, т.е. , то что мы видим вышеMvcAsyncАсинхронный поток задач в начале, тогда это не сработает, накладные расходы очень большие! Таким образом, мы можем использовать пул потоков для управления непосредственно вWebAsyncTaskКонструктор класса проходит черезThreadPoolTaskExecutorэкземпляр объекта.

Давайте сначала посмотрим, что происходит, когда параллельные запросы выполняются для первого случая выше (смоделировано здесьhttp://localhost:7000/demo/getUserWithNoThing.jsonсовершать одновременные вызовы):

Вывод консоли выглядит следующим образом:

The first Thread name is MvcAsync57
The first Thread name is MvcAsync58
The first Thread name is MvcAsync59
The first Thread name is MvcAsync60
The first Thread name is MvcAsync61
The first Thread name is MvcAsync62
The first Thread name is MvcAsync63
The first Thread name is MvcAsync64
The first Thread name is MvcAsync65
The first Thread name is MvcAsync66
The first Thread name is MvcAsync67
The first Thread name is MvcAsync68
The first Thread name is MvcAsync69
The first Thread name is MvcAsync70
The first Thread name is MvcAsync71
The first Thread name is MvcAsync72
The first Thread name is MvcAsync73
The first Thread name is MvcAsync74
The first Thread name is MvcAsync76
The first Thread name is MvcAsync75
The first Thread name is MvcAsync77
The first Thread name is MvcAsync78
The first Thread name is MvcAsync79
The first Thread name is MvcAsync80

Поскольку пул потоков не добавляется, на 100 запросов будет открыто 100 потоков асинхронных задач, что очень дорого и не рекомендуется.

Ниже приведена реализация с использованием пула потоков:

Интерфейс вызова:http://localhost:7000/demo/getUserWithExecutor.json

/**
 * 测试线程池
 * @return
 */
@RequestMapping(value = "getUserWithExecutor.json", method = RequestMethod.GET)
public WebAsyncTask<String> getUserWithExecutor() {
    System.err.println("The main Thread name is " + Thread.currentThread().getName());
    
    // 此处模拟开启一个异步任务,此处传入一个线程池
    WebAsyncTask<String> task1 = new WebAsyncTask<String>(10 * 1000L, executor, () -> {
    	System.err.println("The first Thread name is " + Thread.currentThread().getName());
    	Thread.sleep(5000L);
    	return "任务4顺利执行成功!任何异常都没有抛出!";
    });
    
    // 任务执行完成时调用该方法
    task1.onCompletion(() -> {
    	System.err.println("任务4执行完成啦!");
    });
    
    System.err.println("task4继续处理其他事情!");
    return task1;
}

Пул потоков определяется следующим образом:

@Configuration
public class MyExecutor {
    
    @Bean
    public static ThreadPoolTaskExecutor getExecutor() {
    	ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
    	taskExecutor.setCorePoolSize(30);
    	taskExecutor.setMaxPoolSize(30);
    	taskExecutor.setQueueCapacity(50);
    	taskExecutor.setThreadNamePrefix("huang");// 异步任务线程名以 huang 为前缀
    	return taskExecutor;
    }
}

Параллельное тестирование по вышеуказанному, мы можем получить следующие результаты:

Пример кода этой статьи:GitHub.com/smallercode…

Использование пулов потоков может сэкономить ресурсы сервера и оптимизировать вычислительные возможности сервера. Не забывайте использовать их часто! Спасибо за чтение! Если вы считаете, что это полезно для вас, пожалуйста, начните!