程序的性能优化感悟

一、前言

毕业工作了近六个月了,踩过不少坑,在重构,性能优化方面有不少体会,结合自己的经历和同事的一次优化方面的分享文档,稍微总结一下。

*过早的优化是万恶之源

虽然把优化放在嘴边,但我仍要把这句话放在前面,如果一个日访问量几乎为零的小网站,比如说我的博客,要不要做缓存,垂直拆库,读写分离呢,玩玩还是可以的,谈优化就是多此一举了,首先满足需求,让正确的程序更快,要比让快速的程序正确容易得多。

二、正文

一、理解需求,贴近业务

在接触的大多数优化场景中,我们会发现需要优化的并不是数据库,程序等的性能瓶颈,而是所提供的接口没有完全切合业务。

有这么一个接口:

/**


* 分页查询用户勋章

*/
UserBadgeQueryService.findUserBadgeByMerchantIdWithPage

这个接口逻辑上完全没有问题,性能上也没有优化的余地,但我们排查时发现代码中拿这个接口居然是用来计数的,本来一个count(*)能解决的事情,却分页捞数据统计,这是一个没有切合业务的典型场景。

还有另外一个例子:

某一天,测试反馈,页面一次请求很长时间没反应,排查代码发现定位到了一个循环分页查询的接口,平时几百数据,循环一两次就查完了,但是有几个条件下,数据量多达7w,循环几百次,耗时300多秒,请求早超时了。

分析代码发现,每次分页只有100,循环次数多,另外捞取字段多,其实业务需要的只是一个id,所以优化方法就出来了,重写一个接口,只捞取id字段,并且每次分页2000,这么一处理,7w数据量耗时降为6秒。

用批量的方式操作是一种提升效率的方式,也有同事也分享过在有一些sql执行频繁的业务场景,会采取一种先搜集全部sql,然后一次性执行的操作来优化性能。

优化考虑的第一点就是理解需求,正确的程序往往就是快速的程序。

二、适当缓存

缓存是个好东西,但应该要适当,很多时候一味的缓存看似性能好,但往往会带来意想不到的结果,特别是分布式缓存中,缓存的不一致性非常致命。

java中常见的缓存有:

  1. ehcache:进程内缓存,这个一般当做本地缓存,内部调用非常快,虽然可以分布式部署,利用其自带机制同步,但高并发下一致性还是有问题的,不适合实时性要求极高的业务。
  2. memcache:独立的分布式缓存系统的,非常针对一些需要共享缓存,比如sso系统的session。
  3. Redis:它其实是一个数据库,但因为跑在内存中,硬盘只完成持久化功能,也适合当缓存用。

因此,缓存的使用也是跟业务需求挂钩的。

  • 举例两个遇到场景。

1.某接口需要定时捞取数据,量比较大,耗时数秒,但业务场景中数据每天变化基本没有,允许部分误差,要求尽量降低接口耗时。

方案:使用memcache缓存,缓存时间一天,每天只查询一次数据库,其他结果直接从缓存中读取。

2.某接口,需要查询当月全部数据,即从1号到调用时刻全部数据,之前采用

select * from table
where create_time >'2015-11-01 00:00:00'

这条sql的方式,但由于现在表数据量过亿,即使加了索引,耗时也要400ms,而且调用量大,耗时这么长无法接受。

方案:这个问题,同事想到了一个非常巧妙的解决方案,他把每月的第一条的id缓存一个月

select id from table 
where create_time >'2015-11-01 00:00:00' limit 1

查询时

select * from table where id >= 缓存的id

sql耗时降到3ms,不可思议的快

举一反三:用id代替create_time的方式非常巧妙,这让我想到很多情况我们会对数据按时间排序操作
order by create_time ,其实我们可以这样 order by id ,因为数据插入时间往往跟id自增是对应的,所以很多需要create_time做条件的可以换个思路用主键id过滤,可能会出现上面这种惊人的效率。

三、多线程

多线程也是一个可以提升性能的利器,但是一个治标不治本的方法,确认业务方面没有可以优化的余地后,多线程是可以考虑的一种方式。

比如一些日志处理的逻辑,这些逻辑对整个业务结果没有任何影响,完全让主线程返回结果,让子线程去执行日志记录功能。

这方面也需要特别谨慎,也有同事分享过因为Threadlocal问题导致的比较严重的线上bug的经历。

四、sql及索引

sql及索引之所以放在最后,是因为我认为大部分性能的瓶颈并不是出现程序本身,而是没有理解业务,而索引是一开始就应该考虑的问题,这个并不是放在最后的优化手段。

当我们为一块新业务建立表结构时,索引就应该在此时讨论了。

写好sql,注意索引这就是基本功了,如果自己无法把握,找同事讨论下是个不错的选择。非常幸运的是,公司有着严格的code review机制,sql 的review是其中重要的一环,经过多个人的讨论后,sql方面的问题越来越少。

五、外部沟通

由于大公司的业务规模往往很大,会分部门管理,许多性能的开销并不一定是部门内部的原因,一些接口往往依赖其他部门的功能,所以沟通和快速的反馈非常重要。

第一部分查询用来计数的场景就是因为没有跟外部沟通,别人用我们的分页接口做计数操作,导致期待的实现的场景不一致导致。

小结

其实谈到这么多,关键在理解需求,之所以性能低,是因为没有用最简单的方法解决需求问题,程序做了太多无谓的操作,另外一个就是保持沟通,一个人难以解决问题,团队的讨论和外部的反馈往往是突破口。而这两者也是敏捷开发的核心。

Comments
Write a Comment