1. Introduction
Recently, some thoughts have been raised regarding the system restart. How will ongoing requests be handled during the system restart process? Will consuming messages be lost? Will asynchronously executed tasks be interrupted? Since these problems exist, can’t our application be restarted? But why didn’t these problems occur when our application was constantly rebooting with release iterations? Or did the application do something extra? With these questions in mind, use a scenario simulation to see how the real situation works.
2. The scene
2.1 the HTTP request
2.1.1 Creating a Request
@RestController
public class ShutDownController {
@RequestMapping("shut/down")
public String shutDown(a) throws InterruptedException {
TimeUnit.SECONDS.sleep(20);
return "hello"; }}Copy the code
2.1.2 Invoking a Request
http://localhost:8080/shut/down
2.1.3 Simulating a Restart
Kill -2 Application PIDCopy the code
2.1.4 phenomenon
2.1.5 conclusion
A message indicating that the closed application is inaccessible was displayed during the request execution
2.1.6 Start graceful shutdown
The above phenomenon is very unfriendly to the user, will cause the user a face meng, so what measures can avoid the occurrence of this phenomenon? Is it possible to complete accepted requests and reject new ones before closing the application? The answer is yes, just add graceful shutdown configuration to the configuration file
server:
shutdown: graceful This feature is only available in Spring Boot2.3. Note: You need to use the Kill -2 trigger to shutdown the application, which triggers shutdownHook
spring:
lifecycle:
timeout-per-shutdown-phase: 30s Set the buffer time, note that the unit of time is used to wait for the task to complete.
Copy the code
After the configuration is added, repeat 2.1.2 and 2.1.3 to see the following effect
As you can see, received requests continue to execute even if the application is closed during request execution
2.2 Message consumption
As mentioned in the introduction, if the application is closed during message consumption, will the message be lost or put back into the message queue?
2.2.1 Creating a Producer
@RestController
public class RabbitMqController {
@Autowired
private RabbitTemplate rabbitTemplate;
@GetMapping("/sendBusinessMessage")
public void sendBusinessMessage(a) throws InterruptedException {
rabbitTemplate.convertAndSend(RabbitmqConfig.BUSINESS_EXCHANGE, RabbitmqConfig.BUSINESS_ROUTING_KEY, "send message");
TimeUnit.SECONDS.sleep(10000); }}Copy the code
2.2.2 Creating consumers
@Component
@RabbitListener(queues = RabbitmqConfig.BUSINESS_QUEUE_NAME)
@Slf4j
public class BusinessConsumer {
/** * Operation scenarios: * 1. Start the application using the RabbitmqApplication startup class * 2. Call the /sendBusinessMessage interface to send a message * 3. The RabbitMQ broker sends the message to the consumer * 4. 5. During the process of consuming a message, the application is closed, channel and connection are disconnected, and unack messages are re-placed into the broker * *@paramContent Message content *@paramChannel Channel *@paramMessage Message object */
@RabbitHandler
public void helloConsumer(String content, Channel channel, Message message) {
log.info("Business Consumer Receive message: {}", content);
try {
// Simulate the service execution time
TimeUnit.SECONDS.sleep(10000);
} catch(InterruptedException e) { e.printStackTrace(); }}}Copy the code
2.2.3 Invoking a Request
http://localhost:8080/sendBusinessMessage
2.2.4 Before Closing the application
2.2.5 After Shutting down the application
2.2.6 conclusion
During message consumption, if the application is closed, unack messages are put back into the message queue to ensure that messages will be consumed
2.3 Asynchronous Task
2.3.1 Thread Pool Configuration
@Component
public class ThreadPoolConfig {
@Bean
public ThreadPoolTaskExecutor threadPoolTaskExecutor(a) {
ThreadPoolTaskExecutor threadPoolTaskExecutor = new ThreadPoolTaskExecutor();
threadPoolTaskExecutor.setThreadNamePrefix("test-");
threadPoolTaskExecutor.setCorePoolSize(3);
threadPoolTaskExecutor.setMaxPoolSize(3);
threadPoolTaskExecutor.setQueueCapacity(100);
returnthreadPoolTaskExecutor; }}Copy the code
2.3.2 Asynchronous Task Requests
@Autowired
private ThreadPoolTaskExecutor threadPoolTaskExecutor;
@RequestMapping("async/task")
public void asyncTask(a) throws InterruptedException {
for (int i = 0; i < 10; i++) {
threadPoolTaskExecutor.execute(() -> {
try {
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
throw new RuntimeException();
}
log.info("task execute complete..."); }); }}Copy the code
2.3.3 Invoking a Request
http://localhost:8080/async/task
2.3.4 Simulating a Restart
Kill -2 Application PIDCopy the code
2.3.5 phenomenon
Exception in thread "test-2" Exception in thread "test-1" Exception in thread "test-3" java.lang.RuntimeException at com.boot.example.ShutDownController.lambda$asyncTask$0(ShutDownController.java:37) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) at java.lang.Thread.run(Thread.java:748) java.lang.RuntimeException at com.boot.example.ShutDownController.lambda$asyncTask$0(ShutDownController.java:37) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) at java.lang.Thread.run(Thread.java:748) java.lang.RuntimeException at com.boot.example.ShutDownController.lambda$asyncTask$0(ShutDownController.java:37) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) at java.lang.Thread.run(Thread.java:748)Copy the code
2.3.6 Modifying thread Pool Configurations
Add the following configuration to the thread pool configuration:
threadPoolTaskExecutor.setWaitForTasksToCompleteOnShutdown(true);
threadPoolTaskExecutor.setAwaitTerminationSeconds(120);
Copy the code
2.3.7 What Can I Do After The Configuration Is Modified
2021-12- 091709:40.054 INFO 22383 --- [ test-1] com.boot.example.ShutDownController : task execute complete...
2021-12- 091709:40.055 INFO 22383 --- [ test-3] com.boot.example.ShutDownController : task execute complete...
2021-12- 091709:40.055 INFO 22383 --- [ test-2] com.boot.example.ShutDownController : task execute complete...
2021-12- 091709:50.059 INFO 22383 --- [ test-3] com.boot.example.ShutDownController : task execute complete...
2021-12- 091709:50.059 INFO 22383 --- [ test-1] com.boot.example.ShutDownController : task execute complete...
2021-12- 091709:50.060 INFO 22383 --- [ test-2] com.boot.example.ShutDownController : task execute complete...
2021-12- 0917:10:00.062 INFO 22383 --- [ test-2] com.boot.example.ShutDownController : task execute complete...
2021-12- 0917:10:00.062 INFO 22383 --- [ test-1] com.boot.example.ShutDownController : task execute complete...
2021-12- 0917:10:00.065 INFO 22383 --- [ test-3] com.boot.example.ShutDownController : task execute complete...
2021-12- 0917:10:10.066 INFO 22383 --- [ test-1] com.boot.example.ShutDownController : task execute complete...
Copy the code
Conclusion 2.3.8
When a thread pool is used to execute an asynchronous task, the task cannot be completed without adding the configuration, but the task can still be completed with the configuration added.
3. Summary
To ensure that tasks can still complete during an application restart, turn on the graceful shutdown configuration and add wait task completion and wait time configuration to the thread pool