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