高并发系统性能优化全链路实战:端到端榨干系统性能,百万 QPS 零卡顿

简介: 本文系统阐述高并发系统端到端全链路性能优化方法,涵盖接入层(HTTP/3、CDN、LVS)、网关层(Spring Cloud Gateway调优)、服务层(JDK21虚拟线程、线程池、Undertow、Protobuf)、缓存层(多级缓存、Caffeine、Redis)、数据库(索引/SQL/事务/连接池)及OS硬件层优化,并强调压测定位、避坑指南与闭环迭代。

高并发系统的性能瓶颈从来都不是单点问题,而是端到端全链路的木桶效应——哪怕你把数据库优化到极致,只要接入层、网关层、服务层的任何一个环节出现短板,整个系统的吞吐量和延迟都会被严重拖累。

一、高并发性能优化的核心认知与指标体系

想要做好性能优化,首先要建立正确的认知体系,避免陷入常见的优化误区。

1.1 端到端优化的核心定义

端到端优化,是指覆盖用户发起请求到收到响应的完整链路,对每一个环节进行针对性优化,而非只聚焦于数据库等单点环节。实际生产环境中,80%的性能损耗往往发生在非数据库环节,只优化数据库很难获得质的提升。

1.2 核心性能指标

高并发系统的性能评估,核心关注三个指标:

  • 吞吐量(QPS/TPS):系统每秒能处理的请求数,是高并发能力的核心衡量标准
  • 延迟(RT):请求从发起到收到响应的时间,重点关注P95、P99延迟(95%/99%的请求能在该时间内完成),它决定了用户的最差体验,比平均延迟更重要
  • 可用性:系统正常服务的时间占比,高并发场景下性能瓶颈往往会引发系统雪崩,直接降低可用性

1.3 常见优化误区

  • 误区1:过早优化:在没有压测和瓶颈定位的情况下盲目优化,反而增加系统复杂度,甚至引入bug
  • 误区2:单点优化:只优化数据库,忽略接入层、网关层、网络层的优化,无法突破全链路的性能天花板
  • 误区3:只关注平均延迟:很多系统平均延迟很低,但P99延迟极高,用户实际体验极差
  • 误区4:过度优化:为了提升1%的性能,大幅增加系统复杂度,导致维护成本飙升
  • 误区5:忽略底层系统优化:很多时候系统瓶颈是操作系统参数设置不当,而非应用代码问题

二、端到端全链路优化架构体系

高并发系统的完整请求链路分为7个核心环节,每个环节都有对应的优化空间,整体架构如下:

本文将按照链路顺序,逐个拆解每个环节的优化逻辑、落地方法和可复用代码。

三、用户端与接入层性能优化

这个环节是请求的第一跳,也是最容易被忽略的环节,用户感知到的卡顿,80%都发生在这个环节,而非服务端。核心优化方向是:减少请求数、降低网络延迟、提升接入层吞吐量。

3.1 网络协议优化:从HTTP/1.1到HTTP/3

  • HTTP/1.1的核心问题:队头阻塞,同一个TCP连接同一时间只能处理一个请求,前一个请求未完成时,后续请求只能排队
  • HTTP/2的优化:多路复用,同一个TCP连接可同时处理多个请求,解决了应用层的队头阻塞,但TCP本身的队头阻塞依然存在——丢包后整个连接的所有请求都要等待重传
  • HTTP/3的核心优势:基于QUIC协议(UDP传输),彻底解决TCP的队头阻塞,每个请求都是独立的流,丢包只会影响单个请求;支持0-RTT握手,比TLS1.3的1-RTT更快;支持连接迁移,用户网络切换时连接不会断开。

最新稳定版Nginx 1.26.2已正式支持HTTP/3,下面是配置:

user nginx;

worker_processes auto;

worker_rlimit_nofile 1048576;

events {

   use epoll;

   worker_connections 1048576;

   multi_accept on;

}

http {

   include mime.types;

   default_type application/octet-stream;

   sendfile on;

   tcp_nopush on;

   tcp_nodelay on;

   keepalive_timeout 65;

   keepalive_requests 10000;

   types_hash_max_size 2048;

   gzip on;

   gzip_vary on;

   gzip_min_length 1024;

   gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;

   brotli on;

   brotli_comp_level 6;

   brotli_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript;

   server {

       listen 443 quic reuseport;

       listen 443 ssl;

       http2 on;

       http3 on;

       server_name example.com;

       ssl_certificate /etc/nginx/cert/fullchain.pem;

       ssl_certificate_key /etc/nginx/cert/privkey.pem;

       ssl_protocols TLSv1.2 TLSv1.3;

       ssl_ciphers TLS_AES_256_GCM_SHA384:TLS_CHACHA20_POLY1305_SHA256:TLS_AES_128_GCM_SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;

       ssl_prefer_server_ciphers off;

       ssl_session_cache shared:SSL:10m;

       ssl_session_timeout 1d;

       add_header Alt-Svc 'h3=":443"; ma=86400' always;

       add_header X-Frame-Options DENY always;

       add_header X-Content-Type-Options nosniff always;

       root /usr/share/nginx/html;

       index index.html;

       location /static/ {

           expires 365d;

           add_header Cache-Control "public, immutable";

       }

       location /api/ {

           proxy_pass http://gateway-cluster;

           proxy_set_header Host $host;

           proxy_set_header X-Real-IP $remote_addr;

           proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

           proxy_set_header X-Forwarded-Proto $scheme;

           proxy_connect_timeout 3s;

           proxy_send_timeout 10s;

           proxy_read_timeout 10s;

           proxy_http_version 1.1;

           proxy_set_header Connection "";

       }

   }

   upstream gateway-cluster {

       server 192.168.1.10:8080;

       server 192.168.1.11:8080;

       keepalive 32;

   }

}

配置核心优化点:

  • worker_processes auto:自动匹配CPU核心数,充分利用多核性能
  • worker_rlimit_nofile 1048576:调整单进程最大文件打开数,解决高并发下文件描述符不足的问题
  • 开启HTTP/3、HTTP/2,兼容HTTP/1.1,同时开启TLS1.3,降低握手延迟
  • 开启gzip和brotli双压缩,静态资源体积减少60%以上
  • 静态资源设置长缓存+immutable,避免重复请求
  • upstream开启keepalive长连接,减少和网关层的TCP握手开销

3.2 CDN优化策略

CDN的核心作用是将静态资源缓存到离用户最近的节点,减少跨地域网络延迟,核心优化点:

  1. 全量静态资源接入:HTML、JS、CSS、图片、视频、字体等所有非动态接口内容全部接入CDN
  2. 缓存策略优化:静态资源通过文件hash实现版本更新,设置365天长缓存;热点动态接口可通过CDN边缘计算能力缓存,减少回源请求
  3. 预热与预加载:大促前提前预热热点资源到全量CDN节点;对首屏资源开启DNS预解析、TCP预连接,降低首屏加载时间
  4. 协议对齐:CDN节点开启HTTP/3和TLS1.3,和接入层保持一致

3.3 四层负载均衡优化

四层负载均衡(LVS)工作在TCP层,负责将流量转发到Nginx集群,核心优化点:

  1. 采用DR直接路由模式,回程流量不经过LVS,吞吐量提升10倍以上
  2. 调整Linux内核参数,开启tcp_tw_reusetcp_syncookies,应对高并发TCP连接
  3. 禁用不必要的会话保持,避免用户请求固定到单个Nginx节点,导致负载不均

四、API网关层性能优化

API网关是后端服务的流量入口,网关的性能直接决定了整个系统的吞吐量上限。本文采用最新稳定版Spring Cloud Gateway 4.2.3,基于Spring Boot 3.3.3和JDK21 LTS实现,底层基于Netty 4.1.112.Final异步非阻塞架构,性能是传统Zuul网关的10倍以上。

4.1 网关核心优化方向

网关的性能瓶颈主要集中在路由匹配、过滤器链执行、网络IO、限流熔断逻辑,核心优化如下:

