2024→2026 版本升级:10 万 QPS 会员系统架构,两年后我们改了什么

核心观点:真正的"架构演进"不是画一张全新的架构图,而是在生产环境的炮火中,一边救火一边修修补补,把一个个坑填上,让系统在压力下慢慢变得更健壮。

说明:系统从 2024 年的 10 万 QPS 增长到 2026 年的 15 万 QPS,会员数从 2000 万增长到 3500 万。本文标题沿用了两年前的命名,重在回顾这两年的架构演进历程。

引言

两年前,我写了一篇《10 万 QPS 会员系统架构设计》,在圈内引起了不小的反响。两年后的今天,系统依然在运行,但架构已经面目全非——不是因为我们推翻了重来,而是因为生产环境教会了我们什么才是真正需要的。

这篇文章,我想聊聊这两年我们到底改了什么,为什么改,以及改完之后的真实效果。


一、原架构回顾

先回顾一下两年前的架构:

┌─────────────────────────────────────────────────────────────────────────┐
│                        2024 年架构                                      │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  ┌─────────┐    ┌───────┐    ┌───────────┐    ┌───────────┐            │
│  │   CDN   │───>│ Nginx │───>│  Spring   │    │  MySQL    │            │
│  │(静态资源)│    │(负载均衡)│  │   Boot    │───>│(分库分表) │            │
│  └─────────┘    └───────┘    │   应用层   │    │ 8库64表   │            │
│                              └─────┬─────┘    └───────────┘            │
│                                    │                                   │
│                        ┌───────────┼───────────┐                       │
│                        ▼           ▼           ▼                       │
│                  ┌─────────┐ ┌─────────┐ ┌───────────┐                  │
│                  │  Redis  │ │ RabbitMQ│ │  ELK      │                  │
│                  │(主从哨兵)│ │(消息队列)│ │(日志系统) │                  │
│                  └─────────┘ └─────────┘ └───────────┘                  │
│                                                                         │
│  核心数据:10 万 QPS,2000 万会员,日均交易 500 万笔                        │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

技术栈

  • 语言:Java 17
  • 框架:Spring Boot 3.0
  • 数据库:MySQL 8.0(分库分表)
  • 缓存:Redis 6.0(主从哨兵)
  • 消息队列:RabbitMQ 3.9
  • 日志:ELK Stack
  • 容器:Docker + Kubernetes

二、架构演进之路

2.1 Redis Cluster 替换主从哨兵:热 Key 问题的血泪史

问题发生

2024 年双 11,系统遇到了第一次大规模故障:

现象:Redis 主节点 CPU 飙到 100%,响应时间从 2ms 飙升到 500ms
影响:会员查询接口超时,下游服务雪崩
持续时间:2 小时

根因分析

事后分析发现,问题出在几个"超级热点 Key"上:

Key: "member:vip:count"    → 每秒访问 5000+ 次(统计 VIP 会员数量)
Key: "member:online:list"  → 每秒访问 3000+ 次(在线会员列表)
Key: "promotion:active"    → 每秒访问 2000+ 次(当前活动状态)

主从哨兵架构的问题:

  • 所有读请求都打到主节点(哨兵模式下,从节点只读)
  • 热 Key 导致主节点 CPU 满,无法处理其他请求
  • 从节点资源闲置,但无法分担热 Key 压力

解决方案:Redis Cluster

我们花了三个月,逐步迁移到 Redis Cluster:

// 迁移前:主从哨兵配置
@Bean
public RedisConnectionFactory redisConnectionFactory() {
    RedisStandaloneConfiguration config = new RedisStandaloneConfiguration();
    config.setHostName("redis-master");
    config.setPort(6379);
    return new LettuceConnectionFactory(config);
}

// 迁移后:Cluster 配置
@Bean
public RedisConnectionFactory redisConnectionFactory() {
    RedisClusterConfiguration config = new RedisClusterConfiguration();
    config.addClusterNode(new RedisNode("redis-1", 6379));
    config.addClusterNode(new RedisNode("redis-2", 6379));
    config.addClusterNode(new RedisNode("redis-3", 6379));
    config.addClusterNode(new RedisNode("redis-4", 6379));
    config.addClusterNode(new RedisNode("redis-5", 6379));
    config.addClusterNode(new RedisNode("redis-6", 6379));
    return new LettuceConnectionFactory(config);
}

