java實(shí)現(xiàn)請(qǐng)求緩沖合并的示例代碼
業(yè)務(wù)背景:
我們對(duì)外提供了一個(gè)rest接口給第三方業(yè)務(wù)進(jìn)行調(diào)用,但是由于第三方框架限制,導(dǎo)致會(huì)發(fā)送大量相似無(wú)效請(qǐng)求,例如:接口入?yún)son包含兩個(gè)字段,createBy和receiverList,完整的入?yún)son示例如下:
{
"createBy": "aa",
"receiverList": [
"bb",
"cc"
]
}實(shí)際第三方業(yè)務(wù)會(huì)進(jìn)行多次調(diào)用接口,每次傳遞的數(shù)據(jù)可能如下:
{
"createBy": "aa",
"receiverList": [
"bb"
]
}
或者
{
"createBy": "aa",
"receiverList": [
"cc"
]
}
或者
{
"createBy": "bb",
"receiverList": [
"cc"
]
}
或者
{
"createBy": "aa",
"receiverList": [
"bb",
"cc"
]
}
所有需要對(duì)第三方業(yè)務(wù)傳遞過(guò)來(lái)的數(shù)據(jù)進(jìn)行緩沖合并處理,減輕真正的后臺(tái)服務(wù)的壓力。
代碼實(shí)現(xiàn)
package com.demo;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.concurrent.CustomizableThreadFactory;
import org.springframework.stereotype.Component;
import javax.annotation.PreDestroy;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* Description: 請(qǐng)求合并管理類
*/
@Slf4j
@Component
public class RequestMerger {
// 線程池核心線程數(shù)
private final int corePoolSize = 200;
// 任務(wù)執(zhí)行超時(shí)時(shí)間,單位:毫秒
private final int timeout = 5 * 60 * 1000;
// 隊(duì)列,隊(duì)列長(zhǎng)度為Integer.MAX_VALUE
private final LinkedBlockingQueue<String> requestQueue = new LinkedBlockingQueue<>();
// 定時(shí)器,所有任務(wù)共用線程池
private final ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(corePoolSize,
new CustomizableThreadFactory("schedule-executor-"));
// 是否關(guān)閉標(biāo)志
private final AtomicBoolean isShutdown = new AtomicBoolean(false);
/**
* 構(gòu)造函數(shù),用于初始化請(qǐng)求合并器。
*
* @param batchSize 每次合并的最大請(qǐng)求數(shù)量。
* @param delayMillis 合并請(qǐng)求的周期間隔,單位為毫秒。
*/
public RequestMerger(int batchSize, long delayMillis) {
// 啟動(dòng)定時(shí)器,定期合并請(qǐng)求,延遲delayMillis后開始,之后每隔delayMillis執(zhí)行一次
scheduler.scheduleAtFixedRate(() -> {
if (!isShutdown.get()) {
List<String> batch = new ArrayList<>(batchSize);
int drainedCount = requestQueue.drainTo(batch, batchSize);
log.info("==>scheduler,drainedCount:{},nowQueueCount:{}", drainedCount, requestQueue.size());
if (!batch.isEmpty()) {
// 異步執(zhí)行任務(wù),防止業(yè)務(wù)執(zhí)行時(shí)間過(guò)長(zhǎng)導(dǎo)致業(yè)務(wù)整體延遲過(guò)大
scheduler.submit(() -> {
sendRequestBatch(batch);
});
}
}
}, delayMillis, delayMillis, TimeUnit.MILLISECONDS);
}
/**
* 發(fā)送請(qǐng)求批次的方法。
*
* @param batch 請(qǐng)求批次。
*/
private void sendRequestBatch(List<String> batch) {
Future<?> future = scheduler.submit(() -> {
try {
// 在這里實(shí)現(xiàn)你的請(qǐng)求發(fā)送邏輯
// 可以使用HTTP客戶端庫(kù)(如Apache HttpClient或OkHttp)來(lái)發(fā)送請(qǐng)求
// ...
System.out.println("Sending batch of " + batch.size() + " requests");
} catch (Exception e) {
// 異常處理邏輯
System.err.println("Error sending requests: " + e.getMessage());
}
});
// 嘗試獲取任務(wù)結(jié)果,如果超過(guò)超時(shí)時(shí)間則拋出TimeoutException異常,進(jìn)行取消任務(wù)
try {
// 超時(shí)時(shí)間,單位:毫秒
future.get(timeout, TimeUnit.MILLISECONDS);
} catch (TimeoutException | ExecutionException e) {
// 超時(shí)或執(zhí)行異常時(shí)取消任務(wù)
future.cancel(true);
} catch (Exception e) {
log.error("==>任務(wù)執(zhí)行異常", e);
// 任務(wù)執(zhí)行異常
future.cancel(true);
}
}
/**
* 在對(duì)象銷毀前執(zhí)行的關(guān)閉操作。
* 該方法從請(qǐng)求隊(duì)列中拉取所有未處理的請(qǐng)求,并將它們批量發(fā)送。
* 無(wú)參數(shù)和返回值。
*/
@PreDestroy
public void shutdown() {
isShutdown.set(true);
List<String> batch = new ArrayList<>();
// 獲取請(qǐng)求隊(duì)列中的剩余所有請(qǐng)求
int drainedCount = requestQueue.drainTo(batch);
log.info("==>shutdown,drainedCount:{},nowQueueCount:{}", drainedCount, requestQueue.size());
// 批量發(fā)送收集到的剩余請(qǐng)求
sendRequestBatch(batch);
// 關(guān)閉定時(shí)執(zhí)行器
scheduler.shutdown();
try {
if (!scheduler.awaitTermination(60, TimeUnit.SECONDS)) {
log.error("Scheduler did not terminate gracefully within 60 seconds, force shutting down.");
scheduler.shutdownNow();
}
} catch (InterruptedException e) {
log.warn("Interrupted during scheduler termination, force shutting down.");
scheduler.shutdownNow();
Thread.currentThread().interrupt();
}
}
/**
* 向請(qǐng)求隊(duì)列中添加一個(gè)請(qǐng)求。如果服務(wù)未關(guān)閉,則直接添加到請(qǐng)求隊(duì)列中;
* 如果服務(wù)已關(guān)閉,則將該請(qǐng)求作為一批請(qǐng)求發(fā)送。
*
* @param request 要添加的請(qǐng)求字符串。
*/
public void addRequest(String request) throws InterruptedException {
// 檢查服務(wù)是否已關(guān)閉
if (!isShutdown.get()) {
// 未關(guān)閉,直接添加到請(qǐng)求隊(duì)列
requestQueue.put(request);
} else {
// 已關(guān)閉,將當(dāng)前請(qǐng)求作為一批發(fā)送
List<String> batch = new ArrayList<>();
batch.add(request);
sendRequestBatch(batch);
}
}
}參考資料
注意:此代碼容易導(dǎo)致數(shù)據(jù)丟失。例如:調(diào)用add方法將10個(gè)元素放入隊(duì)列,但是真正獲取到9個(gè)元素。
造成原因:FlushThread#add()中使用offer方法將數(shù)據(jù)放入隊(duì)列,如果此時(shí)隊(duì)列已滿,返回值為false,實(shí)際數(shù)據(jù)未進(jìn)入隊(duì)列,需要額外對(duì)數(shù)據(jù)進(jìn)行處理。
修改建議:調(diào)大隊(duì)列長(zhǎng)度,并且將offer方法改為put方法,保證數(shù)據(jù)最終進(jìn)入隊(duì)列。
到此這篇關(guān)于java實(shí)現(xiàn)請(qǐng)求緩沖合并的文章就介紹到這了,更多相關(guān)java請(qǐng)求緩沖合并內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
詳解SpringBoot 快速整合Mybatis(去XML化+注解進(jìn)階)
本篇文章主要介紹了詳解SpringBoot 快速整合Mybatis(去XML化+注解進(jìn)階),小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-11-11
Java結(jié)合Kotlin實(shí)現(xiàn)寶寶年齡計(jì)算
這篇文章主要為大家介紹了Java結(jié)合Kotlin實(shí)現(xiàn)寶寶年齡計(jì)算示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-06-06
SpringBoot項(xiàng)目中HTTP請(qǐng)求體只能讀一次的解決方案
在基于Spring開發(fā)Java項(xiàng)目時(shí),可能需要重復(fù)讀取HTTP請(qǐng)求體中的數(shù)據(jù),例如使用攔截器打印入?yún)⑿畔⒌?但當(dāng)我們重復(fù)調(diào)用getInputStream()或者getReader()時(shí),通常會(huì)遇到SpringBoot HTTP請(qǐng)求只讀一次的問(wèn)題,本文給出了幾種解決方案,需要的朋友可以參考下2024-08-08
Java跨環(huán)境部署的完整指南(開發(fā)/測(cè)試/生產(chǎn)配置隔離)
在現(xiàn)代軟件開發(fā)中,一次編寫,到處運(yùn)行的 Java 理念雖然廣為人知,但真正實(shí)現(xiàn) 跨環(huán)境無(wú)縫部署 卻遠(yuǎn)非易事,本文將深入探討如何在 Java 項(xiàng)目中實(shí)現(xiàn) 開發(fā)(dev)、測(cè)試(test)、生產(chǎn)(prod) 等多環(huán)境的配置隔離與部署策略,需要的朋友可以參考下2026-03-03
Java?Timer單線程下的定時(shí)任務(wù)舉例詳解
在日常的項(xiàng)目開發(fā)中,多多少少都會(huì)涉及到一些定時(shí)任務(wù)的需求,下面這篇文章主要介紹了Java?Timer單線程下定時(shí)任務(wù)的相關(guān)資料,文中通過(guò)代碼介紹的非常詳細(xì),需要的朋友可以參考下2025-10-10

