提升后端API性能的主要目的是为了提高系统整体的响应速度、并发能力以及可用性。主要原因包括:
提高用户体验
后端API性能好可以减少响应延迟,给用户流畅的体验。
提高系统吞吐量
优化API性能可以提高系统的整体吞吐量,处理更多用户请求。
节省服务器资源成本
优化API提高资源利用率,减少对计算、内存、网络等资源的占用。
提高系统稳定性
良好的API性能可以防止请求积压造成的链路阻塞,减少超时、服务降级等问题。
促进业务发展
性能良好的API可以支撑更复杂的业务场景,为产品迭代和业务增长提供保障。
提升运维效率
代码和架构优化可以降低重复工作量,减少故障排查时间。
增强系统容错能力
优化后可以应对更大的流量冲击和失效情形。
代码优化
通过算法优化、减少IO等方式优化程序,使其高效运行。
缓存使用
通过Redis、Memcache等缓存数据库缓存常用数据,减少数据库查询。
CDN加速
使用CDN缓存静态资源,减少服务器压力。
异步处理
通过消息队列、事件驱动等方式实现异步处理,提高并发能力。
服务拆分
将服务拆分为小的单元服务,采用微服务架构。
流量控制
通过限流、降级等方式控制流量并保护服务稳定运行。
数据库优化
优化数据库模式,使用索引、读写分离等技术提升数据库效率。
并发优化
通过线程池、非阻塞IO等方式提升系统并发性能。
服务器扩容
垂直扩容服务器或利用云服务横向扩容,增强处理能力。
线程池化
池化技术(Pooling)的关键思想就是重用,其目的是为了避免每次需要资源时都要重复创建和销毁,从而提高性能和资源利用率。
以数据库连接池为例,不使用连接池的时候,每次操作数据库都需要:
这样重复创建和关闭连接会非常耗费资源。
而引入连接池后,可以提前创建好一定数量的连接,放入连接池待用。需要时直接从池中取出已有连接使用,操作完毕再放回池中,而不需要重复创建连接。
同样,线程池也是提前创建线程,组成线程池待用,需要时直接派一个空闲线程执行任务,从而避免了频繁创建和销毁线程的资源消耗。
池化技术重在提高资源的重复利用率。目的是为了提高性能,减少不必要的性能开销。现在几乎所有的连接资源都会使用池化技术进行管理。
批量入库
对于需要批量插入或者更新到数据库的操作,可以先批量处理逻辑完之后再统一一次性插入数据库,这样做的优势在于
减少网络交互,提高写入效率。
向数据库批量插入可以减少客户端与数据库之间的网络往返。
可以对数据进行预处理。
可以在入库前对数据进行过滤、转换等优化。
减少索引更新开销。
可以关闭索引,批量插入后再重建索引,从而减少索引更新带来的开销。
可以执行更复杂的SQL逻辑。
批处理可以构建更复杂的SQL逻辑完成数据导入。
提高数据库并发能力。
批量写入可以聚合成少量大事务,可以减少数据库并发Transaction的数量。
异步执行
在设计接口时,对于一些非核心业务逻辑,如果这部分逻辑执行时间长且不影响主业务流程,我们可以考虑“异步”执行这些逻辑。
具体来说,可以通过以下技术方案实现:
使用消息队列,让主业务逻辑快速返回,将非核心逻辑作为消息放入队列异步执行。
设计异步线程或定时任务,在主线程返回后,异步线程负责后台执行非核心逻辑。
利用事件编程模型,主业务逻辑触发事件,事件监听者异步响应事件执行非核心逻辑。
非核心逻辑作为微服务单独部署,主业务快速调用微服务,微服务后台异步执行逻辑。
使用reactor模式,主线程接收请求触发非核心逻辑,再通过多线程异步执行非核心处理。
常见的异步有:
使用缓存
恰当地使用缓存,可以大大的提升接口的性能。
使用缓存的主要好处有:
减少数据库查询,降低后端负载
缓存可以存储热点数据,减少对数据库的查询,降低后端存储系统的压力。
加速读取速度
从缓存读取数据比数据库查询要快得多,可以显著提高访问速度。
改善用户体验
加速系统响应,用户会感受到更流畅的用户体验。
提高系统扩展能力
缓存层可以作为数据库前的缓冲层,让系统支持更高的负载。
降低基础设施成本
减少存储系统扩容提升需求,降低整体IT成本。
保护核心数据系统
缓存可充当外部系统与核心存储之间的屏障。
帮助实现高可用性
缓存可作为备份数据源,在主数据源不可用时提供冗余数据访问。
常见的缓存有:
Redis - 基于内存的键值缓存,支持多种数据结构,性能极高。
Memcached - 简单的内存键值缓存,没有Redis丰富的数据结构。
慢查询优化
可以从以下几点优化:
锁粒度避免过粗
在设计并发程序时,使用锁(mutex)来保护共享资源,但锁的范围不能设计得过于宽泛,这称为锁的粒度问题。
过粗的锁粒度意味着锁的范围过于宽泛,例如对整个应用只有一把大锁。这会带来以下问题:
锁争用过于频繁,并发程度低
可能会发生死锁
锁的获取和释放频繁上下文切换,性能消耗严重
因此需要注意锁粒度的选择:
只在访问共享资源时加锁,不要锁住无关代码
可以将一个大锁拆分为多个细粒度的锁
根据代码逻辑设计锁的范围,避免锁过多或过少
不同的线程访问不同资源应该用不同的锁
示例
private void A(){
}
//共享方法
private void B(){
}
private int C(){
synchronized(this){
A();
B();
}
}
修改为
private void A(){
}
//共享方法
private void B(){
}
private int C(){
A();
synchronized(this){
B();
}
}
串行改并行
在设计程序时,原本采用了串行逻辑,即一个任务完成后再执行下一个任务。这种模型导致任务只能顺序执行,整体吞吐量受到限制。
为提高吞吐量,可以考虑将程序逻辑改为并行执行。具体做法是:
比如 串行

改成
并行
