最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Java高并發(fā)下請求合并處理方式

 更新時間:2023年08月28日 15:08:21   作者:Soda_lw  
這篇文章主要介紹了Java高并發(fā)下請求合并處理方式,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教

Java高并發(fā)下請求合并處理

場景描述

在大并發(fā)量下每秒有一萬個請求向后端查詢數(shù)據(jù),這樣我們就需要向后端請求一萬次,甚至查詢一萬次數(shù)據(jù)庫。

我們要做的請求合并就是每隔一段時間(10ms)將這段時間內(nèi)的請求合并到一起進行批量查詢,減少查詢數(shù)據(jù)庫的次數(shù)。

思考

1、如何存放一段時間內(nèi)的請求?這里我們可以用隊列。

2、如何每隔一段時間執(zhí)行任務?用定時任務線程池。

3、每個請求都是單獨的線程,如何保證各個請求能得到自己的查詢結(jié)果?這里我們使用callable返回查詢結(jié)果,在沒有查到結(jié)果前阻塞線程。

下面來看看具體實現(xiàn)的demo

package cn.codingxiaxw.combine;
import org.springframework.stereotype.Service;
import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.*;
import java.util.stream.Collectors;
@Service
public class QueryService {
//用來存放請求的隊列,我們將請求封裝成了一個Request對象
 private LinkedBlockingQueue<Request> queue = new LinkedBlockingQueue<>() ;
       //這個是我們的單個的查詢方法,假設每隔請求都根據(jù)唯一的code進行查詢
  public Map<String,Object> query(String code){
        //這個request是我們自定義的內(nèi)部類
        Request request = new Request();
        request.code = code;
        CompletableFuture<Map<String,Object>> future = new CompletableFuture<>();
        request.future = future;
        queue.add(request);
        //阻塞 直到返回結(jié)果
        try {
            return future.get();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        return null;
    }
 //這個是個模擬批量查詢的方法
    public List<Map<String,Object>> batchQuery(List<String> codes){
        return null;
    }
    //封裝的請求
    class Request {
        String code;
        CompletableFuture<Map<String,Object>> future;
    }
    @PostConstruct
    public void init(){
    //在init方法中初始化一個定時任務線程,去定時執(zhí)行我們的查詢?nèi)蝿?具體的任務實現(xiàn)是我們根據(jù)唯一code查詢出來的結(jié)果集,以code為key轉(zhuǎn)成map,然后我們隊列中的每個Request對象都有自己的唯一code,我們根據(jù)code一一對應,給相應的future返回對應的查詢結(jié)果。
        ScheduledExecutorService poolExecutor = new ScheduledThreadPoolExecutor(1);
        poolExecutor.scheduleAtFixedRate(()->{
            int size = queue.size();
            //如果沒有請求直接返回
            if(size==0)
                return ;
            List<Request> list = new ArrayList<>();
            for (int i = 0; i < size;i++){
                Request request = queue.poll();
                list.add(request);
            }
            System.out.println("批量處理:"+size);
            List<String> codes = list.stream().map(s->s.code).collect(Collectors.toList());
            //合并之后的結(jié)果集
            List<Map<String, Object>> batchResult = batchQuery(codes);
            Map<String,Map<String,Object>> responseMap = new HashMap<>();
            for (Map<String,Object> result : batchResult) {
                String code = result.get("code").toString();
                responseMap.put(code,result);
            }
            //返回對應的請求結(jié)果
            for (Request request : list) {
                Map<String, Object> response = responseMap.get(request.code);
                request.future.complete(response);
            }
        },0,10,TimeUnit.MILLISECONDS);
    }
}

利用請求合并發(fā)揮高并發(fā)下批量處理的優(yōu)勢

需求分析

我們經(jīng)常會寫一些查詢接口,假設現(xiàn)在我們需要寫一個查詢用戶信息的接口,要求傳入用戶Id,返回用戶名稱。

那么最簡化的流程就是:用戶發(fā)送請求->controller層->service層->dao層->數(shù)據(jù)庫。

每次請求就相當于請求一條用戶信息。

當這個接口被用戶頻繁請求時,此接口就不斷的在做“請求”到“返回”的操作,服務端同時會開辟許多線程幫我們執(zhí)行這些操作,這么多的線程會消耗許多系統(tǒng)資源,服務端承受了巨大壓力。

//單查詢接口
@GetMapping("/getUser")
    public String getUser(Long key){
        long currentMillis = System.currentTimeMillis();
        //單查詢service,大量線程懟到這個service上去
        String userName = userService.getUser(key);
        System.out.printf("##############################################\n");
        System.out.printf("用戶名為:" + userName + "---線程名為:" + Thread.currentThread().getName() +
                "---執(zhí)行時間為:" + (System.currentTimeMillis() - currentMillis) + "\n");
        return userName;
    }

那么我們有什么方式可以優(yōu)化這種操作呢?

我目前能想到的就是利用緩存(緩存熱點數(shù)據(jù))、消息隊列(接收請求慢慢消費達到流量削峰)、多個服務實例(分散請求壓力提高計算能力)等方式應對高并發(fā)場景。

在本文中,我利用另一種思路:把多個請求合并為一個請求,把單查詢變?yōu)榕坎樵?,這樣就能有效減少開辟線程的數(shù)量。

