Java支撐10W高并發(fā)的架構(gòu)設(shè)計核心思路
引言
在現(xiàn)代軟件的開發(fā)中,數(shù)據(jù)處理規(guī)模超過10w的情況已是常態(tài),無論是電商平臺訂單批處理、物聯(lián)網(wǎng)設(shè)備日志分析等,無處不在挑戰(zhàn)數(shù)據(jù)處理的極限,并且,如何高效處理10w條數(shù)據(jù)也已成為如今常見但極具挑戰(zhàn)性的場景面試題,本文將闡釋這一挑戰(zhàn)的核心與架構(gòu)思路。
一.核心需求與挑戰(zhàn)
實際應(yīng)用場景
- 日志處理系統(tǒng):單日產(chǎn)生10萬+條用戶操作日志,需在1小時內(nèi)完成清洗、聚合、存儲
- 批量計算任務(wù):每月用戶賬單生成,涉及10萬用戶的數(shù)據(jù)計算
- 實時分析系統(tǒng):廣告點擊流實時分析,每秒處理千條數(shù)據(jù)
- 數(shù)據(jù)同步作業(yè):跨系統(tǒng)數(shù)據(jù)遷移,單表10萬記錄同步
核心性能瓶頸
- CPU瓶頸:復(fù)雜計算、序列化/反序列化
- 內(nèi)存瓶頸:大對象持有、內(nèi)存泄漏
- I/O瓶頸:數(shù)據(jù)庫查詢、文件讀寫
- 網(wǎng)絡(luò)瓶頸:跨服務(wù)調(diào)用、帶寬限制
想讓程序一次性執(zhí)行10w條數(shù)據(jù)肯定是不可能的,所以我們必然要對整個過程進行優(yōu)化,一般來說,我們可以先從內(nèi)存和時間復(fù)雜度的角度去考慮問題。
二.選擇高效的數(shù)據(jù)結(jié)構(gòu)
首先是數(shù)據(jù)的存儲,我們可以選擇更加高效的數(shù)據(jù)結(jié)構(gòu),如查詢復(fù)雜度O(1)的Map,接下來我們從時間復(fù)雜度和內(nèi)存優(yōu)化分別來解決數(shù)據(jù)存儲時的高額消費。
時間復(fù)雜度優(yōu)化
我們一般使用List去存儲數(shù)據(jù),不過這樣會導(dǎo)致一個問題就是查詢的時候復(fù)雜度達到了O(n)的時間復(fù)雜度,并且處理數(shù)據(jù)不一定要求有序,我們何不使用Map去存儲數(shù)據(jù)。
// 不推薦:O(n)查找
List<User> userList = new ArrayList<>();
User findUser = userList.stream()
.filter(u -> u.getId().equals(targetId))
.findFirst()
.orElse(null);
// 推薦:O(1)查找
Map<String, User> userMap = new HashMap<>(100000 * 4/3 + 1); // 預(yù)分配
User findUser = userMap.get(targetId);內(nèi)存優(yōu)化
- 預(yù)分配內(nèi)存:通過在new對象時指定容量防止擴容機制發(fā)生
- 使用原始類型集合:相比于包裝類內(nèi)存占用減少10倍
// 預(yù)分配容量避免擴容 Map<String, Object> dataMap = new HashMap<>(131072); // 2的冪附近 // 使用原始類型集合 IntArrayList fastIntList = new IntArrayList(100000);
三.合理利用并發(fā)編程
處理完數(shù)據(jù)存儲上的缺陷后,還是無法做到同時處理10w條數(shù)據(jù),我們可以利用java中的并發(fā)編程讓多個線程同時去處理數(shù)據(jù),這樣,假設(shè)有10個線程啟動,那么每個線程只需處理1w個數(shù)據(jù)即可。所以,合理的并發(fā)編程使用極大地幫助我們?nèi)ヌ幚?0w條數(shù)據(jù)。
線程池合理配置
為了能夠最大化利用線程資源,自定義線程池是不二之選,所以線程池的核心參數(shù)配置就尤為重要了,下面我們來展示下配置的示例。
ThreadPoolExecutor executor = new ThreadPoolExecutor(
Runtime.getRuntime().availableProcessors(), // 核心線程數(shù)
Runtime.getRuntime().availableProcessors() * 2, // 最大線程數(shù)
60L, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(10000), // 有界隊列防止內(nèi)存溢出
new NamedThreadFactory("data-processor"),
new ThreadPoolExecutor.CallerRunsPolicy() // 飽和策略
);Runtime.getRuntime().availableProcessors()是配置的關(guān)鍵它的核心價值在于:
- 動態(tài)獲取CPU資源:適應(yīng)不同硬件環(huán)境
- 指導(dǎo)并發(fā)度設(shè)置:線程池、連接池大小
- 實現(xiàn)彈性伸縮:容器化環(huán)境自動適配
- 避免資源浪費:防止過度分配線程
四.分批處理與流式處理
智能分片策略
對數(shù)據(jù)的處理中,往往涉及與數(shù)據(jù)庫的操作,如對訂單的修改或者對數(shù)據(jù)處理狀態(tài)的記錄等。這時我們?nèi)绻粭l一條地修改數(shù)據(jù)庫信息,將會造成頻繁的數(shù)據(jù)庫連接造成較大數(shù)據(jù)庫壓力,此時,我們可以將多條數(shù)據(jù)分批次進行操作。
public class DataShardProcessor {
public void processInBatches(List<Data> allData, int batchSize) {
int total = allData.size();
for (int from = 0; from < total; from += batchSize) {
int to = Math.min(from + batchSize, total);
List<Data> batch = allData.subList(from, to);
// 動態(tài)調(diào)整批次大小
int optimalSize = calculateOptimalBatchSize(batch);
// 對數(shù)據(jù)進行分批操作
processBatch(batch, optimalSize);
}
}
private int calculateOptimalBatchSize(List<Data> batch) {
// 基于數(shù)據(jù)大小、處理復(fù)雜度動態(tài)計算
return Math.max(100, Math.min(1000, 1000000 / batch.get(0).estimatedSize()));
}
}parallelStream并行流處理
平時我們習(xí)慣使用stream流式處理數(shù)據(jù),底層是用單線程順序執(zhí)行任務(wù),當(dāng)遇到map等操作時就會去遍歷整個結(jié)構(gòu),相當(dāng)耗時,所以,我們可以使用parallelStream并行流的方式來提高cpu的利用率,通過將任務(wù)拆解后合并來完成任務(wù)。
List<Result> results = dataList.parallelStream()
.collect(Collectors.groupingByConcurrent(
Data::getCategory, // 并發(fā)分組
Collectors.mapping(this::transform, Collectors.toList())
))
.values().parallelStream()
.flatMap(List::stream)
.collect(Collectors.toList());五.消息隊列解耦
消息隊列不僅能作為解耦上游接收數(shù)據(jù)和下游處理數(shù)據(jù)的中間層,也是數(shù)據(jù)的緩沖區(qū)以避免下游的系統(tǒng)被沖垮。我們可以通過部署多個worker服務(wù)作為消息隊列的消費者,并發(fā)地去隊列里獲取并處理數(shù)據(jù)。
我們使用RocketMQ來演示下基本的配置
# RabbitMQ配置優(yōu)化
spring:
rabbitmq:
host: localhost
port: 5672
# 10萬條數(shù)據(jù)需調(diào)優(yōu)
connection:
connection-timeout: 10000
template:
retry:
enabled: true
max-attempts: 3
listener:
direct:
prefetch: 50 # 單次拉取數(shù)量
concurrency: 4-8 # 消費者數(shù)量
max-concurrency: 8
simple:
concurrency: 4-8
max-concurrency: 8
retry:
max-attempts: 3
stateless: true
六.關(guān)鍵監(jiān)控指標(biāo)和優(yōu)化
關(guān)鍵監(jiān)控指標(biāo)
- 吞吐量指標(biāo):QPS、TPS、數(shù)據(jù)量/秒
- 延遲指標(biāo):P50、P90、P99、P999
- 資源指標(biāo):CPU使用率、GC時間、堆內(nèi)存、I/O等待
- 業(yè)務(wù)指標(biāo):處理成功率、錯誤類型分布
JVM調(diào)優(yōu)關(guān)鍵參數(shù)
# 10萬條數(shù)據(jù)處理的JVM建議配置
java -Xms4g -Xmx4g -Xmn2g \ # 固定堆大小避免波動
-XX:MaxDirectMemorySize=1g \ # 直接內(nèi)存
-XX:+UseG1GC -XX:MaxGCPauseMillis=200 \ # 低延遲GC
-XX:InitiatingHeapOccupancyPercent=35 \ # 早啟動GC
-XX:ParallelGCThreads=4 -XX:ConcGCThreads=2
以上就是Java支撐10W高并發(fā)的架構(gòu)設(shè)計核心思路的詳細(xì)內(nèi)容,更多關(guān)于Java高并發(fā)架構(gòu)設(shè)計的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
淺談springboot之JoinPoint的getSignature方法
這篇文章主要介紹了springboot之JoinPoint的getSignature方法,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-06-06
feignclient?https?接口調(diào)用報證書錯誤的解決方案
這篇文章主要介紹了feignclient?https?接口調(diào)用報證書錯誤的解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2022-03-03

