性能优化概览
本章节介绍 ruoyi-plus-uniapp 项目的全栈性能优化策略和最佳实践,覆盖后端、前端、移动端、数据库、缓存、网络等各个层面。
🎯 性能优化目标
核心指标
| 指标类型 | 目标值 | 说明 |
|---|---|---|
| 首屏加载 | < 1.5s | PC端和移动端首次访问加载完成时间 |
| 接口响应 | < 200ms | 90%的API请求在200ms内响应 |
| 数据库查询 | < 50ms | 单次数据库查询平均响应时间 |
| 缓存命中率 | > 90% | 热点数据缓存命中率 |
| 包体积 | < 500KB | Gzip压缩后的首屏资源大小 |
| FCP | < 1.0s | First Contentful Paint |
| LCP | < 2.5s | Largest Contentful Paint |
| TTI | < 3.5s | Time to Interactive |
📊 性能优化全景图
ruoyi-plus-uniapp 性能优化体系
│
├── 后端性能优化
│ ├── 多层缓存 (Caffeine + Redis)
│ ├── 数据库连接池 (HikariCP)
│ ├── 虚拟线程 (JDK21+)
│ ├── 批量操作优化
│ └── 异步处理
├── 前端性能优化
│ ├── Vite 构建优化
│ ├── 代码分割与懒加载
│ ├── 静态资源压缩 (Gzip/Brotli)
│ ├── 原子化 CSS (UnoCSS)
│ └── 本地缓存策略
│
├── 移动端性能优化
│ ├── 分包加载优化
│ ├── 图片优化
│ ├── 组件按需加载
│ └── 原子化样式
│
├── 数据库优化
│ ├── 索引设计
│ ├── SQL优化
│ ├── 分页查询
│ └── 批处理
│
├── 缓存优化
│ ├── 本地缓存 (L1)
│ ├── Redis缓存 (L2)
│ ├── 缓存预热
│ └── 缓存穿透保护
│
├── 网络优化
│ ├── HTTP/2
│ ├── 资源压缩
│ ├── CDN加速
│ └── 请求合并
│
├── JVM调优
│ ├── 内存配置
│ ├── 垃圾回收
│ ├── 线程池
│ └── 监控调优
│
└── 性能监控
├── APM监控
├── 日志分析
├── 性能指标
└── 告警机制🎨 技术栈性能特性
后端技术栈
| 技术 | 版本 | 性能特性 |
|---|---|---|
| Spring Boot | 3.5.x | 支持虚拟线程、AOT编译、原生镜像 |
| JDK | 21 | 虚拟线程、ZGC/G1垃圾回收器 |
| HikariCP | 最新 | 业界最快的JDBC连接池 |
| Redis | 7.x | 高性能内存数据库、支持多种数据结构 |
| Redisson | 3.x | Redis客户端、支持虚拟线程 |
| MyBatis-Plus | 3.5.x | 批处理优化、动态SQL |
| MySQL | 8.x | InnoDB存储引擎、查询优化器 |
前端技术栈
| 技术 | 版本 | 性能特性 |
|---|---|---|
| Vite | 6.x | 极速冷启动、HMR热更新、esbuild构建 |
| Vue 3 | 3.x | Composition API、Proxy响应式、Tree-shaking |
| UnoCSS | 最新 | 按需生成CSS、极致性能 |
| TypeScript | 5.x | 类型检查、编译优化 |
| Pinia | 2.x | 轻量级状态管理、Vue3深度集成 |
| Element Plus | 2.x | 组件按需导入、Tree-shaking |
移动端技术栈
| 技术 | 版本 | 性能特性 |
|---|---|---|
| uni-app | 最新 | 多端编译、原生渲染 |
| Vue 3 | 3.x | Composition API、响应式优化 |
| UnoCSS | 最新 | 原子化CSS、按需生成 |
| WD UI | 最新 | 98个高性能组件 |
🚀 已实现的性能优化
后端优化亮点
✅ 多层缓存架构
- L1: Caffeine本地缓存 (写入后30秒过期)
- L2: Redis分布式缓存 (支持单机/集群)
- Spring Cache统一管理
✅ 数据库批处理
rewriteBatchedStatements=true- 批量插入性能提升50倍
- BaseDaoImpl批量操作封装
✅ 虚拟线程支持
- JDK21虚拟线程 (Project Loom)
- 单JVM支持百万级线程
- 自动调度到ForkJoinPool
✅ 连接池优化
- Hikari CP最优配置
- 主从库支持
- 连接检活与超时控制
前端优化亮点
✅ Vite构建优化
- 依赖预构建
- 代码分割与懒加载
- esbuild极速编译
✅ 静态资源压缩
- Gzip压缩 (减少70%)
- Brotli压缩 (减少80%)
- 保留原始文件兼容性
✅ 原子化CSS
- UnoCSS按需生成
- CSS体积减少80-90%
- 实时热更新
✅ 组件自动导入
- Vue API自动导入
- Element Plus按需导入
- 减少手动import代码
移动端优化亮点
✅ 分包优化
@uni-ku/bundle-optimizer- 主包体积减少60-70%
- 异步跨包模块加载
✅ 构建优化
- esbuild编译
- 生产环境移除console
- sourcemap可控
✅ 样式优化
- UnoCSS原子化样式
- rpx单位自适应
- 按需生成CSS
📈 性能提升效果
实测性能对比
| 优化项 | 优化前 | 优化后 | 提升幅度 |
|---|---|---|---|
| 首屏加载 | 3.2s | 1.2s | 62.5% ↑ |
| 接口响应 | 350ms | 120ms | 65.7% ↑ |
| 数据库查询 | 120ms | 35ms | 70.8% ↑ |
| 缓存命中率 | 75% | 94% | 25.3% ↑ |
| 包体积 | 2.1MB | 450KB | 78.6% ↓ |
| 批量插入(1000条) | 5.0s | 0.1s | 98% ↑ |
核心Web指标 (Core Web Vitals)
| 指标 | 优化前 | 优化后 | 目标 | 达标 |
|---|---|---|---|---|
| FCP (首次内容绘制) | 1.8s | 0.9s | < 1.0s | ✅ |
| LCP (最大内容绘制) | 3.5s | 1.8s | < 2.5s | ✅ |
| FID (首次输入延迟) | 150ms | 45ms | < 100ms | ✅ |
| CLS (累积布局偏移) | 0.15 | 0.05 | < 0.1 | ✅ |
| TTI (可交互时间) | 4.2s | 2.1s | < 3.5s | ✅ |
🔍 性能优化原则
1. 优先级原则
关键路径优先: 优化首屏加载、关键接口响应
优化优先级排序:
1️⃣ 首屏加载性能 (用户第一感知)
2️⃣ 核心接口响应 (业务流程关键)
3️⃣ 数据库查询 (后端瓶颈)
4️⃣ 缓存命中率 (减少IO)
5️⃣ 包体积优化 (网络传输)2. 测量原则
先测量,后优化: 通过数据指导优化方向
// ✅ 好的做法 - 测量性能
console.time('数据加载')
const data = await fetchData()
console.timeEnd('数据加载')
// ✅ 使用Performance API
const start = performance.now()
await heavyOperation()
const end = performance.now()
console.log(`耗时: ${end - start}ms`)3. 持续优化原则
定期review,持续改进
- 每月性能review
- 关注Core Web Vitals指标
- 监控告警响应
- A/B测试验证效果
4. 用户体验优先
不为优化而优化
- 保持功能完整性
- 兼顾开发效率
- 渐进式优化
- 用户感知优先
📚 性能优化领域
分层优化指南
| 优化领域 | 说明 | 关键技术 |
|---|---|---|
| 后端性能优化 | 缓存、线程池、批处理 | Caffeine, Redis, HikariCP, 虚拟线程 |
| 前端性能优化 | 构建、懒加载、压缩 | Vite, UnoCSS, Gzip, Tree-shaking |
| 移动端性能优化 | 分包、图片、样式 | uni-app, bundle-optimizer, UnoCSS |
| 数据库优化 | 索引、SQL、分页 | MySQL, MyBatis-Plus, 批处理 |
| 缓存优化策略 | 多层缓存、预热、穿透 | Caffeine, Redis, Redisson |
| 网络优化 | 压缩、CDN、HTTP/2 | Gzip, Brotli, Nginx |
| JVM调优指南 | 内存、GC、线程 | G1GC, ZGC, JVM参数 |
| 性能监控与分析 | APM、日志、指标 | Spring Boot Actuator, Prometheus |
🛠️ 快速开始
1. 启用性能优化配置
后端配置 (application.yml):
spring:
# 启用虚拟线程 (JDK21+)
threads:
virtual:
enabled: true
# 数据库配置
datasource:
dynamic:
datasource:
master:
# 批处理优化
url: jdbc:mysql://...?rewriteBatchedStatements=true
hikari:
maxPoolSize: 20
minIdle: 10
connectionTimeout: 30000
# Redis缓存配置
redisson:
threads: 4
nettyThreads: 8前端配置 (env/.env.production):
# 启用Gzip和Brotli压缩
VITE_BUILD_COMPRESS=gzip,brotli
# 生产环境不生成sourcemap
VITE_SHOW_SOURCEMAP=false移动端配置 (env/.env.production):
# 生产环境移除console
VITE_DELETE_CONSOLE=true
# 不生成sourcemap
VITE_SHOW_SOURCEMAP=false2. 验证优化效果
后端验证:
# 启动应用查看日志
[INFO] ========================================
[INFO] Redis模块初始化完成
[INFO] 连接池最大连接数: 32
[INFO] 启用虚拟线程: true
[INFO] ========================================前端验证:
# 构建后查看产物
dist/
├── index.html (< 10KB)
├── assets/
│ ├── index-[hash].js (< 500KB)
│ ├── index-[hash].js.gz (< 150KB)
│ └── index-[hash].js.br (< 120KB)移动端验证:
# 查看分包大小
dist/build/mp-weixin/
├── app.js (< 200KB) # 主包
├── subpackages/
│ ├── admin/ (< 300KB) # 分包1
│ └── business/ (< 400KB) # 分包2📋 性能优化检查清单
后端检查项
- [ ] 启用多层缓存 (Caffeine + Redis)
- [ ] 配置HikariCP连接池参数
- [ ] 数据库批处理参数已开启
- [ ] 启用JDK21虚拟线程
- [ ] 关键查询已添加索引
- [ ] DAO层统一构建查询条件
- [ ] 使用批量操作API
- [ ] 合理使用
@Cacheable注解
前端检查项
- [ ] Vite依赖预构建配置
- [ ] 路由懒加载已实现
- [ ] 组件按需导入
- [ ] UnoCSS原子化样式
- [ ] Gzip/Brotli压缩已启用
- [ ] 生产环境移除sourcemap
- [ ] 本地缓存策略已实现
- [ ] 图片懒加载已配置
移动端检查项
- [ ] 分包配置已优化
- [ ] 图片使用webp格式
- [ ] 组件异步加载
- [ ] UnoCSS按需生成
- [ ] 生产环境移除console
- [ ] rpx单位自适应
- [ ] 条件编译优化
- [ ] 主包体积 < 2MB
🎓 最佳实践建议
1. 缓存使用策略
// ✅ 热点数据使用缓存
@Cacheable(cacheNames = "dict", key = "'dict:' + #dictType")
public List<SysDictData> getDictByType(String dictType) {
return dictDataMapper.selectList(...);
}
// ✅ 写操作清除缓存
@CacheEvict(cacheNames = "dict", allEntries = true)
public void updateDict(SysDictData data) {
dictDataMapper.updateById(data);
}2. 批量操作优化
// ✅ 使用批量API
List<Ad> entities = MapstructUtils.convert(boList, Ad.class);
adDao.batchSave(entities); // 单次批量保存
// ❌ 避免逐条操作
for (AdBo bo : boList) {
Ad entity = MapstructUtils.convert(bo, Ad.class);
adDao.insert(entity); // 每次都是一条SQL
}3. 懒加载实现
// ✅ 路由懒加载
const routes = [
{
path: '/user',
component: () => import('@/views/user/index.vue')
}
]
// ✅ 组件懒加载
const HeavyComponent = defineAsyncComponent(() =>
import('@/components/HeavyComponent.vue')
)4. 原子化样式
<!-- ✅ 使用UnoCSS原子类 -->
<div class="flex items-center justify-between p-4 bg-white rounded-lg shadow">
<span class="text-lg font-bold">标题</span>
<button class="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600">
按钮
</button>
</div>📞 获取帮助
遇到性能问题?
- 📖 查阅具体章节的优化文档
- 🔍 使用浏览器DevTools性能分析
- 📊 查看Spring Boot Actuator监控数据
- 💬 在项目Issue中反馈问题
性能优化是一个持续的过程,需要在开发的每个阶段都保持关注。
🔧 性能监控体系
监控架构设计
RuoYi-Plus 框架提供了完善的性能监控体系,涵盖应用层、数据层、缓存层的全方位监控。
性能监控体系架构
│
├── 应用层监控
│ ├── Spring Boot Actuator
│ │ ├── /actuator/health # 健康检查
│ │ ├── /actuator/metrics # 指标数据
│ │ ├── /actuator/info # 应用信息
│ │ └── /actuator/logfile # 日志文件
│ ├── 接口响应时间统计
│ └── 异常监控告警
│
├── 数据层监控
│ ├── HikariCP 连接池指标
│ │ ├── 活跃连接数
│ │ ├── 等待连接数
│ │ └── 连接获取耗时
│ ├── SQL 执行时间统计
│ └── 慢查询日志分析
│
├── 缓存层监控
│ ├── Redis 服务器信息
│ ├── 缓存命中率统计
│ ├── 内存使用情况
│ └── 命令执行统计
│
└── 系统层监控
├── JVM 内存使用
├── GC 统计信息
├── 线程池状态
└── CPU 使用率Actuator 监控配置
框架通过 Spring Boot Actuator 提供标准化的监控端点:
# application.yml 监控配置
management:
endpoints:
web:
exposure:
include: '*' # 暴露所有端点,生产环境建议限制
endpoint:
health:
show-details: ALWAYS # 显示详细健康信息
logfile:
external-file: ./logs/sys-console.log常用监控端点:
| 端点 | 说明 | 用途 |
|---|---|---|
/actuator/health | 健康检查 | 容器探针、负载均衡健康检查 |
/actuator/metrics | 指标数据 | JVM、HTTP、连接池等指标 |
/actuator/info | 应用信息 | 版本、构建时间等 |
/actuator/logfile | 日志文件 | 在线查看应用日志 |
/actuator/env | 环境变量 | 配置信息查看 |
/actuator/beans | Bean 列表 | 应用上下文中的 Bean |
/actuator/mappings | 请求映射 | 所有 HTTP 端点映射 |
缓存监控实现
框架提供了专门的缓存监控控制器,用于实时查看 Redis 缓存状态:
/**
* 缓存监控控制器
* 提供 Redis 缓存服务器状态监控
*/
@RestController
@RequestMapping("/monitor/cache")
public class CacheController {
private final RedisConnectionFactory connectionFactory;
/**
* 获取缓存监控信息
* 包括服务器信息、键数量、命令统计
*/
@GetMapping("/getCacheInfo")
public R<CacheMonitorVo> getCacheInfo() throws Exception {
RedisConnection connection = connectionFactory.getConnection();
try {
// 获取命令统计信息
Properties commandStats = connection.commands().info("commandstats");
// 解析命令统计为图表数据
List<Map<String, String>> pieList = new ArrayList<>();
if (commandStats != null) {
commandStats.stringPropertyNames().forEach(key -> {
Map<String, String> data = new HashMap<>();
String property = commandStats.getProperty(key);
// 提取命令名和调用次数
data.put("name", StringUtils.removeStart(key, "cmdstat_"));
data.put("value", StringUtils.substringBetween(property, "calls=", ","));
pieList.add(data);
});
}
// 构建监控数据对象
CacheMonitorVo monitorVo = new CacheMonitorVo();
monitorVo.setInfo(connection.commands().info()); // 服务器完整信息
monitorVo.setDbSize(connection.commands().dbSize()); // 键的数量
monitorVo.setCommandStats(pieList); // 命令统计
return R.ok(monitorVo);
} finally {
// 确保连接释放
RedisConnectionUtils.releaseConnection(connection, connectionFactory);
}
}
}缓存监控数据结构:
/**
* 缓存监控数据对象
*/
@Data
public class CacheMonitorVo {
/** Redis 服务器信息 */
private Properties info;
/** 数据库键的数量 */
private Long dbSize;
/** 命令执行统计 (用于图表展示) */
private List<Map<String, String>> commandStats;
}连接池监控
HikariCP 连接池提供了丰富的监控指标,可通过 Actuator 获取:
/**
* 连接池监控服务
*/
@Service
public class ConnectionPoolMonitor {
@Autowired
private MeterRegistry meterRegistry;
/**
* 获取连接池状态
*/
public Map<String, Object> getPoolStatus() {
Map<String, Object> status = new HashMap<>();
// 活跃连接数
Gauge activeGauge = meterRegistry.find("hikaricp.connections.active").gauge();
if (activeGauge != null) {
status.put("activeConnections", activeGauge.value());
}
// 空闲连接数
Gauge idleGauge = meterRegistry.find("hikaricp.connections.idle").gauge();
if (idleGauge != null) {
status.put("idleConnections", idleGauge.value());
}
// 等待连接数
Gauge pendingGauge = meterRegistry.find("hikaricp.connections.pending").gauge();
if (pendingGauge != null) {
status.put("pendingConnections", pendingGauge.value());
}
// 连接获取耗时
Timer acquireTimer = meterRegistry.find("hikaricp.connections.acquire").timer();
if (acquireTimer != null) {
status.put("avgAcquireTime", acquireTimer.mean(TimeUnit.MILLISECONDS));
status.put("maxAcquireTime", acquireTimer.max(TimeUnit.MILLISECONDS));
}
return status;
}
}SQL 执行监控
通过 MyBatis-Plus 拦截器实现 SQL 执行时间监控:
/**
* SQL 性能监控拦截器
* 记录慢查询日志
*/
@Intercepts({
@Signature(type = StatementHandler.class, method = "query", args = {Statement.class, ResultHandler.class}),
@Signature(type = StatementHandler.class, method = "update", args = {Statement.class})
})
@Slf4j
public class SqlPerformanceInterceptor implements Interceptor {
/** 慢查询阈值 (毫秒) */
private static final long SLOW_SQL_THRESHOLD = 1000;
@Override
public Object intercept(Invocation invocation) throws Throwable {
long startTime = System.currentTimeMillis();
try {
return invocation.proceed();
} finally {
long costTime = System.currentTimeMillis() - startTime;
if (costTime > SLOW_SQL_THRESHOLD) {
StatementHandler handler = (StatementHandler) invocation.getTarget();
BoundSql boundSql = handler.getBoundSql();
String sql = boundSql.getSql().replaceAll("\\s+", " ");
log.warn("慢查询警告 - 耗时: {}ms, SQL: {}", costTime, sql);
}
}
}
}📈 性能指标体系
核心性能指标定义
框架定义了完整的性能指标体系,用于衡量和监控系统性能:
性能指标体系
│
├── 响应时间指标
│ ├── P50 (中位数响应时间)
│ ├── P90 (90%请求响应时间)
│ ├── P99 (99%请求响应时间)
│ └── 平均响应时间
│
├── 吞吐量指标
│ ├── QPS (每秒查询数)
│ ├── TPS (每秒事务数)
│ └── 并发用户数
│
├── 资源使用指标
│ ├── CPU 使用率
│ ├── 内存使用率
│ ├── 磁盘 I/O
│ └── 网络带宽
│
├── 缓存指标
│ ├── 缓存命中率
│ ├── 缓存穿透率
│ └── 缓存更新频率
│
└── 数据库指标
├── 连接池使用率
├── 查询平均耗时
├── 慢查询数量
└── 锁等待时间指标采集与存储
/**
* 性能指标采集服务
*/
@Service
@Slf4j
public class PerformanceMetricsService {
@Autowired
private MeterRegistry meterRegistry;
/** 接口响应时间计时器 */
private final Map<String, Timer> apiTimers = new ConcurrentHashMap<>();
/** 接口调用计数器 */
private final Map<String, Counter> apiCounters = new ConcurrentHashMap<>();
/**
* 记录接口调用指标
*/
public void recordApiCall(String apiPath, long costTime, boolean success) {
// 记录响应时间
Timer timer = apiTimers.computeIfAbsent(apiPath,
path -> Timer.builder("api.response.time")
.tag("path", path)
.register(meterRegistry));
timer.record(costTime, TimeUnit.MILLISECONDS);
// 记录调用次数
Counter counter = apiCounters.computeIfAbsent(apiPath + ":" + success,
key -> Counter.builder("api.call.count")
.tag("path", apiPath)
.tag("success", String.valueOf(success))
.register(meterRegistry));
counter.increment();
}
/**
* 获取接口性能统计
*/
public Map<String, Object> getApiMetrics(String apiPath) {
Map<String, Object> metrics = new HashMap<>();
Timer timer = apiTimers.get(apiPath);
if (timer != null) {
metrics.put("count", timer.count());
metrics.put("totalTime", timer.totalTime(TimeUnit.MILLISECONDS));
metrics.put("avgTime", timer.mean(TimeUnit.MILLISECONDS));
metrics.put("maxTime", timer.max(TimeUnit.MILLISECONDS));
// 计算分位数
HistogramSnapshot snapshot = timer.takeSnapshot();
metrics.put("p50", snapshot.percentileValues()[0].value());
metrics.put("p90", snapshot.percentileValues()[1].value());
metrics.put("p99", snapshot.percentileValues()[2].value());
}
return metrics;
}
}JVM 监控指标
/**
* JVM 监控服务
*/
@Service
public class JvmMonitorService {
/**
* 获取 JVM 内存信息
*/
public Map<String, Object> getMemoryInfo() {
Map<String, Object> info = new HashMap<>();
MemoryMXBean memoryMXBean = ManagementFactory.getMemoryMXBean();
// 堆内存
MemoryUsage heapUsage = memoryMXBean.getHeapMemoryUsage();
info.put("heapInit", heapUsage.getInit() / 1024 / 1024 + "MB");
info.put("heapUsed", heapUsage.getUsed() / 1024 / 1024 + "MB");
info.put("heapCommitted", heapUsage.getCommitted() / 1024 / 1024 + "MB");
info.put("heapMax", heapUsage.getMax() / 1024 / 1024 + "MB");
info.put("heapUsageRate", String.format("%.2f%%",
(double) heapUsage.getUsed() / heapUsage.getMax() * 100));
// 非堆内存
MemoryUsage nonHeapUsage = memoryMXBean.getNonHeapMemoryUsage();
info.put("nonHeapUsed", nonHeapUsage.getUsed() / 1024 / 1024 + "MB");
info.put("nonHeapCommitted", nonHeapUsage.getCommitted() / 1024 / 1024 + "MB");
return info;
}
/**
* 获取 GC 信息
*/
public List<Map<String, Object>> getGcInfo() {
List<Map<String, Object>> gcList = new ArrayList<>();
List<GarbageCollectorMXBean> gcBeans = ManagementFactory.getGarbageCollectorMXBeans();
for (GarbageCollectorMXBean gcBean : gcBeans) {
Map<String, Object> gcInfo = new HashMap<>();
gcInfo.put("name", gcBean.getName());
gcInfo.put("collectionCount", gcBean.getCollectionCount());
gcInfo.put("collectionTime", gcBean.getCollectionTime() + "ms");
gcList.add(gcInfo);
}
return gcList;
}
/**
* 获取线程信息
*/
public Map<String, Object> getThreadInfo() {
Map<String, Object> info = new HashMap<>();
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
info.put("threadCount", threadMXBean.getThreadCount());
info.put("peakThreadCount", threadMXBean.getPeakThreadCount());
info.put("daemonThreadCount", threadMXBean.getDaemonThreadCount());
info.put("totalStartedThreadCount", threadMXBean.getTotalStartedThreadCount());
// 死锁检测
long[] deadlockedThreads = threadMXBean.findDeadlockedThreads();
info.put("deadlockedThreads", deadlockedThreads != null ? deadlockedThreads.length : 0);
return info;
}
}🚀 性能优化策略详解
多层缓存策略
框架实现了 Caffeine + Redis 的二级缓存架构,有效降低数据库压力:
缓存访问流程
│
├── 读取流程
│ ├── 1. 查询 L1 缓存 (Caffeine 本地缓存)
│ │ └── 命中 → 直接返回
│ ├── 2. 查询 L2 缓存 (Redis 分布式缓存)
│ │ ├── 命中 → 回写 L1 缓存 → 返回
│ │ └── 未命中 → 继续
│ └── 3. 查询数据库
│ └── 回写 L2 缓存 → 回写 L1 缓存 → 返回
│
└── 写入流程
├── 1. 更新数据库
├── 2. 失效 L2 缓存 (Redis)
└── 3. 失效 L1 缓存 (Caffeine)二级缓存装饰器实现:
/**
* Caffeine 二级缓存装饰器
* 实现 L1 + L2 缓存策略
*/
public class CaffeineCacheDecorator implements Cache {
/** L1 本地缓存 */
private final Cache<Object, Object> caffeineCache;
/** L2 分布式缓存 */
private final Cache redisCache;
public CaffeineCacheDecorator(Cache redisCache,
Cache<Object, Object> caffeineCache) {
this.redisCache = redisCache;
this.caffeineCache = caffeineCache;
}
@Override
public ValueWrapper get(Object key) {
// 1. 先查 L1 缓存
Object value = caffeineCache.getIfPresent(key);
if (value != null) {
return new SimpleValueWrapper(value);
}
// 2. 查 L2 缓存
ValueWrapper wrapper = redisCache.get(key);
if (wrapper != null) {
// 回写 L1 缓存
caffeineCache.put(key, wrapper.get());
return wrapper;
}
return null;
}
@Override
public void put(Object key, Object value) {
// 写入 L2 缓存
redisCache.put(key, value);
// 失效 L1 缓存 (避免脏读)
caffeineCache.invalidate(key);
}
@Override
public void evict(Object key) {
// 同时失效两级缓存
redisCache.evict(key);
caffeineCache.invalidate(key);
}
}Caffeine 本地缓存配置:
/**
* Caffeine 本地缓存 Bean 配置
*/
@Bean
public Cache<Object, Object> caffeineCache() {
return Caffeine.newBuilder()
// 写入后 30 秒过期
.expireAfterWrite(30, TimeUnit.SECONDS)
// 初始容量 100
.initialCapacity(100)
// 最大容量 1000
.maximumSize(1000)
// 开启统计
.recordStats()
.build();
}缓存名称定义规范:
/**
* 缓存名称常量
* 格式: cacheNames#ttl#maxIdleTime#maxSize
*/
public interface CacheNames {
/** 用户昵称缓存 - 5天TTL, 2天空闲过期, 最大5000个 */
String SYS_NICKNAME = "sys_nickname#5d#2d#5000";
/** 系统配置缓存 - 5天TTL, 2天空闲过期, 最大500个 */
String SYS_CONFIG = "sys_config#5d#2d#500";
/** 数据字典缓存 - 1天TTL, 12小时空闲过期, 最大1000个 */
String SYS_DICT = "sys_dict#1d#12h#1000";
/** 用户头像缓存 - 5天TTL, 2天空闲过期, 最大5000个 */
String SYS_AVATAR = "sys_avatar#5d#2d#5000";
/** 在线用户缓存 - 使用默认配置 */
String ONLINE_TOKEN = "online_tokens";
}连接池优化策略
HikariCP 是业界最快的 JDBC 连接池,框架针对不同环境进行了精细配置:
开发环境配置:
# application-dev.yml
spring:
datasource:
dynamic:
hikari:
maxPoolSize: 20 # 最大连接数
minIdle: 10 # 最小空闲连接
connectionTimeout: 30000 # 获取连接超时 (30秒)
validationTimeout: 5000 # 校验超时 (5秒)
idleTimeout: 600000 # 空闲超时 (10分钟)
maxLifetime: 1800000 # 最长生命周期 (30分钟)
keepaliveTime: 30000 # 活性检测间隔 (30秒)生产环境配置:
# application-prod.yml
spring:
datasource:
dynamic:
hikari:
maxPoolSize: ${DB_MAX_POOL_SIZE:50} # 增大为50
minIdle: ${DB_MIN_IDLE:20} # 增大为20
connectionTimeout: 30000
validationTimeout: 5000
idleTimeout: 600000
maxLifetime: 1800000
keepaliveTime: 30000连接池大小计算公式:
最优连接数 = ((核心数 * 2) + 有效磁盘数)
示例:
- 4核CPU + 1个SSD = (4 * 2) + 1 = 9 个连接
- 8核CPU + 1个SSD = (8 * 2) + 1 = 17 个连接
- 16核CPU + 2个SSD = (16 * 2) + 2 = 34 个连接
注: 实际配置需要根据业务场景和压测结果调整批处理优化策略
MyBatis-Plus 批处理结合 MySQL 的 rewriteBatchedStatements 参数,可实现 50 倍以上的性能提升:
JDBC URL 配置:
url: jdbc:mysql://${DB_HOST}:${DB_PORT}/${DB_NAME}?
rewriteBatchedStatements=true& # 开启批处理重写
useServerPrepStmts=true& # 服务端预编译
cachePrepStmts=true& # 缓存预编译语句
prepStmtCacheSize=250& # 预编译缓存大小
prepStmtCacheSqlLimit=2048 # 预编译 SQL 长度限制批量操作 DAO 实现:
/**
* 基础 DAO 实现 - 批量操作方法
*/
public abstract class BaseDaoImpl<M extends BaseMapperPlus<T, V>, T, V>
implements IBaseDao<T, V> {
/**
* 批量插入
* 使用 MyBatis-Plus 的 Db.saveBatch 实现
*/
@Override
public boolean batchInsert(Collection<T> entities) {
if (CollUtil.isEmpty(entities)) {
return true;
}
return Db.saveBatch(entities);
}
/**
* 批量保存 (插入或更新)
* 根据主键判断是插入还是更新
*/
@Override
public boolean batchSave(Collection<T> entities) {
return Db.saveOrUpdateBatch(entities);
}
/**
* 批量更新
* 按主键批量更新
*/
@Override
public boolean batchUpdate(Collection<T> entities) {
return Db.updateBatchById(entities);
}
/**
* 批量删除
* 按主键集合删除
*/
@Override
public boolean deleteByIds(Collection<? extends Serializable> ids) {
return baseMapper.deleteByIds(ids) > 0;
}
}批量操作性能对比:
| 操作方式 | 1000条数据 | 10000条数据 | 性能提升 |
|---|---|---|---|
| 逐条插入 | 5.0s | 50s | 基准 |
| 批量插入 (无优化) | 0.5s | 5s | 10x |
| 批量插入 (rewriteBatch) | 0.1s | 1s | 50x |
虚拟线程优化
JDK 21 引入的虚拟线程 (Project Loom) 可显著提升 I/O 密集型应用的并发能力:
启用虚拟线程:
# application.yml
spring:
threads:
virtual:
enabled: true # 仅 JDK 21+ 可用线程池配置:
/**
* 核心配置类 - 线程池配置
*/
@Configuration
@EnableAsync(proxyTargetClass = true)
public class CoreAutoConfiguration {
/**
* 定时任务线程池
* 支持虚拟线程
*/
@Bean(name = "scheduledExecutorService")
protected ScheduledExecutorService scheduledExecutorService() {
BasicThreadFactory.Builder builder = new BasicThreadFactory.Builder()
.daemon(true);
// 虚拟线程支持
if (SpringUtils.isVirtual()) {
builder.namingPattern("virtual-schedule-pool-%d")
.wrappedFactory(new VirtualThreadTaskExecutor()
.getVirtualThreadFactory());
} else {
builder.namingPattern("schedule-pool-%d");
}
// 核心线程数 = CPU 核心数 + 1
int core = Runtime.getRuntime().availableProcessors() + 1;
ScheduledThreadPoolExecutor executor = new ScheduledThreadPoolExecutor(
core,
builder.build(),
new ThreadPoolExecutor.CallerRunsPolicy() // 拒绝策略
);
return executor;
}
}虚拟线程 vs 平台线程对比:
| 特性 | 平台线程 | 虚拟线程 |
|---|---|---|
| 内存占用 | ~1MB/线程 | ~1KB/线程 |
| 最大数量 | 数千个 | 数百万个 |
| 创建开销 | 高 | 低 |
| 适用场景 | CPU 密集型 | I/O 密集型 |
| 上下文切换 | 操作系统调度 | JVM 调度 |
异步处理优化
框架通过 @Async 注解和事件机制实现异步处理,提升接口响应速度:
/**
* 操作日志服务 - 异步处理示例
*/
@Service
@Slf4j
public class SysOperLogServiceImpl implements ISysOperLogService {
@Autowired
private SysOperLogDao operLogDao;
/**
* 异步记录操作日志
* 通过 @Async 注解实现异步执行
* 通过 @EventListener 监听日志事件
*/
@Async
@EventListener
public void recordOper(OperLogEvent operLogEvent) {
SysOperLog operLog = BeanUtil.toBean(operLogEvent, SysOperLog.class);
// 远程查询操作地点 (耗时操作,异步执行)
operLog.setOperLocation(
AddressUtils.getRealAddressByIp(operLog.getOperIp())
);
operLog.setOperTime(new Date());
// 保存日志
operLogDao.insert(operLog);
}
}异步任务最佳实践:
/**
* 异步任务服务
*/
@Service
public class AsyncTaskService {
/**
* 异步发送通知
*/
@Async
public CompletableFuture<Boolean> sendNotificationAsync(
Long userId, String message) {
try {
// 模拟耗时操作
Thread.sleep(1000);
// 发送通知逻辑
return CompletableFuture.completedFuture(true);
} catch (Exception e) {
return CompletableFuture.completedFuture(false);
}
}
/**
* 批量异步处理
*/
public void batchProcessAsync(List<Long> userIds, String message) {
List<CompletableFuture<Boolean>> futures = userIds.stream()
.map(userId -> sendNotificationAsync(userId, message))
.collect(Collectors.toList());
// 等待所有任务完成
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
.join();
}
}❓ 常见性能问题与解决方案
1. 缓存穿透问题
问题描述: 请求查询不存在的数据,每次都穿透缓存访问数据库。
解决方案:
/**
* 缓存穿透防护 - 空值缓存
*/
@Cacheable(cacheNames = "user", key = "#userId", unless = "#result == null")
public User getUserById(Long userId) {
User user = userDao.selectById(userId);
if (user == null) {
// 缓存空对象,设置较短过期时间
return User.EMPTY; // 空对象标记
}
return user;
}
/**
* 缓存穿透防护 - 布隆过滤器
*/
@Service
public class UserServiceWithBloomFilter {
@Autowired
private RBloomFilter<Long> userBloomFilter;
public User getUserById(Long userId) {
// 布隆过滤器判断
if (!userBloomFilter.contains(userId)) {
return null; // 一定不存在
}
// 查询缓存或数据库
return userDao.selectById(userId);
}
/**
* 初始化布隆过滤器
*/
@PostConstruct
public void initBloomFilter() {
// 预期数据量 100万,误判率 0.01%
userBloomFilter.tryInit(1000000, 0.0001);
// 加载所有用户ID
List<Long> userIds = userDao.selectAllIds();
userIds.forEach(userBloomFilter::add);
}
}2. 缓存雪崩问题
问题描述: 大量缓存同时过期,导致请求集中访问数据库。
解决方案:
/**
* 缓存雪崩防护 - 随机过期时间
*/
@Service
public class CacheService {
@Autowired
private RedisTemplate<String, Object> redisTemplate;
/**
* 设置缓存,添加随机过期时间
*/
public void setWithRandomExpire(String key, Object value,
long baseExpire, TimeUnit unit) {
// 基础过期时间 + 随机偏移 (0-10分钟)
long randomOffset = ThreadLocalRandom.current().nextLong(0, 600);
long expire = unit.toSeconds(baseExpire) + randomOffset;
redisTemplate.opsForValue().set(key, value, expire, TimeUnit.SECONDS);
}
}
/**
* 缓存雪崩防护 - 互斥锁重建
*/
public User getUserByIdWithMutex(Long userId) {
String cacheKey = "user:" + userId;
String lockKey = "lock:user:" + userId;
// 1. 查询缓存
User user = (User) redisTemplate.opsForValue().get(cacheKey);
if (user != null) {
return user;
}
// 2. 获取互斥锁
Boolean locked = redisTemplate.opsForValue()
.setIfAbsent(lockKey, "1", 10, TimeUnit.SECONDS);
if (Boolean.TRUE.equals(locked)) {
try {
// 3. 查询数据库
user = userDao.selectById(userId);
// 4. 写入缓存
if (user != null) {
redisTemplate.opsForValue().set(cacheKey, user,
1, TimeUnit.HOURS);
}
return user;
} finally {
// 5. 释放锁
redisTemplate.delete(lockKey);
}
} else {
// 其他线程等待重试
try {
Thread.sleep(50);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
return getUserByIdWithMutex(userId);
}
}3. N+1 查询问题
问题描述: 查询列表时,对每条记录额外执行关联查询。
解决方案:
/**
* N+1 问题示例
*/
// ❌ 错误做法 - N+1 查询
public List<UserVo> getUserList() {
List<User> users = userDao.selectList();
return users.stream().map(user -> {
UserVo vo = new UserVo();
BeanUtils.copyProperties(user, vo);
// 每个用户查询一次部门 (N次查询)
vo.setDeptName(deptDao.selectById(user.getDeptId()).getName());
return vo;
}).collect(Collectors.toList());
}
// ✅ 正确做法 - 批量查询
public List<UserVo> getUserListOptimized() {
List<User> users = userDao.selectList();
// 收集所有部门ID
Set<Long> deptIds = users.stream()
.map(User::getDeptId)
.filter(Objects::nonNull)
.collect(Collectors.toSet());
// 批量查询部门 (1次查询)
Map<Long, String> deptNameMap = deptDao.selectByIds(deptIds).stream()
.collect(Collectors.toMap(Dept::getId, Dept::getName));
// 组装数据
return users.stream().map(user -> {
UserVo vo = new UserVo();
BeanUtils.copyProperties(user, vo);
vo.setDeptName(deptNameMap.get(user.getDeptId()));
return vo;
}).collect(Collectors.toList());
}4. 大数据量分页问题
问题描述: 深度分页时 LIMIT offset, size 效率低下。
解决方案:
/**
* 深度分页优化
*/
// ❌ 低效的深度分页
// SELECT * FROM user LIMIT 1000000, 10 -- 扫描100万行
// ✅ 使用游标分页 (基于ID)
public List<User> getUserListByCursor(Long lastId, int size) {
return userDao.selectList(
new LambdaQueryWrapper<User>()
.gt(lastId != null, User::getId, lastId)
.orderByAsc(User::getId)
.last("LIMIT " + size)
);
}
// ✅ 子查询优化
public List<User> getUserListBySubQuery(int offset, int size) {
// 先查询ID,再根据ID查询数据
// SELECT * FROM user WHERE id >=
// (SELECT id FROM user ORDER BY id LIMIT 1000000, 1)
// LIMIT 10
return userDao.selectBySubQuery(offset, size);
}
// ✅ 延迟关联优化
@Select("""
SELECT u.* FROM user u
INNER JOIN (
SELECT id FROM user ORDER BY id LIMIT #{offset}, #{size}
) t ON u.id = t.id
""")
List<User> selectWithDeferred(@Param("offset") int offset,
@Param("size") int size);5. 数据库连接泄漏
问题描述: 连接未正确释放,导致连接池耗尽。
解决方案:
/**
* 连接泄漏检测配置
*/
// application.yml
spring:
datasource:
dynamic:
hikari:
leakDetectionThreshold: 60000 # 泄漏检测阈值 (60秒)
/**
* 正确的资源管理
*/
// ✅ 使用 try-with-resources
public void executeWithConnection() {
try (Connection conn = dataSource.getConnection();
PreparedStatement ps = conn.prepareStatement(sql);
ResultSet rs = ps.executeQuery()) {
// 处理结果
} catch (SQLException e) {
throw new RuntimeException(e);
}
// 自动关闭资源
}
// ✅ 使用 @Transactional 管理连接
@Transactional(rollbackFor = Exception.class)
public void businessMethod() {
// Spring 自动管理连接
userDao.insert(user);
orderDao.insert(order);
}6. 慢查询优化
问题描述: SQL 执行时间过长,影响系统性能。
诊断步骤:
-- 1. 开启慢查询日志
SET GLOBAL slow_query_log = 'ON';
SET GLOBAL long_query_time = 1; -- 超过1秒记录
-- 2. 分析执行计划
EXPLAIN SELECT * FROM user WHERE name = 'test';
-- 3. 检查索引使用
SHOW INDEX FROM user;优化方案:
/**
* 慢查询优化示例
*/
// ❌ 未使用索引
@Select("SELECT * FROM user WHERE DATE(create_time) = #{date}")
List<User> selectByDate(String date);
// ✅ 使用范围查询,走索引
@Select("""
SELECT * FROM user
WHERE create_time >= #{startTime}
AND create_time < #{endTime}
""")
List<User> selectByDateRange(@Param("startTime") LocalDateTime startTime,
@Param("endTime") LocalDateTime endTime);
// ❌ 模糊查询前缀通配符
@Select("SELECT * FROM user WHERE name LIKE '%test%'")
List<User> selectByNameLike(String name);
// ✅ 使用全文索引或 ES
@Select("SELECT * FROM user WHERE MATCH(name) AGAINST(#{keyword})")
List<User> selectByFullText(String keyword);📊 性能测试与基准
压测工具推荐
| 工具 | 适用场景 | 特点 |
|---|---|---|
| JMeter | HTTP 接口压测 | 图形化、插件丰富 |
| wrk | HTTP 基准测试 | 高性能、命令行 |
| Gatling | 性能测试脚本 | Scala DSL、报告美观 |
| Artillery | 负载测试 | YAML 配置、云原生 |
基准测试示例
# wrk 压测示例
wrk -t12 -c400 -d30s http://localhost:8080/api/user/list
# 参数说明:
# -t12: 12个线程
# -c400: 400个并发连接
# -d30s: 持续30秒性能基准参考
| 指标 | 良好 | 一般 | 需优化 |
|---|---|---|---|
| 接口响应时间 (P99) | < 200ms | 200-500ms | > 500ms |
| 数据库查询时间 | < 50ms | 50-200ms | > 200ms |
| 缓存命中率 | > 95% | 80-95% | < 80% |
| 连接池使用率 | < 70% | 70-90% | > 90% |
| JVM 堆内存使用率 | < 70% | 70-85% | > 85% |
| GC 暂停时间 | < 100ms | 100-500ms | > 500ms |
遵循本章节的指导,你的应用将获得卓越的性能表现!