热 Key 优化策略

// 本地缓存 + Redis 二级缓存
@Service
public class HotKeyService {
    
    private final Cache<String, Object> localCache = Caffeine.newBuilder()
        .maximumSize(100)
        .expireAfterWrite(5, TimeUnit.SECONDS)
        .build();
    
    @Autowired
    private StringRedisTemplate redisTemplate;
    
    public Object getHotKey(String key) {
        Object value = localCache.getIfPresent(key);
        if (value != null) {
            return value;
        }
        
        value = redisTemplate.opsForValue().get(key);
        if (value != null) {
            localCache.put(key, value);
        }
        return value;
    }
}

效果对比

指标迁移前(主从哨兵)迁移后(Cluster)
P99 延迟200ms25ms
CPU 峰值100%30%
单节点 QPS15,00080,000
故障恢复时间5 分钟30 秒

2.2 Virtual Threads 替换线程池:连接数提升的魔法

问题发生

随着业务增长,会员系统的并发连接数从 5 万增长到 15 万,传统线程池遇到了瓶颈:

现象:线程池队列满,拒绝策略触发,大量请求被丢弃
影响:会员登录成功率从 99.9% 下降到 95%
持续时间:持续 3 天(逐步恶化)

根因分析

传统线程池配置:
- corePoolSize: 200
- maxPoolSize: 500
- queueCapacity: 1000

问题:
- 每个线程占用约 1MB 栈空间 → 500 线程 = 500MB
- 线程切换开销大 → CPU 大部分时间在切换线程
- 连接池耗尽 → 数据库连接不够用

解决方案:Virtual Threads

Java 21 发布后,我们第一时间开始试点 Virtual Threads:

// 迁移前:传统线程池
@Bean
public ExecutorService taskExecutor() {
    return new ThreadPoolExecutor(
        200,
        500,
        60L, TimeUnit.SECONDS,
        new LinkedBlockingQueue<>(1000),
        new ThreadFactory() {
            private final AtomicInteger counter = new AtomicInteger(0);
            @Override
            public Thread newThread(Runnable r) {
                Thread t = new Thread(r, "task-" + counter.incrementAndGet());
                t.setDaemon(true);
                return t;
            }
        },
        new ThreadPoolExecutor.CallerRunsPolicy()
    );
}

// 迁移后:Virtual Threads
@Bean
public ExecutorService taskExecutor() {
    return Executors.newVirtualThreadPerTaskExecutor();
}

数据库连接池优化

// 连接池配置
@Bean
public DataSource dataSource() {
    HikariConfig config = new HikariConfig();
    config.setJdbcUrl("jdbc:mysql://localhost:3306/member");
    config.setUsername("user");
    config.setPassword("password");
    config.setMaximumPoolSize(200);
    config.setMinimumIdle(50);
    config.setConnectionTimeout(30000);
    config.setIdleTimeout(600000);
    return new HikariDataSource(config);
}

⚠️ 关键警告:连接池成为新瓶颈

迁移到 Virtual Threads 后,我们发现了一个新问题:虚拟线程可以创建大量并发请求,但数据库连接池只有 200 个连接。这导致虚拟线程在等待数据库连接时大量阻塞,浪费了虚拟线程的优势。

解决方案:使用 Semaphore 限制并发

@Service
public class DatabaseService {
    
    private static final int MAX_CONCURRENT_QUERIES = 200;
    private final Semaphore semaphore = new Semaphore(MAX_CONCURRENT_QUERIES);
    
    @Autowired
    private JdbcTemplate jdbcTemplate;
    
    public <T> T executeWithLimit(Supplier<T> task) throws InterruptedException {
        semaphore.acquire();
        try {
            return task.get();
        } finally {
            semaphore.release();
        }
    }
    
    public List<Member> queryMembers(String condition) throws InterruptedException {
        return executeWithLimit(() -> 
            jdbcTemplate.query("SELECT * FROM members WHERE " + condition, 
                new MemberRowMapper()));
    }
}

这样既保留了 Virtual Threads 的优势,又避免了连接池耗尽的问题。

