参考《大型网站技术架构》,其中包含一些个人理解,如有错误或说的不清楚的地方,请一定指出,谢谢。
这篇文章讲的比较浅,如果有更深入的需要,欢迎留言或是查阅资料。
在谈性能优化之前,我们先来看一看什么是性能。
网站性能是客观的指标,可以具体体现到响应时间、吞吐量等技术指标;同时也是主观的感受,而感受是一种与具体参与者相关的东西,用户间的感受、用户与工程师间的感受都是不同的。
用户感受到的响应时间,从用户发出请求(比如点击按钮)开始,包括用户计算机和网站服务器通信的时间(网络传输的时间,可能是运维同学关注的点)、网站服务器处理的时间(作为一个普通后端CRUD工程师,这个是我们关注的重点)、用户计算机浏览器构造请求解析响应数据的时间(前段同学优化所关注的点)。
我们做一个网站,面向的是用户,那么,要提高网站性能,也可以从提高用户体验开始。
1.让用户感到快
同类型的两个网站,A网站服务器平均每个请求的处理时间是500毫秒,B网站服务器平均每个请求的处理时间是1秒。用户普遍反映B网站的速度快,为什么?
经过观察,A网站在请求处理过程中,给用户呈现的是一片空白的界面;而反观B网站,虽然请求处理慢,但在用户等待过程中,用户看到的是一个小鸡不断地下蛋。
大多数用户被小鸡下蛋的动画吸引了,就不觉得等待无聊,就会觉得快。
所以,我们想提高性能,最主要的目的是要提高用户体验,不一定非要加索引、读写分离、代码优化等等等。有时候,加一个小小的等待动画即可。
2.前端优化
本人是小后端,这一部分属于纸上谈兵了,如有错误,欢迎指出
我们也不能仅仅加一个动画,如果响应时间过长,就算让用户看一个动画片,长此以往,用户也会不耐烦的。所以让我们来愉快地开始我们真正的优化吧!
2.1 前端代码优化
2.1.1 减少http请求
我们所用的http是基于tcp的,还记不记得?tcp传输首先要经过三次握手建立连接,因此,代价是挺高的。
我们可以尽量减少请求来优化性能,手段有:合并css,合并js,合并图片。将浏览器一次访问需要的js、css合并为一个文件,这样浏览器只需要一次请求。
2.1.2 使用浏览器缓存
对于更新频率较低的静态资源,总是传输没有什么意义,可以将他们缓存在浏览器中,来改善性能。通过设置http头中的Cache-Control和Expires属性,可以设定浏览器缓存。
如果静态资源发生变化需要及时应用到客户端浏览器,我们可以通过改变文件名来实现。
使用浏览器缓存策略的网站在更新静态资源时,应采用批量更新的方法,比如需要更新10个图标文件,不宜把10个文件一次全部更新,而是应一个文件一个文件的更新,并有一定是间隔时间,以免用户浏览器突然大量缓存失效,集中更新缓存,产生大量的网络传输,造成服务器负载骤增、网络堵塞的情况。
2.1.3 启用压缩
在服务器端对文件进行压缩,在浏览器端对文件解压缩,可有效减少网络传输的数据量。
但此方法会占用浏览器、服务器的资源(例如CPU、内存等)。因此,在带宽不是瓶颈,而服务器资源不足等情况下慎用。
2.1.4 CSS在上方,JS在下方
浏览器会在下载完全部CSS之后才对整个页面进行渲染,因此较好的做法是将CSS放在页面最上面,让浏览器尽快下载CSS。JS则相反,浏览器在加载JS后立即执行,有可能会阻塞整个页面,造成页面显示缓慢。
但如果页面解析时就要用到JS,则放在下面就不合适了。
2.1.5 减少cookie传输
cookie包含在每次请求和响应中,因此cookie中尽量不要放太多东西。
2.2 前端架构优化
个人觉得,在web服务器前的东西都属于前端范围,包括CDN、反向代理、负载均衡等。
我们先从CDN说起。
2.2.1 CDN加速
CDN其实本质上依然是一个缓存,只不过部署在网络运营商的机房。因此该缓存就处于里用户最近的地方,用户就可以以最快的速度获取数据,即我们之前学网络的时候所学的网络访问的第一跳。
CDN一般缓存一些访问频度很高的静态资源,如图片、文件、CSS等。
2.2.2 反向代理加速
我们的反向代理,其实也可以通过配置缓存功能加速WEB请求,其实本质上还是一个缓存,就不多说了。
2.3 应用服务器的性能优化
铛铛铛,终于到我们激动人心的后端时刻了。
作为一个后端同学,我关注的主要是接口的速率,那么接口怎么加速呢?无非是使用缓存加速数据读取、使用集群提高吞吐能力、使用异步消息加快请求响应、对代码进行优化从而改善程序性能。
2.3.1 分布式缓存
又是缓存!看来缓存真的很重要,其实也不用奇怪,毕竟磁盘访问是毫秒级的,而内存访问是微秒级的,想要加速,把磁盘中的热点数据放在内存中缓存,是一个很好的选择。其次,如果我们缓存计算后的结果,那么下次我们需要这个结果的时候,我们就无需再进行一次计算,也省了计算的时间。
可见,缓存对于我们,是很重要的一个东西,因此,也有有心人利用这一点,搞出了许多攻击,扯远了。。。
缓存本质上是一个哈希表,熟悉数据结构的同学们知道,读写哈希表的时间复杂度为O(1),因此,它很快!
我们用缓存主要来存储那些读写比很高、很少变化的数据,比如商品的类目信息、热门商品、热搜等等。
应用程序读取数据时,先看看缓存中有没有,如果读取不到或已过期,则到数据库中读取(写到这里,我的脑海中已经浮现出了一大堆代码哇咔咔)。
2.3.2 使用集群
再网站高并发访问的场景下,使用负载均衡技术为一个应用构建一个由多台服务器组成的服务器集群,并将请求分发到多台服务器上处理,从而降低单个服务器的负载,加快响应速度。
2.3.3 异步
在我们没有引入消息队列的时期,用户的请求直接进入数据库,而我们知道,数据库读写是磁盘IO,很慢,那么人们为了提高吞吐量,引入了消息队列,用户的请求写入消息队列后立即返回,再由消息队列的消费者(此情此景,消费者是数据库)读取消息队列中的消息,异步处理(此情此景,是写入数据库),由于消息队列速度远快于数据库(这时同学可能会疑惑,拿Kafka来说,同样是操作磁盘,为何Kafka快呢?这时因为Kafka被设计成避免随机IO,而数据库操作充满了随机IO),因此用户的响应延迟可以得到有效的改善。
2.3.4 代码优化
现在开始贴近我们可爱的普通后端开发同学的生活了,作为一个可爱的普通后端开发,我每天的工作就是增删改查,那么我就不用学习了吗?不是的。
比如说,我们要在for循环中通过网络请求调用别人的接口,那么我们想,这个for循环主要是IO操作,我们可以用多线程来提高它的效率,最大限度地利用我们的资源。