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