文章目录
-
-
- ❓实际场景
- ❌无效方案:使用synchronized上锁
- 💡方案对比
- 方案一:原子替换(推荐)
-
- [1.1 核心实现](#1.1 核心实现)
- [1.2 定时任务配置](#1.2 定时任务配置)
- [1.3 Spring Boot 配置](#1.3 Spring Boot 配置)
- 方案二:分段处理(大数据量优化)
-
- [2.1 分段Map管理器](#2.1 分段Map管理器)
- 方案三:双缓冲方案(零等待)
-
- [3.1 双缓冲实现](#3.1 双缓冲实现)
- 方案四:带版本控制的事务方案
-
- [4.1 版本控制实现](#4.1 版本控制实现)
- [🎈 完整的生产级实现](#🎈 完整的生产级实现)
-
- [1. 综合管理器(推荐用于生产)](#1. 综合管理器(推荐用于生产))
- [2 监控端点](#2 监控端点)
- [3. 并发测试](#3. 并发测试)
-
❓实际场景
假设我们有一个场景, 它短时高发, 也就是会一瞬间出现很多数据, 这些数据需要持久化到数据库, 但是时效性又不高, 那么该如何设计呢?
思考中...
我们可以定义一个公共静态的ConcurrentMap,现将这些数据存放在map里面, 然后每隔一段时间将缓存数据批量写入数据库, 并同时将其清空。
注意点: 虽然ConcurrentMap是线程安全的,但是读取并清空这个操作需要原子性,即不能有其他线程在清空过程中插入数据导致数据丢失或重复处理。
❌无效方案:使用synchronized上锁
第一时间容易想到的是使用synchronized上锁, 但它其实是一个无效方案
示例代码:
public class CacheManager {
private static final ConcurrentMap<String, Object> cacheMap = new ConcurrentHashMap<>();
private static final Object lock = new Object();
// 定时任务调用的方法
public static Map<String, Object> getAndClear() {
synchronized (lock) {
Map<String, Object> oldMap = new HashMap<>(cacheMap);
cacheMap.clear();
return oldMap;
}
}
}
问题随之而来, 其他操作cacheMap的方法也需要加锁吗?
答案是肯定的, 如果其他方法(如put、get)也需要保证在getAndClear时不被同时执行,那么它们也需要加锁。
但是这样会降低并发性能, 因此我们需要仔细考虑是否真的需要完全互斥。
💡方案对比
| 方案 | 原理 | 优点 | 缺点 | 适用场景 |
|---|---|---|---|---|
| 原子替换 | 使用 AtomicReference原子替换整个 Map |
简单高效,无锁竞争 | 替换瞬间可能丢失少量数据 | 高并发,允许微小数据丢失 |
| 分段处理 | 将 Map 分段,分批处理 | 减少锁竞争,平滑处理 | 实现复杂,需要维护分段 | 大数据量,要求平滑处理 |
| 双缓冲 | 使用两个 Map 交替工作 | 零等待,高性能 | 内存占用翻倍 | 极高并发,不能有等待 |
| 事务快照 | 基于版本号或时间戳 | 精确控制,可回溯 | 实现复杂,存储开销 | 需要精确数据一致性 |
方案一:原子替换(推荐)
1.1 核心实现
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.atomic.AtomicReference;
import java.util.Map;
public class AtomicMapManager {
// 使用 AtomicReference 包装 ConcurrentMap
private static final AtomicReference<ConcurrentMap<String, Object>> mapRef =
new AtomicReference<>(new ConcurrentHashMap<>());
/**
* 定时任务:原子性地获取并清空Map
*/
public void scheduledClearAndProcess() {
// 原子替换:获取当前Map并替换为空Map
ConcurrentMap<String, Object> currentMap =
mapRef.getAndSet(new ConcurrentHashMap<>());
// 处理获取到的数据(此时其他线程写入新Map,不影响当前处理)
processMapData(currentMap);
}
/**
* 业务写入方法(线程安全)
*/
public void putData(String key, Object value) {
// 直接操作当前Map引用
mapRef.get().put(key, value);
}
/**
* 处理Map数据
*/
private void processMapData(ConcurrentMap<String, Object> data) {
if (data.isEmpty()) {
return;
}
// 示例:打印或持久化数据
System.out.println("Processing " + data.size() + " records:");
for (Map.Entry<String, Object> entry : data.entrySet()) {
System.out.println(entry.getKey() + ": " + entry.getValue());
// 这里可以添加数据库持久化、发送消息等逻辑
}
// 清空处理完的数据(可选,因为已经是局部变量)
data.clear();
}
}
1.2 定时任务配置
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
@Component
public class MapScheduledTask {
private final AtomicMapManager mapManager = new AtomicMapManager();
/**
* 每5分钟执行一次清理和处理
*/
@Scheduled(fixedRate = 5 * 60 * 1000) // 5分钟
public void scheduledMapProcessing() {
try {
mapManager.scheduledClearAndProcess();
} catch (Exception e) {
// 记录日志但不要抛出异常,避免定时任务终止
System.err.println("定时任务执行失败: " + e.getMessage());
}
}
/**
* 更精细的定时配置
*/
@Scheduled(cron = "0 */5 * * * ?") // 每5分钟的0秒执行
public void scheduledWithCron() {
mapManager.scheduledClearAndProcess();
}
}
1.3 Spring Boot 配置
@Configuration
@EnableScheduling
public class SchedulingConfig implements SchedulingConfigurer {
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
taskRegistrar.setScheduler(taskExecutor());
}
@Bean(destroyMethod = "shutdown")
public Executor taskExecutor() {
return Executors.newScheduledThreadPool(5);
}
}
方案二:分段处理(大数据量优化)
2.1 分段Map管理器
public class SegmentedMapManager {
// 分成多个段,减少锁竞争
private static final int SEGMENTS = 16;
private static final ConcurrentMap<String, Object>[] segmentMaps =
new ConcurrentHashMap[SEGMENTS];
static {
for (int i = 0; i < SEGMENTS; i++) {
segmentMaps[i] = new ConcurrentHashMap<>();
}
}
/**
* 根据key哈希选择段
*/
private int getSegmentIndex(String key) {
return Math.abs(key.hashCode()) % SEGMENTS;
}
/**
* 写入数据
*/
public void putData(String key, Object value) {
int segmentIndex = getSegmentIndex(key);
segmentMaps[segmentIndex].put(key, value);
}
/**
* 分段处理:减少单次处理的数据量
*/
public void scheduledSegmentProcessing() {
for (int i = 0; i < SEGMENTS; i++) {
processSegment(i);
}
}
/**
* 处理单个段
*/
private void processSegment(int segmentIndex) {
ConcurrentMap<String, Object> currentSegment = segmentMaps[segmentIndex];
// 创建新段替换旧段
ConcurrentMap<String, Object> newSegment = new ConcurrentHashMap<>();
segmentMaps[segmentIndex] = newSegment;
// 处理旧段数据
if (!currentSegment.isEmpty()) {
processMapData(currentSegment);
}
}
/**
* 并行处理所有段(性能优化)
*/
public void scheduledParallelProcessing() {
List<CompletableFuture<Void>> futures = new ArrayList<>();
for (int i = 0; i < SEGMENTS; i++) {
final int segmentIndex = i;
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
processSegment(segmentIndex);
});
futures.add(future);
}
// 等待所有段处理完成
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
}
}
方案三:双缓冲方案(零等待)
3.1 双缓冲实现
public class DoubleBufferMapManager {
// 双缓冲:两个Map交替使用
private volatile ConcurrentMap<String, Object> currentBuffer = new ConcurrentHashMap<>();
private volatile ConcurrentMap<String, Object> backBuffer = new ConcurrentHashMap<>();
// 用于原子切换缓冲区
private final AtomicBoolean isSwitching = new AtomicBoolean(false);
/**
* 写入数据(总是写入当前缓冲区)
*/
public void putData(String key, Object value) {
currentBuffer.put(key, value);
}
/**
* 切换缓冲区并处理数据
*/
public void switchAndProcess() {
// 使用CAS确保只有一个线程执行切换
if (!isSwitching.compareAndSet(false, true)) {
return; // 已经有线程在执行切换
}
try {
// 交换缓冲区引用
ConcurrentMap<String, Object> bufferToProcess = currentBuffer;
currentBuffer = backBuffer;
backBuffer = new ConcurrentHashMap<>(); // 准备新的后备缓冲区
// 处理数据
if (!bufferToProcess.isEmpty()) {
processMapData(bufferToProcess);
}
} finally {
isSwitching.set(false);
}
}
/**
* 获取当前缓冲区大小(用于监控)
*/
public int getCurrentBufferSize() {
return currentBuffer.size();
}
}
方案四:带版本控制的事务方案
4.1 版本控制实现
public class VersionedMapManager {
// 带版本号的Map条目
private static class VersionedEntry {
final Object value;
final long version;
VersionedEntry(Object value, long version) {
this.value = value;
this.version = version;
}
}
private final ConcurrentMap<String, VersionedEntry> dataMap =
new ConcurrentHashMap<>();
private final AtomicLong versionCounter = new AtomicLong(0);
/**
* 写入数据(带版本号)
*/
public void putData(String key, Object value) {
long newVersion = versionCounter.incrementAndGet();
dataMap.put(key, new VersionedEntry(value, newVersion));
}
/**
* 基于版本号获取快照
*/
public Map<String, Object> getSnapshotBefore(long maxVersion) {
Map<String, Object> snapshot = new HashMap<>();
for (Map.Entry<String, VersionedEntry> entry : dataMap.entrySet()) {
if (entry.getValue().version <= maxVersion) {
snapshot.put(entry.getKey(), entry.getValue().value);
}
}
return snapshot;
}
/**
* 清理旧版本数据
*/
public void cleanupBeforeVersion(long cleanupVersion) {
Iterator<Map.Entry<String, VersionedEntry>> iterator =
dataMap.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String, VersionedEntry> entry = iterator.next();
if (entry.getValue().version <= cleanupVersion) {
iterator.remove();
}
}
}
/**
* 定时任务:版本控制方式处理
*/
public void scheduledVersionedProcessing() {
long currentVersion = versionCounter.get();
// 获取当前版本之前的快照
Map<String, Object> snapshot = getSnapshotBefore(currentVersion);
if (!snapshot.isEmpty()) {
processMapData(new ConcurrentHashMap<>(snapshot));
// 清理已处理的数据(可选的,根据需求决定)
cleanupBeforeVersion(currentVersion);
}
}
}
🎈 完整的生产级实现
1. 综合管理器(推荐用于生产)
@Component
@Slf4j
public class ProductionMapManager {
private final AtomicReference<ConcurrentMap<String, Object>> mapRef =
new AtomicReference<>(new ConcurrentHashMap<>());
// 监控指标
private final AtomicLong totalProcessed = new AtomicLong(0);
private final AtomicLong lastProcessCount = new AtomicLong(0);
private final AtomicLong errorCount = new AtomicLong(0);
/**
* 写入数据
*/
public void put(String key, Object value) {
try {
mapRef.get().put(key, value);
} catch (Exception e) {
log.error("写入数据失败 key: {}", key, e);
errorCount.incrementAndGet();
}
}
/**
* 定时处理任务(带监控和容错)
*/
@Scheduled(fixedRate = 300000) // 5分钟
public void scheduledProcessing() {
long startTime = System.currentTimeMillis();
try {
// 原子替换Map
ConcurrentMap<String, Object> currentData =
mapRef.getAndSet(new ConcurrentHashMap<>());
int recordCount = currentData.size();
if (recordCount == 0) {
log.debug("没有数据需要处理");
return;
}
log.info("开始处理 {} 条记录", recordCount);
// 处理数据
boolean success = processWithRetry(currentData);
if (success) {
totalProcessed.addAndGet(recordCount);
lastProcessCount.set(recordCount);
log.info("成功处理 {} 条记录, 耗时 {}ms",
recordCount, System.currentTimeMillis() - startTime);
} else {
errorCount.incrementAndGet();
log.error("处理数据失败, 记录数: {}", recordCount);
}
} catch (Exception e) {
errorCount.incrementAndGet();
log.error("定时任务执行异常", e);
}
}
/**
* 带重试的数据处理
*/
private boolean processWithRetry(ConcurrentMap<String, Object> data) {
int maxRetries = 3;
for (int attempt = 1; attempt <= maxRetries; attempt++) {
try {
processMapData(data);
return true;
} catch (Exception e) {
log.warn("数据处理第 {} 次尝试失败", attempt, e);
if (attempt == maxRetries) {
log.error("数据处理最终失败,已重试 {} 次", maxRetries);
return false;
}
// 指数退避重试
try {
Thread.sleep(1000L * attempt);
} catch (InterruptedException ie) {
Thread.currentThread().interrupt();
return false;
}
}
}
return false;
}
/**
* 获取监控指标
*/
public Map<String, Object> getMetrics() {
Map<String, Object> metrics = new HashMap<>();
metrics.put("currentMapSize", mapRef.get().size());
metrics.put("totalProcessed", totalProcessed.get());
metrics.put("lastProcessCount", lastProcessCount.get());
metrics.put("errorCount", errorCount.get());
metrics.put("timestamp", System.currentTimeMillis());
return metrics;
}
/**
* 手动触发处理(用于测试或紧急情况)
*/
public boolean triggerManualProcessing() {
log.warn("手动触发数据处理");
scheduledProcessing();
return true;
}
}
2 监控端点
@RestController
@RequestMapping("/api/map-manager")
@Slf4j
public class MapManagerController {
@Autowired
private ProductionMapManager mapManager;
/**
* 获取监控指标
*/
@GetMapping("/metrics")
public ResponseEntity<Map<String, Object>> getMetrics() {
return ResponseEntity.ok(mapManager.getMetrics());
}
/**
* 手动触发处理
*/
@PostMapping("/process-now")
public ResponseEntity<String> processNow() {
try {
boolean success = mapManager.triggerManualProcessing();
return success ?
ResponseEntity.ok("处理任务已触发") :
ResponseEntity.status(500).body("处理任务触发失败");
} catch (Exception e) {
log.error("手动触发处理失败", e);
return ResponseEntity.status(500).body("处理失败: " + e.getMessage());
}
}
/**
* 健康检查端点
*/
@GetMapping("/health")
public ResponseEntity<Map<String, String>> healthCheck() {
Map<String, String> health = new HashMap<>();
health.put("status", "UP");
health.put("timestamp", Instant.now().toString());
return ResponseEntity.ok(health);
}
}
3. 并发测试
@SpringBootTest
class MapManagerTest {
@Autowired
private ProductionMapManager mapManager;
@Test
void testConcurrentAccess() throws InterruptedException {
int threadCount = 10;
int operationsPerThread = 1000;
ExecutorService executor = Executors.newFixedThreadPool(threadCount);
// 并发写入
for (int i = 0; i < threadCount; i++) {
final int threadId = i;
executor.submit(() -> {
for (int j = 0; j < operationsPerThread; j++) {
String key = "thread-" + threadId + "-key-" + j;
mapManager.put(key, "value-" + j);
}
});
}
executor.shutdown();
assertTrue(executor.awaitTermination(1, TimeUnit.MINUTES));
// 触发处理
mapManager.triggerManualProcessing();
// 验证指标
Map<String, Object> metrics = mapManager.getMetrics();
assertTrue((Long)metrics.get("totalProcessed") > 0);
}
}