多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

拒绝670亿背后的技术底气:高并发架构如何支撑业务高速增长

拒绝670亿背后的技术底气:高并发架构如何支撑业务高速增长 前阵子有家公司因为“拒绝670亿”的融资消息刷了屏很多人调侃这是最“凡尔赛”的商业决策。但仔细想想一家公司在巨额资金面前敢说“不”背后往往不只是创始人情怀那么简单更关键的是业务模型、技术底盘和工程组织已经到了一个相对从容的阶段。换句话说技术底气是商业选择的重要支撑。本文不谈八卦也不做投资分析而是借这个话题聊一聊当一家公司有机会冲击超高估值时它的技术体系到底需要长成什么样。我们会从高并发架构、数据层设计、微服务治理、可观测性与工程效率几个维度展开配合可复用的配置和代码示例帮助大家建立一套应对业务高速增长的架构思路。无论你是后端开发者、架构师还是技术负责人这篇文章都应该能带来一些参考。1. 背景高估值企业的技术压力从哪里来1.1 高估值对应的往往是高增长预期投资人愿意给出巨额估值看重的不是公司当下的收入而是未来几年的增长曲线。而增长一旦进入加速期流量、数据量、团队规模都会同步膨胀。技术系统如果还是按初创期“能跑就行”的思路搭建很快就会出现各种瓶颈接口超时、数据库连接被打满、发布一次要停服半小时、线上问题半天定位不到根因。我见过不少这样的场景业务增速极快技术团队每天都在“救火”核心链路频繁告警新功能上线靠熬夜代码质量开始滑坡。这时候就算估值翻倍技术负责人心里也是虚的因为系统随时可能撑不住。所以一家公司有没有底气拒绝巨额融资技术端至少要看三件事系统能不能承载未来三到五倍的增长。新功能迭代能不能保持稳定、快速。线上问题能不能快速发现、定位、恢复。这三件事每一件都对应具体的技术建设。1.2 技术建设不是堆机器而是做架构设计很多团队遇到性能问题第一反应是加服务器、加带宽但这只能解决一时的问题。真正健康的做法是把架构设计到“能水平扩展”的轨道上让每一台新机器都能真正分担压力而不是让压力集中在一两个核心节点上。后面我们会逐步展开这些设计细节。2. 可扩展架构的核心先让服务无状态2.1 什么是无状态服务无状态Stateless这个词在分布式系统里非常关键。简单理解一个服务实例不保存与用户会话相关的本地状态任何一次请求都可以由任意一个实例独立处理。有状态的服务长什么样呢比如早期很多单体应用把用户登录信息存在内存Session里一旦这台服务器挂了用户就要重新登录更麻烦的是负载均衡把请求转发到另一台服务器时Session对不上体验就很差。而无状态服务的做法是Session数据放到独立的存储层比如Redis服务实例本身不保存状态。这样流量上来后可以随意横向加机器请求均匀分散到所有实例任何一台机器宕机流量也会自动漂移走。2.2 代码示例基于 JWT 的无状态认证无状态认证是目前很主流的做法客户端在登录后拿到一个Token后续请求带上Token服务端校验签名即可不需要在服务端保存会话信息。下面是一个基于 Spring Boot 和 JJWT 库的简单无状态认证示例// 文件路径src/main/java/com/example/stateless/JwtUtil.java package com.example.stateless; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import io.jsonwebtoken.security.Keys; import javax.crypto.SecretKey; import java.util.Date; public class JwtUtil { // 生产环境不要写死在代码里建议放到配置中心或环境变量 private static final SecretKey SECRET_KEY Keys.secretKeyFor(SignatureAlgorithm.HS256); private static final long EXPIRE_MILLIS 60 * 60 * 1000; // 1小时 public static String generateToken(String username) { Date now new Date(); Date expireAt new Date(now.getTime() EXPIRE_MILLIS); return Jwts.builder() .setSubject(username) .setIssuedAt(now) .setExpiration(expireAt) .signWith(SECRET_KEY) .compact(); } public static String parseToken(String token) { return Jwts.parserBuilder() .setSigningKey(SECRET_KEY) .build() .parseClaimsJws(token) .getBody() .getSubject(); } }再写一个简单的拦截器校验请求头里的 Token// 文件路径src/main/java/com/example/stateless/AuthInterceptor.java package com.example.stateless; import org.springframework.web.servlet.HandlerInterceptor; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; public class AuthInterceptor implements HandlerInterceptor { Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { String token request.getHeader(Authorization); if (token ! null token.startsWith(Bearer )) { try { String username JwtUtil.parseToken(token.substring(7)); request.setAttribute(username, username); return true; } catch (Exception e) { response.setStatus(401); return false; } } response.setStatus(401); return false; } }这里的设计要点是JWT 自包含用户信息服务端不需要存储 Session因此任意实例都能处理任意请求这就是典型的无状态。2.3 无状态带来的扩展优势无状态服务扩容时只需要两步打包新实例注册到服务发现或负载均衡。不需要迁移 Session不需要担心“某台机器上的用户”被转走。这也是容器化和弹性伸缩的前提Kubernetes 中 HPAHorizontal Pod Autoscaler能够自动扩缩容前提就是工作负载可水平拆分。3. 数据层设计从单库到分库分表3.1 数据库为什么容易成为瓶颈大部分业务系统最核心的状态都落在数据库里业务量增长后第一个扛不住的基本都是数据库。常见的表现有慢查询变多接口响应时间升高。数据库 CPU 飙高。连接数被打满应用报获取连接超时。磁盘 IO 持续高位。解决思路不是上来就分库分表而是先做几件性价比更高的事优化慢 SQL补充合适的索引。引入缓存把读多写少的数据从数据库挪到 Redis。读写分离把查询压力分流到从库。再往上才是分库分表。3.2 缓存层的引入缓存设计不是简单“加一层 Redis”就结束需要考虑缓存穿透、缓存击穿、缓存雪崩等一系列问题。先看一个基于 Spring Data Redis 的查询缓存示例// 文件路径src/main/java/com/example/cache/ProductService.java package com.example.cache; import com.example.cache.model.Product; import org.springframework.data.redis.core.RedisTemplate; import org.springframework.stereotype.Service; import java.time.Duration; Service public class ProductService { private final RedisTemplateString, Object redisTemplate; private final ProductRepository productRepository; public ProductService(RedisTemplateString, Object redisTemplate, ProductRepository productRepository) { this.redisTemplate redisTemplate; this.productRepository productRepository; } public Product getProduct(Long id) { String key product: id; Object cached redisTemplate.opsForValue().get(key); if (cached ! null) { return (Product) cached; } // 防止缓存穿透查询不到的数据也缓存空值 Product product productRepository.findById(id).orElse(null); if (product null) { redisTemplate.opsForValue().set(key, null, Duration.ofSeconds(60)); return null; } redisTemplate.opsForValue().set(key, product, Duration.ofMinutes(30)); return product; } }这个例子重点体现了“缓存空值”的做法可以防止大量恶意请求打到数据库层。不过如果并发极高还需要考虑用分布式锁避免缓存击穿。3.3 分库分表什么时候做分库分表是重量级改造通常建议在单库超过一定规模、写入并发明显受限时才考虑。例如用户表达到千万级单表索引维护成本变高时就可以按用户 ID 取模分库分表。ShardingSphere 是目前国内常用的分库分表中间件下面是一个简化配置示例# 文件路径application-sharding.yaml dataSources: ds0: url: jdbc:mysql://localhost:3306/ds0?useSSLfalseserverTimezoneAsia/Shanghai username: root password: change_me ds1: url: jdbc:mysql://localhost:3306/ds1?useSSLfalseserverTimezoneAsia/Shanghai username: root password: change_me rules: sharding: tables: user: actualDataNodes: ds${0..1}.user_${0..1} tableStrategy: standard: shardingColumn: id shardingAlgorithmName: user_inline databaseStrategy: standard: shardingColumn: id shardingAlgorithmName: db_inline shardingAlgorithms: user_inline: type: INLINE props: algorithm-expression: user_${id % 2} db_inline: type: INLINE props: algorithm-expression: ds${id % 2}需要注意分库分表后跨库查询、事务、分页排序都会变得复杂。生产环境做这类改造一定要有完善的迁移方案和数据校验手段不能一边改一边丢数据。4. 微服务拆分从单体走向服务化4.1 拆分的正确动机很多人拆微服务是因为“行业都在拆”但微服务不是银弹。单体应用在业务简单、团队规模小的时候开发和部署效率都很高。真正需要拆分的信号是团队人数多了多人同时改一个仓库频繁冲突。不同模块的发布节奏差异大核心链路发布被低频模块阻塞。某个模块资源消耗特别大希望独立扩缩容。技术栈需要异构比如某个模块适合用 Go 写。拆分时要按业务边界切而不是按功能层切。比如“用户服务”“订单服务”“支付服务”是合理拆分“Controller 服务”“Service 服务”“DAO 服务”就是灾难性拆分。4.2 服务注册与发现微服务架构下服务实例的 IP 是动态变化的必须引入注册中心。Nacos 是目前国内使用非常广泛的一个注册中心同时支持配置管理。服务启动后向 Nacos 上报自己的地址消费者通过服务名从 Nacos 获取可用实例列表。Spring Cloud Alibaba 的集成方式如下# 文件路径application.yml spring: application: name: order-service cloud: nacos: discovery: server-addr: 127.0.0.1:8848消费者通过 OpenFeign 调用// 文件路径src/main/java/com/example/consumer/UserClient.java package com.example.consumer; import org.springframework.cloud.openfeign.FeignClient; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; FeignClient(name user-service) public interface UserClient { GetMapping(/users/{id}) UserInfo getUser(PathVariable(id) Long id); }这样业务代码只需要关心服务名不关心具体实例地址实例上下线由注册中心动态同步。4.3 限流、熔断与降级微服务调用链路变长后任何一个节点出问题都可能引发雪崩。比如订单服务调用库存服务库存服务响应变慢订单服务的线程池被慢慢占满接着上游网关也堆积请求最终整条链路瘫痪。所以限流和熔断是微服务架构里的基础设施。Sentinel 是阿里开源的一款轻量级流量控制组件配合 Spring Cloud Alibaba 使用非常方便。下面是一个 Sentinel 熔断规则的简单配置// 文件路径src/main/java/com/example/flow/FlowRuleConfig.java package com.example.flow; import com.alibaba.csp.sentinel.slots.block.RuleConstant; import com.alibaba.csp.sentinel.slots.block.flow.FlowRule; import com.alibaba.csp.sentinel.slots.block.flow.FlowRuleManager; import org.springframework.context.annotation.Configuration; import javax.annotation.PostConstruct; import java.util.ArrayList; import java.util.List; Configuration public class FlowRuleConfig { PostConstruct public void initFlowRules() { ListFlowRule rules new ArrayList(); FlowRule rule new FlowRule(); rule.setResource(order-service-query); rule.setGrade(RuleConstant.FLOW_GRADE_QPS); rule.setCount(500); rules.add(rule); FlowRuleManager.loadRules(rules); } }上面的配置表示对order-service-query这个资源每秒最多通过 500 个请求超过部分直接拒绝从而保护下游系统。需要注意的是限流规则应该根据真实压测数据来设置而不是拍脑袋。设得太低会误伤正常用户设得太高又起不到保护作用。5. 可观测性没有监控就没有底气5.1 三根支柱Metrics、Logging、Tracing公司敢不敢拒绝巨额融资也取决于一个很现实的问题线上出故障时技术团队能不能快速定位。这就要说到可观测性。可观测性一般包含三个方向Metrics指标监控比如 QPS、RT、错误率、CPU、内存。Logging日志收集比如业务操作日志、异常日志。Tracing链路追踪比如一个请求从网关到各个微服务的完整路径。三者各自解决不同问题指标让你知道系统当前健康度日志让你看到具体错误内容链路追踪告诉你一次请求经历了哪些服务、每一跳耗时多少。5.2 快速接入一个简单的健康检查端点即使还没有完整的监控平台服务本身也应该暴露健康检查接口。Spring Boot Actuator 提供了现成能力# 文件路径application.yml management: endpoints: web: exposure: include: health,info,metrics endpoint: health: show-details: always配置之后访问/actuator/health就能看到服务健康状态。Kubernetes 的探针、负载均衡的健康检查都可以复用这个接口curl http://localhost:8080/actuator/health返回示例{ status: UP, components: { db: { status: UP }, redis: { status: UP } } }5.3 日志规范与链路追踪很多团队排查问题慢不是因为工具不够而是日志太乱。日志里没有 traceId也没有业务唯一标识出了错只能靠时间戳猜关系。比较推荐的做法在网关层生成全局 TraceId通过 HTTP Header 向下游传递。所有服务在打印日志时带上这个 TraceId这样筛选日志时一条链路就能拉通。如果项目已经上了微服务建议直接引入 SkyWalking 或 Zipkin 这类链路追踪系统它们能自动生成和传递 TraceId不需要业务代码手工处理。6. 工程效率高速迭代的组织保障6.1 CI/CD 打通交付流水线估值高、业务增速快的团队代码发布频率通常不会低。如果发一次版本还要手动跑测试、手动上传包、手动执行 SQL 脚本那效率会严重拖后腿。建议至少做到代码提交后自动触发静态检查和单元测试。构建产物自动推送到镜像仓库。经人工审批后自动部署到测试环境或生产环境。失败自动回滚或一键回滚。一个基于 GitHub Actions 的简单构建推送示例# 文件路径.github/workflows/build.yml name: CI Build on: push: branches: [ main ] jobs: build: runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkoutv4 - name: Set up JDK 17 uses: actions/setup-javav3 with: distribution: temurin java-version: 17 - name: Run tests run: mvn clean test - name: Package application run: mvn clean package -DskipTests - name: Login to Docker Hub uses: docker/login-actionv3 with: username: ${{ secrets.DOCKER_USERNAME }} password: ${{ secrets.DOCKER_PASSWORD }} - name: Build and push Docker image uses: docker/build-push-actionv5 with: context: . push: true tags: | ${{ secrets.DOCKER_IMAGE }}:${{ github.sha }}这里需要强调真正落地的流水线还应该包含数据库变更脚本的自动执行、配置管理、灰度发布等环节但核心思路是一样的把重复的手工操作交给自动化工具。6.2 环境隔离与配置管理很多线上故障都跟配置有关不小心把测试环境的地址写到了生产配置或者某个新参数只有生产环境配了但测试环境没配。要解决这个问题建议引入配置中心。Apollo 或 Nacos 都可以做配置中心这里以 Apollo 为例看一下配置的基本接入方式。在application.properties中配置app.idmy-high-growth-app apollo.metahttp://apollo-config-service:8080 apollo.bootstrap.enabledtrue apollo.bootstrap.namespacesapplication配置中心的好处是配置变更可以实时推送不需要重启应用还能做权限控制、灰度发布、变更审计。生产环境的配置变更最好走审批流程尤其是数据库连接、第三方密钥这类敏感配置。6.3 发布策略灰度与回滚融资背景下的增长往往伴随频繁的功能发布如果每次发布都是全量一刀切风险很高。推荐用小流量灰度发布方式例如先发布到 1% 的节点观察核心指标。确认无异常扩大到 10%继续观察。逐步扩展到 50%、100%。如果过程中指标异常立刻把流量摘掉回滚到上一个稳定版本。Kubernetes 的 Deployment 滚动更新加上 Service 的标签选择器可以比较方便地实现这种发布策略。7. 常见问题与排查思路7.1 流量突增后接口大面积超时问题现象常见原因解决思路接口响应时间从 50ms 涨到 5s数据库慢查询或连接池耗尽先查看数据库慢日志抓取慢 SQL补充索引或引入缓存少量节点 CPU 打满其他节点空闲负载均衡策略问题或服务存在热点数据检查负载策略确认热点 key 是否集中在个别实例网关大量 503/504下游服务线程池耗尽或熔断未生效查看下游服务线程池指标配置合理的熔断和降级策略排查这类问题建议固定一套顺序先看监控大盘确认故障范围再查日志定位异常时间点然后用链路追踪确认具体故障服务最后结合 SQL 慢日志和中间件指标定位根因。不要一上来就猜。7.2 缓存引入后数据不一致问题现象常见原因解决思路用户看到旧数据缓存更新和数据库更新不是原子操作优先更新数据库再删除缓存推荐与延迟双删配合缓存雪崩大量 key 同时过期过期时间加随机偏移量避免集中过期缓存击穿热点 key 过期瞬间大量请求打到数据库使用互斥锁或提前续期热点 key缓存不一致是分布式系统里最难处理的问题之一没有一劳永逸的方案只能结合业务场景选择取舍。如果业务对一致性要求极高建议优先考虑数据库直查而不是一开始就上缓存。7.3 微服务拆分后问题反增问题现象常见原因解决思路在线问题变多拆分后接口调用链路变长引入链路追踪先摸清完整调用拓扑发布互相依赖服务之间边界划分不合理重新梳理业务边界避免服务之间紧耦合重复代码严重拆分时没有同步沉淀公共组件抽取公共 SDK 或内部依赖仓库统一维护微服务拆分不是终点拆完之后的治理才是更耗精力的事情。如果团队规模不到几十人建议慎重拆微服务先把模块边界在代码工程内划清楚未来需要拆的时候也有基础。8. 最佳实践与工程建议结合前面几节内容再总结几条对实际项目最有帮助的建议。8.1 从第一天就按“可扩展”的方式写代码不要求初创公司一开始就上微服务但至少要做到服务实例之间状态隔离用户状态放到外部存储。数据库连接池、线程池参数可配置。每个接口都有基本的限流和超时设置。核心业务链路打上日志埋点。等到业务量真正上来这些基础能力会省去大量重构成本。8.2 重视容量规划和压测很多公司只有出了故障才想起来做压测。更合理的做法是定期对核心链路做全链路压测摸清当前最大容量提前知道系统的瓶颈在哪里并根据业务增长预测提前部署扩容。压测时要特别注意不要在生产环境随意压测尽量使用独立压测集群。压测数据要脱敏避免污染线上数据。压测要覆盖数据库、缓存、消息队列、第三方接口等所有依赖。8.3 配置和生产环境变更要谨慎不管公司估值多少生产环境的安全红线不能乱碰。所有配置变更走配置中心保留变更历史和审计日志。数据库变更先备份再在测试环境执行最后在低峰期操作生产。涉及删除数据的操作必须明确 WHERE 条件先查询确认影响行数再执行。敏感密钥不要提交到代码仓库使用密钥管理服务或环境变量。8.4 建设技术团队的风险意识技术负责人要时刻提醒团队融资和估值是商业层面的东西技术团队更应该关注的是“系统稳不稳定”“交付快不快”“故障恢复快不快”。一个经得起增长考验的架构才是公司敢于做长期策略选择的底气。9. 总结与下一步学习方向回到文章开头那个话题。拒绝巨额资金这件事能成为新闻说明大部分公司还是很难拒绝这样的机会。但从技术角度想真正应该学习的是一家公司要支撑起如此高的估值预期背后的技术建设远不只是买几台服务器那么简单。本文提到的几个方向你可以按优先级逐步推进无状态化改造让系统具备水平扩展的基础。缓存和数据库优化守住核心数据链路。微服务化和流量控制保证长链路调用的稳定性。可观测性建设让故障从“未知”变成“已知”。CI/CD 和配置管理让迭代速度匹配业务增长速度。下一步可以继续深入的方向包括Kubernetes 容器化与弹性伸缩、全链路压测实践、数据一致性方案分布式事务、基于 Service Mesh 的流量治理等。每一条都值得单独花时间研究和落地。如果这篇文章对你有帮助欢迎收藏备用。后续我也会继续整理更多关于高并发架构、微服务治理和工程效率提升的实战内容。
返回列表