具體實現(xiàn)

首先我們定義一個用戶請求類Request:

//用戶請求類
public class RequestTest {
    //請求條件
    private Long key;
    //傳話人
    private CompletableFuture<String> future;
    public CompletableFuture<String> getFuture() {
        return future;
    }
    public void setFuture(CompletableFuture<String> future) {
        this.future = future;
    }
    public Long getKey() {
        return key;
    }
    public void setKey(Long key) {
        this.key = key;
    }
}

接著是請求合并的主要代碼:

//存放請求的隊列
    LinkedBlockingDeque<RequestTest> queue = new LinkedBlockingDeque<>();
    //初始化方法
    @PostConstruct
    public void init() {
        //定時執(zhí)行的線程池,每隔5毫秒執(zhí)行一次(間隔時間可以由業(yè)務決定),把所有堆積的請求
        ScheduledExecutorService executorService = Executors.newScheduledThreadPool(1);
        executorService.scheduleAtFixedRate(() -> {
            //在這里具體執(zhí)行批量查詢邏輯
            int size = queue.size();
            if (size == 0) {
                //若沒有請求堆積,直接返回,等10毫秒再執(zhí)行一次
                return;
            }
            //若有請求堆積把所有請求都拿出來
            List<RequestTest> requestTests = new ArrayList<>();
            for (int i = 0; i < size; i++) {
                //把請求拿出來
                RequestTest poll = queue.poll();
                requestTests.add(poll);
            }
            //至此請求已經(jīng)被合并了
            System.out.printf("##############################################\n");
            System.out.printf("請求合并了" + requestTests.size() + "條!\n");
            //組裝批量查詢條件
            List<Long> keyList = new ArrayList<>();
            for (RequestTest requestTest : requestTests) {
                keyList.add(requestTest.getKey());
            }
            //進行批量查詢
            List<User> nameList = userService.getUserList(keyList);
            //把批查結(jié)果放入一個map
            Map<Long,String> map = new HashMap<>();
            for(User user:nameList){
                map.put(user.getId(),user.getName());
            }
            for (RequestTest requestTest : requestTests){
                //把放在map中的結(jié)果集放回給對應的線程
                //future是對應每個請求的,因為是每個請求線程都傳了自己的future是對應的過來
                requestTest.getFuture().complete(map.get(requestTest.getKey()));
            }
        }, 0, 5, TimeUnit.MILLISECONDS);
    }
    //請求合并
    @GetMapping("/requestMerge/getUser")
    public String getUserRequestMerge(Long key) throws InterruptedException, ExecutionException {
        long currentMillis = System.currentTimeMillis();
        //CompletableFuture可以使一個線程執(zhí)行操作后,主動返回值給另一個線程
        CompletableFuture<String> future = new CompletableFuture<>();
        RequestTest requestTest = new RequestTest();
        //把future(把future可以認為是線程間的"傳話人")放到等待隊列中去,讓定時調(diào)度的線程池執(zhí)行并返回值
        requestTest.setFuture(future);
        requestTest.setKey(key);
        //把requestTest加入等待隊列(LinkedBlockingDeque)
        queue.add(requestTest);
        //future(傳話人)阻塞直到有值返回
        String userName = future.get();
        System.out.printf("用戶名為:" + userName + "---線程名為:"+Thread.currentThread().getName()+
                "---執(zhí)行時間為:"+(System.currentTimeMillis() - currentMillis)+"\n");
        return userName;
    }

到這里我們就完成了一個請求合并的demo,接著我們測試運行結(jié)果。

這里我用了jemeter(jemeter的用法可以網(wǎng)上找一找)進行測試,對單查詢接口和請求合并接口分別進行了20000次的請求,以下是結(jié)果對比:

(1)單查詢運行結(jié)果:

可以看到系統(tǒng)開辟了許多線程來處理請求,jemeter的聚合報告如下:

(2)請求合并運行結(jié)果:

可以看到多條線程被合并成了一條線程來處理,jemeter的聚合報告如下:

通過以上數(shù)據(jù),我們可以看到,請求合并比單查詢的吞吐量要大,并且在運行過程中,流量浮動的范圍比較小。

至此,我們就完成了利用請求合并發(fā)揮高并發(fā)下批量處理的優(yōu)勢。

心得

本文我利用了LinkedBlockingDeque阻塞隊列、ScheduledExecutorService定時執(zhí)行線程池和CompletableFuture線程通信來完成了請求合并的demo。

并且通過實驗證明高并發(fā)下批量處理比單個處理更有優(yōu)勢。

當然,我的demo并不完善。而且請求合并也有一些弊端,比如如果定時線程池的間隔時間比較長,反而會造成請求堆積時間太長,用戶不能快速得到響應。

