SpringBoot 使用多线程提高RESTful 服务的性能

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/a2011102394/article/details/80672786

1、同步处理

  • 编写测试用例

    @RunWith(SpringRunner.class)
    @SpringBootTest
    public class AsyncControllerTest {
    
        @Autowired
        private WebApplicationContext wac;
    
        private MockMvc mockMvc;
    
    
        @Before
        public void setUp() throws Exception {
            mockMvc = MockMvcBuilders.webAppContextSetup(wac).build();
        }
    
        @Test
        public void order() throws Exception {
            mockMvc.perform(MockMvcRequestBuilders.get("/order").contentType(MediaType.APPLICATION_JSON_UTF8))
                    .andExpect(MockMvcResultMatchers.status().isOk());
        }
    }
    
  • 同步处理代码

    @RestController
    @RequestMapping("/order")
    @Slf4j
    public class AsyncController {
    
        @GetMapping
        public String order() throws InterruptedException {
            log.info("主线程开始");
            Thread.sleep(1000);
            log.info("主线程结束");
            return "success";
        }
    }
    
  • 运行结果如下:

2.使用Runnable实现异步处理

  • 代码如下:

    @GetMapping
    public Callable<String> order() throws InterruptedException {
        log.info("主线程开始");
        Callable<String> result = new Callable<String>() {
            @Override
            public String call() throws Exception {
    
                log.info("副线程开始");
                Thread.sleep(1000);
                log.info("副线程返回");
                return  "success";
            }
        };
        log.info("主线程结束");
        return result;
    }
    
  • 测试结果如下:

    主线程几乎在同时就返回了,并不等待副线程处理结束。

3. 用DeferredResult处理异步请求

之所以要使用DeferredResult来处理异步请求,是因为Runnable的方式并不能完全处理所有的需求。

使用Runnable的方式进行异步处理的时候,副线程必须是由主线程调起的。

在实际的业务场景中,不能满足需求。

  • 需求案例(以下单操作为例)

    在上图所示的业务流程中,接收下单请求的应用和真正处理下单逻辑的应用并不在一个应用,甚至不在同一台服务器中。

    线程1接收到http请求之后,会把请求放在消息队列中,应用2所在的服务器,监听到有下单的http请求之后,由应用2来处理下单的逻辑。

    当下单的逻辑处理完成之后,会将结果放置在消息队列中,同时在应用1中有另外一个线程,线程2监听到消息队列中有下单完成的消息之后,根据下单的结果做出相应的http响应。

    在整个的场景之中,线程1和线程2完全是隔离的状态,使用Runnable的方式就不能满足需求了。

  • 编码设计

    1. 使用一个对象来模拟消息队列,当收到下单请求之后,延迟1秒之后,在消息队列中放置“订单完成”的消息。

    2. 线程1的处理:

    3. 线程2的处理

      线程2作为一个监听器,监听到消息队列中有订单完成的消息时,把结果返回。

    4. DeferredResult

      在线程1和线程2之间传递DefrredResult对象,这样才可以实现在线程2中使用线程1中生成的DeferredResult将处理的结果进行返回。

  • 编码(示例代码为了简化过程,没有使用线程池

    1. 模拟消息队列对象

      @Component
      public class MockQueue {
      
          /**
           * 下单消息
           */
          private String placeOrder;
      
          /**
           * 订单完成的消息
           */
          private String completeOrder;
      
      
          public String getPlaceOrder() {
              return placeOrder;
          }
      
          public void setPlaceOrder(String placeOrder)  {
      		
              new Thread(() -> {
                  log.info("接到下单请求, " + placeOrder);
                  try {
                      Thread.sleep(1000);
                  } catch (Exception e) {
                      e.printStackTrace();
                  }
                  this.completeOrder = placeOrder;
                  log.info("下单请求处理完毕," + placeOrder);
              }).start();
      
          }
      
          public String getCompleteOrder() {
              return completeOrder;
          }
      
          public void setCompleteOrder(String completeOrder) {
              this.completeOrder = completeOrder;
          }
      }
      
    2. DeferredResultHolder对象

      @Component
      @Getter
      @Setter
      public class DeferredResultHolder {
          private Map<String, DeferredResult<String>> map = new HashMap<String, DeferredResult<String>>();
      }
      
    3. 监听消息队列

      @Slf4j
      @Component
      public class QueueListener implements ApplicationListener<ContextRefreshedEvent> {
      
          @Autowired
          private MockQueue mockQueue;
      
          @Autowired
          private DeferredResultHolder deferredResultHolder;
      
          /**
           * 监听Spring容器
           * @param contextRefreshedEvent
           */
          @Override
          public void onApplicationEvent(ContextRefreshedEvent contextRefreshedEvent) {
              new Thread(()->{
                  while (true) {
                      if (StringUtils.isNotBlank(mockQueue.getCompleteOrder())) {
                          String orderNumber = mockQueue.getCompleteOrder();
                          log.info("返回订单处理结果:"+orderNumber);
                          deferredResultHolder.getMap().get(orderNumber).setResult("place order success");
                          mockQueue.setCompleteOrder(null);
                      }else{
                          try {
                              Thread.sleep(100);
                          } catch (InterruptedException e) {
                              e.printStackTrace();
                          }
                      }
                  }
              }).start();
          }
      }
      
    4. 测试结果如下:

补充

在继承了WebMvcConfigurerAdapter类的配置类中可以注册异步拦截器,如下所示

猜你喜欢

转载自blog.csdn.net/a2011102394/article/details/80672786