4.1.1 路由规则优化

  • 高频访问的路由按优先级放在最前面,减少匹配次数
  • 避免使用复杂正则作为路由断言,尽量用Path、Method等简单断言
  • 禁用所有不需要的路由,减少匹配循环次数

4.1.2 过滤器链优化

  • 只保留业务必需的过滤器,禁用所有非必要的内置过滤器
  • 过滤器逻辑尽量轻量化,避免在过滤器中执行耗时IO操作,必须执行时需用异步非阻塞方式
  • 合并相同逻辑的过滤器,减少过滤器链长度,降低方法调用开销

4.1.3 Netty底层参数调优

Spring Cloud Gateway底层基于Netty,参数配置直接决定网关性能,配置如下(application.yml):

spring:
 cloud:
   gateway:
     httpclient:
       connect-timeout: 3000
       response-timeout: 10s
       pool:
         type: elastic
         max-idle-time: 30s
         max-life-time: 60s
         max-connections: 10000
         acquire-timeout: 3000
     server:
       netty:
         connection-timeout: 3s
         idle-timeout: 30s
         max-initial-line-length: 4096
         max-header-size: 8192
         max-chunk-size: 8192
server:
 netty:
   connection-timeout: 3s
 reactor:
   netty:
     worker-count: 16
     selector-count: 4

配置说明:

  • httpclient.pool:配置转发请求的弹性连接池,最大连接数10000,应对高并发转发
  • reactor.netty.worker-count:Netty worker线程数,IO密集型场景设置为CPU核心数*4,8核CPU设置为16
  • 所有超时时间设置合理值,避免慢请求占用连接导致连接池耗尽

4.1.4 限流熔断优化

高并发场景下,限流熔断是网关的核心能力,避免后端服务被流量打垮。我们采用Redis令牌桶算法实现分布式限流,基于Spring Cloud Gateway内置的RequestRateLimiter,实现如下:

首先引入依赖(pom.xml):

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
        xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">

   <modelVersion>4.0.0</modelVersion>
   <parent>
       <groupId>org.springframework.boot</groupId>
       <artifactId>spring-boot-starter-parent</artifactId>
       <version>3.3.3</version>
       <relativePath/>
   </parent>
   <groupId>com.example</groupId>
   <artifactId>gateway-demo</artifactId>
   <version>0.0.1-SNAPSHOT</version>
   <name>gateway-demo</name>
   <properties>
       <java.version>21</java.version>
       <spring-cloud.version>2023.0.3</spring-cloud.version>
   </properties>
   <dependencies>
       <dependency>
           <groupId>org.springframework.cloud</groupId>
           <artifactId>spring-cloud-starter-gateway</artifactId>
       </dependency>
       <dependency>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-data-redis-reactive</artifactId>
       </dependency>
       <dependency>
           <groupId>org.springframework.cloud</groupId>
           <artifactId>spring-cloud-starter-circuitbreaker-reactor-resilience4j</artifactId>
       </dependency>
       <dependency>
           <groupId>io.micrometer</groupId>
           <artifactId>micrometer-registry-prometheus</artifactId>
       </dependency>
   </dependencies>
   <dependencyManagement>
       <dependencies>
           <dependency>
               <groupId>org.springframework.cloud</groupId>
               <artifactId>spring-cloud-dependencies</artifactId>
               <version>${spring-cloud.version}</version>
               <type>pom</type>
               <scope>import</scope>
           </dependency>
       </dependencies>
   </dependencyManagement>
   <build>
       <plugins>
           <plugin>
               <groupId>org.springframework.boot</groupId>
               <artifactId>spring-boot-maven-plugin</artifactId>
           </plugin>
       </plugins>
   </build>
</project>

然后实现限流维度的KeyResolver,指定按用户ID/IP限流:

package com.example.gatewaydemo.config;
import org.springframework.cloud.gateway.filter.ratelimit.KeyResolver;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import reactor.core.publisher.Mono;
import java.util.Optional;
@Configuration
public class GatewayRateLimitConfig {
   @Bean
   public KeyResolver userKeyResolver() {
       return exchange -> Mono.just(
           Optional.ofNullable(exchange.getRequest().getHeaders().getFirst("X-User-Id"))
                   .orElseGet(() -> exchange.getRequest().getRemoteAddress().getAddress().getHostAddress())
       );
   }
   @Bean
   public KeyResolver ipKeyResolver() {
       return exchange -> Mono.just(exchange.getRequest().getRemoteAddress().getAddress().getHostAddress());
   }
}

最后是完整的路由、限流、熔断配置(application.yml):

