使用多執行緒提高rest服務效能

萱萱發表於2019-03-01

tomcat管理執行緒數量有限,當達到一定請求數量時,無法繼續接受請求,使用多執行緒的方式,可以呼叫一個非同步執行緒來執行。

執行邏輯如下圖,tomcat就收http請求,呼叫一個副執行緒進行處理,副執行緒處理後,將結果返回給主執行緒。在副執行緒處理整個業務邏輯的過程中,主執行緒可以空閒出來,去處理其他請求。使得伺服器的吞吐量可以有一個很大的提升。

使用多執行緒提高rest服務效能

用同步方式和非同步方式編寫兩個請求

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

使用多執行緒提高rest服務效能

在瀏覽器中輸入http://localhost:8082/reed/async

使用多執行緒提高rest服務效能

在實際開發中,可能遇到接收請求和響應請求不是同一個執行緒的場景。如下圖,此時使用callable無法滿足業務需求。

可以使用 DeferredResult來處理此類業務場景。

使用多執行緒提高rest服務效能

以下單處理場景為例,應用伺服器1的執行緒1收到下單請求,將下單資訊傳送給訊息佇列。應用伺服器2消費訊息,進行下單處理。下單完成後,將結果返回給訊息佇列, 應用伺服器有另外一個執行緒2來監聽訊息佇列,當發現有訂單處理結果的訊息,根據訊息的結果返回HTTP響應。

執行緒1和執行緒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這個物件map的key可以理解為訂單號。

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的訂單的處理過程,結果如下。

使用多執行緒提高rest服務效能

相關文章