dbaplus社群

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

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

Image

我们的目标是支持 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 != null) return 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(@PathVariableString 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社群欢迎广大技术人员投稿,投稿邮箱:[email protected]