Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

10.3 典型场景优化实践

前面我们讲了性能分析方法和优化方法论,这一节我们针对实际开发中常见的场景,介绍具体的优化实践技巧,覆盖CPU、内存、IO、网络、数据库、前端等各个层面。

CPU密集型场景优化

CPU密集型场景的主要瓶颈在CPU计算,常见于科学计算、音视频编码、加密解密、复杂业务逻辑处理等场景。

优化方法

  1. 算法与数据结构优化

    • 优先优化算法复杂度,把O(n²)的算法降到O(n)或O(n log n),这是最有效的优化
    • 选择合适的数据结构,比如频繁查找用哈希表,有序数据用二分查找,避免线性遍历
    • 减少不必要的计算,缓存重复计算的结果,避免重复计算
    • 提前退出循环,减少不必要的计算
  2. 减少运行时开销

    • 减少不必要的对象创建和销毁,尤其是在循环中,复用对象减少GC开销(Java/Python等带GC的语言)
    • 避免过多的函数调用和上下文切换,热点路径的函数可以考虑内联
    • 减少锁竞争,用无锁数据结构、原子操作代替重量级锁,减少锁的粒度和持有时间
    • 优化循环,把循环内不变的计算提到循环外,减少循环内的操作
  3. 并发优化

    • 利用多核CPU,把计算任务拆分,多线程并行执行,注意拆分的任务粒度要合适,避免线程切换开销超过收益
    • CPU密集型任务的线程数建议设置为CPU核心数+1,不要设置太多,避免频繁上下文切换
    • 使用线程池复用线程,避免频繁创建销毁线程的开销
  4. 编译/运行时优化

    • 开启编译器优化选项,比如GCC的-O2/-O3优化,Go的编译优化
    • 使用更高效的语言/库实现核心计算逻辑,比如C/Rust实现的库比纯Python/Java快很多
    • 使用SIMD指令优化,利用CPU的向量计算能力并行处理数据
    • 热点代码做JIT编译优化,解释执行的热点函数编译为本地代码执行
  5. 硬件加速

    • 并行计算任务可以考虑用GPU加速,比如CUDA/OpenCL,适合大规模并行计算
    • 使用专用的硬件加速卡,比如加密卡、编码卡等

内存使用优化

内存优化主要目标是减少内存占用,避免内存泄漏,提升内存访问效率。

优化方法

  1. 减少内存占用

    • 使用更高效的数据结构,比如用数组代替链表,用更小的数据类型,比如能用int就不用long,能用短结构就不用长结构
    • 避免不必要的内存分配,对象复用、对象池、内存池等,减少内存分配和GC压力
    • 压缩数据,比如整数压缩、字符串编码优化,减少数据占用的内存
    • 稀疏数据使用更紧凑的存储方式,比如位图、稀疏数组,避免浪费空间
  2. 提升内存访问效率

    • 利用局部性原理,优化数据结构内存布局,提升缓存命中率,比如数组顺序访问比链表随机访问快很多
    • 避免伪共享,多线程并发修改的变量做缓存行填充,避免缓存行冲突
    • 大块连续内存分配比分散的小内存分配效率高,访问速度也快
    • 大内存访问考虑使用大页(Huge Page),减少TLB miss,提升地址转换效率
  3. 避免内存泄漏和内存浪费

    • 及时释放不再使用的资源,关闭文件、连接,释放对象引用,避免内存泄漏
    • 缓存要设置合理的大小和过期策略,避免缓存无限增长占用内存
    • 避免大对象分配和内存碎片,使用内存池减少碎片
    • 带GC的语言避免创建不必要的临时对象,减少GC频率和停顿时间
  4. 内存相关参数调优

    • JVM调优:调整堆大小、新生代老年代比例、选择合适的垃圾回收器,减少GC停顿
    • 调整操作系统的内存参数:swappiness、overcommit_memory等,根据场景优化
    • 不需要持久化的数据尽量放在内存中,减少磁盘IO,用内存换取性能

磁盘IO优化

磁盘IO是最常见的性能瓶颈之一,尤其是HDD的随机IO性能很低,优化IO能带来显著的性能提升。