效果对比

指标迁移前(传统线程池)迁移后(Virtual Threads)
最大并发连接5,00050,000+
P99 延迟300ms80ms
内存占用1.5GB500MB
线程切换开销极低
请求拒绝率5%0.1%

2.3 OpenTelemetry 替换 ELK:可观测性升级

问题发生

随着微服务数量从 10 个增长到 30 个,ELK 的问题越来越明显:

现象:
1. 日志查询慢,一个 Trace 需要在多个 Kibana 索引中搜索
2. 链路追踪不完整,TraceID 在异步线程中丢失
3. 监控指标分散在不同系统(Prometheus + ELK + Zipkin)
4. 告警规则维护困难,每个服务一套规则

根因分析

ELK 本质上是一个日志系统,不是完整的可观测性平台:

  • 日志:ELK 擅长,但查询慢、成本高
  • 指标:需要额外部署 Prometheus
  • 追踪:需要额外部署 Zipkin/Jaeger
  • 关联:三者之间无法自动关联

解决方案:OpenTelemetry

我们用 OpenTelemetry 替换了 ELK,实现了日志、指标、追踪的统一:

// OpenTelemetry 配置
@Configuration
public class OtelConfig {
    
    @Bean
    public OpenTelemetry openTelemetry() {
        return OpenTelemetrySdk.builder()
            .setTracerProvider(SdkTracerProvider.builder()
                .addSpanProcessor(SimpleSpanProcessor.create(
                    OtlpGrpcSpanExporter.builder().build()))
                .build())
            .setMeterProvider(SdkMeterProvider.builder()
                .registerMetricReader(PeriodicMetricReader.builder(
                    OtlpGrpcMetricExporter.builder().build()).build())
                .build())
            .setLoggerProvider(SdkLoggerProvider.builder()
                .addLogRecordProcessor(SimpleLogRecordProcessor.create(
                    OtlpGrpcLogRecordExporter.builder().build()))
                .build())
            .buildAndRegisterGlobal();
    }
}

自动注入 TraceID

// 过滤器:自动注入 TraceID 到 MDC
@Component
public class TraceIdFilter implements Filter {
    
    @Override
    public void doFilter(ServletRequest request, ServletResponse response, 
                        FilterChain chain) throws IOException, ServletException {
        SpanContext spanContext = Span.current().getSpanContext();
        if (spanContext.isValid()) {
            MDC.put("traceId", spanContext.getTraceId());
        }
        try {
            chain.doFilter(request, response);
        } finally {
            MDC.remove("traceId");
        }
    }
}

效果对比

指标迁移前(ELK)迁移后(OpenTelemetry)
Trace 查询时间10-30 秒< 1 秒
指标覆盖度60%100%
告警规则数50+15(统一规则)
问题定位时间30 分钟5 分钟
存储成本降低 40%

2.4 新增 AI 会员推荐模块:从规则引擎到机器学习

业务背景

会员系统需要根据用户行为推荐个性化内容,但传统的规则引擎越来越难以满足需求:

传统规则:
- 用户购买过 A 商品 → 推荐同类商品
- 用户浏览过 B 页面 → 推荐相关商品
- VIP 用户 → 推荐高端商品

问题:
- 规则太多,维护困难
- 推荐效果差,点击率只有 5%
- 无法发现潜在的用户兴趣

解决方案:AI 推荐系统

我们搭建了基于机器学习的推荐系统:

// 推荐服务
@Service
public class RecommendationService {
    
    @Autowired
    private RecommendationClient recommendationClient;
    
    public List<RecommendItem> getRecommendations(Long userId, int count) {
        RecommendRequest request = RecommendRequest.builder()
            .userId(userId)
            .count(count)
            .context(getUserContext(userId))
            .build();
        
        return recommendationClient.recommend(request);
    }
    
    private UserContext getUserContext(Long userId) {
        // 获取用户画像、行为历史等
        return UserContext.builder()
            .age(getUserAge(userId))
            .gender(getUserGender(userId))
            .interests(getUserInterests(userId))
            .purchaseHistory(getPurchaseHistory(userId))
            .build();
    }
}

推荐系统架构

