十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

大型网站高并发高可用架构设计指南:从容量评估到故障演练

大型网站高并发高可用架构设计指南:从容量评估到故障演练 简介对大型网站建设者而言这份指南系统梳理了高性能、高并发、高可用架构的完整设计思路适合已有一定IT基础、正在负责或准备参与系统架构设计的工程师。内容从大型网站的特点与架构目标切入覆盖分层、分割、分布式、集群、缓存、异步、冗余等常见架构模式并结合前端、浏览器、应用层、代码与存储优化以及高可用保障、数据伸缩、服务扩展与安全架构等关键议题帮助读者建立从理论到落地的整体认知。资源包内共1个文件为docx格式文档约3.19MB篇幅完整。文中还以大型电商网站为例拆解从初期到成熟阶段的架构演变过程让工程师能直观体会系统成长轨迹并在设计规划时做出更合理的决策。目前该指南已有1124人学习适合用于企业架构讨论、技术方案预研和个人能力提升。1. 大型网站的高性能高并发高可用架构先定目标再谈模型电商大促的流量峰值往往是日常的几十倍秒杀场景下瞬时请求能冲到每秒几十万次。如果架构师在设计阶段没有把“高可用”和“高并发”当成两个独立的约束去考虑上线后大概率会在第一轮压测时就暴露问题。高并发考验的是系统在单位时间内能处理多少请求衡量的指标是吞吐量和响应时间高可用则要求在部分节点宕机、依赖服务变慢、网络抖动时系统依然对外提供正确服务。两者之间天然存在冲突为了高并发做水平扩展节点数量变多故障概率反而上升为了高可用做冗余和一致性协商又会让每次请求多付出额外的网络开销。构建大型网站的架构方案核心工作就是围绕这两个目标做取舍而不是去找一套“全能”的框架。这篇内容会从容量评估、应用层伸缩、数据层分离、故障保障四个维度给出能够直接落地参考的架构设计路径适合后端工程师、架构师和需要自己把控系统演进的团队技术负责人阅读。2. 性能目标的量化容量评估与核心指标建模2.1 从业务量推导并发目标的换算模型不要写代码先做数学题。架构设计的第一步不是选中间件而是确认系统需要扛住多大的并发量。常见的错误是架构师直接拍脑袋说“我们要支持每秒十万 QPS”然后按这个数字设计系统结果业务上线一年后真实峰值只有 3000。合理的做法是根据业务预测值反推再留出冗余。假设一个电商站点日均 PV 为 1000 万通常业务访问集中在 4 小时的高峰时段内那么平均每秒请求数为10,000,000 / (4 * 3600) ≈ 694 QPS但这是平均值的算法线上流量有明显的毛刺双十一样的高峰因子可能达到平均值的 10 到 20 倍。所以实际容量目标建议按(日均PV / 高峰秒数) × 峰值系数来计算。带宽也要同步估算如果单请求平均响应体为 50KB那么8000 QPS × 50KB ≈ 400MB/s的出口带宽这会直接影响 CDN 和后端服务器的网络选型。2.1.1 容量规划的进阶参考表以下是我在实际项目中常用的容量评估参照表适用于常规的后端服务评估维度计算公式参考经验值峰值 QPS日均 PV ÷ 高峰秒数 × 高峰系数高峰系数 5~15单机承载 QPS无状态应用 本地缓存3000~15000数据库读写比读请求 ÷ 写请求典型业务 10:1Redis 命中率缓存命中次数 ÷ 总读次数目标 ≥ 90%服务可用性1 - (故障时长 ÷ 统计周期)99.9% 以上表中每一列都是部署容量规划的输入项而不是事后统计项。单机 QPS 的取值取决于业务代码的耗时如果接口平均响应时间RT是 20ms单线程每秒可处理 50 个请求开 200 个线程则理论承载 10000 QPS。RT 越长需要越多的线程和机器。2.2 用 wrk 和 JMeter 击穿容量水位线容量估算结束后立刻开放压测环境。不需要等待完整集群部署好先把用户注册、商品查询、订单提交这三个核心链路搭出来用 wrk 压最薄的那条链路用来修正自己的估算误差。wrk -t12 -c400 -d30s --latency http://your-gateway:8080/api/v1/products参数说明-t12表示启动 12 个线程-c400建立 400 个并发连接-d30s持续压测 30 秒。--latency用于输出延迟分布数据。注意 wrk 本身只适合压测简单的 HTTP 接口如果请求需要构造签名、登录态或业务数据用 JMeter 加上前置处理器更合适。压测结果的关注重点不是平均值而是 P99 延迟。当 P99 超过 200ms 时即使平均延迟只有 50ms用户侧也能感知到明显卡顿。压测报告中另一个关键信息是错误率和吞吐量的交叉点。持续增加并发线程数如果吞吐量不再上升但错误率开始出现说明系统已经达到水位线。此时要记录 CPU、内存、GC 停顿和数据库连接池使用率四个指标它们能帮助定位瓶颈是逻辑层还是数据层。3. 应用层的高并发设计无状态化与水平扩展3.1 为什么大型网站首选无状态服务水平扩展的前提是任意一台服务器都能处理任意一个请求。如果用户会话 Session 保存在本机内存里负载均衡把请求转发到另一台机器时用户登录态就丢了。高并发架构里最常见的约束手段就是消除状态把状态外置到一个所有节点共享的存储中。首选是 Redis也可以根据场景选择其他中间件但核心原则不变应用进程尽量保持无状态。具体到实现将原本放在HttpSession中的用户信息迁移到 Redis使用sessionId作为 key并设置合理的过期时间。或者直接采用 JWT 之类的令牌方案把用户标识和权限信息编码进 Token 中由客户端在每次请求时携带。两种方案各有利弊Redis 集中存储便于服务端主动失效但多一次网络往返JWT 不占服务端存储但注销和续期逻辑复杂。3.2 Nginx 与网关层的负载均衡配置参数应用层水平扩展的落地形态是负载均衡器加服务实例组。以典型的 Nginx 网关为例配置文件中需要关注的参数远不止 upstream 地址列表upstream backend { least_conn; keepalive 32; server 10.0.0.11:8080 max_fails3 fail_timeout30s; server 10.0.0.12:8080 max_fails3 fail_timeout30s; } server { listen 80; location /api/ { proxy_pass http://backend; proxy_connect_timeout 3s; proxy_read_timeout 10s; proxy_next_upstream error timeout http_502; } }负载均衡策略选least_conn而不是默认的轮询是因为每个请求的处理时间差异很大连接数少的节点往往负载更低keepalive 32使 Nginx 保留到后端的空闲连接避免每来一个请求都重新握手max_fails3 fail_timeout30s表示节点在 30 秒内失败 3 次就标记为不可用后续请求自动转发到其他节点。proxy_next_upstream允许 Nginx 在后端返回超时或 502 时自动重试其他节点这里隐含着幂等性的前提要求如果请求不满足幂等重试可能造成重复下单。3.2.1 弹性伸缩与 Kubernetes 高可用组件当网关层和服务实例组放到了 Kubernetes 里高并发弹性能力靠的是 HPAHorizontal Pod Autoscaler和探针机制。HPA 根据 Pod 的 CPU 使用率或自定义指标自动调整副本数组件本身的配置要防止抖动apiVersion: autoscaling/v2 kind: HorizontalPodAutoscaler metadata: name: backend-hpa spec: scaleTargetRef: apiVersion: apps/v1 kind: Deployment name: backend minReplicas: 3 maxReplicas: 20 metrics: - type: Resource resource: name: cpu target: type: Utilization averageUtilization: 70minReplicas: 3保证基础冗余maxReplicas: 20限制扩容上限以防流量异常时资源被无限撑爆averageUtilization: 70是触发扩容的阈值设置过低会导致频繁扩缩容。同时要在 Deployment 中配置存活探针和就绪探针就绪探针应指向一个能反映业务真实可用性的接口而不是只检测进程是否存活。3.3 后端代码中的高并发处理要点写同步阻塞代码时每个请求占用一个线程而 Tomcat 默认最大线程数是 200。如果接口内部调用了两个下游服务每个下游耗时 500ms单线程每秒只能处理 2 个请求200 个线程最多支撑 400 QPS。想要提升并发能力需要引入异步化。使用 CompletableFuture 将两个无依赖的下游调用并行执行总耗时从 1000ms 降到 500ms单位时间吞吐量直接翻倍。CompletableFutureOrderResult orderFuture CompletableFuture.supplyAsync(() - orderService.getOrder(orderId), executor); CompletableFutureUserInfo userFuture CompletableFuture.supplyAsync(() - userService.getUser(userId), executor); CompletableFuture.allOf(orderFuture, userFuture).join();代码中的executor必须单独定义线程池不能直接使用默认的 ForkJoinPool。默认池的并行度等于 CPU 核心数做 IO 密集型任务时线程根本不够用。线程池参数建议按核心线程数 CPU核数 × 2来设置队列长度设在 1000 到 2000 之间拒绝策略使用 AbortPolicy 并配合监控报警。每个下游调用必须设置超时时间建议外层整体超时控制在 800ms 以内避免依赖服务变慢时把所有线程拖死。4. 数据层的高性能与高可用缓存、读写分离与分片4.1 Redis 缓存设计与高可用体质的搭建数据层往往是大型网站真正的瓶颈因为应用层可以快速加机器数据库却不容易横向扩展。业界标准做法是分层挡流量先查本地缓存再查 Redis最后才落到数据库。Redis 缓存设计要解决三个经典问题缓存穿透、缓存击穿和缓存雪崩。缓存穿透指查询一个不存在的数据请求直接打到数据库。解决办法是缓存空值并设置短过期时间比如 60 秒或者在查询入口加入布隆过滤器。缓存击穿指某个热点 key 过期瞬间大量请求同时落到数据库。解决办法是使用分布式锁控制回源查询或者设置热点 key 永不过期、异步刷新。缓存雪崩指大量 key 在同一时间过期导致数据库瞬间压力过载。解决办法是将过期时间打散例如在基础 TTL 上增加随机 30 至 120 秒的偏移。Redis 的高可用不能只靠单机至少做到主从加哨兵模式。主节点负责写请求从节点同步数据并提供读能力。Sentinel 集群监控主节点状态在主节点故障时自动执行故障转移。更进一步的方案是 Redis Cluster 分片模式数据按 CRC16 哈希分布在多个主节点上每个主节点又带从节点实现冗余。分组架构时注意读多写少的场景优先考虑哨兵模式加读写分离数据量超过单机内存容量且写入规模大时再切换为集群模式。4.2 MySQL 读写分离与高可用架构的基础配置大型网站的数据库读写比例通常在 10:1 以上把读流量从主库剥离是性价比最高的优化手段。典型部署是一个主库承载写操作两个从库通过 binlog 同步数据应用层通过数据库中间件如 MyCat、ShardingSphere或应用内的路由层将 select 请求分发到从库其余语句发送到主库。配置从库同步的关键参数如下CHANGE MASTER TO MASTER_HOST10.0.1.10, MASTER_USERrepl, MASTER_PASSWORDRepl123, MASTER_LOG_FILEmysql-bin.000001, MASTER_LOG_POS107; START SLAVE;执行完START SLAVE后用SHOW SLAVE STATUS\G检查两个关键字段Slave_IO_Running和Slave_SQL_Running两者都必须是Yes状态。这里有个容易踩坑的地方从库同步会存在延迟尤其在高并发写入场景下延迟可能达到秒级。如果刚创建完订单立刻查订单列表同步延迟会导致查不到数据。常见的规避方案是让订单首次查询强制走主库或者等待一个短暂时间后再读从库。4.3 分库分表高并发下的最终落点当单表数据量超过千万量级或者单库写入吞吐量接近上限就要做分库分表。拆分维度通常有两个垂直拆分按业务域划分订单库、用户库、商品库水平拆分按某个分片键取模或范围拆分。水平拆分是处理高并发写入的核心手段。以订单表为例按用户 ID 取模拆分到 4 个库每个库再拆成 4 张表共 16 张表CREATE TABLE IF NOT EXISTS order_${uid % 4} ( order_id bigint NOT NULL AUTO_INCREMENT, user_id bigint NOT NULL, amount decimal(10,2) NOT NULL, status tinyint NOT NULL, create_time datetime NOT NULL, PRIMARY KEY (order_id), KEY idx_user_id (user_id) ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;表名中的${uid % 4}代表路由算法就是根据用户 ID 取模后选择具体的物理表。这个方案的代价是跨库查询能力被弱化例如按订单 ID 查询时由于订单 ID 并未包含分片信息必须广播到所有分片再聚合结果。设计阶段应尽量让所有查询都带上分片键比如用户中心强制要求传入 user_id这是使用分库分表前必须接受的最大局限。5. 分布式架构可靠性保障一致性、超时重试与幂等5.1 从单机事务到分布式架构设计的思维转换应用拆分成微服务后一个业务操作往往涉及多个服务调用。比如下单操作要扣减库存、创建订单、扣款和发消息四个操作分布在多个进程中单机数据库事务无从谈起。大型网站的架构策略通常是放弃强一致性接受最终一致性。常见的落地方案是本地消息表加定时任务主服务在自己的数据库中写业务数据的同时写入一条本地消息记录然后通过消息队列发送给下游服务。如果消息发送失败定时任务扫描未发送的消息并重新投递下游消费时做幂等处理防止重复消息导致重复扣款。伪代码逻辑如下def create_order(user_id, product_id, amount): with db.transaction(): insert_order(user_id, product_id, amount) insert_outbox_message(order.created, {order_id: order_id}) # 事务提交后异步发送 MQ mq_client.send(order.created, message_payload)逻辑说明insert_order和insert_outbox_message在同一数据库事务内保证业务数据和待发送消息的一致事务提交成功后再将消息发送到 MQ。如果mq_client.send抛异常本地消息表中仍保留记录由定时任务兜底重发。5.2 后端代码中必须注意的超时、三态与幂等设计分布式架构设计里网络调用一定有失败的可能。调用下游 API 时超时是必然会发生的事件关键问题是超时后怎么办。如果下游只是超时但实际已经处理了请求你重试一次就等于操作了两次产生重复数据。所以判断是否可重试的原则是读请求可以任意重试写请求必须基于幂等设计后才能重试否则重试的时间参数就要做间隔退避策略。幂等设计有两个常见做法。一是为每一次写操作生成全局唯一的请求 ID数据库表对请求 ID 建唯一索引重复插入直接报错不影响业务数据。二是利用状态机订单状态从“待支付”到“已支付”是单向流转重复消费支付成功的消息时若发现状态已经是“已支付”则直接返回成功。/** * 第三方支付回调处理天然存在重复推送需要幂等 */ public PayResult handlePayCallback(String orderId, String transactionId) { // 尝试加锁防止并发重复处理 boolean locked redisLock.tryLock(PAY_LOCK_ orderId, 5, TimeUnit.SECONDS); if (!locked) { throw new ConflictException(pay callback is processing); } try { // 唯一索引约束order_id transaction_id payOrderMapper.insertIgnore(orderId, transactionId); orderService.updateStatusToPaid(orderId); return PayResult.success(); } finally { redisLock.unlock(PAY_LOCK_ orderId); } }insertIgnore利用数据库唯一索引拦截重复记录redisLock.tryLock则避免并发回调同时进入更新逻辑。两套机制互相补位锁的过期时间要大于处理该请求所需的最大耗时否则第一个请求还没处理完第二个请求已经拿到锁进入最终还是会被唯一约束拦截。5.3 高可用架构设计的降级与限流落地高并发场景下系统一定会过载。CPU 使用率超过 90% 时再继续接收请求只会让所有请求排队延迟飙升直到雪崩。限流是对自身的保护降级是对依赖的保护。限流的实现方式可以直接使用 Sentinel 或 Resilience4j业务场景的姿势是# Sentinel Dashboard 配置项 - resource: /api/v1/orders grade: 1 # 1 代表 QPS 模式 count: 2000 degradeRule: grade: 2 # 2 代表 RT 比例模式 count: 300 # RT 超过 300ms 记为慢调用 timeWindow: 60grade: 1限定接口每秒最多 2000 次请求超过的直接返回流控错误。degradeRule用于熔断当接口慢调用比例超过阈值默认配置中可以设置比例数值后续请求直接短路不再打到下游。这里要提到写后端代码时需要注意的另一个关键点降级和限流的触发依据不能只看 QPS还要看调用链路上的依赖健康状态否则一个慢依赖会把所有资源耗尽而限流却认为是业务流量过大误判方向。6. 故障演练与压测验收验证你的架构是否真的高可用6.1 用混沌工程思路做故障注入架构设计完成后必须验证但验证的方式不是等线上出问题再复盘而是主动制造故障。ChaosBlade 是常用的故障注入工具可以直接在生产环境或预发环境模拟节点宕机、网络延迟、磁盘 IO 变高。执行案例# 模拟 k8s 中某个 Pod 不能访问 blade create k8s pod-network loss --percent 100 --interface eth0 --local-port 8080 # 恢复故障 blade destroy {uid}模拟 Pod 网络故障后观察三个层面网关层是否能自动剔除故障节点HPA 是否触发了新的副本补齐用户侧请求错误率是否在可接受范围。这些观察要在故障注入后持续 5 分钟因为有些故障的传导有延迟比如连接池在故障初期还在继续发放连接过了超时时间才暴露。6.2 高并发测试中的观察口径与性能回归基线故障恢复之后运行全链路压测。JMeter 脚本覆盖完整的业务链路包括登录、查询、下单、支付回调。压测策略不能一上来就干极限分三个梯度第一轮跑到预估峰值的 50%第二轮 100%第三轮 120% 或者直到出现错误。每一轮结束后记录吞吐量、P99 延迟和错误率三个关键指标作为下一次架构改造后的回归基线。实操中的压测参数参考压测梯度并发线程数Ramp-Up 时间预期 P99通过标准50% 水位20010s≤ 150ms0 错误100% 水位40020s≤ 250ms错误率 0.1%120% 超载50030s≤ 400ms触发限流且服务不宕机这里最关键的验收标准不是 100% 水位下的响应时间而是 120% 超载场景下系统是否以“优雅”的方式失败限流生效、错误请求快速返回、数据库连接池不被打满、CPU 不长时间处于 100% 状态。真正的可恢复性还要看压测结束后的 2 分钟内P99 是否能回落到正常水平。本文还有配套的精品资源点击获取
返回列表