从0到百万用户:后悔当初架构没这样设计,现在全是坑……

The Latency Gambler 2025-05-29 10:12:12
项目上线时,我们仅有100名用户。但几个月后,用户数迅速突破 10,000,接着是 100,000。此时,扩展性问题比用户增长来得更快。

 

 

我们的目标是支持 100 万用户,但原本支撑 1,000 用户的架构已不堪重负。回顾过去,以下是我希望从第一天就建立起来的架构,以及在高压下扩展的宝贵经验。

 

阶段 1:单体的成功(直到它失败)

 

最初的架构非常简单:

 

  • Spring Boot 应用

  • MySQL 数据库

  • NGINX 负载均衡器

  • 所有组件部署在一台虚拟机(VM)上

 

  •  
[ Client ] → [ NGINX ] → [ Spring Boot App ] → [ MySQL ]

 

该架构轻松应对 500 并发用户。但当并发用户达到 5,000 时:

 

  • CPU 满载

  • 查询变慢

  • 系统可用性跌破 99%

 

监控显示问题根源:数据库锁、GC 停顿、线程竞争。

 

阶段 2:增加服务器(却忽略了真正的瓶颈)

 

我们在 NGINX 后增加了多个应用服务器:

 

  •  
[ Client ] → [ NGINX ] → [ App1 | App2 | App3 ] → [ MySQL ]

 

读请求 扩展良好,但 写请求 仍集中在 单 MySQL 实例。

 

负载测试结果:

 

  •  
  •  
  •  
  •  
  •  
| Users | Avg Response Time || ----- | ----------------- || 1000  | 120ms             || 5000  | 480ms             || 10000 | 3.2s              |

 

瓶颈并非 CPU,而是 数据库。

 

阶段 3:引入缓存层

 

我们添加 Redis 作为读密集型查询的缓存:

 

  •  
  •  
  •  
  •  
  •  
  •  
  •  
public User getUser(String id) {    User cached = redisTemplate.opsForValue().get(id);    if (cached != nullreturn cached;    User user = userRepository.findById(id).orElseThrow();    redisTemplate.opsForValue().set(id, user, 10, TimeUnit.MINUTES);    return user;}

 

效果显著:

 

  • 数据库负载减少 60%

  • 缓存命中的读请求响应时间降至 200ms 以内

 

1,000个并发用户请求的基准测试:

 

  •  
  •  
  •  
  •  
| Approach   | Avg Latency | DB Queries || ---------- | ----------- | ---------- || No Cache   | 150ms       | 1000       || With Cache | 20ms        | 50         |

 

阶段 4:拆分单体架构

 

将核心功能拆分为 微服务:

 

  • 用户服务

  • 帖子服务

  • 动态流服务

 

每个服务拥有独立数据库表(初期共享同一数据库实例)。

 

服务间通过 REST API 通信:

 

  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
@RestControllerpublic class FeedController {    @GetMapping("/feed/{userId}")    public Feed getFeed(@PathVariable String userId) {        User user = userService.getUser(userId);        List<Post> posts = postService.getPostsForUser(userId);        return new Feed(user, posts);    }}

 

但链式 REST 调用导致 延迟叠加:一个请求可能触发 3-4 次内部调用。

 

用户量激增时,性能急剧下降。

 

阶段 5:消息队列与异步处理

 

引入 Kafka 处理异步任务:

 

  • 用户注册触发 Kafka 事件

  • 下游服务通过消费事件替代同步 REST 调用

 

  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
// PublishkafkaTemplate.send("user-signed-up", newUserId);
// Consume@KafkaListener(topics = "user-signed-up")public void handleSignup(String userId) {    recommendationService.prepareWelcomeRecommendations(userId);}

 

使用Kafka后,注册延迟从1.2秒降至300毫秒,因为昂贵的下游任务超出了带宽。

 

阶段 6:数据库扩展

 

当用户数达到 50 万时,即使有缓存,单 MySQL 实例仍无法支撑。

 

解决方案:

 

  • 读写分离 → 分离读/写流量

  • 分片 → 按用户 ID 分区(例如 0-999k、100 万-200 万等)

  • 归档表 → 将冷数据移出主表

 

分片查询路由示例:

 

  •  
  •  
  •  
  •  
  •  
if (userId < 1000000) {    return jdbcTemplate1.query(...);else {    return jdbcTemplate2.query(...);}

 

效果:减少 写竞争,各分片查询时间显著优化。

 

阶段 7:可观测性

 

用户量突破 10 万后,缺乏监控导致故障排查困难。

 

我们引入:

 

  • 分布式追踪(Jaeger + OpenTelemetry)

  • 集中式日志(ELK 技术栈)

  • Prometheus + Grafana 监控面板

 

Grafana 面板示例:

 

  •  
  •  
  •  
  •  
  •  
| Metric         | Value   || -------------- | ------- || P95 latency    | 280ms   || DB connections | 120/200 || Kafka lag      | 0       |

 

改进前,定位延迟峰值需 数小时;改进后仅需 几分钟。

 

阶段 8:CDN 与边缘缓存

 

用户量达 100 万时,40% 流量来自 静态资源(图片、头像、JS 文件)。

 

我们将静态资源迁移至 Cloudflare CDN,并设置强缓存策略:

 

  •  
  •  
  •  
  •  
Asset              | Origin Latency | CDN Latency || ------------------ | -------------- | ----------- |/static/app.js     | 400ms          | 40ms        |/images/avatar.png | 300ms          | 35ms        |

 

效果:源站流量减少 70%。

 

若重头再来:更早构建的最终架构

 

如果重新开始,我会跳过中间阶段,直接采用以下架构:

 

  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
  •  
[ Client ]     ↓  [ CDN + Edge Caching ]     ↓  [ API Gateway → Service Mesh ]     ↓  [ Microservices + Kafka + Redis Cache ]     ↓  [ Sharded Database + Read Replicas ]

 

关键经验:

 

  • 缓存是必选项,而非可选项

  • 数据库扩展需提前设计

  • 异步处理至关重要

  • 可观测性越早投入回报越大

 

扩展的核心不是“堆服务器”,而是 逐层消除瓶颈。

 

最终基准测试(100 万用户,1,000 RPS)

 

  •  
  •  
  •  
  •  
  •  
  •  
  •  
Metric             | Value  || ------------------ | ------ |P95 API Latency    | 210ms  |Error Rate         | <0.1%  || Cache Hit Ratio    | 85%    || DB Query Rate      | 50 qps || Kafka Consumer Lag | 0      |

 

总结

 

扩展到 100 万用户的关键不是追逐新技术,而是按正确顺序解决正确问题。

 

支撑前 1,000 用户的架构,无法支撑下一个百万。

 

在问题爆发前,提前设计应对方案。

 

-

 

你在扩展过程中踩过哪些架构大坑?欢迎分享。

 

作者丨The Latency Gambler    编译丨Rio

来源丨网址:https://medium.com/@kanishks772/scaling-to-1-million-users-the-architecture-i-wish-i-knew-sooner-39c688ded2f1
dbaplus社群欢迎广大技术人员投稿,投稿邮箱:editor@dbaplus.cn
最新评论
访客 2024年04月08日

如果字段的最大可能长度超过255字节,那么长度值可能…

访客 2024年03月04日

只能说作者太用心了,优秀

访客 2024年02月23日

感谢详解

访客 2024年02月20日

一般干个7-8年(即30岁左右),能做到年入40w-50w;有…

访客 2023年08月20日

230721

活动预告