优化方法

  1. 减少IO次数

    • 增加缓存:用Redis、Guava Cache等缓存热点数据,尽量少访问磁盘,这是最有效的优化
    • 批量操作:批量读写代替多次小IO,比如批量写数据库、批量读文件,减少系统调用开销和IO次数
    • 合并小IO:把多个小的IO请求合并成一个大的IO,提升效率
    • 预读:提前读取需要的数据到缓存,利用顺序IO的高性能
  2. 优化IO模式

    • 尽量用顺序IO代替随机IO,顺序IO的性能是随机IO的几十上百倍(HDD),比如数据库的WAL日志就是顺序写
    • 选择合适的IO模型:高并发场景用IO多路复用、异步IO代替阻塞IO,提升吞吐量
    • 直接IO:有自己的缓存机制的场景可以用直接IO绕过系统Page Cache,减少内存拷贝
    • 异步IO:非阻塞处理IO,避免线程阻塞,提升CPU利用率
  3. 文件系统和磁盘优化

    • 优先使用SSD代替HDD,随机IO性能提升几十上百倍,对于随机IO多的场景效果明显
    • 选择合适的文件系统:XFS/EXT4适合通用场景,Btrfs/ZFS适合大容量存储和快照等特性
    • 调整文件系统挂载参数:noatime不更新访问时间,提升IO性能
    • RAID优化:RAID0提升性能,RAID10兼顾性能和可靠性,根据场景选择合适的RAID级别
    • 磁盘分区对齐,4K对齐提升SSD性能
  4. 数据存储优化

    • 数据压缩:存储前压缩数据,减少数据量,虽然增加CPU开销,但减少IO开销,整体收益更高,尤其是冷数据
    • 日志合并:LSM树结构(比如LevelDB、RocksDB)把随机写转换成顺序写,大幅提升写性能
    • 避免小文件:大量小文件会浪费磁盘空间,inode占用多,读写性能差,尽量合并小文件,比如用tar打包或者存储在数据库中
    • 冷热数据分离:热数据存在高性能存储,冷数据存在低成本大容量存储,提升性价比

网络性能优化

网络IO也是常见的性能瓶颈,尤其是分布式系统中,网络调用的开销很大。

优化方法

  1. 减少网络请求次数

    • 合并请求:多个小请求合并成一个大请求,减少网络往返次数,比如前端的资源合并、接口合并
    • 批量操作:批量获取数据、批量写入,避免多次调用
    • 数据压缩:请求和响应数据压缩,比如gzip/br压缩,减少传输数据量,降低带宽占用和传输时间
    • 缓存:静态资源用CDN缓存,接口数据用客户端缓存、服务端缓存,重复请求不用回源
  2. 优化传输效率

    • 减少传输数据量:去掉不必要的字段,使用更高效的序列化协议,比如Protobuf代替JSON,体积小序列化快
    • 选择合适的传输协议:长连接代替短连接,减少三次握手四次挥手开销;QUIC/HTTP3代替TCP,减少握手延迟和队头阻塞
    • 合适的缓冲区大小:设置合理的Socket发送和接收缓冲区,提升吞吐量
    • 协议优化:TCP参数调优,比如调整滑动窗口大小、开启TCP BBR拥塞控制,提升网络传输效率
  3. 架构层面优化

    • 就近访问:CDN加速静态资源,边缘计算节点让用户就近访问,减少物理距离带来的延迟
    • 服务部署在同一机房/可用区,减少跨机房的网络延迟
    • 负载均衡:请求分发到多个服务器,提升整体吞吐量
    • 异步非阻塞:网络IO用异步非阻塞模型,提升系统并发能力,避免线程阻塞
  4. 可靠性与性能平衡

    • 超时与重试:设置合理的超时时间,失败重试避免网络波动影响,重试要有退避策略,避免雪崩
    • 熔断降级:下游服务故障时熔断,避免级联失败,保证核心功能可用
    • 流量控制:限流、削峰填谷,避免流量突增打垮服务

数据库性能优化

数据库是大部分业务系统的核心瓶颈,数据库优化的收益通常很明显。

