高并发系统的架构设计模式——缓存、异步、削峰填谷与最终一致性

发布时间:2026/7/19 17:17:03
高并发系统的架构设计模式——缓存、异步、削峰填谷与最终一致性 高并发系统的架构设计模式——缓存、异步、削峰填谷与最终一致性一、高并发的本质从请求量到系统约束的转换高并发系统的设计表面上是应对每秒 N 万请求的问题实质上是在有限的计算资源、有限的 I/O 带宽和有限的一致性窗口之间寻求最优解。一个请求从到达网关到返回响应中间经过的每一个环节都有其固有开销——CPU 计算、内存访问、磁盘 I/O、网络往返——而高并发架构的本质就是用空间换时间、用异步换吞吐、用最终一致性换可用性。在开始深入具体的模式之前先明确一个基本的判断框架任何高并发优化手段都可以归结为以下四类之一减少单位请求的资源消耗缓存、连接复用、零拷贝削平瞬时流量峰值消息队列、限流、排队提高系统的并行处理能力异步化、多级线程池、虚拟线程放宽一致性约束最终一致性、读写分离、CQRS理解了这个框架就会发现大量看似不同的优化手段其实在遵循相同的底层逻辑。二、高并发模式全景从流量入口到数据落地的完整链路典型的电商系统流量处理链路从 CDN 到数据库每一层都在进行流量削减CDN 和缓存拦截了 90% 以上的读请求消息队列将瞬时写峰值摊平为稳态吞吐读写分离则进一步释放了数据库的计算资源。具体链路如下客户端请求首先经过 CDN/边缘缓存缓存命中则直接返回未命中则进入 API 网关API 网关进行限流熔断检查拒绝则降级响应放行则进入本地缓存 L1本地缓存命中则返回结果未命中则查询分布式缓存 L2分布式缓存命中则更新 L1 并返回未命中则进入业务服务处理写操作通过消息队列进行异步处理读操作直接访问数据库数据库层采用主从架构主库负责写入通过 Binlog 同步到从库从库负责读取并进行数据一致性校验三、多级缓存架构从本地到分布式的逐级穿透控制缓存是高并发架构的第一道防线。但许多团队的缓存策略停留在给热点数据加个 Redis的层面缺少对缓存穿透、缓存击穿、缓存雪崩的系统性防范。本地缓存Caffeine提供纳秒级访问延迟但受限于单机内存容量和跨实例一致性。适合写少读多、对一致性要求不高的配置类和字典类数据。分布式缓存Redis提供微秒级访问延迟容量可水平扩展但需要处理网络开销和连接池管理。两级缓存L1 L2结合了二者的优势Caffeine 做 L1TTL 设为秒级Redis 做 L2TTL 设为分钟级。数据一致性通过消息广播Redis Pub/Sub 或 MQ来保证。/** * 两级缓存管理器CaffeineL1 RedisL2 * 实现缓存穿透保护、热点数据预热和一致性控制 */ Component public class TwoLevelCacheManager { private final CacheString, Object localCache; private final RedisTemplateString, Object redisTemplate; // 空值占位符防止缓存穿透 private static final Object NULL_PLACEHOLDER new Object(); public TwoLevelCacheManager(RedisTemplateString, Object redisTemplate) { this.redisTemplate redisTemplate; // L1 本地缓存最大 10000 条写入后 30 秒过期 this.localCache Caffeine.newBuilder() .maximumSize(10_000) .expireAfterWrite(30, TimeUnit.SECONDS) .recordStats() // 开启统计用于监控命中率 .build(); } /** * 两级缓存读取逐级穿透 * L1 未命中 - L2 查询 - 数据库回源 - 逐级回填 */ SuppressWarnings(unchecked) public T T get(String key, ClassT type, FunctionString, T dbLoader) { // 1. 查询 L1 本地缓存 Object l1Value localCache.getIfPresent(key); if (l1Value ! null) { if (l1Value NULL_PLACEHOLDER) { return null; // 命中了空值标记直接返回 null } return (T) l1Value; } // 2. 查询 L2 分布式缓存 try { Object l2Value redisTemplate.opsForValue().get(key); if (l2Value ! null) { if (l2Value NULL_PLACEHOLDER) { localCache.put(key, NULL_PLACEHOLDER); return null; } // L2 命中回填 L1 localCache.put(key, l2Value); return (T) l2Value; } } catch (Exception e) { // Redis 不可用时降级跳过 L2直接回源数据库 log.warn(Redis 查询异常降级直接回源数据库, key: {}, key, e); } // 3. 回源数据库加分布式锁防止缓存击穿 T dbValue loadFromDbWithLock(key, dbLoader); // 4. 回填两级缓存 if (dbValue ! null) { localCache.put(key, dbValue); try { redisTemplate.opsForValue().set(key, dbValue, 5, TimeUnit.MINUTES); } catch (Exception e) { log.warn(Redis 写入失败仅更新 L1 缓存, key: {}, key, e); } } else { // 缓存空值防止穿透TTL 较短 localCache.put(key, NULL_PLACEHOLDER); try { redisTemplate.opsForValue().set(key, NULL_PLACEHOLDER, 60, TimeUnit.SECONDS); } catch (Exception ignored) { // 空值缓存写入失败不影响业务 } } return dbValue; } /** * 基于 Redis 分布式锁的回源加载防止缓存击穿 * 同一时刻只有一个线程执行数据库查询 */ private T T loadFromDbWithLock(String key, FunctionString, T dbLoader) { String lockKey lock:cache: key; try { Boolean locked redisTemplate.opsForValue() .setIfAbsent(lockKey, 1, 10, TimeUnit.SECONDS); if (Boolean.TRUE.equals(locked)) { try { return dbLoader.apply(key); } finally { redisTemplate.delete(lockKey); } } else { // 未获取到锁短暂等待后重试 L2 缓存 Thread.sleep(100); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } catch (Exception e) { log.error(分布式锁操作异常, key: {}, key, e); } // 降级直接回源小概率并发穿透但不会阻塞 return dbLoader.apply(key); } }四、削峰填谷与异步化消息队列的工程实践消息队列是削峰填谷的核心工具。在秒杀、大促等场景下前端流量可能是日常的 10~100 倍如果所有请求都同步写入数据库数据库瞬间就会被压垮。消息队列的作用是将同步的写入压力转换为异步的消费过程。生产者在成功投递消息后即可返回消费者按自己的处理能力匀速消费。这样数据库面对的就不再是峰值 QPS而是消费者线程数 × 单线程处理能力的稳态值。但在实践中消息队列引入了三个新的问题消息可靠性生产者需要确认机制ACK 事务消息RocketMQ 支持保证投递成功消费者需要幂等处理防止重复消费。延迟增加异步化本质上是用延迟换吞吐。对于强实时性要求的场景如余额查询不能简单地走消息队列需要配合缓存做优先级路由。顺序性严格顺序的消息需要分区有序Partition Orderly如通过用户 ID 哈希分区或者使用单线程消费者。五、最终一致性的工程落地与架构取舍高并发系统的最后一道防线是最终一致性。当缓存、异步、削峰填谷都部署到位后剩下的问题就是在分布式事务无法保证强一致性的约束下如何确保数据最终收敛到正确状态最终一致性的工程落地需要三个组件配合幂等性所有写操作必须支持幂等通过业务唯一键或分布式事务 ID使重复执行不会产生副作用。补偿机制通过定时任务扫描中间状态的数据对超时未完成的交易进行补偿或回滚。对账系统定期全量或增量对账找出差异并告警——这是兜底保障任何分布式系统在极端情况下都可能出现数据不一致。一个核心原则是优先保证可用性在可用性不妥协的前提下通过异步机制逼近数据一致性。这个原则决定了方案选择的方向——对于支付场景即便牺牲部分性能也必须保证最终一致性对于日志采集场景可以允许少量丢失但必须保证高吞吐。高并发架构的设计没有银弹只有理解了每种模式背后的权衡Trade-off才能针对具体场景做出恰当的决策。