这篇文章总结一下服务器网关及之前部分的优化,如客户端的优化,CDN/DNS等。
这里我们先谈一谈客户端缓存优化的手段。一般我们后端在说到缓存,第一时间想到的往往是redis,其实缓存在架构层次还有很多其他可以实现的地方,如客户端缓存。对不同的场景,我们可以尝试不同的客户端优化手段。
1,资源下载
压缩资源:对资源进行压缩,如某些业务场景,不需要展示全像素的图片,可以图片压缩成缩略图,存储在文件系统中,前端需要时,将缩略图传输给前端进行展示,只有在必要时,才会将全像素的原图传输给前端。或者如我们在下载某些资源时,常常是zip格式,这也是对资源进行压缩的表现。
删除不必要的cookie:浏览器第一次访问服务器时,会生成一个session对象保存在服务器中,浏览器中保存sessionId作为查询用户信息的凭证[1,2],而浏览器在访问服务器时,将sessionid放在了cookie中。对于开发来说,有时候经常会随手携带上cookie。但其实对于接口不需要的session和cookie的,我们可以不用带上cookie,避免带宽的占用。
js删除无效字符和注释:这个的作用有两个,1是降低体积。2是为了代码安全,防止代码被轻易理解,进而对于服务器的恶意攻击。
css压缩:类似js压缩。1是删除注释,无效的字符、空格、回车。可以借助压缩网站进行压缩[3]。2是样式的语义合并
http请求压缩:head中加参数:Accept-Encoding:gzip,deflate
,表示 客户端 可以接受的压缩内容的格式,客户端接收到数据后可以以这种格式进行解压;服务端响应时,在head中加参数: Content-Encoding:gzip
,并在服务端手动做一些压缩的操作。
此外由于这种方式浏览器需要等待服务器将数据压缩完再发送给浏览器,等待时间长。因此往往配合transfer-encoding:chunked
来使用,服务器将会把数据分块压缩并直接传输给浏览器,浏览器不用等待所有数据都压缩完再解压[4]。
将多个小图标合并为雪碧图(CSS Sprite, CSS精灵):如官网边缘常常显示多个小图标,这种小图标如果每个都单独请求接口,对于网络的压力会更大。可以使用这种叫做雪碧图[5,6]的技术,将多个小图标合并为雪碧图,可以减轻后端网络的压力。前端通过指定background-position来指定小图标坐标。
前端 < svg >生成矢量图代替后端直接传输矢量图图片:< svg >可以用于生成矢量图[7],这种生成的矢量图可以无限放大而不失真,同时后端也只用传输矢量图的数据而不是图片本身,减小了网络压力。
js文件合并打包:将多个js文件合并为一个文件,便于下载。
使用base64编码对图片进行转换后再压缩:如果图片不适用前面直接对图片压缩为缩略图的方式,可以使用这种方式,先使用base64将图片转换为字符串,再使用压缩算法对字符串进行压缩。客户端那里反向解压再转换回图片。如一篇文章[8]中举例,使用base64编码后,字符串大小和图片本身差不多大。再使用gzip压缩,压缩率可达50%以上。
转移第三方:将文件系统的压力转移给第三方,如阿里云OSS服务。
2,资源缓存优化
资源缓存是对资源下载的优化之一,但是客户端缓存的优化比较复杂,这里单独拆出来讲。通俗的说,资源缓存有客户端缓存/页面缓存,代理服务器缓存/CDN缓存,这些缓存并不是孤立存在,经常是同时存在的。此时需要前端与后端一起对缓存进行控制,如下所述。
客户端/页面缓存 & CDN/代理缓存[13]:控制 客户端、各级代理(中间的各个节点),对页面资源的缓存。请求头中添加Cache-Control,有些参数是客户端请求时添加的缓存请求指令,有些参数是服务端响应时添加的缓存响应指令,列举参数如下[13]:
缓存策略
:
//public和private是服务器做响应时添加的参数才能生效。
pubic(服务器 响应): 各级都能缓存。private(服务器 响应): 只能 客户端 缓存,中间各级不缓存,默认就是private。no-cache: (请求,响应):可以缓存,但是不能直接使用缓存,要去服务端验证一下(即缓存协商)。no-store: (请求,响应):哪都不要存。
缓存有效期
:
max-age=秒(服务器 响应),缓存可以存活的相对时间。s-maxage=秒(服务器 响应),在各级cache服务器(CDN,代理服务器[14])存活的时间,仅在为public缓存生效,如果是客户端缓存忽略。max-stale=秒(客户端 请求),可以忍受从代理服务器拿缓存资源过期的时间。min-fresh=秒(客户端 请求),需要的代理服务器缓存资源的新鲜度,即要求代理服务器的缓存资源至少要在过期时间之前min-fresh秒才可以使用和获取
重新验证和加载的设置
:
must-revalidate(服务器 响应)[15]: 资源过期后,服务器重新验证之前,不可以使用该资源。proxy-revalidate(服务器 响应): 作用与must-revalidate类似,但是仅对各级缓存节点有效,对客户端私有缓存无效。no-transform(服务器 响应): 不允许代理对资源格式如Content-Encoding、Content-Range、Content-Type进行改变,因为如非透明代理或者如Google's Light Mode可能对图像格式进行转换,以便节省缓存空间或者减少缓慢链路上的流量。only-if-cached(客户端 请求): 只要各级代理服务器与CDN的资源,不要服务器的资源,也不需要检查服务器资源的更新。
使用缓存,就可能存在缓存导致的一致性问题。源站更新了缓存,CDN或者代理服务器如何保证一致性呢?
对于CDN,比如阿里云提供的CDN[16],提供了刷新功能。在调用CDN服务提供商的刷新API接口后,CDN会刷新缓存节点中的数据,大约五分钟左右生效,这段期间,会将请求路由给源站。
对于代理服务器,如nginx,ATS,则没有这么智能了。那它们如何解决一致性问题呢?
(1),更新文件名。
更新的文件名可以根据版本号或url时间戳的变化而变化,如my.js,my-1.js。这种方案要求客户端和服务器要达成一致,那么客户端请求代理服务器时,查不到新名字的资源,就会被路由到源站进行资源的查询与资源刷新,旧名字的资源超时后被代理服务器删掉。
(2),协商缓存验证缓存有效性
前面讲过一个叫做缓存协商的策略(Cache-Control:no-cache
),即查询到CDN/代理缓存后,不会直接返回客户端,而是去服务器验证有效性再返回客户端。
协商缓存主要有四个头字段,它们两两组合配合使用,Last-Modified
和 If-Modified-Since
一组验证修改时间,Etag
和 If-None-Match
一组验证版本号,当同时存在的时候会以 Etag
和 If-None-Match
为主。当命中协商缓存的时候,服务器会返回HTTP状态码304,让客户端直接从本地缓存里面读取文件[13],未命中协商缓存,会从服务器返回具体资源,状态码200。
修改时间:
If-Modified-Since:请求头,当前缓存资源最近修改时间,由浏览器告诉服务器。其实就是第一次访问服务端返回的Last-Modified的值。Last-Modified:响应头,服务器中资源最近修改时间,由服务器告诉浏览器。
版本号:
If-None-Match:请求头,缓存资源标识,由浏览器告诉服务器。其实就是第一次访问服务端返回的Etag的值。Etag:响应头,资源标识,由服务器告诉浏览器。
3,客户端连接优化
http长短连接:http1.0仅支持短链接,不支持长连接,而现在的http协议大多至少为http1.1协议是支持长连接的。需要注意的是,http协议是基于请求/响应模式的协议,不管是长连接还是短连接都是响应结束就关闭。http协议中的长短连接,是指的是否复用OSI七层模型中传输层的tcp协议[9],即http长连接中,请求响应结束后,一段时间内,tcp协议不会关闭,等待http协议复用tcp协议。而短连接则是每次都会三次握手建立新的tcp连接,并且再结束时四次挥手。
http1.1建立长连接,在请求头中加入Connection:keep-alive
即可,Keep-Alive: timeout=60
表示超时时间为60秒[10]。
长轮询与短轮询:准确得说,长轮询是服务端的一种模式。我们平时的服务端,收到客户端的请求,去查询数据,不管查到与否,会直接将响应返回。这个是短轮询,也就是轮询[11](客户端轮询查询服务器),这种方式实现简单,但是对于需要多次频繁请求的业务场景,往往会对服务器造成很大的压力。这时服务端可以使用长轮询[11],即服务端阻塞一段时间,在数据有变化了以后,再将数据返回给客户端,或者直到请求超时返回。在阻塞的这段期间,可以使用redis的pubsub订阅发布或者MQ来接收数据变化的通知。或者使用循环,阻塞指定时间后查询数据库,直到超时或有数据更新。
双工通信:对于客户端轮询服务端造成的网络开销问题,也可以使用Netty来实现一个websocket服务,websocket是一个基于tcp协议的双工通信协议[12],即可以客户端给服务端发信息,也可以服务端给客户端发信息。这里可以客户端每隔一段时间给客户端发请求,也可以服务端数据变化发给客户端。具体的要看业务。
双工通信与长轮询的区别在于,长轮询由服务器控制,而双工通信,客户端和服务端是平等的。相同点是都可以减小网络连接与销毁的开销。
4,优化加载顺序
懒加载:仅仅加载 最基础的元素,以后再根据用户的操作,进行局部加载。将原来一次性要加载的内容拆分成了多次加载,目的在于将数据进行分流,常见于h5/app界面、树形组件、折叠面板、标签页等。
使用时尽量灵活一些,支持多种情况。到了不得不看具体数据的时候,才调用后端。但是上述场景是否使用懒加载是不一定的,对于一些经常不变的数据,就没必要了,一切要以适合业务为准。
预加载:
1.同一个域名下,
预加载拉取资源
[17],
#告诉浏览器立即加载资源
<link rel="preload" href="xxxx.js" />
#告诉浏览器空闲时再加载资源
<link rel="prefetch" href="xxxx.js" />
如在一些小说网站的下一页会使用预加载,刚进入页面时下一页的页码是灰色的,过几秒变成了绿色的,代表此时预加载完成,可以提高用户的体验。
2,不同域名下,
打开浏览器的dns预解析
[18],
<meta http-equiv="x-dns-prefetch-control" content="on">
设置dns解析目标
,
#使用不完整的url域名解析,这些域名前要加//[18]。
<link rel="dns-prefetch" href="//www.baidu.com">
预加载拉取资源
[17],
#告诉浏览器立即加载资源
<link rel="preload" href="xxxx.js" />
#告诉浏览器空闲时再加载资源
<link rel="prefetch" href="xxxx.js" />
5,客户端数据库
pc客户端可以使用h5支持的html web sql数据库[19],安卓上可以使用sqlite数据库[20],pc浏览器上的web sql如下:
如在访问百度时,本地存储中存了一些永不过期的数据,会话存储存了一些生命周期为会话级的数据。借助web sql数据库,可以实现更多缓存和存储的操作,减小对于后端的访问量。
6,动静分离
前面讲了客户端缓存,CDN/代理服务器缓存,可能有人会讲,静态数据的缓存不少讲了么,为什么还要讲动静分离呢?这部分需要重申的就是,什么是静态数据,静态数据应该放在哪里?
什么是静态数据?
静态数据不仅包括 传统意义上的页面/图片等,也包括和访问者无关的非个性化数据。
如一篇新闻,假设其和用户推荐无关,那么这篇新闻就可以放入CDN中作为静态数据。
再比如,有时电商网站访问高峰期,猜你喜欢会关闭动态推荐,改为所有用户推荐一样的数据,那么这些数据也可以作为静态数据,放入CDN或代理服务器缓存中。
静态数据应该放在哪里?
放在离客户端最近的地方,包括不限于浏览器缓存、CDN、代理服务器缓存等。
要怎么做?
链接和数据做唯一kv映射,根据url可以直接从CDN获得一个静态数据。如果缓存到了客户端,甚至都不用发http请求了(如下面京东商城的图片)。特殊展示的元素做分离:尽可能的多做静态化。将页面中的cookie,与用户个性化相关的东西去掉。
举个例子,京东商城的产品详情页,如果url定义为www.xxx.com/getItem?itemId=1类似于这种,如此是把产品定义为动态数据,商品数据从后端数据库返回,图片数据要从文件系统服务器查询后返回,如此,商品数据+样式文件,渲染出最终的h5页面。
在业务允许的范围内,我们把如果我们把页面视为静态数据,那么用户可以直接访问h5的url。下面举例京东商城,就是直接把商品详情页链接和数据做映射,直接访问h5资源。
而图片是由代理服务器nginx处理返回的,X-Cache-Lookup通常为CDN添加,cache hit表示缓存命中[21]。
这种方式,最终的目的就是流量的分流,借助CDN和代理服务器,缓解了后端的压力。
先写这么多,后面有再补充。
参考文章:
[1],Session(会话)的三种创建方式
[2],一文讲透Token与Cookie、Session的区别
[3],性能优化: 资源合并与压缩 – 压缩(前端开发过程中 JavaScript、HTML、CSS 文件的压缩)
[4],HTTP 传输内容的压缩
[5],浅谈 CSS Sprites 雪碧图应用
[6],CSS Sprite(雪碧图)简单使用
[7],SVG 教程
[8],Base64详解:玩转图片Base64编码
[9],http的长连接和短连接(史上最通俗!)以及应用场景
[10],HTTP长连接、短连接使用及测试
[11],实现Web端即时通讯的方法:轮询(短轮询)、长轮询(comet)、长连接(SSE)、WebSocket
[12],什么是WebSocket (经常听别人讲感觉很高大上其实不然)
[13],彻底弄懂前端缓存
[14],缓存代理服务器的实现机制和技术选型
[15],Cache-Control
[16],刷新和预热资源
[17],预加载属性 preload 与 prefetch 区别
[18],meta标签属性x-dns-prefetch-control的含义
[19],HTML5 Web SQL 数据库
[20],SQLite 教程
[21],腾讯云 CDN 的 X-Cache-Lookup