高并发架构设计
本篇笔记主要整理与思考高并发架构设计中常见的系统瓶颈、安全隐患,并重点分析双写一致性、并发数据竞争以及 Redis 缓存设计的避坑方案。
2. 核心挑战与问题
Section titled “2. 核心挑战与问题”2.1. 系统性能问题
Section titled “2.1. 系统性能问题”- 服务端吞吐压力:由于计算资源、网卡带宽或线程池等限制导致高并发下响应变慢,甚至服务雪崩。
- 解决方案:引入负载均衡(Nginx/LVS)、微服务分布式部署、服务限流(Sentinel/RateLimiter)与降级、应用代码异步化调优。
- 数据库吞吐压力:高并发直达数据库,引发连接池爆满、磁盘 I/O 锁死。
- 解决方案:引入 Redis/Memcached 缓存架构,实施数据库分库分表、读写分离、主从架构分区,并进行慢 SQL 优化与索引重构。
2.2. 数据一致性问题
Section titled “2.2. 数据一致性问题”在高并发双写场景下,由于网络延迟与并发交错,容易引发 缓存与数据库数据不一致 以及多线程下的 资源竞争。
2.3. 消息队列问题
Section titled “2.3. 消息队列问题”在高并发引入异步队列(MQ)进行削峰填谷时,需要面临以下消息处理的可靠性挑战:
- 消息丢失问题
- 消息重复消费与幂等性设计
- 消息堆积处理机制
2.4. 安全防护问题
Section titled “2.4. 安全防护问题”- DDoS/CC 攻击:通过海量僵尸网络请求占满服务器带宽与线程资源,致使服务瘫痪。
- SQL 注入:高并发下个别未做防注入的安全接口在大流量遍历下,容易暴露敏感库数据。
- XSS 攻击:向页面注入恶意脚本,窃取高并发下活跃用户的 Cookie/Token。
- CSRF 攻击:跨站请求伪造,在并发场景下冒用受害者身份进行非法状态变更。
3. 双写最终一致性设计
Section titled “3. 双写最终一致性设计”由于分布式系统 CAP 定理的限制,追求高并发时难以保证缓存与数据库的“强一致性”,主要目标是实现 最终一致性。
以下对比常见的双写机制:
- 先更新缓存,后更新数据库
- 弊端:若数据库更新失败,缓存中依然保留的是脏数据,后续读请求会一直读取到错误数据。
- 先更新数据库,后更新缓存
- 弊端:在高并发并发写入时,因网络原因可能导致缓存被旧值覆盖,同时面临缓存更新失败的脏数据风险。
- 先删除缓存,后更新数据库
- 弊端:在删除缓存后,若数据库更新未完成前,有一个并发读请求进入,会将数据库中的旧值再次加载到缓存中,造成长期不一致。
- 先删除缓存,后更新数据库,延迟指定秒数再次删除(延迟双删)
- 解决:通过延迟第二次删除,确保在更新数据库期间由读请求产生的脏缓存被清理。
- 弊端:延迟时间难以精准评估;同时仍然需要面临缓存删除失败的问题。
- 先更新数据库,后删除缓存(Cache Aside Pattern)
- 业内常用:在并发读写冲突极低的情况下(如读完成快于写),不一致的概率很小。
- 弊端:若删除缓存步骤失败,会导致脏数据。需结合 消息队列重订订阅 或 Canal 监听 Binlog 自动重试 机制,确保缓存能被成功删除。
4. 并发资源竞争控制
Section titled “4. 并发资源竞争控制”为防止多个并发请求同时修改同一行数据引起的数据覆盖或状态失真,常采用以下并发锁机制:
- 悲观锁:假定冲突概率极高。在读取和修改数据时直接加排他锁(如
SELECT ... FOR UPDATE),阻塞其他事务直至提交释放。适用于写多读少、一致性要求极其严苛的场景。 - 乐观锁:假定冲突概率较低。在获取和计算数据时不加锁,仅在执行数据更新的瞬间进行核对(如版本号核对)。
4.1. 乐观锁版本号控制示例
Section titled “4.1. 乐观锁版本号控制示例”在数据库表中增加 version 字段:
-- 1. 获取数据及当前版本号SELECT id, value, version FROM t_inventory WHERE id = 1; -- 假设获得 version = 10
-- 2. 业务计算完毕后准备更新UPDATE t_inventorySET value = new_value, version = version + 1WHERE id = 1 AND version = 10;5. Redis 缓存三大经典挑战
Section titled “5. Redis 缓存三大经典挑战”5.1. 缓存穿透
Section titled “5.1. 缓存穿透”- 定义:用户请求的数据在 缓存 和 数据库 中都不存在。导致每次请求都穿透到数据库进行无意义的查询。
- 危害:恶意流量持续查询不存在的 Key,可轻易击垮数据库。
- 防御方案:
- 接口防刷校验:前端限流及用户鉴权。
- 缓存空对象:查询不到数据时在 Redis 中写入一个 null 并设置极短的过期时间(如 60 秒)。
- 布隆过滤器 (Bloom Filter):在请求到达缓存层之前,使用布隆过滤器快速拦截不存在的 Key(注意布隆过滤器的误判率与动态更新机制)。
5.2. 缓存击穿
Section titled “5.2. 缓存击穿”- 定义:某个极度热门的 Key(如爆款商品的抢购数据)在 失效的瞬间,大量并发请求越过缓存直奔数据库。
- 危害:数据库连接池瞬间占满,响应延迟陡增。
- 防御方案:
- 热点 Key 永不过期:不设置物理 TTL,由后台守护线程异步刷新缓存。
- 互斥锁 (Mutex Lock):在缓存失效时,仅允许一个线程获取分布式锁去加载数据库并重建缓存,其他线程等待并重试。
5.3. 缓存雪崩
Section titled “5.3. 缓存雪崩”- 定义:大量不同的缓存 Key 在 同一时间集中失效,或者 Redis 集群整体宕机,导致全量流量瞬间压垮数据库。
- 危害:数据库大面积瘫痪,甚至引发整个业务链条的系统崩溃。
- 防御方案:
- 均匀过期时间:为每个 Key 的 TTL 增加一个随机抖动值(例如
TTL = 30分钟 + random(1~5分钟))。 - 多级缓存架构:本地缓存(Guava/Caffeine)与分布式缓存(Redis)多级配合。
- 高可用 Redis 部署:配置 Redis 哨兵(Sentinel)或 Redis Cluster 集群。
- 限流降级保护:当数据库压力到达临界值,自动开启熔断保护。
- 均匀过期时间:为每个 Key 的 TTL 增加一个随机抖动值(例如