合同管理系统性能优化:从数据库到前端的全链路实践
一、性能诊断体系
基于全链路监控的性能瓶颈定位:
1.1 性能指标矩阵
层级 | 关键指标 | 监控工具 | 合同系统阈值 |
---|---|---|---|
数据库 | QPS/慢查询/锁等待 | Prometheus+Percona | 慢查询≤50ms |
应用服务 | RT/错误率/线程池 | SkyWalking | TP99≤200ms |
缓存 | 命中率/穿透率 | Redis Stat | 命中率≥95% |
前端 | FCP/LCP/TTI | Lighthouse | LCP≤2.5s |
1.2 全链路追踪示例
合同查询请求的调用链分析:
关键路径分析:
1. N+1查询问题:获取合同列表后循环查询签署状态
2. 缓存穿透:不存在的合同ID导致频繁查库
3. 大文件下载:PDF合同未启用分块传输
二、数据库优化
针对合同业务特征的SQL与存储优化:
2.1 MySQL优化策略
优化方向 | 具体措施 | 实施效果 | 适用场景 |
---|---|---|---|
索引优化 | 联合索引(user_id,status) | 查询速度提升10倍 | 用户合同列表 |
查询重构 | JOIN改为应用层处理 | CPU降低40% | 多表关联查询 |
分库分表 | 按合同ID哈希分片 | 支撑10万QPS | 合同存储主表 |
2.2 慢SQL优化案例
优化前(执行时间1.2s):
SELECT * FROM contracts c LEFT JOIN sign_records s ON c.id = s.contract_id WHERE c.user_id = 12345 AND c.status IN ('PENDING','SIGNED') ORDER BY c.create_time DESC LIMIT 1000;
优化后(执行时间85ms):
-- 步骤1:使用覆盖索引获取ID SELECT id FROM contracts WHERE user_id = 12345 AND status IN ('PENDING','SIGNED') ORDER BY create_time DESC LIMIT 1000; -- 步骤2:批量获取详情(应用层缓存sign_records) SELECT * FROM contracts WHERE id IN (?,?,...); SELECT * FROM sign_records WHERE contract_id IN (?,?,...);
执行计划对比:
三、缓存架构设计
多级缓存体系应对高并发查询:
3.1 缓存策略矩阵
缓存层级 | 技术实现 | 缓存时长 | 合同场景用例 |
---|---|---|---|
浏览器缓存 | Cache-Control | 5分钟 | 静态模板文件 |
CDN缓存 | 边缘节点 | 1小时 | 合同PDF预览 |
应用缓存 | Caffeine | 30秒 | 审批流配置 |
分布式缓存 | Redis集群 | 24小时 | 合同元数据 |
3.2 热点缓存方案
Redis集群+本地缓存的二级架构:
public class ContractCacheService { // 本地缓存(Caffeine) private final Cache<String, Contract> localCache = Caffeine.newBuilder() .maximumSize(10_000) .expireAfterWrite(30, TimeUnit.SECONDS) .build(); // 获取合同(防止缓存穿透) public Contract getContract(String contractId) { // 1. 查本地缓存 Contract contract = localCache.getIfPresent(contractId); if (contract != null) return contract; // 2. 查Redis(Lua脚本原子操作) String script = "if redis.call('exists',KEYS[1])==1 then " + "return redis.call('get',KEYS[1]) " + "else " + "redis.call('setex',KEYS[1],ARGV[2],ARGV[1]) " + "return ARGV[1] end"; String result = redisTemplate.execute( script, Collections.singletonList("contract:" + contractId), "NULL", "300"); // 3. 空值缓存处理 if ("NULL".equals(result)) { localCache.put(contractId, Contract.EMPTY); return Contract.EMPTY; } // 4. 反序列化数据 contract = deserialize(result); localCache.put(contractId, contract); return contract; } }
缓存预热策略:
# 定时任务预热热点合同 @Scheduled(cron = "0 0 8 * * ?") public void preloadHotContracts() { // 查询昨日热点合同 List<String> hotContracts = contractMapper.selectHotContracts(); // 批量加载到Redis redisTemplate.executePipelined(connection -> { hotContracts.forEach(id -> { Contract contract = contractMapper.selectById(id); connection.setEx( ("contract:" + id).getBytes(), 86400, serialize(contract) ); }); return null; }); }
四、JVM层优化
针对合同处理场景的JVM参数调优:
4.1 JVM配置对比
配置项 | 默认值 | 优化值 | 优化效果 |
---|---|---|---|
堆内存 | 1/4物理内存 | 固定4G | 避免OOM |
GC算法 | Parallel GC | G1 GC | STW降低60% |
元空间 | 动态调整 | 固定256M | 避免Full GC |
线程栈 | 1M | 512K | 节省内存 |
4.2 签署服务JVM参数
生产环境配置:
# 基础配置 -Xms4g -Xmx4g -XX:MetaspaceSize=256m -XX:MaxMetaspaceSize=256m -Xss512k # G1垃圾回收器 -XX:+UseG1GC -XX:MaxGCPauseMillis=200 -XX:InitiatingHeapOccupancyPercent=45 # 内存溢出时dump -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/logs/heap.hprof # 监控配置 -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:/logs/gc.log
GC日志分析工具:
1.
gceasy.io
- 在线分析GC日志2.
GCViewer
- 离线分析工具3.
jstat -gcutil [pid]
- 实时监控
五、前端性能提升
合同管理Console的加载速度优化:
5.1 优化措施
优化点 | 技术方案 | 实施效果 | 适用页面 |
---|---|---|---|
懒加载 | React.lazy | 首屏加载快2s | 合同详情页 |
虚拟列表 | react-window | 万级列表流畅 | 合同列表页 |
Web Worker | PDF.js | 主线程不阻塞 | 合同预览页 |
5.2 合同列表优化
虚拟滚动实现:
import { FixedSizeList as List } from 'react-window'; const ContractList = ({ contracts }) => ( <List height={600} itemCount={contracts.length} itemSize={80} width="100%" > {({ index, style }) => ( <div style={style}> <ContractItem data={contracts[index]} /> </div> )} </List> ); // 优化前后的渲染性能对比 +---------------------+------------+-----------+ | 指标 | 优化前 | 优化后 | +---------------------+------------+-----------+ | 万条数据渲染时间 | 4200ms | 60ms | | 内存占用 | 1.2GB | 85MB | | 滚动FPS | 12 | 60 | +---------------------+------------+-----------+
Webpack分包配置:
// vue.config.js module.exports = { chainWebpack: config => { config.optimization.splitChunks({ chunks: 'all', maxSize: 244 * 1024, cacheGroups: { pdfWorker: { test: /[\\/]node_modules[\\/]pdfjs-dist[\\/]/, name: 'pdf-worker', priority: 10 } } }); } };