海量数据高并发读写方案设计

一、高并发系统核心分类(按读写特征)

核心判定维度:用户规模数量级、响应时间要求、读写频率,Java 面试中常结合电商、IM、支付等场景考察分类及对应技术选型。

1. 高并发读为主系统

核心特征 :读请求量远大于写,读响应要求毫秒级,写响应可容忍延迟。典型场景:搜索引擎、电商商品搜索 / 商品描述 & 图片 & 价格查询、静态资源访问。

2. 高并发写为主系统

核心特征 :写请求高频且实时性要求高,读请求相对较少。典型场景:广告计费 / 扣费系统、日志落盘系统。

3. 高并发读写混合系统

核心特征 :C 端用户同时发起高并发读和写,数据一致性要求高(部分场景强一致,部分最终一致)。典型场景:电商库存 / 秒杀系统、支付 / 微信红包系统、IM / 微博 / 朋友圈、12306 票务系统。

二、高并发读核心解决方案

Java 后端中高并发读是面试必考模块,核心策略围绕缓存 / 副本、并发优化、重写轻读 展开,最终落地为读写分离(CQRS) 架构。

策略 1:加缓存 / 读副本(最常用)

本质是空间换时间,通过数据冗余降低主库 / 源系统压力,Java 开发中需掌握不同缓存方案的选型、问题及解决。

方案 1:本地缓存 & 集中式缓存
  • 本地缓存:Caffeine、Guava Cache(Java 常用),优点是无网络开销,缺点是缓存不共享、内存占用高。

  • 集中式缓存:Redis、Memcached(Java 主流),<k,v>/<k,list>/<k,hash > 结构,适配关系型数据库单表数据缓存。

  • 缓存更新方式

    1. 主动更新:数据库数据变更时,主动删除 / 更新缓存(如 MySQL Binlog 监听 + Canal)。
    2. 被动更新:缓存过期 / 不存在时,查询数据库回源更新(懒加载)。
  • 面试高频问题:缓存四大问题及解决

    表格

    问题 现象描述 常见解决方法(Java)
    缓存穿透 大量不存在的 Key 查询,穿透缓存压垮数据库 布隆过滤器过滤无效 Key、缓存空值(设置短过期时间)
    缓存击穿 热点 Key 失效瞬间,大并发请求直接访问数据库 互斥锁(Redis SETNX)、热点 Key 永不过期、分布式锁(Redisson)
    缓存雪崩 大量 Key 同时过期 / 缓存宕机,所有请求压垮数据库 过期时间随机化、多级缓存(本地 + 集中式)、缓存集群高可用(Redis Cluster)
    缓存高可用 缓存服务宕机导致系统雪崩 缓存集群部署、主从复制、哨兵模式、熔断降级(Sentinel/Hystrix)
方案 2:MySQL Master/Slave(读副本)
  • 核心作用:主库负责写,从库负责读,分担主库读压力,Java 中通过 MyBatis 多数据源配置实现。
  • 适用场景:复杂业务数据查询,无法通过简单 K-V 缓存满足的场景。
  • 注意点:主从同步存在延迟,需结合业务容忍度设计(最终一致性)。
方案 3:CDN / 静态文件加速 / 动静分离
  • 静态内容:图片、HTML/JS/CSS、视频(Java 中通过 Nginx+CDN 部署)。
  • 动态内容:用户个性化数据、实时计算结果(由 Java 后端服务生成)。
  • 核心逻辑:静态内容缓存到全网 CDN 节点,用户就近访问,避免穿透到源服务器。
缓存 / 副本策略整体架构:

策略 2:并发读优化

将串行读改为并行读,降低请求总耗时,Java 中结合 RPC 框架、并发工具实现。

方案 1:异步 RPC
  • 核心原理:并行调用多个无耦合的 RPC 接口,总耗时 = 最大单接口耗时(同步为耗时之和)。
  • Java 实现:Dubbo 异步调用、Feign 异步调用、CompletableFuture 实现接口并行调用。
  • 适用场景:一个请求需要调用多个独立的微服务接口(如商品详情页查询商品、库存、评价)。
方案 2:冗余请求(对冲请求)
  • 核心原理:针对分布式系统长尾延迟问题,超时后向其他服务器发送重复请求,取最快返回结果。
  • 关键参数:等待时间 = 内部服务 95% 请求的响应时间。
  • 效果:Google 测试显示,仅 2% 额外请求可将 99.9% 请求响应时间从 1800ms 降至 74ms。
  • Java 注意点:需实现请求取消机制,避免无效请求占用服务器资源。

