针对老项目,去年做了许多降本增效的事情,其中发现最多的就是接口耗时过长的问题,就集中搞了一次接口性能优化。本文将给小伙伴们分享一下接口优化的通用方案。
1.批处理
批量思想:批量操作数据库,这个很好理解,我们在循环插入场景的接口中,可以在批处理执行完成后一次性插入或更新数据库,避免多次IO。
//for循环单笔入库list.stream().forEatch(msg->{insert();});
//批量入库batchInsert();
2.异步处理
3.空间换时间
一个很好理解的空间换时间的例子是合理使用缓存,针对一些频繁使用且不频繁变更的数据,可以提前缓存起来,需要时直接查缓存,避免频繁地查询数据库或者重复计算。
也就是预取思想,就是提前要把查询的数据,提前计算好,放入缓存或者表中的某个字段,用的时候会大幅提高接口性能。跟上面那个例子很像,但是关注点不同。
举个简单的例子:理财产品,会有根据净值计算年化收益率的数据展示需求,利用净值去套用年化收益率计算公式计算的逻辑我们可以采用预处理,这样每一次接口调用直接取对应字段就可以了。
5.池化思想
我们都用过数据库连接池,线程池等,这就是池思想的体现,它们解决的问题就是避免重复创建对象或创建连接,可以重复利用,避免不必要的损耗,毕竟创建销毁也会占用时间。
池化思想包含但并不局限于以上两种,总的来说池化思想的本质是预分配与循环使用,明白这个原理后,我们即使是在做一些业务场景的需求时,也可以利用起来。
6.串行改并行

@Transactional(value = "taskTransactionManager", propagation = Propagation.REQUIRED, isolation = Isolation.READ_COMMITTED, rollbackFor = {RuntimeException.class, Exception.class})public BasicResult purchaseRequest(PurchaseRecord record) {BasicResult result = new BasicResult();//插入账户任务taskMapper.insert(ManagerParamUtil.buildTask(record, TaskEnum.Task_type.pension_account.type(), TaskEnum.Account_bizType.purchase_request.type()));//插入同步任务taskMapper.insert(ManagerParamUtil.buildTask(record, TaskEnum.Task_type.pension_sync.type(), TaskEnum.Sync_bizType.purchase.type()));//插入影像件上传任务taskMapper.insert(ManagerParamUtil.buildTask(record, TaskEnum.Task_type.pension_sync.type(), TaskEnum.Sync_bizType.cert.type()));result.setInfo(ResultInfoEnum.SUCCESS);return result;}
上面这块代码主要是申购申请完成后,执行一系列的后续操作,如果现在新增申购完成后,发送push通知用户的需求。很有可能我们会在后面直接追加,如下图所示:事务中嵌套RPC调用,即非DB操作,这些非DB操作如果耗时较大的话,可能会出现大事务问题。大数据引发的问题主要有:死锁、接口超时、主从延迟等。
@Transactional(value = "taskTransactionManager", propagation = Propagation.REQUIRED, isolation = Isolation.READ_COMMITTED, rollbackFor = {RuntimeException.class, Exception.class})public BasicResult purchaseRequest(PurchaseRecord record) {BasicResult result = new BasicResult();...pushRpc.doPush(record);result.setInfo(ResultInfoEnum.SUCCESS);return result;}
所以为避免大事务问题,我们可以通过以下方案规避:
9.优化程序结构
10.深分页问题
深分页问题比较常见,分页我们一般最先想到的就是 limit ,为什么会慢,我们可以看下这个SQL:
select * from purchase_record where productCode = 'PA9044' and status=4 order by orderTime desc limit 100000,200
limit 100000,200 意味着会扫描100200行,然后返回200行,丢弃掉前100000行。所以执行速度很慢。一般可以采用标签记录法来优化,比如:
这样优化的好处是命中了主键索引,无论多少页,性能都还不错,但是局限性是需要一个连续自增的字段
11.SQL优化
12.锁粒度避免过粗
错误的加锁方式:
//非共享资源private void notShare(){}//共享资源private void share(){}private int wrong(){synchronized (this) {share();notShare();}}
正确的加锁方式:
//非共享资源private void notShare(){}//共享资源private void share(){}private int right(){notShare();synchronized (this) {share();}}
我相信很多接口的效率问题不是一朝一夕形成的,在需求迭代的过程中,为了需求快速上线,采取直接累加代码的方式去实现功能,这样会造成以上这些接口性能问题。