spring:
 application:
   name: gateway-demo
 data:
   redis:
     host: 192.168.1.20
     port: 6379
     password: your-redis-password
     lettuce:
       pool:
         max-active: 100
         max-idle: 50
         min-idle: 10
         max-wait: 3000ms
 cloud:
   gateway:
     routes:
       - id: user-service
         uri: lb://user-service
         predicates:
           - Path=/user/**
         filters:
           - name: RequestRateLimiter
             args:
               redis-rate-limiter.replenishRate: 1000
               redis-rate-limiter.burstCapacity: 2000
               redis-rate-limiter.requestedTokens: 1
               key-resolver: "#{@userKeyResolver}"
           - name: CircuitBreaker
             args:
               name: userServiceCircuitBreaker
               fallbackUri: forward:/fallback/user
       - id: order-service
         uri: lb://order-service
         predicates:
           - Path=/order/**
         filters:
           - name: RequestRateLimiter
             args:
               redis-rate-limiter.replenishRate: 500
               redis-rate-limiter.burstCapacity: 1000
               key-resolver: "#{@ipKeyResolver}"
           - name: CircuitBreaker
             args:
               name: orderServiceCircuitBreaker
               fallbackUri: forward:/fallback/order
     httpclient:
       connect-timeout: 3000
       response-timeout: 10s
       pool:
         type: elastic
         max-connections: 10000
         max-idle-time: 30s
         acquire-timeout: 3000
resilience4j:
 circuitbreaker:
   instances:
     userServiceCircuitBreaker:
       slidingWindowSize: 100
       failureRateThreshold: 50
       waitDurationInOpenState: 10000
       permittedNumberOfCallsInHalfOpenState: 10
     orderServiceCircuitBreaker:
       slidingWindowSize: 100
       failureRateThreshold: 50
       waitDurationInOpenState: 10000
       permittedNumberOfCallsInHalfOpenState: 10
server:
 port: 8080
management:
 endpoints:
   web:
     exposure:
       include: health,info,prometheus,gateway
 endpoint:
   health:
     show-details: always

配置说明:

  • replenishRate:令牌桶每秒填充的令牌数,即每秒允许的平均请求数
  • burstCapacity:令牌桶最大容量,即允许的突发请求数
  • 熔断配置:50%请求失败时熔断器打开,10秒后进入半开状态,允许10个请求测试服务可用性
  • 集成Prometheus监控,可实时观察网关的QPS、延迟、限流次数、熔断次数等指标

经过以上优化,Spring Cloud Gateway单节点(8核16G)可稳定支撑5万+QPS,P99延迟控制在10ms以内。

五、服务层核心性能优化

服务层是业务逻辑的核心,本文基于JDK21 LTS、Spring Boot 3.3.3,从虚拟线程、线程池、Web容器、接口优化、JVM调优五个维度,拆解全量优化方案。

5.1 JDK21虚拟线程:高并发的终极利器

JDK21正式发布的虚拟线程,是Java高并发编程的革命性特性,彻底解决了传统平台线程在IO密集型场景下的性能瓶颈。

5.1.1 虚拟线程的底层原理

传统平台线程与操作系统内核线程一一对应,每个平台线程默认栈内存为1M,创建1万个平台线程就需要10G内存,且内核线程调度开销极大,上下文切换需要内核态与用户态的切换,因此传统Java应用最多只能创建几千个平台线程,无法应对百万级并发。

虚拟线程是JVM管理的轻量级用户态线程,与内核线程是M:N的映射关系,多个虚拟线程可挂载在同一个平台线程(载体线程)上执行,每个虚拟线程的栈内存仅几百字节,JVM可轻松创建上百万个虚拟线程。

最核心的优化是:当虚拟线程执行阻塞IO操作(网络调用、数据库查询、Redis调用等)时,JVM会将虚拟线程从载体线程上卸载,让载体线程执行其他虚拟线程;阻塞操作完成后,再将虚拟线程重新挂载到载体线程执行。这样载体线程始终处于运行状态,CPU利用率可提升至90%以上,彻底解决了IO密集型场景下的线程阻塞问题。

这里明确区分虚拟线程与平台线程的核心差异:

特性 虚拟线程 平台线程
调度主体 JVM用户态调度 操作系统内核调度
内存占用 几百字节 1M左右
最大数量 百万级 几千级
上下文切换开销 极低,用户态切换 极高,内核态切换
适用场景 IO密集型任务 CPU密集型任务

5.1.2 虚拟线程的正确使用

Spring Boot 3.2+已完美支持虚拟线程,仅需一行配置即可开启,所有Spring MVC请求、@Async异步方法、定时任务都会自动使用虚拟线程执行,无需修改业务代码。

application.properties配置:

spring.threads.virtual.enabled=true

虚拟线程的完整使用示例,包含异步编排:

package com.example.service;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import java.util.concurrent.CompletableFuture;
@Service
public class UserService {
   private final UserMapper userMapper;
   private final OrderClient orderClient;
   private final RedisClient redisClient;
   public UserService(UserMapper userMapper, OrderClient orderClient, RedisClient redisClient) {
       this.userMapper = userMapper;
       this.orderClient = orderClient;
       this.redisClient = redisClient;
   }
   // 同步接口,开启虚拟线程后自动用虚拟线程执行
   public UserDetailVO getUserDetail(Long userId) {
       // 阻塞IO1:查询Redis
       UserCacheVO cacheVO = redisClient.get("user:" + userId, UserCacheVO.class);
       if (cacheVO != null) {
           return convertToDetailVO(cacheVO);
       }
       // 阻塞IO2:查询数据库
       UserDO userDO = userMapper.selectById(userId);
       if (userDO == null) {
           throw new RuntimeException("用户不存在");
       }
       // 阻塞IO3:远程调用订单服务
       List<OrderVO> orderList = orderClient.getOrderListByUserId(userId);
       UserDetailVO detailVO = convertToDetailVO(userDO, orderList);
       // 异步写入Redis,不阻塞主线程
       saveUserCacheAsync(userId, detailVO);
       return detailVO;
   }
   // 异步方法,自动用虚拟线程执行
   @Async
   public CompletableFuture<Void> saveUserCacheAsync(Long userId, UserDetailVO detailVO) {
       redisClient.setEx("user:" + userId, 300, detailVO);
       return CompletableFuture.completedFuture(null);
   }
   // 异步编排,并行执行多个阻塞IO,大幅降低接口延迟
   public UserDetailVO getUserDetailAsync(Long userId) {
       // 并行查询用户信息
       CompletableFuture<UserDO> userFuture = CompletableFuture.supplyAsync(
           () -> userMapper.selectById(userId)
       );
       // 并行查询订单列表
       CompletableFuture<List<OrderVO>> orderFuture = CompletableFuture.supplyAsync(
           () -> orderClient.getOrderListByUserId(userId)
       );
       // 等待所有异步任务完成,合并结果
       return CompletableFuture.allOf(userFuture, orderFuture)
           .thenApply(v -> {
               UserDO userDO = userFuture.join();
               List<OrderVO> orderList = orderFuture.join();
               return convertToDetailVO(userDO, orderList);
           }).join();
   }
}

5.1.3 虚拟线程的避坑指南

  1. 不要用虚拟线程执行CPU密集型任务:虚拟线程的优势是处理阻塞IO,CPU密集型任务会持续占用载体线程,导致其他虚拟线程无法调度,性能反而不如平台线程。CPU密集型任务应使用平台线程池,核心线程数设置为CPU核心数+1
  2. 避免使用synchronized同步块:JDK21已对synchronized做了优化,但虚拟线程阻塞时不会释放载体线程,会导致载体线程被阻塞。建议使用ReentrantLock,它支持虚拟线程的友好阻塞,会在虚拟线程阻塞时释放载体线程
  3. 不要用ThreadLocal存储大量数据:创建上百万个虚拟线程时,ThreadLocal会导致内存占用飙升,建议使用JDK21引入的ScopedValue,专为虚拟线程设计,内存占用更低、性能更好
  4. 不要池化虚拟线程:虚拟线程的创建成本极低,每次任务创建新的虚拟线程即可,池化反而会限制虚拟线程的数量,失去其核心优势,JDK官方明确不建议池化虚拟线程

5.2 线程池的正确使用与优化

除了虚拟线程,传统平台线程池在CPU密集型场景下依然必不可少,生产环境必须避免线程池的错误用法。

5.2.1 线程池参数的正确设置

线程池参数的核心设置原则是:根据任务类型(CPU密集型/IO密集型)匹配合理参数。

  1. CPU密集型任务:任务以计算、逻辑处理为主,CPU利用率高,阻塞时间少
  • 核心线程数:CPU核心数 + 1(避免CPU核心因页缺失暂停,保证CPU利用率)
  • 最大线程数:与核心线程数一致,避免过多线程导致CPU上下文切换频繁
  • 队列:有界队列,容量设置为1000左右,避免任务堆积导致OOM
  • 拒绝策略:CallerRunsPolicy,让调用线程执行任务,起到流量削峰的作用
  1. IO密集型任务:任务以阻塞IO操作为主,CPU利用率低,大部分时间处于阻塞等待状态
  • 核心线程数:CPU核心数 * 2
  • 最大线程数:CPU核心数 * 10,阻塞时间越长,最大线程数可适当增大
  • 队列:有界队列,容量设置为最大线程数的10倍左右
  • 拒绝策略:AbortPolicy,直接抛出异常,避免系统过载

这里纠正一个常见错误:Executors.newFixedThreadPool()创建的线程池使用无界队列,高并发场景下会导致任务无限堆积,最终引发OOM,生产环境必须用ThreadPoolExecutor手动创建线程池,设置有界队列。

5.2.2 线程池配置示例

package com.example.config;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.concurrent.*;
@Configuration
public class ThreadPoolConfig {
   private static final int CPU_CORE = Runtime.getRuntime().availableProcessors();
   // CPU密集型任务线程池
   @Bean("cpuIntensiveThreadPool")
   public ExecutorService cpuIntensiveThreadPool() {
       return new ThreadPoolExecutor(
               CPU_CORE + 1,
               CPU_CORE + 1,
               0L,
               TimeUnit.MILLISECONDS,
               new LinkedBlockingQueue<>(1000),
               new ThreadFactoryBuilder().setNameFormat("cpu-pool-%d").setDaemon(true).build(),
               new ThreadPoolExecutor.CallerRunsPolicy()
       );
   }
   // IO密集型任务平台线程池(不使用虚拟线程时使用)
   @Bean("ioIntensiveThreadPool")
   public ExecutorService ioIntensiveThreadPool() {
       return new ThreadPoolExecutor(
               CPU_CORE * 2,
               CPU_CORE * 10,
               60L,
               TimeUnit.SECONDS,
               new LinkedBlockingQueue<>(10000),
               new ThreadFactoryBuilder().setNameFormat("io-pool-%d").setDaemon(true).build(),
               new ThreadPoolExecutor.AbortPolicy()
       );
   }
}

5.3 Web容器优化

Spring Boot默认的Web容器是Tomcat,高并发场景下,Undertow的性能更优,它基于XNIO的异步非阻塞架构,吞吐量更高、资源占用更低。

切换Undertow只需修改pom.xml,排除Tomcat并引入Undertow:

<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-web</artifactId>
   <exclusions>
       <exclusion>
           <groupId>org.springframework.boot</groupId>
           <artifactId>spring-boot-starter-tomcat</artifactId>
       </exclusion>
   </exclusions>
</dependency>
<dependency>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-undertow</artifactId>
</dependency>

Undertow生产级配置(application.properties):

server.undertow.threads.io=16

server.undertow.threads.worker=200

server.undertow.max-http-post-size=10MB

server.undertow.http2.enabled=true

server.undertow.no-request-timeout=60000

server.undertow.options.socket.TCP_NODELAY=true

server.undertow.options.socket.SO_REUSEADDR=true

server.undertow.options.socket.SO_KEEPALIVE=true

配置说明:

  • io线程数:负责处理IO事件,默认是CPU核心数*2,8核CPU设置为16
  • worker线程数:负责处理业务逻辑,开启虚拟线程后可适当调小,因为业务逻辑由虚拟线程执行

5.4 接口性能优化

接口性能优化的核心原则是:减少阻塞、减少IO、减少计算、并行执行。

  1. 异步化:将非核心同步操作改为异步操作,比如日志记录、缓存写入、消息发送,不阻塞主线程,降低接口RT
  2. 并行化:多个无依赖的IO操作,用CompletableFuture并行执行,将串行RT转为并行RT,比如同时查询用户、订单、商品信息,接口RT可从300ms降至100ms
  3. 序列化优化:JSON序列化性能损耗较大,高并发场景建议使用Protocol Buffers(Protobuf)序列化,序列化速度是JSON的5-10倍,序列化后体积仅为JSON的1/3,大幅降低网络传输和CPU开销
  4. 避免频繁GC:接口中避免创建大量临时对象,尤其是大对象,比如字符串拼接使用StringBuilder,而非+号,避免创建大量String对象
  5. 懒加载:只加载需要的数据,避免全量查询,比如分页查询只返回当前页数据,VO对象只返回前端需要的字段,减少序列化开销

Protobuf序列化实战示例

首先引入依赖:

<dependency>
   <groupId>com.google.protobuf</groupId>
   <artifactId>protobuf-java</artifactId>
   <version>3.25.5</version>
</dependency>
<dependency>
   <groupId>io.grpc</groupId>
   <artifactId>protobuf-java-util</artifactId>
   <version>1.66.0</version>
</dependency>

编写Protobuf schema文件(user.proto):

syntax = "proto3";
option java_package = "com.example.proto";
option java_outer_classname = "UserProto";
message UserVO {
   int64 user_id = 1;
   string user_name = 2;
   int32 age = 3;
   string phone = 4;
   repeated OrderVO order_list = 5;
}
message OrderVO {
   int64 order_id = 1;
   string order_no = 2;
   int32 amount = 3;
   int64 create_time = 4;
}

通过Protobuf编译器生成Java类后,即可在代码中使用:

// 序列化
UserProto.UserVO userVO = UserProto.UserVO.newBuilder()
   .setUserId(1L)
   .setUserName("test")
   .setAge(20)
   .setPhone("13800138000")
   .addOrderList(UserProto.OrderVO.newBuilder()
       .setOrderId(1L)
       .setOrderNo("ORDER123456")
       .setAmount(100)
       .setCreateTime(System.currentTimeMillis())
       .build())
   .build();
byte[] bytes = userVO.toByteArray();
// 反序列化
UserProto.UserVO parsedUser = UserProto.UserVO.parseFrom(bytes);

5.5 JVM优化

JDK21的默认JVM参数已做了大量优化,默认使用G1垃圾收集器,大部分应用无需过度优化,仅需调整几个核心参数即可。

5.5.1 JVM参数配置

-Xms4g -Xmx4g -XX:NewRatio=2 -XX:SurvivorRatio=8 -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/log/jvm/heapdump.hprof -Xlog:gc*:/var/log/jvm/gc.log -XX:+UseStringDeduplication -XX:+OptimizeStringConcat

参数说明:

  • -Xms4g -Xmx4g:堆内存初始值与最大值一致,避免堆内存动态调整的开销,生产环境建议设置为物理内存的50%-70%
  • -XX:NewRatio=2:老年代与新生代的比例为2:1,新生代占堆内存的1/3,适配大部分业务场景
  • -XX:+UseG1GC:使用G1垃圾收集器,JDK21默认,适配大内存、低延迟场景
  • -XX:MaxGCPauseMillis=200:设置最大GC停顿时间为200ms,G1会自动调整新生代大小满足该要求
  • -XX:+UseStringDeduplication:开启字符串去重,减少字符串内存占用
  • -XX:+HeapDumpOnOutOfMemoryError:OOM时自动生成堆转储文件,方便排查问题

5.5.2 JVM优化避坑指南

  1. 不要盲目调大新生代:新生代太大会导致Young GC停顿时间变长,太小会导致频繁Young GC,需根据GC日志调整
  2. 不要使用CMS收集器:CMS已在JDK14中被移除,JDK21已不支持,建议使用G1或ZGC(ZGC停顿时间可控制在1ms以内,适配对延迟要求极高的场景)
  3. 不要设置过大的堆内存:堆内存太大会导致Full GC停顿时间变长,32G以上的堆内存会禁用压缩指针,导致内存占用反而变大
  4. 所有优化必须基于GC日志和压测结果,无数据支撑的优化都是盲目优化

六、缓存层全链路性能优化

缓存是高并发系统的核心组件,80%的高并发场景都是读多写少,缓存可将99%的读请求拦截在数据库之前,大幅降低数据库压力。本文基于最新稳定版Redis 7.4.1,从多级缓存设计、缓存异常场景解决、Redis性能优化三个维度,拆解全量优化方案。

6.1 多级缓存架构设计

高并发场景下,单级Redis缓存无法满足极致性能需求,需设计多级缓存架构,将热点数据尽量放在离CPU最近的地方,减少网络IO开销,整体流程如下:

多级缓存的层级:

  1. L1级缓存:CPU缓存,JVM内置,开发人员可通过代码优化提升命中率,比如连续数组访问比随机链表访问性能高很多
  2. L2级缓存:本地内存缓存,基于Caffeine实现,访问延迟在纳秒级别,无网络IO开销,适配热点不变数据
  3. L3级缓存:分布式缓存,Redis集群,访问延迟在毫秒级别,适配全量热点数据,支持分布式环境共享

6.2 本地缓存Caffeine实战

Caffeine是当前Java领域性能最高的本地缓存库,基于W-TinyLFU淘汰算法,命中率远高于Guava Cache,性能是Guava Cache的10倍以上,最新稳定版为3.1.8。

6.2.1 Caffeine集成与配置

首先引入依赖:

<dependency>
   <groupId>com.github.ben-manes.caffeine</groupId>
   <artifactId>caffeine</artifactId>
   <version>3.1.8</version>
</dependency>

配置类实现:

package com.example.config;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.concurrent.TimeUnit;
@Configuration
public class CaffeineConfig {
   // 本地缓存,存储热点用户数据
   @Bean("userLocalCache")
   public Cache<String, Object> userLocalCache() {
       return Caffeine.newBuilder()
               .maximumSize(10000)
               .expireAfterWrite(5, TimeUnit.MINUTES)
               .expireAfterAccess(2, TimeUnit.MINUTES)
               .recordStats()
               .build();
   }
   // 本地缓存,存储系统配置数据
   @Bean("configLocalCache")
   public Cache<String, Object> configLocalCache() {
       return Caffeine.newBuilder()
               .maximumSize(1000)
               .expireAfterWrite(60, TimeUnit.MINUTES)
               .recordStats()
               .build();
   }
}

6.2.2 多级缓存使用示例

package com.example.service;
import com.github.benmanes.caffeine.cache.Cache;
import org.springframework.stereotype.Service;
import java.util.Random;
@Service
public class UserService {
   private final Cache<String, Object> userLocalCache;
   private final RedisClient redisClient;
   private final UserMapper userMapper;
   public UserService(Cache<String, Object> userLocalCache, RedisClient redisClient, UserMapper userMapper) {
       this.userLocalCache = userLocalCache;
       this.redisClient = redisClient;
       this.userMapper = userMapper;
   }
   public UserVO getUserById(Long userId) {
       String key = "user:" + userId;
       // 1. 先查本地缓存
       UserVO userVO = (UserVO) userLocalCache.getIfPresent(key);
       if (userVO != null) {
           return userVO;
       }
       // 2. 本地缓存未命中,查Redis缓存
       userVO = redisClient.get(key, UserVO.class);
       if (userVO != null) {
           userLocalCache.put(key, userVO);
           return userVO;
       }
       // 3. Redis未命中,查数据库
       userVO = userMapper.selectById(userId);
       if (userVO != null) {
           // 缓存过期时间加随机值,避免缓存雪崩
           redisClient.setEx(key, 300 + new Random().nextInt(100), userVO);
           userLocalCache.put(key, userVO);
       }
       return userVO;
   }
}

6.3 缓存三大异常场景的解决方案

高并发场景下,缓存穿透、缓存击穿、缓存雪崩是必须解决的核心问题,这里明确区分三个易混淆的场景:

场景 定义 根本原因
缓存穿透 大量查询不存在的数据,请求直接穿透缓存打到数据库 缓存和数据库中都无对应数据
缓存击穿 热点key过期,大量请求同时打到数据库 单个热点key过期,并发请求量大
缓存雪崩 大量key同时过期或Redis集群宕机,大量请求打到数据库 大量key同时过期,或缓存服务不可用

6.3.1 缓存穿透的解决方案

  1. 布隆过滤器:将所有存在的key存入布隆过滤器,请求先查布隆过滤器,若布隆过滤器判定不存在,则数据一定不存在,直接返回,无需查询缓存和数据库。Redis 7.4内置了RedisBloom模块,无需额外安装
  2. 空值缓存:对于数据库中不存在的数据,缓存一个空值到Redis,过期时间设置为30秒,避免重复查询数据库

布隆过滤器实战示例: 首先引入Redisson依赖(适配Redis布隆过滤器):

<dependency>
   <groupId>org.redisson</groupId>
   <artifactId>redisson-spring-boot-starter</artifactId>
   <version>3.37.0</version>
</dependency>

代码实现:

package com.example.service;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class UserService {
   private final RedissonClient redissonClient;
   private final UserMapper userMapper;
   public UserService(RedissonClient redissonClient, UserMapper userMapper) {
       this.redissonClient = redissonClient;
       this.userMapper = userMapper;
   }
   // 项目启动时执行,初始化布隆过滤器
   public void initUserBloomFilter() {
       RBloomFilter<Long> bloomFilter = redissonClient.getBloomFilter("user_bloom_filter");
       // 初始化布隆过滤器,预期数据量100万,误判率0.01%
       bloomFilter.tryInit(1000000, 0.0001);
       // 将所有用户ID存入布隆过滤器
       List<Long> userIdList = userMapper.selectAllUserId();
       for (Long userId : userIdList) {
           bloomFilter.add(userId);
       }
   }
   public UserVO getUserById(Long userId) {
       RBloomFilter<Long> bloomFilter = redissonClient.getBloomFilter("user_bloom_filter");
       // 布隆过滤器判定不存在,直接返回
       if (!bloomFilter.contains(userId)) {
           return null;
       }
       // 布隆过滤器判定存在,再查询缓存和数据库
       // 后续缓存、数据库查询逻辑省略
       return userMapper.selectById(userId);
   }
}

6.3.2 缓存击穿的解决方案

  1. 热点key永不过期:对于核心热点key(比如首页热点商品),设置永不过期,后台异步更新缓存
  2. 互斥锁:热点key过期时,仅允许一个线程查询数据库并更新缓存,其他线程等待缓存更新完成后再查询,避免大量请求同时打到数据库,基于Redisson分布式锁实现

互斥锁实战示例:

public UserVO getUserById(Long userId) {
   String key = "user:" + userId;
   UserVO userVO = redisClient.get(key, UserVO.class);
   if (userVO != null) {
       return userVO;
   }
   // 缓存未命中,加分布式锁
   String lockKey = "lock:user:" + userId;
   RLock lock = redissonClient.getLock(lockKey);
   try {
       // 尝试加锁,等待时间3秒,锁过期时间10秒
       if (lock.tryLock(3, 10, TimeUnit.SECONDS)) {
           try {
               // 双重检查,避免加锁过程中其他线程已更新缓存
               userVO = redisClient.get(key, UserVO.class);
               if (userVO != null) {
                   return userVO;
               }
               // 查询数据库
               userVO = userMapper.selectById(userId);
               if (userVO != null) {
                   // 更新缓存,过期时间加随机值
                   redisClient.setEx(key, 300 + new Random().nextInt(100), userVO);
               }
           } finally {
               lock.unlock();
           }
       } else {
           // 加锁失败,等待100ms后重试
           Thread.sleep(100);
           return getUserById(userId);
       }
   } catch (InterruptedException e) {
       Thread.currentThread().interrupt();
   }
   return userVO;
}

6.3.3 缓存雪崩的解决方案

  1. 过期时间加随机值:给缓存过期时间添加随机偏移量,比如5分钟+0-100秒随机值,避免大量key同时过期
  2. Redis集群高可用:采用主从+哨兵+集群的部署模式,避免Redis单点故障,Redis 7.4集群模式支持自动分片、故障转移
  3. 服务熔断与限流:Redis集群不可用时,开启熔断,避免大量请求打到数据库,同时通过网关限流控制进入系统的请求量
  4. 多级缓存兜底:本地缓存可扛住大部分热点请求,即使Redis集群宕机,也能保证系统核心功能可用

6.4 Redis性能优化

Redis性能优化分为客户端优化、服务端优化、数据结构优化、持久化优化四个维度。

6.4.1 客户端优化

  1. 使用Lettuce客户端:Spring Boot 2.0+默认使用Lettuce,基于Netty的异步非阻塞客户端,性能远高于Jedis,支持连接池、集群、哨兵模式
  2. 连接池优化:设置合理的连接池参数,最大连接数设置为100左右,避免过多连接导致Redis服务端压力过大
  3. 批量操作:使用pipeline批量执行命令,减少网络IO次数,批量查询性能比多次单命令执行高10倍以上
  4. 避免大key:单个key的value不要超过10KB,大key会导致网络传输、序列化开销大,甚至阻塞Redis主线程

6.4.2 服务端配置

Redis 7.4优化配置(redis.conf):

daemonize yes

port 6379

bind 0.0.0.0

protected-mode no

requirepass your-redis-password

maxclients 10000

maxmemory 8g

maxmemory-policy volatile-lru

appendonly yes

appendfsync everysec

aof-use-rdb-preamble yes

rdbcompression yes

rdbchecksum yes

save 900 1

save 300 10

save 60 10000

tcp-keepalive 300

tcp-backlog 511

timeout 0

lazyfree-lazy-eviction yes

lazyfree-lazy-expire yes

lazyfree-lazy-server-del yes

replica-lazy-flush yes

配置说明:

  • maxmemory:设置Redis最大内存,建议为物理内存的50%-70%,避免使用swap分区导致性能急剧下降
  • maxmemory-policy:内存淘汰策略,volatile-lru对设置了过期时间的key使用LRU算法淘汰,适配大部分业务场景
  • lazyfree-lazy-*:开启惰性删除,删除大key时使用后台线程异步删除,避免阻塞主线程
  • appendfsync everysec:AOF持久化每秒同步一次,平衡性能与数据安全性,最多丢失1秒数据

6.4.3 数据结构优化

  1. 选择合适的数据结构:用户签到记录使用Bitmap,内存占用仅为Set的1/10000;排行榜使用SortedSet,性能远高于List排序
  2. 压缩列表优化:对于小的Hash、List、Set、SortedSet,Redis会使用压缩列表存储,内存占用更低,需合理设置压缩列表的最大元素个数和大小
  3. 禁用高危命令:禁止使用keys *hgetallsmembers等全量遍历命令,会阻塞Redis主线程,应使用scan命令分批遍历

6.4.4 持久化优化

  1. 开启RDB+AOF混合持久化:Redis 4.0+支持混合持久化,AOF文件包含RDB全量数据和增量AOF命令,重启恢复速度远快于纯AOF,同时保证数据安全性
  2. 避免业务高峰期执行RDB持久化:RDB持久化会fork子进程,fork时会阻塞主线程,内存越大阻塞时间越长,应在业务低峰期执行
  3. 关闭AOF自动重写,手动在低峰期执行:AOF自动重写可能在业务高峰期触发,导致Redis性能下降,建议关闭自动重写,每天凌晨低峰期手动执行BGREWRITEAOF命令

七、数据库层性能优化

数据库是系统的最后一道防线,也是高并发场景下最容易出现性能瓶颈的环节。本文基于最新稳定版MySQL 8.4.2 LTS,从SQL优化、索引优化、事务优化、连接池优化、服务端优化五个维度,拆解全量优化方案。

7.1 MySQL核心架构与底层原理

MySQL分为Server层和存储引擎层,核心架构如下:

  • Server层:包含连接器、查询缓存、解析器、优化器、执行器,负责MySQL核心服务功能,所有跨存储引擎的功能都在这一层实现
  • 存储引擎层:负责数据的存储和提取,插件式架构,MySQL 5.5+默认使用InnoDB存储引擎,支持事务、行级锁、外键、崩溃恢复,是生产环境唯一选择

InnoDB存储引擎的核心是两个日志:redo log和binlog,这里明确区分两者的差异:

特性 redo log binlog
所属层 InnoDB存储引擎层 Server层
核心作用 崩溃恢复,保证事务持久性 数据归档、主从复制
内容类型 物理日志,记录数据页的修改 逻辑日志,记录SQL原始逻辑
写入方式 循环写入,文件大小固定 追加写入,不会覆盖历史内容
生命周期 MySQL重启后失效 永久存储

7.2 索引优化

90%的MySQL性能问题都是索引不合理导致的,InnoDB默认使用B+树索引,所有数据都存在叶子节点,叶子节点之间用双向链表连接,适配范围查询、排序、分组。

7.2.1 聚簇索引与非聚簇索引

这里明确区分两个易混淆的索引类型:

  1. 聚簇索引:即主键索引,叶子节点存储整行数据,InnoDB表必须有聚簇索引,一个表只能有一个聚簇索引
  2. 非聚簇索引:即二级索引,叶子节点存储主键的值,查询时先通过二级索引找到主键,再通过聚簇索引找到整行数据,这个过程称为回表,一个表可以有多个二级索引

7.2.2 索引设计的黄金法则

  1. 优先使用联合索引,避免多个单列索引:联合索引可覆盖多个查询条件,而多个单列索引MySQL只会选择其中一个最优的,无法同时使用
  2. 联合索引遵循最左前缀原则:查询必须从索引的最左列开始匹配,不能跳过中间列,否则后续列无法用到索引。比如联合索引idx(a,b,c)a=? and b=? and c=?可使用全索引,a=? and c=?只能使用第一列,b=? and c=?完全无法使用索引
  3. 索引列不能参与计算、函数操作、隐式类型转换,否则会导致索引失效
  4. 尽量使用覆盖索引,避免回表:查询的列都在索引中,无需回表查询聚簇索引,性能提升10倍以上
  5. 控制索引数量:一个表的索引数量不要超过5个,索引会降低插入、更新、删除的性能,因为每次修改数据都要更新对应的索引
  6. 避免冗余索引:已有联合索引idx(a,b),就无需再创建单列索引idx(a)

7.2.3 索引失效的常见场景

  1. 索引列使用函数操作、计算、表达式
  2. 索引列发生隐式类型转换
  3. 模糊查询以%开头
  4. 使用OR连接非索引列
  5. 联合索引不满足最左前缀原则
  6. 使用不等于(!=<>)、not inis not null操作(大部分场景会导致索引失效)
  7. MySQL优化器判断全表扫描比走索引更快时,会放弃索引

7.2.4 索引优化示例

首先创建用户表:

CREATE TABLE `user` (
 `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
 `user_name` varchar(50) NOT NULL COMMENT '用户名',
 `phone` varchar(20) NOT NULL COMMENT '手机号',
 `age` int NOT NULL COMMENT '年龄',
 `gender` tinyint NOT NULL COMMENT '性别:1男,2女',
 `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
 `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
 PRIMARY KEY (`id`),
 UNIQUE KEY `uk_phone` (`phone`),
 KEY `idx_name_age_gender` (`user_name`,`age`,`gender`),
 KEY `idx_create_time` (`create_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='用户表';

正确的查询示例,使用覆盖索引,无需回表:

EXPLAIN SELECT user_name, age, gender FROM user WHERE user_name = 'test' AND age = 20 AND gender = 1;

错误的查询示例,索引失效:

-- 索引列使用函数,索引失效
EXPLAIN SELECT * FROM user WHERE DATE_FORMAT(create_time, '%Y-%m-%d') = '2024-01-01';
-- 正确写法,使用索引idx_create_time
EXPLAIN SELECT * FROM user WHERE create_time >= '2024-01-01' AND create_time < '2024-01-02';

-- 隐式类型转换,phone是varchar类型,传入数字导致索引失效
EXPLAIN SELECT * FROM user WHERE phone = 13800138000;
-- 正确写法,传入字符串,使用索引uk_phone
EXPLAIN SELECT * FROM user WHERE phone = '13800138000';

-- 模糊查询以%开头,索引失效
EXPLAIN SELECT * FROM user WHERE user_name LIKE '%test%';
-- 正确写法,前缀匹配,使用索引idx_name_age_gender
EXPLAIN SELECT * FROM user WHERE user_name LIKE 'test%';

7.3 SQL优化

SQL优化的核心原则是:减少扫描的行数,减少回表的次数,减少排序、分组的开销。

  1. 避免使用select *,只查询需要的列,减少数据传输开销,更容易使用覆盖索引
  2. 避免使用子查询,尽量用join关联查询,MySQL 8.0+优化器已对子查询做了优化,但join性能更稳定
  3. 避免关联超过3个表,关联表过多会导致优化器选择错误的执行计划,性能下降
  4. 分页查询优化:offset很大时,limit offset, size性能极差,因为MySQL会扫描前面的offset行再丢弃,优化方法是使用主键id过滤,比如select * from user where id > 100000 limit 10,性能比limit 100000, 10高100倍以上
  5. 避免在where子句中使用or连接非索引列,会导致索引失效,可用union all代替
  6. 避免使用order by rand(),会导致全表扫描后排序,性能极差

分页查询优化示例

错误的分页查询,offset很大时性能极差:

SELECT * FROM user ORDER BY id LIMIT 100000, 10;

正确的优化写法,使用主键id过滤:

SELECT * FROM user WHERE id > 100000 ORDER BY id LIMIT 10;

若id不连续,或排序字段不是id,可使用子查询先找到主键id,再关联查询:

SELECT u.* FROM user u
INNER JOIN (SELECT id FROM user ORDER BY create_time LIMIT 100000, 10) AS t ON u.id = t.id;

7.4 事务优化

InnoDB事务的ACID特性:原子性、一致性、隔离性、持久性,事务优化的核心是:缩小事务范围,减少锁的持有时间,避免死锁。

7.4.1 事务隔离级别

MySQL有4种事务隔离级别,默认是REPEATABLE READ(可重复读):

  1. READ UNCOMMITTED:可读取其他事务未提交的数据,会出现脏读、不可重复读、幻读,生产环境绝对禁止使用
  2. READ COMMITTED:只能读取其他事务已提交的数据,避免了脏读,会出现不可重复读、幻读,大部分互联网公司使用该级别,锁范围更小,并发性能更高
  3. REPEATABLE READ:同一个事务内多次读取同一个数据的结果一致,避免了脏读、不可重复读,InnoDB通过MVCC和间隙锁解决了幻读问题,MySQL默认隔离级别
  4. SERIALIZABLE:所有事务串行执行,避免了所有问题,但并发性能极差,生产环境绝对禁止使用

生产环境建议使用READ COMMITTED隔离级别,锁范围更小,不会出现间隙锁,并发性能更高,同时binlog格式设置为row,不会出现主从复制不一致的问题。

7.4.2 事务优化的核心手段

  1. 尽量缩小事务范围,将非核心操作放到事务外面,比如日志记录、缓存更新、消息发送,减少事务执行时间和锁的持有时间
  2. 避免在事务中执行耗时的IO操作,比如远程调用、文件操作,会导致事务长时间不提交,锁一直持有,引发其他事务阻塞甚至死锁
  3. 避免在事务中循环操作数据库,比如循环插入数据,应使用批量插入,减少事务执行次数
  4. 合理设置事务超时时间,避免事务长时间不提交占用连接
  5. 避免长事务,长事务会导致undo log无法清理,占用大量磁盘空间,同时导致MVCC视图过旧,查询性能下降

7.4.3 事务优化示例

错误的事务写法,事务范围过大,包含远程调用:

@Transactional(rollbackFor = Exception.class)
public void createOrder(OrderCreateDTO dto)
{
   validateParam(dto);
   // 远程调用商品服务,耗时IO操作
   ProductVO productVO = productClient.getProductById(dto.getProductId());
   // 远程调用库存服务,耗时IO操作
   inventoryClient.deductStock(dto.getProductId(), dto.getQuantity());
   // 插入订单数据
   OrderDO orderDO = convertToDO(dto);
   orderMapper.insert(orderDO);
   // 发送消息、更新缓存
   mqClient.sendOrderCreateEvent(orderDO);
   redisClient.setEx("order:" + orderDO.getId(), 300, orderDO);
}

正确的事务写法,缩小事务范围,仅将数据库操作放在事务中:

public void createOrder(OrderCreateDTO dto) {
   validateParam(dto);
   // 远程调用放在事务外
   ProductVO productVO = productClient.getProductById(dto.getProductId());
   inventoryClient.deductStock(dto.getProductId(), dto.getQuantity());
   // 仅数据库操作放在事务中
   OrderDO orderDO = createOrderInTransaction(dto);
   // 非核心操作放在事务外
   mqClient.sendOrderCreateEvent(orderDO);
   redisClient.setEx("order:" + orderDO.getId(), 300, orderDO);
}
@Transactional(rollbackFor = Exception.class)
public OrderDO createOrderInTransaction(OrderCreateDTO dto)
{
   OrderDO orderDO = convertToDO(dto);
   orderMapper.insert(orderDO);
   return orderDO;
}

7.5 连接池优化

Spring Boot默认使用HikariCP连接池,是当前Java领域性能最高的连接池,比Druid、C3P0性能高很多。

7.5.1 HikariCP配置

spring.datasource.url=jdbc:mysql://192.168.1.30:3306/test?useUnicode=true&characterEncoding=utf8&useSSL=false&serverTimezone=Asia/Shanghai&allowPublicKeyRetrieval=true

spring.datasource.username=root

spring.datasource.password=your-mysql-password

spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

spring.datasource.hikari.maximum-pool-size=20

spring.datasource.hikari.minimum-idle=5

spring.datasource.hikari.idle-timeout=300000

spring.datasource.hikari.max-lifetime=1800000

spring.datasource.hikari.connection-timeout=30000

spring.datasource.hikari.leak-detection-threshold=60000

配置说明:

  • maximum-pool-size:最大连接数,Oracle官方推荐计算公式:connections = ((core_count * 2) + effective_spindle_count),8核CPU、SSD磁盘设置为20是合理的。连接数太多会导致CPU上下文切换频繁、锁竞争激烈,性能反而下降
  • minimum-idle:最小空闲连接数,保持连接池中的最小空闲连接,避免请求到来时再创建连接
  • leak-detection-threshold:连接泄漏检测阈值,设置为60秒,连接被占用超过60秒会打印警告日志,帮助排查连接泄漏问题

7.6 MySQL服务端优化

MySQL 8.4.2 LTS配置优化(my.cnf):

[mysqld]

port=3306

datadir=/var/lib/mysql

socket=/var/lib/mysql/mysql.sock

pid-file=/var/run/mysqld/mysqld.pid

user=mysql

default-storage-engine=InnoDB

character-set-server=utf8mb4

collation-server=utf8mb4_0900_ai_ci

max_connections=1000

wait_timeout=600

interactive_timeout=600

innodb_buffer_pool_size=12G

innodb_buffer_pool_instances=8

innodb_log_buffer_size=64M

innodb_max_undo_log_size=4G

innodb_log_file_size=2G

innodb_log_files_in_group=2

innodb_flush_log_at_trx_commit=1

sync_binlog=1

innodb_flush_method=O_DIRECT

innodb_read_io_threads=8

innodb_write_io_threads=8

innodb_io_capacity=2000

innodb_io_capacity_max=4000

innodb_autoinc_lock_mode=2

innodb_thread_concurrency=0

sql_mode=STRICT_TRANS_TABLES,NO_ENGINE_SUBSTITUTION

lower_case_table_names=1

slow_query_log=ON

slow_query_log_file=/var/log/mysql/slow.log

long_query_time=1

log_queries_not_using_indexes=ON

配置说明:

  • innodb_buffer_pool_size:InnoDB缓冲池大小,最核心的参数,用来缓存表的数据和索引,越大越好,建议设置为物理内存的50%-70%
  • innodb_buffer_pool_instances:缓冲池实例个数,建议设置为与CPU核心数一致,减少锁竞争
  • innodb_flush_log_at_trx_commit=1:事务提交时将redo log刷新到磁盘,保证事务持久性,生产环境必须设置为1
  • sync_binlog=1:每次事务提交时将binlog刷新到磁盘,保证binlog不丢失,生产环境必须设置为1
  • innodb_flush_method=O_DIRECT:使用直接IO,绕过操作系统缓存,避免双缓存,性能更高
  • 开启慢查询日志,超过1秒的SQL都会被记录,方便排查慢SQL问题

八、操作系统与硬件层优化

很多时候系统的性能瓶颈不是应用代码,而是操作系统和硬件配置不当,Linux操作系统优化分为CPU、内存、磁盘IO、网络四个维度。

8.1 CPU优化

  1. 关闭CPU节能模式,设置为性能模式,避免CPU自动降频导致性能下降
  2. 绑定CPU亲和性,将Java进程绑定到特定的CPU核心上,避免CPU上下文切换,提升缓存命中率,使用命令taskset -cp 0-7 pid绑定Java进程到0-7号CPU核心
  3. 禁用NUMA内存交错,让进程优先使用本地内存,提升内存访问速度

8.2 内存优化

  1. 关闭swap分区,swap分区访问速度比物理内存慢几百倍,高并发场景下使用swap会导致系统性能急剧下降
  2. 设置vm.swappiness=0,尽量不使用swap分区,仅当物理内存不足时才使用
  3. 设置vm.overcommit_memory=1,允许内核过量分配内存,适配Java应用的堆内存预分配机制

8.3 磁盘IO优化

  1. 使用SSD固态硬盘,随机读写性能是机械硬盘的100倍以上,高并发场景必须使用SSD
  2. 调整磁盘IO调度算法,SSD使用noopnone调度算法,机械硬盘使用deadline调度算法
  3. 关闭磁盘atime记录,在/etc/fstab中设置noatime,nodiratime,避免每次访问文件都写入磁盘,减少IO开销
  4. 使用XFS文件系统,比ext4性能更高,支持更大的文件和分区,MySQL、Redis都推荐使用XFS

8.4 网络优化

Linux内核TCP参数优化(/etc/sysctl.conf):

net.ipv4.ip_forward = 0

net.ipv4.conf.all.accept_source_route = 0

net.ipv4.conf.all.accept_redirects = 0

net.ipv4.conf.all.send_redirects = 0

net.ipv4.tcp_syncookies = 1

net.ipv4.tcp_max_syn_backlog = 65535

net.ipv4.tcp_synack_retries = 2

net.ipv4.tcp_syn_retries = 2

net.ipv4.tcp_tw_reuse = 1

net.ipv4.tcp_tw_recycle = 0

net.ipv4.tcp_fin_timeout = 30

net.ipv4.tcp_keepalive_time = 300

net.ipv4.tcp_keepalive_intvl = 30

net.ipv4.tcp_keepalive_probes = 3

net.ipv4.ip_local_port_range = 1024 65535

net.ipv4.tcp_max_tw_buckets = 200000

net.ipv4.tcp_max_orphans = 200000

net.core.rmem_default = 262144

net.core.rmem_max = 16777216

net.core.wmem_default = 262144

net.core.wmem_max = 16777216

net.core.netdev_max_backlog = 65535

net.core.somaxconn = 65535

net.ipv4.tcp_rmem = 4096 87380 16777216

net.ipv4.tcp_wmem = 4096 65536 16777216

net.ipv4.tcp_mtu_probing = 1

配置说明:

  • net.ipv4.tcp_syncookies=1:开启SYN cookies,防止SYN洪水攻击
  • net.ipv4.tcp_tw_reuse=1:允许重用TIME_WAIT状态的连接,避免高并发下端口耗尽
  • net.ipv4.tcp_tw_recycle=0:关闭TIME_WAIT快速回收,避免NAT环境下连接失败
  • net.core.somaxconn=65535:调整监听队列大小,避免高并发下连接被拒绝
  • net.core.netdev_max_backlog=65535:调整网络设备接收队列大小,避免高并发下数据包丢失

修改完配置后,执行sysctl -p命令生效。

九、全链路压测与瓶颈定位

所有的优化都必须基于压测和瓶颈定位,没有压测的优化都是盲目优化。本文采用最新稳定版JMeter 5.6.3进行压测,SkyWalking 9.7.0进行全链路监控,定位性能瓶颈。

9.1 压测方案设计

压测分为三个核心阶段:

  1. 基准压测:单接口压测,测试系统的基准性能,找到单接口的最大QPS和RT
  2. 混合场景压测:模拟线上真实业务场景,多个接口按比例混合压测,测试系统的整体性能
  3. 稳定性压测:长时间压测(比如72小时),测试系统的稳定性,排查内存泄漏、OOM、频繁GC等问题

压测核心关注指标:吞吐量(QPS)、响应时间(平均RT、P95、P99、P999 RT)、错误率、CPU利用率、内存利用率、磁盘IO、网络IO。

9.2 全链路监控与瓶颈定位

SkyWalking是国产开源全链路APM工具,完美支持Spring Cloud微服务架构,可监控从用户端到数据库的全链路请求,精准定位性能瓶颈。

瓶颈定位的核心步骤:

  1. 查看系统整体指标:QPS、RT、错误率是否达到预期
  2. 查看服务器资源指标:CPU、内存、磁盘IO、网络IO是否存在资源瓶颈
  3. 查看全链路追踪:找到请求的哪个环节耗时最长,是网关、服务、Redis还是数据库
  4. 针对性优化:慢SQL优化索引和SQL,Redis慢查询优化命令和数据结构,服务层耗时优化代码、线程池、JVM

9.3 性能优化的闭环

性能优化是一个持续的闭环过程:压测 -> 定位瓶颈 -> 优化 -> 再压测 -> 再定位,直到达到预期的性能目标,不要期望一次优化就能解决所有问题,需循序渐进、逐步迭代。

十、高并发优化的避坑指南与总结

10.1 易混淆技术点的明确区分

  • 同步vs异步:同步是调用方等待结果返回再继续执行;异步是调用方不等待结果返回,结果通过回调通知返回
  • 阻塞vs非阻塞:阻塞是调用方等待结果时线程被挂起,不占用CPU;非阻塞是调用方等待结果时线程不被挂起,继续执行其他任务,轮询检查结果
  • 虚拟线程vs平台线程:核心区别是调度主体、内存占用、适用场景,虚拟线程适配IO密集型任务,平台线程适配CPU密集型任务
  • 缓存穿透vs缓存击穿vs缓存雪崩:核心区别是触发场景和根本原因,穿透是查询不存在的数据,击穿是单个热点key过期,雪崩是大量key同时过期或缓存宕机
  • 聚簇索引vs非聚簇索引:核心区别是叶子节点存储的内容,聚簇索引存储整行数据,非聚簇索引存储主键值,需要回表

10.2 高并发优化的常见坑

  1. 过早优化:无压测、无瓶颈定位的盲目优化,增加系统复杂度甚至引入bug
  2. 过度优化:为了提升1%的性能,大幅增加系统复杂度,导致维护成本飙升
  3. 单点优化:只优化数据库,忽略全链路其他环节,性能提升有限
  4. 忽略可用性:为了提升性能牺牲系统可用性,比如关闭事务持久化导致数据丢失
  5. 忽略监控:无监控无法验证优化效果,也无法定位系统瓶颈
  6. 不做压测验证:优化后不做压测,无法确认优化效果,甚至引入新的问题

10.3 总结

高并发系统的性能优化,从来都不是单点的技术炫技,而是端到端全链路的系统工程。从用户端发起请求的第一跳,到数据最终落盘的最后一环,每个环节都有优化的空间,每个细节都决定了系统的性能上限。优化的核心逻辑是:先通过压测和监控定位瓶颈,再针对性优化;先优化架构,再优化代码;先解决80%的性能损耗点,再打磨20%的细节,循序渐进,持续迭代。

目录
相关文章
|
2天前
|
人工智能 JSON 机器人
让龙虾成为你的“公众号分身” | 阿里云服务器玩Openclaw
本文带你零成本玩转OpenClaw:学生认证白嫖6个月阿里云服务器,手把手配置飞书机器人、接入免费/高性价比AI模型(NVIDIA/通义),并打造微信公众号“全自动分身”——实时抓热榜、AI选题拆解、一键发布草稿,5分钟完成热点→文章全流程!
10241 34
让龙虾成为你的“公众号分身” | 阿里云服务器玩Openclaw
|
14天前
|
人工智能 安全 Linux
【OpenClaw保姆级图文教程】阿里云/本地部署集成模型Ollama/Qwen3.5/百炼 API 步骤流程及避坑指南
2026年,AI代理工具的部署逻辑已从“单一云端依赖”转向“云端+本地双轨模式”。OpenClaw(曾用名Clawdbot)作为开源AI代理框架,既支持对接阿里云百炼等云端免费API,也能通过Ollama部署本地大模型,完美解决两类核心需求:一是担心云端API泄露核心数据的隐私安全诉求;二是频繁调用导致token消耗过高的成本控制需求。
5926 14
|
22天前
|
人工智能 JavaScript Ubuntu
5分钟上手龙虾AI!OpenClaw部署(阿里云+本地)+ 免费多模型配置保姆级教程(MiniMax、Claude、阿里云百炼)
OpenClaw(昵称“龙虾AI”)作为2026年热门的开源个人AI助手,由PSPDFKit创始人Peter Steinberger开发,核心优势在于“真正执行任务”——不仅能聊天互动,还能自动处理邮件、管理日程、订机票、写代码等,且所有数据本地处理,隐私完全可控。它支持接入MiniMax、Claude、GPT等多类大模型,兼容微信、Telegram、飞书等主流聊天工具,搭配100+可扩展技能,成为兼顾实用性与隐私性的AI工具首选。
23186 120
|
8天前
|
人工智能 JavaScript API
解放双手!OpenClaw Agent Browser全攻略(阿里云+本地部署+免费API+网页自动化场景落地)
“让AI聊聊天、写代码不难,难的是让它自己打开网页、填表单、查数据”——2026年,无数OpenClaw用户被这个痛点困扰。参考文章直击核心:当AI只能“纸上谈兵”,无法实际操控浏览器,就永远成不了真正的“数字员工”。而Agent Browser技能的出现,彻底打破了这一壁垒——它给OpenClaw装上“上网的手和眼睛”,让AI能像真人一样打开网页、点击按钮、填写表单、提取数据,24小时不间断完成网页自动化任务。
1927 4

热门文章

最新文章