┌─────────────────────────────────────────────────────────────┐
│                    AI 推荐模块                               │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  ┌─────────────┐    ┌─────────────┐    ┌─────────────┐      │
│  │ 特征工程    │───>│ 模型训练    │───>│ 模型服务    │      │
│  │ 用户行为分析│    │ XGBoost     │    │ TensorFlow  │      │
│  │ 商品画像    │    │ 协同过滤    │    │ Serving    │      │
│  └─────────────┘    └─────────────┘    └──────┬──────┘      │
│                                               │             │
│                                               ▼             │
│  ┌─────────────────────────────────────────────────────┐    │
│  │                    推荐缓存层                         │    │
│  │  Redis + Caffeine 本地缓存,热点推荐结果加速         │    │
│  └─────────────────────────────────────────────────────┘    │
│                                                             │
└─────────────────────────────────────────────────────────────┘

效果对比

指标迁移前(规则引擎)迁移后(AI 推荐)
推荐点击率5%18%
推荐转化率2%8%
规则维护成本高(每月更新)低(自动训练)
用户满意度⭐⭐⭐⭐⭐⭐⭐⭐

三、2026 年架构全景图

┌─────────────────────────────────────────────────────────────────────────┐
│                        2026 年架构(高亮部分为新增/变更)                 │
├─────────────────────────────────────────────────────────────────────────┤
│                                                                         │
│  ┌─────────┐    ┌───────┐    ┌───────────┐    ┌───────────┐            │
│  │   CDN   │───>│ Nginx │───>│  Spring   │    │  MySQL    │            │
│  │(静态资源)│    │(负载均衡)│  │   Boot    │───>│(分库分表) │            │
│  └─────────┘    └───────┘    │   应用层   │    │ 8库64表   │            │
│                              │  Java 21   │    └───────────┘            │
│                              │Virtual Threads│                          │
│                              └─────┬─────┘                              │
│                                    │                                   │
│                ┌───────────────────┼───────────────────┐                │
│                ▼                   ▼                   ▼                │
│          ┌─────────────┐   ┌─────────┐   ┌─────────────────────┐        │
│          │ Redis       │   │RabbitMQ │   │ OpenTelemetry       │        │
│          │ Cluster     │   │(消息队列)│   │(日志/指标/追踪一体) │        │
│          │(6节点集群)  │   │         │   │                     │        │
│          └─────────────┘   └─────────┘   └─────────────────────┘        │
│                                    │                                   │
│                                    ▼                                   │
│                          ┌─────────────────┐                           │
│                          │   AI 推荐模块   │ ⬅ 新增                    │
│                          │ 特征/训练/服务  │                           │
│                          └─────────────────┘                           │
│                                                                         │
│  核心数据:15 万 QPS,3500 万会员,日均交易 1000 万笔                      │
│                                                                         │
└─────────────────────────────────────────────────────────────────────────┘

变更汇总

组件2024 年2026 年变更原因
语言Java 17Java 21Virtual Threads
Redis主从哨兵Cluster热 Key 问题
线程池传统线程池Virtual Threads并发连接数瓶颈
可观测性ELKOpenTelemetry统一日志/指标/追踪
推荐系统规则引擎AI 推荐推荐效果差

四、关键指标仪表盘

┌────────────────────────────────────────────────────────────────────┐
│                    会员系统关键指标对比                             │
├────────────────────────────────────────────────────────────────────┤
│                                                                    │
│  ┌──────────────┬────────────────┬────────────────┬─────────────┐  │
│  │ 指标         │ 2024 年        │ 2026 年        │ 变化率      │  │
│  ├──────────────┼────────────────┼────────────────┼─────────────┤  │
│  │ QPS          │ 100,000        │ 150,000        │ +50%        │  │
│  │ 会员数       │ 2000 万        │ 3500 万        │ +75%        │  │
│  │ 日均交易     │ 500 万笔       │ 1000 万笔      │ +100%       │  │
│  │ P99 延迟     │ 200ms          │ 50ms           │ -75%        │  │
│  │ 可用性       │ 99.9%          │ 99.99%         │ +0.09%      │  │
│  │ Redis CPU    │ 100%           │ 30%            │ -70%        │  │
│  │ 内存占用     │ 4GB            │ 3GB            │ -25%        │  │
│  │ 推荐点击率   │ 5%             │ 18%            │ +260%       │  │
│  │ 问题定位时间 │ 30 分钟        │ 5 分钟         │ -83%        │  │
│  └──────────────┴────────────────┴────────────────┴─────────────┘  │
│                                                                    │
└────────────────────────────────────────────────────────────────────┘