优化方法

  1. 索引优化

    • 合理创建索引:频繁查询的字段、条件字段、关联字段建索引,避免全表扫描
    • 联合索引遵循最左前缀原则,覆盖索引避免回表查询
    • 避免索引滥用:不要给每个字段都建索引,索引会降低写性能,占用存储空间
    • 定期优化索引:删除无用、重复的索引,重建碎片多的索引
  2. SQL语句优化

    • 避免SELECT *,只查询需要的字段,减少数据传输和内存占用
    • 避免大表关联查询,复杂查询可以考虑拆成多个简单查询,或者做适当的冗余
    • 批量操作代替循环单条操作,减少交互次数
    • 避免深分页,比如limit 100000,10性能差,用游标分页或者上次ID条件优化
    • 避免在where条件中对字段做函数运算、类型转换,导致索引失效
  3. 数据库架构优化

    • 读写分离:主库写,从库读,提升读性能
    • 分库分表:数据量大的时候垂直拆分(按业务分库)、水平拆分(按字段分表),提升处理能力
    • 增加缓存层:热点数据用Redis等缓存,减少数据库查询压力
    • 搜索引擎:复杂查询、全文搜索场景用Elasticsearch等搜索引擎,避免数据库做复杂查询
    • 异步写入:非核心写操作异步化,通过消息队列异步写入数据库,提升响应速度
  4. 数据库参数调优

    • 内存配置:给数据库分配足够的内存,提升缓存命中率,比如MySQL的innodb_buffer_pool_size设置为物理内存的50%-70%
    • 连接数配置:合理设置最大连接数,避免连接数过多占用过多内存
    • 持久化策略调整:可以接受少量数据丢失的场景,调整刷盘策略,提升写性能
    • 存储优化:数据库用SSD存储,提升IO性能

前端性能优化

前端性能直接影响用户体验,首屏加载速度、交互流畅度对用户留存影响很大。

优化方法

  1. 资源加载优化

    • 减少资源体积:JS/CSS/HTML压缩混淆,图片压缩,用WebP/AVIF等更高效的图片格式
    • 减少HTTP请求:合并JS/CSS资源,雪碧图合并小图片,减少请求次数
    • 资源缓存:静态资源设置合理的HTTP缓存头,利用浏览器缓存,避免重复加载
    • CDN加速:静态资源放到CDN,用户就近访问,提升加载速度
    • 按需加载:路由懒加载、组件懒加载、图片懒加载,首屏只加载必要的资源
    • 预加载/预解析:提前加载后续会用到的资源,提升后续页面打开速度
  2. 运行时性能优化

    • 减少重绘重排:避免频繁修改DOM和样式,批量修改,用CSS transform代替top/left等触发布局的属性
    • 防抖节流:滚动、输入等频繁触发的事件做防抖节流,减少执行次数
    • 长列表优化:虚拟滚动,只渲染可视区域的内容,避免大量DOM节点占用内存和渲染时间
    • Web Worker:复杂计算放到Web Worker中执行,避免阻塞主线程
    • 垃圾回收优化:避免频繁创建大量临时对象,减少GC导致的卡顿
  3. 体验优化

    • 骨架屏、loading状态提升用户感知体验,减少等待焦虑
    • 接口请求并行化,避免串行请求增加等待时间
    • 接口错误降级、弱网优化,提升异常情况下的用户体验
    • 离线缓存:PWA Service Worker实现离线访问,弱网环境也能使用基础功能

通用优化原则

  1. 缓存为王:能加缓存的地方尽量加缓存,用空间换时间,是性价比最高的优化方式
  2. 批量处理:批量读写、批量请求,减少IO和交互次数
  3. 异步非阻塞:非核心路径异步处理,提升响应速度和系统吞吐量
  4. 数据就近访问:数据尽可能靠近计算的地方,减少数据传输开销
  5. 权衡取舍:没有完美的优化,在性能、复杂度、成本之间找到合适的平衡点

性能优化是一个持续的过程,要结合具体的业务场景和瓶颈点,针对性地优化,不要盲目套用技巧,所有优化都要经过测量验证效果。

思考问题

  1. 数据库查询慢,你会从哪些方面排查和优化?
  2. 前端页面首屏加载慢,常见的优化思路有哪些?
  3. 高并发场景下,系统的响应时间变长,可能的瓶颈有哪些?怎么排查?
  4. 为什么说顺序IO比随机IO快很多?在实际开发中怎么利用这个特性优化性能?