Java如何利用線程池和Redis實(shí)現(xiàn)高效數(shù)據(jù)入庫(kù)
利用線程池和Redis實(shí)現(xiàn)高效數(shù)據(jù)入庫(kù)
在高并發(fā)環(huán)境中,進(jìn)行數(shù)據(jù)入庫(kù)是一項(xiàng)具有挑戰(zhàn)性的任務(wù)。
本文將介紹如何利用線程池和Redis實(shí)現(xiàn)數(shù)據(jù)的實(shí)時(shí)緩存和批量入庫(kù)處理,確保系統(tǒng)的性能和穩(wěn)定性。
主要思路和組件介紹
思路概述
在高并發(fā)情況下,數(shù)據(jù)入庫(kù)需要解決兩個(gè)主要問題:實(shí)時(shí)性和穩(wěn)定性。
通過將數(shù)據(jù)首先存儲(chǔ)在Redis緩存中,可以快速響應(yīng)和處理大量的數(shù)據(jù)請(qǐng)求,然后利用線程池定期批量將數(shù)據(jù)從Redis取出并入庫(kù),以減少數(shù)據(jù)庫(kù)壓力和提高整體性能。
主要組件
- BatchDataStorageService:核心服務(wù)類,負(fù)責(zé)數(shù)據(jù)的實(shí)時(shí)緩存和定期批量入庫(kù)處理。
- CacheService:簡(jiǎn)易緩存服務(wù)類,使用ConcurrentHashMap實(shí)現(xiàn)內(nèi)存緩存,用于快速存取和處理數(shù)據(jù)。
- RedisUtils:封裝了對(duì)Redis的操作,用于數(shù)據(jù)的持久化存儲(chǔ)和高速讀取。
- BatchWorker:實(shí)現(xiàn)了Runnable接口,處理從Redis中讀取數(shù)據(jù)并執(zhí)行批量入庫(kù)的任務(wù)。
- BatchTimeoutCommitThread:定時(shí)監(jiān)控?cái)?shù)據(jù)是否達(dá)到設(shè)定的批次或超時(shí)時(shí)間,并觸發(fā)數(shù)據(jù)入庫(kù)操作。
詳細(xì)代碼解析
- BatchDataStorageService
package io.jack.service.impl;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* 數(shù)據(jù)批量入庫(kù)服務(wù)類
*/
@Component
@Slf4j
public class BatchDataStorageService implements InitializingBean {
/**
* 最大批次數(shù)量
*/
@Value("${app.db.maxBatchCount:800}")
private int maxBatchCount;
/**
* 最大線程數(shù)
*/
@Value("${app.db.maxBatchThreads:100}")
private int maxBatchThreads;
/**
* 超時(shí)時(shí)間,單位毫秒
*/
@Value("${app.db.batchTimeout:3000}")
private int batchTimeout;
/**
* 當(dāng)前批次數(shù)量
*/
private int batchCount = 0;
/**
* 當(dāng)前批次號(hào)
*/
private static long batchNo = 0;
/**
* 線程池執(zhí)行器
*/
private ExecutorService executorService = null;
/**
* 緩存服務(wù)
*/
@Resource
private CacheService cacheService;
/**
* 設(shè)備實(shí)時(shí)服務(wù)
*/
@Resource
private DeviceRealTimeService deviceRealTimeService;
/**
* Redis工具類
*/
@Resource
private RedisUtils redisUtils;
/**
* 初始化線程池
*/
@Override
public void afterPropertiesSet() {
executorService = Executors.newFixedThreadPool(maxBatchThreads);
}
/**
* 保存設(shè)備實(shí)時(shí)數(shù)據(jù)
*
* @param deviceRealTimeDTO 設(shè)備實(shí)時(shí)數(shù)據(jù)傳輸對(duì)象
*/
public void saveRealTimeData(DeviceRealTimeDTO deviceRealTimeDTO) {
final String failedCacheKey = "device:real_time:failed_records";
try {
// 生成批次和持續(xù)時(shí)間的緩存鍵
String durationKey = "device:real_time:batchDuration" + batchNo;
String batchKey = "device:real_time:batch" + batchNo;
// 如果當(dāng)前批次持續(xù)時(shí)間不存在,則創(chuàng)建并啟動(dòng)超時(shí)處理線程
if (!cacheService.exists(durationKey)) {
cacheService.put(durationKey, System.currentTimeMillis());
new BatchTimeoutCommitThread(batchKey, durationKey, failedCacheKey).start();
}
// 將設(shè)備實(shí)時(shí)數(shù)據(jù)加入當(dāng)前批次
cacheService.lPush(batchKey, deviceRealTimeDTO);
if (++batchCount >= maxBatchCount) {
// 達(dá)到最大批次,執(zhí)行入庫(kù)邏輯
dataStorage(durationKey, batchKey, failedCacheKey);
}
} catch (Exception ex) {
log.warn("[DB:FAILED] 設(shè)備上報(bào)記錄入批處理集合異常: " + ex.getMessage() + ", DeviceRealTimeDTO: " + JSON.toJSONString(deviceRealTimeDTO), ex);
cacheService.lPush(failedCacheKey, deviceRealTimeDTO);
} finally {
updateRealTimeData(deviceRealTimeDTO);
}
}
/**
* 更新實(shí)時(shí)數(shù)據(jù)到Redis
*
* @param deviceRealTimeDTO 設(shè)備實(shí)時(shí)數(shù)據(jù)傳輸對(duì)象
*/
private void updateRealTimeData(DeviceRealTimeDTO deviceRealTimeDTO) {
redisUtils.set("real_time:" + deviceRealTimeDTO.getDeviceId(), JSONArray.toJSONString(deviceRealTimeDTO));
}
/**
* 批量入庫(kù)處理
*
* @param durationKey 持續(xù)時(shí)間標(biāo)識(shí)
* @param batchKey 批次標(biāo)識(shí)
* @param failedCacheKey 錯(cuò)誤記錄標(biāo)識(shí)
*/
private void dataStorage(String durationKey, String batchKey, String failedCacheKey) {
batchNo++;
batchCount = 0;
cacheService.del(durationKey);
if (batchNo >= Long.MAX_VALUE) {
batchNo = 0;
}
executorService.execute(new BatchWorker(batchKey, failedCacheKey));
}
/**
* 批量工作線程
*/
private class BatchWorker implements Runnable {
private final String failedCacheKey;
private final String batchKey;
public BatchWorker(String batchKey, String failedCacheKey) {
this.batchKey = batchKey;
this.failedCacheKey = failedCacheKey;
}
@Override
public void run() {
final List<DeviceRealTimeDTO> deviceRealTimeDTOList = new ArrayList<>();
try {
// 從緩存中獲取批量數(shù)據(jù)
DeviceRealTimeDTO deviceRealTimeDTO = cacheService.lPop(batchKey);
while (deviceRealTimeDTO != null) {
deviceRealTimeDTOList.add(deviceRealTimeDTO);
deviceRealTimeDTO = cacheService.lPop(batchKey);
}
long timeMillis = System.currentTimeMillis();
try {
// 將DTO轉(zhuǎn)換為實(shí)體對(duì)象并批量入庫(kù)
List<DeviceRealTimeEntity> deviceRealTimeEntityList = ConvertUtils.sourceToTarget(deviceRealTimeDTOList, DeviceRealTimeEntity.class);
deviceRealTimeService.insertBatch(deviceRealTimeEntityList);
} finally {
cacheService.del(batchKey);
log.info("[DB:BATCH_WORKER] 批次:" + batchKey + ",保存設(shè)備上報(bào)記錄數(shù):" + deviceRealTimeDTOList.size() + ", 耗時(shí):" + (System.currentTimeMillis() - timeMillis) + "ms");
}
} catch (Exception e) {
log.warn("[DB:FAILED] 設(shè)備上報(bào)記錄批量入庫(kù)失?。? + e.getMessage() + ", DeviceRealTimeDTO: " + deviceRealTimeDTOList.size(), e);
for (DeviceRealTimeDTO deviceRealTimeDTO : deviceRealTimeDTOList) {
cacheService.lPush(failedCacheKey, deviceRealTimeDTO);
}
}
}
}
/**
* 批次超時(shí)提交線程
*/
class BatchTimeoutCommitThread extends Thread {
private final String batchKey;
private final String durationKey;
private final String failedCacheKey;
public BatchTimeoutCommitThread(String batchKey, String durationKey, String failedCacheKey) {
this.batchKey = batchKey;
this.durationKey = durationKey;
this.failedCacheKey = failedCacheKey;
this.setName("batch-thread-" + batchKey);
}
@Override
public void run() {
try {
Thread.sleep(batchTimeout);
} catch (InterruptedException e) {
log.error("[DB] 內(nèi)部錯(cuò)誤,直接提交:" + e.getMessage());
}
if (cacheService.exists(durationKey)) {
// 達(dá)到最大批次的超時(shí)間,執(zhí)行入庫(kù)邏輯
dataStorage(durationKey, batchKey, failedCacheKey);
}
}
}
}- CacheService
package io.jack.service;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.stereotype.Component;
import java.util.LinkedList;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicLong;
/**
* 緩存服務(wù)類,提供簡(jiǎn)易的緩存機(jī)制
*/
@Component
public class CacheService implements InitializingBean {
/**
* 內(nèi)存緩存,用于存儲(chǔ)數(shù)據(jù)
*/
private Map<String, Object> objectCache = new ConcurrentHashMap<>();
/**
* 統(tǒng)計(jì)緩存,用于統(tǒng)計(jì)計(jì)數(shù)
*/
private Map<String, AtomicLong> statCache = new ConcurrentHashMap<>();
/**
* 初始化統(tǒng)計(jì)緩存
*/
@Override
public void afterPropertiesSet() {
statCache.put("terminals", new AtomicLong(0));
statCache.put("connections", new AtomicLong(0));
}
/**
* 增加指定統(tǒng)計(jì)項(xiàng)的計(jì)數(shù)
*
* @param statName 統(tǒng)計(jì)項(xiàng)名稱
* @return 增加后的計(jì)數(shù)值
*/
public long incr
(String statName) {
statCache.putIfAbsent(statName, new AtomicLong(0));
return statCache.get(statName).incrementAndGet();
}
/**
* 減少指定統(tǒng)計(jì)項(xiàng)的計(jì)數(shù)
*
* @param statName 統(tǒng)計(jì)項(xiàng)名稱
* @return 減少后的計(jì)數(shù)值
*/
public long decr(String statName) {
statCache.putIfAbsent(statName, new AtomicLong(0));
return statCache.get(statName).decrementAndGet();
}
/**
* 獲取指定統(tǒng)計(jì)項(xiàng)的當(dāng)前計(jì)數(shù)值
*
* @param statName 統(tǒng)計(jì)項(xiàng)名稱
* @return 當(dāng)前計(jì)數(shù)值
*/
public long stat(String statName) {
statCache.putIfAbsent(statName, new AtomicLong(0));
return statCache.get(statName).get();
}
/**
* 存儲(chǔ)數(shù)據(jù)
*
* @param key 緩存鍵
* @param object 緩存數(shù)據(jù)
*/
public <T> void put(String key, T object) {
objectCache.put(key, object);
}
/**
* 獲取數(shù)據(jù)
*
* @param key 緩存鍵
* @return 緩存數(shù)據(jù)
*/
public <T> T get(String key) {
return (T) objectCache.get(key);
}
/**
* 刪除數(shù)據(jù)
*
* @param key 緩存鍵
*/
public void remove(String key) {
objectCache.remove(key);
}
/**
* 存儲(chǔ)哈希表數(shù)據(jù)
*
* @param key 哈希表鍵
* @param subkey 哈希表子鍵
* @param value 哈希表值
*/
public void hSet(String key, String subkey, Object value) {
synchronized (objectCache) {
Map<String, Object> submap = (Map<String, Object>) objectCache.computeIfAbsent(key, k -> new ConcurrentHashMap<>());
submap.put(subkey, value);
}
}
/**
* 獲取哈希表數(shù)據(jù)
*
* @param key 哈希表鍵
* @param subkey 哈希表子鍵
* @return 哈希表值
*/
public <T> T hGet(String key, String subkey) {
synchronized (objectCache) {
Map<String, Object> submap = (Map<String, Object>) objectCache.get(key);
return submap != null ? (T) submap.get(subkey) : null;
}
}
/**
* 判斷哈希表子鍵是否存在
*
* @param key 哈希表鍵
* @param subkey 哈希表子鍵
* @return 是否存在
*/
public boolean hExists(String key, String subkey) {
synchronized (objectCache) {
Map<String, Object> submap = (Map<String, Object>) objectCache.get(key);
return submap != null && submap.containsKey(subkey);
}
}
/**
* 將數(shù)據(jù)推入列表
*
* @param key 列表鍵
* @param value 列表值
*/
public void lPush(String key, Object value) {
synchronized (objectCache) {
LinkedList<Object> queue = (LinkedList<Object>) objectCache.computeIfAbsent(key, k -> new LinkedList<>());
queue.addLast(value);
}
}
/**
* 從列表中彈出數(shù)據(jù)
*
* @param key 列表鍵
* @return 列表值
*/
public <T> T lPop(String key) {
synchronized (objectCache) {
LinkedList<Object> queue = (LinkedList<Object>) objectCache.get(key);
return queue != null && !queue.isEmpty() ? (T) queue.removeLast() : null;
}
}
/**
* 刪除緩存數(shù)據(jù)
*
* @param key 緩存鍵
*/
public void del(String key) {
objectCache.remove(key);
}
/**
* 判斷緩存鍵是否存在
*
* @param key 緩存鍵
* @return 是否存在
*/
public boolean exists(String key) {
return objectCache.containsKey(key);
}
}詳細(xì)講解
BatchDataStorageService
字段和初始化:
maxBatchCount:配置文件中指定的最大批次大小,用于控制每批處理的數(shù)據(jù)量。maxBatchThreads:線程池的最大線程數(shù),影響處理并發(fā)能力。batchTimeout:批次超時(shí)時(shí)間,用于控制數(shù)據(jù)處理的最遲時(shí)間。batchCount:當(dāng)前批次中的數(shù)據(jù)條數(shù),用于判斷是否需要提交批次數(shù)據(jù)。batchNo:批次號(hào),用于標(biāo)識(shí)不同的批次。executorService:用于執(zhí)行批量處理任務(wù)的線程池。cacheService、deviceRealTimeService、redisUtils:分別用于緩存操作、數(shù)據(jù)存儲(chǔ)和 Redis 操作。
方法詳解:
afterPropertiesSet:初始化線程池,以便在后續(xù)操作中執(zhí)行批量處理任務(wù)。saveRealTimeData:- 將實(shí)時(shí)數(shù)據(jù)推入緩存中,檢查是否需要提交批次數(shù)據(jù)。
- 如果超時(shí)或數(shù)據(jù)量達(dá)到閾值,則調(diào)用
dataStorage方法處理數(shù)據(jù)。
updateRealTimeData:將數(shù)據(jù)更新到 Redis,確保實(shí)時(shí)數(shù)據(jù)的可用性。dataStorage:- 執(zhí)行批量數(shù)據(jù)的存儲(chǔ)操作,并提交工作線程處理數(shù)據(jù)。
BatchWorker:- 從緩存中獲取數(shù)據(jù)并執(zhí)行入庫(kù)操作,將成功的數(shù)據(jù)記錄日志,將失敗的數(shù)據(jù)放入失敗緩存。
BatchTimeoutCommitThread:- 處理批次超時(shí)邏輯,即使在未滿批次的情況下也會(huì)提交數(shù)據(jù),確保數(shù)據(jù)及時(shí)處理。
CacheService
字段:
objectCache:用于存儲(chǔ)普通緩存數(shù)據(jù)。statCache:用于存儲(chǔ)統(tǒng)計(jì)數(shù)據(jù),例如計(jì)數(shù)器等。
方法詳解:
put/get/remove:基本的緩存操作,支持存儲(chǔ)、獲取和刪除數(shù)據(jù)。hSet/hGet/hExists:- 對(duì)哈希表進(jìn)行操作,支持設(shè)置、獲取和檢查鍵值對(duì)。
lPush/lPop:- 對(duì)列表進(jìn)行操作,支持推入和彈出數(shù)據(jù)。
incr/decr/stat:- 對(duì)統(tǒng)計(jì)數(shù)據(jù)進(jìn)行操作,支持增加、減少和獲取當(dāng)前值。
總結(jié)
本文介紹了如何在高并發(fā)環(huán)境下利用線程池和Redis實(shí)現(xiàn)高效的數(shù)據(jù)入庫(kù)。通過將數(shù)據(jù)首先存入Redis緩存,并利用線程池定期批量處理入庫(kù)操作,能夠有效提升系統(tǒng)的性能和穩(wěn)定性。完整代碼包括核心的BatchDataStorageService服務(wù)類、CacheService緩存服務(wù)類以及RedisUtils工具類,均提供了詳細(xì)的注釋和解析,以便讀者理解和實(shí)現(xiàn)類似的高并發(fā)數(shù)據(jù)處理系統(tǒng)
以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。
相關(guān)文章
Java中@Pattern注解常用的校驗(yàn)正則表達(dá)式學(xué)習(xí)筆記
對(duì)于正則這個(gè)東西,對(duì)我來說一直是很懵逼的,每次用每次查,然后還是記不住,下面這篇文章主要給大家介紹了關(guān)于Java中@Pattern注解常用的校驗(yàn)正則表達(dá)式學(xué)習(xí)筆記的相關(guān)資料,需要的朋友可以參考下2022-07-07
使用IntelliJ IDEA調(diào)式Stream流的方法步驟
本文主要介紹了使用IntelliJ IDEA調(diào)式Stream流的方法步驟,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-05-05
Springboot整合hibernate validator 全局異常處理步驟詳解
本文分步驟給大家介紹Springboot整合hibernate validator 全局異常處理,補(bǔ)呢文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧2024-01-01
SpringBoot+actuator和admin-UI實(shí)現(xiàn)監(jiān)控中心方式
這篇文章主要介紹了SpringBoot+actuator和admin-UI實(shí)現(xiàn)監(jiān)控中心方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-05-05
Netty事件循環(huán)主邏輯NioEventLoop的run方法分析
這篇文章主要介紹了Netty事件循環(huán)主邏輯NioEventLoop的run方法分析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2022-03-03
Java中replace與replaceAll的區(qū)別與測(cè)試
replace和replaceAll是JAVA中常用的替換字符的方法,下面這篇文章主要給大家介紹了關(guān)于Java中replace與replaceAll的區(qū)別與測(cè)試,文中通過實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-09-09
Java中Double除保留后小數(shù)位的幾種方法(小結(jié))
這篇文章主要介紹了Java中Double保留后小數(shù)位的幾種方法,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-07-07

