程序猿和攻城狮们天天写代码,难免碰到别人反映程序慢。我自己就碰到了许多次这样的问题。现在将一些心得记录总结一下,以便大家和未来的自己参考。总体原则是:不能无的放矢。
曾经面试过一个人,问对方说如果有一天客服反馈说你编写的页面加载很慢,那你应该怎么办?对方想了想:加缓存呗。可是此时连慢的原因都还不知道呢,加缓存能管用么?不管如何,首先得知道哪里慢,然后针对慢的地方对症下药,才是解决之道。没有数据的性能调优就是耍流氓!就像这几天霾绕帝都,让人想起炒菜油烟的梗。难道所有人不炒菜雾霾就能消失?首先还是要收集数据,到底慢(mái)的主要原(chéng)因(fèn)是什么?
前端
十年前,那会儿还没有chrome呢。当时web页面调得没有什么问题了,然后在客户那边渲染就是很慢。看了一下数据量确实很大,于是用最原始的办法:加上alert()来人肉估计一下运行时间,然后就发现下面这段代码很慢:
for (var i = 0; i 后端
后端可以发挥的空间相对来说更大一些。缓存用好了是一剂良药,用不好就是一剂毒药。毕竟[命名和缓存失效是计算机科学里面最难应对的两件事](http://martinfowler.com/bliki/TwoHardThings.html)。前一阵子某电商的模块里,从后端获取商品极慢,然后就发现了gateway在获取商品的API里,竟然向商品服务发了几百个请求,不慢才怪呢。统计了一下,这里面至少有90%都是参数相同的重复请求,使用30秒的短缓存,便可以立即减少重复请求,提高90%的性能,这样用户的感知就非常明显了。但实际上,还是需要分析代码,为什么会有这么多的重复请求?多半还是代码写的有问题。缓存是治标之道,立竿见影,但要治本,还是需要从代码上着手,优化代码本身。还有一些可以做的,是提供合并查询的API,例如除了提供getSkuById()以外,再提供一个getSkusByIds()的方法,当然还要小心不要出现[n+1](http://stackoverflow.com/questions/97197/what-is-the-n1-selects-issue)的问题。还有一个C#里经常会碰到的性能问题,那就是过早地计算linq的实际值。Java 8的流里也有类似的可能。
有时候,性能问题与需求有关系。曾经碰到过的一个需求是:用户注册的时候,要求昵称不能重复。如果重复了,推荐3个以递增数字结尾的新昵称给用户。例如,ggg被注册了,推荐ggg1、ggg2、ggg3给用户。当然还需要判断ggg1、ggg2、ggg3是否也被注册了,否则还得往后加。而验证昵称的代码因为需要访问数据库导致比较慢。有一段时间,用户很喜欢使用这个昵称:༺༻,都排到100号了。每当新用户想使用这个昵称的时候,后台都需要判断100次以上,因为༺༻1到༺༻100都被占用了。这时候就可以跟产品经理讨论讨论,是否需要推荐昵称?是否可以改变推荐昵称的方式?例如增加日期到推荐昵称里以避免重复。必要时,还可以挑战一下:有多少用户使用了我们系统推荐的昵称?如果需求实在是硬邦邦完全不能变,那只能考虑一些技术手段了,例如给数据库里用户表的昵称字段增加索引、缓存常见昵称的当前最大值、甚至动大刀子弄个昵称服务,或是备份数据库等,看看是否付出能够值回票价了。还有一次是推荐好友的功能,要求随机同城异性等级高,说不定还需要考虑年龄星座喜好呢,这些大多是不常用的字段,也滤不掉太多数据,查找起来自然很慢,这样也需要看看业务价值究竟有多大。
后端不像前端那样打开浏览器就能看到性能信息,也很难通过调试的方式来看生产环境为什么慢。这个时候如果框架里有处理时间日志,就会对排除性能问题非常有帮助。如果配置了ELK或者Splunk,就可以轻松找到可疑的接口。用户模块新上线的时候,就曾经直接定位到推荐好友的API速度慢,从而找到那条缓慢的SQL。哪怕是模块正常工作期间,也能通过日志看到一些有用信息。有天晚上从日志里发现用户注册API特别慢,后来知道原来是电信/联通的问题,当天如果通过微信/微博注册的用户,由于网络不通畅,导致整个用户注册响应都有问题。如果部署在阿里云上,云抽风了也并不罕见。另外所有与外部请求有关的模块,都有潜在的性能风险。使用外部请求的模块一般都有微信/微博授权、微信/支付宝付款、SMS网关、埋点、物流/天气接口、各种云服务/CDN等等。