同時在請求數(shù)量比較小時,請求合并的場景也是沒有必要的。

總結(jié)

以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • mybatis-plus 攔截器敏感字段加解密的實現(xiàn)

    mybatis-plus 攔截器敏感字段加解密的實現(xiàn)

    數(shù)據(jù)庫在保存數(shù)據(jù)時,對于某些敏感數(shù)據(jù)需要脫敏或者加密處理,本文主要介紹了mybatis-plus 攔截器敏感字段加解密的實現(xiàn),感興趣的可以了解一下
    2021-11-11
  • Spring的Event使用及說明

    Spring的Event使用及說明

    本文介紹了Spring中的事件機制,包括事件的實現(xiàn)方式、推送、監(jiān)聽器、異步事件、事件異常處理器等,并簡要講述了Java自帶的觀察者模式和事件機制
    2026-03-03
  • 在SpringBoot接口中正確地序列化時間字段的方法

    在SpringBoot接口中正確地序列化時間字段的方法

    文章主要介紹在 Spring Boot 接口中正確序列化時間字段的方法,包括 Java 中Date和LocalDateTime類型的區(qū)別,JSON 序列化和請求參數(shù)中時間字段的處理,如時間字符串的格式配置、時間戳的使用及相關(guān)配置,還提到了在 Swagger UI 中的類型設置,需要的朋友可以參考下
    2024-11-11
  • jpa?onetomany?使用級連表刪除被維護表數(shù)據(jù)時的坑

    jpa?onetomany?使用級連表刪除被維護表數(shù)據(jù)時的坑

    這篇文章主要介紹了jpa?onetomany?使用級連表刪除被維護表數(shù)據(jù)時的坑,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-12-12
  • synchronized底層原理之JVM層面的鎖實現(xiàn)細節(jié)與流程

    synchronized底層原理之JVM層面的鎖實現(xiàn)細節(jié)與流程

    本文從JVM底層視角,詳細拆解了synchronized的實現(xiàn)邏輯,涵蓋鎖的存儲載體(對象頭的MarkWord)、鎖的觸發(fā)指令(monitorenter/monitorexit指令和ACC_SYNCHRONIZED標志位)以及鎖的調(diào)度機制,感興趣的朋友跟隨小編一起看看吧
    2026-01-01
  • 使用Java獲取文件樹的代碼實現(xiàn)

    使用Java獲取文件樹的代碼實現(xiàn)

    Java語言提供了豐富的庫和工具,使得我們可以方便地獲取和操作Java文件的語法樹(AST, Abstract Syntax Tree),在這篇博客中,我們將探討如何使用Java來獲取一個Java文件的語法樹,并展示詳細的代碼示例和運行結(jié)果,需要的朋友可以參考下
    2024-08-08
  • SpringBoot中shiro過濾器的重寫與配置詳解

    SpringBoot中shiro過濾器的重寫與配置詳解

    在前后端分離跨域訪問的項目中shiro進行權(quán)限攔截失效 (即使有正確權(quán)限的訪問也會被攔截) 時造成302重定向錯誤等問題,為解決這個問題,就需要進行shiro過濾器的重寫以及配置。本文詳細介紹了解決方法,需要的可以參考一下
    2022-04-04
  • java中靜態(tài)代碼塊與構(gòu)造方法的執(zhí)行順序判斷

    java中靜態(tài)代碼塊與構(gòu)造方法的執(zhí)行順序判斷

    對靜態(tài)代碼塊以及構(gòu)造函數(shù)的執(zhí)行先后順序,一直很迷惑,直到最近看到一段代碼,發(fā)現(xiàn)終于弄懂了,所以這篇文章主要給大家介紹了關(guān)于如何判斷java中靜態(tài)代碼塊與構(gòu)造方法的執(zhí)行順序的相關(guān)資料,需要的朋友可以參考下。
    2017-12-12
  • java中為什么要謹慎使用Arrays.asList、ArrayList的subList

    java中為什么要謹慎使用Arrays.asList、ArrayList的subList

    這篇文章主要介紹了java中為什么要謹慎使用Arrays.asList、ArrayList的subList,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2021-02-02
  • Spring數(shù)據(jù)訪問模板化方法

    Spring數(shù)據(jù)訪問模板化方法

    今天小編就為大家分享一篇關(guān)于Spring數(shù)據(jù)訪問模板化,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧
    2019-01-01

最新評論

岳普湖县| 桃源县| 南投县| 天全县| 车险| 台南县| 呼伦贝尔市| 儋州市| 乌鲁木齐县| 宣威市| 永城市| 武强县| 镇安县| 德州市| 信阳市| 乌鲁木齐县| 曲水县| 南宁市| 安图县| 清苑县| 双峰县| 石柱| 黄石市| 连云港市| 日喀则市| 金坛市| 北川| 如皋市| 东安县| 称多县| 南澳县| 芒康县| 湘西| 洞头县| 华池县| 县级市| 杭锦后旗| 邵武市| 常宁市| 阆中市| 姚安县|