五、仍未解决的问题

真实的架构演进不是完美的,我们还有很多问题没解决:

5.1 MySQL 分库分表的瓶颈

问题:
- 分库分表后,跨库查询困难
- 数据迁移复杂,扩容成本高
- 事务一致性难以保证

现状:
- 暂时没有更好的方案,只能继续优化 SQL
- 正在评估 TiDB 作为替代方案

5.2 RabbitMQ 的消息堆积

问题:
- 高峰期消息堆积严重,需要人工干预
- 死信队列处理不完善
- 消息顺序性无法保证

现状:
- 增加了消费者数量
- 优化了消息处理逻辑
- 正在评估 Kafka 作为替代方案

5.3 AI 推荐系统的冷启动

问题:
- 新用户没有历史行为数据,推荐效果差
- 新商品没有曝光数据,难以被推荐

现状:
- 使用热门商品兜底
- 逐步收集用户行为数据
- 探索冷启动算法

六、架构演进的思考

6.1 架构演进的本质

很多人以为架构演进是:
  ┌─────────┐    ┌─────────┐
  │ 旧架构  │───>│ 新架构  │
  └─────────┘    └─────────┘

但真实的架构演进是:
  ┌─────────┐    ┌─────────┐    ┌─────────┐    ┌─────────┐
  │ 旧架构  │───>│ 修修补补 │───>│ 再修补  │───>│ 新架构  │
  └─────────┘    └─────────┘    └─────────┘    └─────────┘
  
  每一步都是在生产环境的压力下,发现问题、解决问题、再发现新问题的循环。

6.2 什么时候该重构

条件是否重构
当前架构能满足业务需求❌ 不需要
当前架构无法满足业务需求,但有临时方案❌ 先临时方案
当前架构无法满足业务需求,且没有临时方案✅ 需要重构
重构成本 < 维护成本✅ 需要重构
重构成本 > 维护成本❌ 不值得

6.3 架构师的职责

架构师不是画架构图的人,而是:
1. 解决问题的人:在生产环境出现问题时,能快速定位并解决
2. 权衡利弊的人:在多个方案中选择最适合当前业务的
3. 持续演进的人:不追求完美,追求不断改进
4. 背锅的人:出问题时第一个站出来承担责任

七、总结

这两年的架构演进,让我明白了一个道理:

真正的架构不是设计出来的,是演化出来的。

我们没有画过一张全新的架构图,而是在原有的基础上,根据业务的变化和生产环境的反馈,一点点地修改、优化、替换。每一次变更都不是心血来潮,而是被逼无奈——要么是遇到了性能瓶颈,要么是遇到了故障,要么是业务需求发生了变化。

这种"修修补补"的演进方式,看似不完美,但却是最真实、最有效的。因为它保证了系统的稳定性,让业务能够持续发展,同时也让我们的技术团队在实战中不断成长。

互动话题:你的系统经历过哪些架构演进?最大的挑战是什么?欢迎留言分享!


附录

技术栈对比

分类2024 年2026 年
语言Java 17Java 21
框架Spring Boot 3.0Spring Boot 3.2
数据库MySQL 8.0MySQL 8.0
缓存Redis 6.0(主从哨兵)Redis 7.0(Cluster)
消息队列RabbitMQ 3.9RabbitMQ 3.11
可观测性ELK StackOpenTelemetry
推荐系统规则引擎AI 推荐(XGBoost + TensorFlow)
容器Docker + K8s 1.25Docker + K8s 1.29

参考资料


订阅提醒:关注我获取更多架构实战经验分享!


标题:2024→2026 版本升级:10 万 QPS 会员系统架构,两年后我们改了什么
作者:jiangyi
地址:http://www.jiangyi.space/articles/2026/07/29/1785082535642.html
公众号:服务端技术精选
    评论
    0 评论
avatar

取消