策略 3:重写轻读(将计算从读端移到写端)

面试常考微博 Feeds 流实现 ,核心是写扩散 / 推拉结合,Java 中结合 Redis+MySQL 实现。

方案 1:微博 Feeds 流的实现(推拉结合)
1. 基础设计:发件箱 + 收件箱(写扩散)
  • 发件箱:存储用户自己发布的内容(MySQL+Redis),全量持久化。
  • 收件箱:存储关注用户的内容(Redis List),限制长度(如 2000 条),满足高并发读。
  • 写逻辑:用户发内容,先写入自己发件箱,后台异步推送到粉丝收件箱(Java 异步线程 / 消息队列)。
  • 读逻辑:直接读取 Redis 收件箱,无需实时聚合,毫秒级返回。
2. 优化:推拉结合(解决大 V 粉丝过多问题)
  • :粉丝数 < 阈值(如 5000),发布内容直接推送到所有粉丝收件箱。
  • :粉丝数 > 阈值(如大 V),仅推送给在线粉丝,普通用户读 Feeds 流时主动拉取大 V 内容。
  • 聚合:读端将 "推的内容 + 拉的内容" 按时间排序,分页返回(Java 中通过 Comparator/Stream 实现)。
3. 数据持久化与分片
  • Redis:存储最近的热点 Feeds 流(满足高并发读)。
  • MySQL :全量存储,按user_id + 时间范围 分片,配合二级索引表(user_id, 月份,count) 实现分页查询。
Feeds 流推拉结合架构:
方案 2:多表关联查询优化(宽表 + 搜索引擎)
  • 问题:分库分表后,数据库原生 Join 失效,程序聚合排序分页性能差。
  • 解决方案 1:宽表提前将多表关联数据计算好,存入宽表(Java 中通过定时任务 / Canal 监听 Binlog 更新宽表),读端直接查询宽表。
  • 解决方案 2:搜索引擎将多表 Join 结果构造成文档,存入 Elasticsearch(ES),利用 ES 的高效排序和分页能力满足高并发读。
  • Java 实现:Canal 监听 MySQL Binlog,同步数据到 ES / 宽表,通过 MyBatis/ES Client 查询。

总结:读写分离(CQRS 架构)

Command Query Responsibility Separation,命令(写)与查询(读)职责分离,是高并发读的终极解决方案,Java 微服务中主流落地架构。

CQRS 核心特征
  1. 读写设计不同的数据结构:写库用关系型数据库(MySQL),读库用缓存 / 宽表 / ES。
  2. 写端:分库分表抵抗写压力;读端:缓存 / ES/CDN 抵抗读压力。
  3. 读写数据同步:定时任务 /消息队列 /Binlog 监听(Canal)
  4. 数据一致性:最终一致性 (读比写有延迟),核心数据可做强一致性(如用户自身数据)。
  5. 数据可靠性:同步通道需保证不丢失、不乱序(如 RocketMQ/Kafka 持久化)。
CQRS 架构整体流程:

三、高并发写核心解决方案

高并发写的核心思路是拆分、异步、批量 ,Java 中结合分库分表、消息队列、流式计算实现,需重点掌握落地细节。

策略 1:数据分片(拆分)

将数据 / 请求拆分为多份,并行处理,分散单节点写压力,Java 后端中主流基于Sharding-JDBC实现。

1. 数据库分库分表
  • 分表:单表数据量过大,拆分为多张表(如用户表按 user_id 取模),同库,利用 CPU / 内存资源。
  • 分库:单库写压力过大,拆分为多个库,多台机器,水平扩展写能力。
  • 分片维度 :按主键(user_id/order_id)时间范围业务维度(如按地区)。
  • Java 实现:Sharding-JDBC(轻量级,无中间件)、MyCat(中间件)。
2. 中间件分片
  • Redis:Redis Cluster(按槽位分片),Java 中通过 JedisCluster/Spring Data Redis 实现。
  • ES:分布式索引(按文档 ID 分片),并行写入 / 查询。
3. 核心注意点
  • 分片规则需提前规划,避免后期数据迁移。
  • 避免跨分片查询 / 写 (如联表、范围查询),如需跨分片,Java 中通过归并排序处理。
  • 分片后需考虑数据扩容(如弹性分片、预分片)。

策略 2:异步化(核心)

将同步写改为异步写,快速返回请求,后台处理业务 ,Java 中通过消息队列实现,是解决高并发写的最核心手段。

核心原理

客户端发送请求后,服务端将请求放入消息队列(如 RocketMQ/Kafka),立即返回 "请求接收成功",后台消费者从队列取消息异步处理。

