Повышение производительности службы отдыха с помощью многопоточности

задняя часть

Количество потоков управления Tomcat ограничено. При достижении определенного количества запросов он не может продолжать принимать запросы. Используя многопоточность, можно вызвать асинхронный поток для выполнения.

Логика выполнения показана на рисунке ниже: tomcat получает HTTP-запрос, вызывает вторичный поток для обработки и возвращает результат в основной поток после того, как вторичный поток обработает его. В процессе обработки вторичным потоком всей бизнес-логики основной поток может простаивать для обработки других запросов. Пропускная способность сервера может быть значительно улучшена.

Напишите два запроса синхронно и асинхронно

package com.ustc.reed.controller.async;
 
 
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
 * @author xuanxuan
 *
 */
import java.util.concurrent.Callable;
 
@RestController
public class AsyncController {
    private static Logger logger  = LoggerFactory.getLogger(AsyncController.class);
 
    @GetMapping("/sync")
    public String sync() throws Exception{
        logger.info("主线程开始");
        Thread.sleep(1000);
 
        logger.info("主线程结束");
        return "success";
    }
 
    @GetMapping("/async")
    public Callable<String> async() throws Exception{
        logger.info("主线程开始");
        Callable<String> result = new Callable<String>() {
            @Override
            public String call() throws Exception {
                logger.info("副线程开始");
				Thread.sleep(1000);
				logger.info("副线程返回");
				return "success";
            }
        };
        logger.info("主线程结束");
        return result;
    }
}

Введите http://localhost:8082/reed/sync в браузере.

Введите http://localhost:8082/reed/async в браузере.

В реальной разработке вы можете столкнуться со сценариями, в которых получение запроса и ответ на запрос не являются одним и тем же потоком. Как показано на рисунке ниже, использование callable в настоящее время не может соответствовать бизнес-требованиям.

DeferredResult можно использовать для обработки таких бизнес-сценариев.

В качестве примера возьмем следующий сценарий обработки заказа: Поток 1 сервера приложений 1 получает запрос заказа и отправляет информацию о заказе в очередь сообщений. Сервер 2 приложений принимает сообщение и обрабатывает заказ. После размещения заказа результат возвращается в очередь сообщений.У сервера приложений есть еще один поток 2 для мониторинга очереди сообщений.Когда он находит сообщение с результатом обработки заказа, он возвращает HTTP-ответ в соответствии с результатом обработки сообщение.

Thread 1 и Thread 2 полностью изолированы, и никто не знает о существовании друг друга.

Из-за нехватки места создайте новую MockQueue для имитации обработки заказов.

package com.ustc.reed.service.sync;
 
 
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
/**
 * created by xuanxuan on 2019/1/6
 */
 
@Component
public class MockQueue {
    private String placeOrder;   //下单消息
 
    private String completeOrder;   //下单成功消息
 
    private Logger logger = LoggerFactory.getLogger(getClass());
 
    public String getPlaceOrder() {
        return placeOrder;
    }
 
    public void setPlaceOrder(String placeOrder) throws Exception {
        new Thread(() -> {
            logger.info("接到下单请求, " + placeOrder);
            try {
                Thread.sleep(1000);
            } catch (Exception e) {
                e.printStackTrace();
            }
            this.completeOrder = placeOrder;
            logger.info("下单请求处理完毕," + placeOrder);
        }).start();
    }
 
    public String getCompleteOrder() {
        return completeOrder;
    }
 
    public void setCompleteOrder(String completeOrder) {
        this.completeOrder = completeOrder;
    }
}

Для каждого номера заказа будет один результат обработки. Как показано на рисунке, DeferredResultHolder может проходить между потоком 1 и потоком 2. Ключ карты объектов DeferredResult можно понимать как порядковый номер.

package com.ustc.reed.service.sync;
 
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.async.DeferredResult;
 
import java.util.HashMap;
import java.util.Map;
 
/**
 * created by xuanxuan on 2019/1/6
 */
@Component
public class DeferredResultHolder {
 
    private Map<String,DeferredResult<String>> map = new HashMap<>();
 
    public Map<String, DeferredResult<String>> getMap() {
        return map;
    }
 
    public void setMap(Map<String, DeferredResult<String>> map) {
        this.map = map;
    }
}

Контролируйте, выполнен ли заказ, и возвращайте результат заказа, когда он выполнен.

package com.ustc.reed.service.sync;
 
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.stereotype.Component;
 
/**
 * created by xuanxuan on 2019/1/6
 */
 
@Component
public class QueueListener implements ApplicationListener<ContextRefreshedEvent> {
 
    private Logger logger = LoggerFactory.getLogger(QueueListener.class);
 
    @Autowired
    private MockQueue mockQueue;
 
    @Autowired
    private DeferredResultHolder deferredResultHolder;
 
    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
 
        new Thread(
                new Runnable() {
                    @Override
                    public void run() {
                        while (true){
 
                            if(StringUtils.isNotBlank(mockQueue.getCompleteOrder())){
                                String orderNumber = mockQueue.getCompleteOrder();
                                logger.info("返回订单结果:"+orderNumber);
                                deferredResultHolder.getMap().get(orderNumber).setResult("place order success");
                                mockQueue.setCompleteOrder(null);
                            }else {
                                try {
                                    Thread.sleep(100);
                                } catch (InterruptedException e) {
                                    e.printStackTrace();
                                }
                            }
 
                        }
                    }
                }
 
        ).start();
 
    }
}

Код запроса выглядит следующим образом.

package com.ustc.reed.controller.async;
 
 
import com.ustc.reed.service.sync.DeferredResultHolder;
import com.ustc.reed.service.sync.MockQueue;
import org.apache.commons.lang3.RandomStringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.async.DeferredResult;
/**
 * @author xuanxuan
 *
 */
import java.util.concurrent.Callable;
 
@RestController
public class AsyncController {
    private static Logger logger  = LoggerFactory.getLogger(AsyncController.class);
 
    @Autowired
    private MockQueue mockQueue;
 
    @Autowired
    private DeferredResultHolder deferredResultHolder;
 
   
    @GetMapping("/mqasync")
    public DeferredResult<String> mqasync() throws Exception{
        logger.info("主线程开始");
        String orderNumber = RandomStringUtils.randomNumeric(8);
        mockQueue.setPlaceOrder(orderNumber);
 
        DeferredResult<String> result = new DeferredResult<>();
        deferredResultHolder.getMap().put(orderNumber, result);
        return result;
 
    }
}

Введите http://localhost:8082/reed/mqasync в браузере, чтобы имитировать обработку заказа с номером 05804777. Результаты следующие.