高併發處理,從Java8新特性說起

一、前言

隨着公司業務的擴展和用戶的增加,我們的網關接口不得不面對高併發的場景。
如何處理高併發請求呢?除了在系統架構上,分庫分表、分佈集羣,異步處理等處理方式。本文來聊一聊,通過Java語言本身,來進行高併發場景的優化。

二、核心思路

流程圖
如圖所示
1、多客戶端同時向服務器發起請求。
2、服務器將獲取到的請求,添加到請求隊列。
3、由一個定時任務(比如10ms執行一次),獲取隊列的全部元素,並將之包裝成爲請求列表,注意這裏的隊列是線程安全的。
4、不再單次請求一條數據,而是發送批量數據,獲取到批量數據

//單次請求
getStoreOrderByOrderCode(String code);
//批量請求
getStoreOrderListByOrderCodeBatch(List<String> codeList);

5、遍歷獲取到的列表,將數據發送至對應線程。
6、多客戶端獲取到服務器的響應。

三、代碼

廢話不多說,開整

1、遠程調用的服務

/**
 * @author xuyuanpeng
 * @version 1.0
 * @date 2019-05-15 14:28
 */
@Service
public class RemoteService {
    /**
     * 假裝這是一個遠程接口
     * @param code
     * @return
     */
    public StoreOrder getOrderByCode(String code) {
        StoreOrder order=new StoreOrder();
        order.setCreateTime(System.currentTimeMillis());
        order.setId(new Random().nextInt());
        order.setOrderCode(code);
        return order;
    }

    /**
     * 假裝這是一個批量請求的遠程接口
     * @param codes
     * @return
     */
    public List<StoreOrder> getOrderListByCodeBatch(List<String> codes){
        List<StoreOrder> list = new ArrayList<>();
        for(String code : codes){
            list.add(this.getOrderByCode(code));
        }
        return list;
    }
}

2、模擬客戶端批量請求

   @Autowired
   private IPerformanceOrderService performanceOrderService;
   private static final Integer number=10000;

   private CountDownLatch cdl=new CountDownLatch(number);

  @org.junit.Test
  public void testThread3(){
        //在線程中進行wait,當 cdl.countDown() 遞減,直至=0是,wait完畢
        //這時創建的1000個線程,也創建完畢,1000個線程內容,執行同時執行

        long startTime=System.currentTimeMillis();

        for(int i=0;i<number;i++){
            Thread thread = new Thread(()->{
                try {
                    cdl.await();
                    StoreOrder order = performanceOrderService.getOrderByCode("CODE_"+new Random().nextInt());
//                    System.out.println("RESULT>>>"+JsonMapper.toJsonString(order));
                } catch (InterruptedException | ExecutionException e) {
                    e.printStackTrace();
                }
            });
            thread.start();
            cdl.countDown();
        }
        long finshTime=System.currentTimeMillis()-startTime;
        System.out.println("處理完成時間:"+finshTime);
        //避免以上不輸出,直接down掉
        try {
            Thread.sleep(50000);
        }catch (Exception e){
            // who care?
        }
    }
變量說明:

1.performanceOrderService是一個service層爲一個訂單服務。
2.CountDownLatch

CountDownLatch是一個同步工具類,它允許一個或多個線程一直等待,直到其他線程的操作執行完後再執行

初始化時,定義它的值,在業務代碼之前,進行wait。
通過countDown()方法,對其值進行-1
當值爲0時,喚醒wait的線程。執行wait後面的方法。
本文中,我們先批量的start了多個線程,當線程創建並start完畢,最後一起執行裏面的方法。

3、服務類

@Service
public class PerformanceOrderService implements IPerformanceOrderService {
    @Autowired
    private RemoteService remoteService;

    //線程安全的隊列
    LinkedBlockingDeque<XRequest> queue=new LinkedBlockingDeque<>();

    @Override
    public StoreOrder getOrderByCode(String code) throws ExecutionException, InterruptedException {
        XRequest request=new XRequest();
        request.setOrderCode(code);

        //JDK 新特性 存儲線程完成結果,並可以分發回線程
        CompletableFuture<StoreOrder> future = new CompletableFuture<>();

        request.setFuture(future);

        queue.add(request);

        //阻塞中 等待原廠接口調用完成
        return future.get();
    }

    @PostConstruct
    public void init(){
        System.out.println("PerformanceOrderService>>>init");
        // Executors.newSingleThreadScheduledExecutor()=Executors.newScheduledThreadPool(1)
        ScheduledExecutorService scheduledExecutorService =
                Executors.newSingleThreadScheduledExecutor();
        scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
            @Override
            public void run() {
                Integer size=queue.size();
                if(size==0){
                    return;
                }
                ArrayList<XRequest> reqList = new ArrayList<>();
                for(int i = 0 ;i < size; i++){
                    XRequest req = queue.poll();
                    reqList.add(req);
                }

                System.out.println("批量處理的數據量爲:"+size);
                //將list數據,處理成批量處理的參數,然後請求批量處理接口
                List<String> codeList = new ArrayList<>();
                for(XRequest request : reqList){
                    codeList.add(request.getOrderCode());
                }

                //根據批量參數  請求 批量處理方法
                List<StoreOrder> result = remoteService.getOrderListByCodeBatch(codeList);

                //將唯一標識,與相應結果進行對應
                Map<String,StoreOrder> bindData=new HashMap<>();
                for (StoreOrder storeOrder : result){
                    String code =storeOrder.getOrderCode();
                    bindData.put(code,storeOrder);
                }
                //通過XRequest 下發至對應線程
                for(XRequest request : reqList){
                    StoreOrder storeOrder=bindData.get(request.getOrderCode());
                    CompletableFuture<StoreOrder> future = request.getFuture();
                    future.complete(storeOrder);
                }
            }
        },0,10, TimeUnit.MILLISECONDS);
    }
}
相關說明

1、LinkedBlockingDeque 隊列,用來存儲請求
2、 @PostConstruct
在被聲明爲@Service或者@Controller的類中,裏面被聲明爲@PostConstruct的方法,在系統初始化時,會被執行。
3、CompletableFuture

既然CompletableFuture類實現了CompletionStage接口,首先我們需要理解這個接口的契約。它代表了一個特定的計算的階段,可以同步或者異步的被完成。你可以把它看成一個計算流水線上的一個單元,最終會產生一個最終結果,這意味着幾個CompletionStage可以串聯起來,一個完成的階段可以觸發下一階段的執行,接着觸發下一次,接着……
除了實現CompletionStage接口, CompletableFuture也實現了future接口, 代表一個未完成的異步事件。CompletableFuture提供了方法,能夠顯式地完成這個future,所以它叫CompletableFuture

流程說明

1、一個請求進入方法,會同時生成CompletableFuture對象。將此對象和方法的參數,加入請求隊列
2、系統在初識時生產了一個定時任務,每隔10ms執行一次。
3、定時任務獲取到當前全部的請求隊列,依次pop出所有元素封裝爲list。並將元素包裝爲批量請求的參數,向遠程方法進行請求。
4、獲取到請求結果,遍歷請求的list,獲取到orderCode與請求結果進行匹配,如果匹配上,在獲取請求list中的CompletableFuture,通過CompletableFuture通知回對應線程,完成消息請求

四、總結

此方法,特別適合處理同一時間段,大批量的網絡數據請求的情況。
需要理解並使用CompletableFuture、ScheduledExecutorService

五、源碼

文章中引用部分源於其他博客。由於丟失出處,並未貼出引用地址。
https://gitee.com/xyp_YF/HConcurrenceDemo

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章