典型案例
案例 1:短信验证码注册 / 登录
  • 同步问题:调用第三方短信平台(HTTP 调用,1-2s),Tomcat 线程被阻塞,并发量低。
  • 异步实现:Java 后端将短信请求放入 RocketMQ,返回 "发送中",消费者线程调用短信平台,完成发送。
  • 优势:内网通信无阻塞,Tomcat 可处理海量请求,消息队列削峰填谷。
案例 2:电商订单拆单
  • 业务场景:用户下单多个商品(来自不同卖家),需拆分为多个子订单,卖家分别发货。
  • 异步实现:支付成功后,Java 后端将拆单请求放入消息队列,立即返回 "支付成功",消费者线程异步完成拆单、创建子订单、通知卖家。
  • 核心原则不阻碍主流程的业务,全部异步化
案例 3:广告计费系统
  • 异步实现 :用户点击 / 浏览广告,请求先落盘(日志 / 消息队列),立即返回,后续通过流式计算(Flink/Spark) 异步处理反作弊、扣费。
  • Java 配合:日志采集(Logback/Log4j2)、消息队列(Kafka)、流式计算(Flink Java API)。
案例 4:写内存 + Write-Ahead Log(WAL)
  • 核心思路:借鉴 MySQL Redo Log,先写内存(Redis)保证高性能,再落日志保证数据可靠性。
  • Java 实现:扣库存 / 扣余额时,先通过 Redis INCR/DECR 完成写操作,同时将操作日志写入消息队列 / MySQL,若 Redis 宕机,重放日志恢复数据。
  • 适用场景:超高并发写(如秒杀扣库存),要求毫秒级响应,同时保证数据不丢失。
异步化核心架构:

策略 3:批量写(合并)

将多个小的写请求合并为一个大的写请求,减少数据库 IO 次数,提升写性能,Java 中结合定时任务、消息队列实现。

典型案例
案例 1:广告计费系统合并扣费
  • 业务场景:同一个广告主的多个扣费请求(如 10 次点击,每次扣 1 元)。
  • 批量实现:Java 消费者从消息队列批量取消息,按广告主 ID 分组,累加扣费金额(10 次合并为扣 10 元),一次更新数据库。
  • 优势:将 10 次数据库更新变为 1 次,大幅降低 IO 压力。
案例 2:MySQL 小事务合并 & Java 业务借鉴
  • MySQL 原理:对同一个 SKU 的多次扣库存(10 次扣 1 个),内核合并为 1 次扣 10 个,减少事务开销。

  • Java 业务实现 :如缓存更新,一个表多次修改,仅需删除一次 Redis 缓存(通过 Set 去重,批量删除)。

    java

    运行

    复制代码
    // Java代码示例:批量删除缓存(借鉴小事务合并)
    Set<String> factKeys = new HashSet<>();
    for (CanalEntry.Entry entry : message.getEntries()) {
        String tableName = entry.getHeader().getTableName();
        factKeys.add(tableMapKey.get(tableName)); // 去重,避免重复删除
    }
    for (String key : factKeys) {
        redisTemplate.delete(key); // 批量删除
    }
  • 多机房场景:事务合并是加速数据库跨机房复制的重要手段,减少网络传输次数。

批量写核心流程(Mermaid)
相关推荐
我命由我123454 小时前
Android多进程开发 - AIDL 参数方向、AIDL 传递自定义对象、AIDL 传递自定义对象(参数方向)
android·java·java-ee·kotlin·android studio·android jetpack·android-studio
SunnyDays10114 小时前
Java 删除与重置 Excel 文件密码(文件级与工作表级完整示例)
java·删除excel文件密码·重置excel文件密码·解除excel工作表保护
Dcs4 小时前
删掉 40 行代码,性能飙 400 倍?OpenJDK 这次把“慢如 /proc”按在地上摩擦!
java
t***442312 小时前
【Springboot3+vue3】从零到一搭建Springboot3+vue3前后端分离项目之后端环境搭建
java
daidaidaiyu12 小时前
SpringCloud 微服务实现一则 (Eureka + Hystrix)
java·spring
小哥不太逍遥13 小时前
Technical Report 2024
java·服务器·前端
edisao13 小时前
序幕-内部审计备忘录
java·jvm·算法
shehuiyuelaiyuehao14 小时前
22Java对象的比较
java·python·算法
常利兵14 小时前
吃透Java操作符高阶:位操作符+赋值操作符全解析(Java&C区别+实战技巧+面试考点)
java·c语言·面试