一个核心系统在持续开发的过程中,除了要满足业务需求外,还要考虑系统未来的架构,以及追求最佳的系统可用性、高性能和稳定性。这个过程是一个长期积累和重构的过程。每个应用程序都必须满足自己的特定需求,因为业务条件、应用程序场景、用户期望和功能复杂性各不相同。但是,如果应用程序必须响应用户,那么我们必须从用户的角度考虑感知处理时间常数。事实上,虽然生活节奏越来越快——至少我们是这么觉得的,但人的感知和反应时间并没有改变:这张表解释了网络性能社区总结的经验法则:页面必须在250以内呈现ms,或者至少提供视觉反馈以防止用户走开。如果你想感觉快,你必须在几百毫秒内响应用户操作。如果超过1s,用户预期的流程就会被打断,心思就会转移到其他任务上,而如果超过10s,除非你有反馈,否则用户基本会终止任务!下面将从前端、服务器端、缓存、底线等方面谈谈如何优化京东的三级列表页面。前端优化京东三级列表页从优化到上线经历了两次618和一次双11测试,日访问量上亿,页面打开时间20-80毫秒(部分地区或以下)低带宽将大于100ms)。优化的四大原则精简页面,先展示首屏;延迟加载需要用户交互的部分;懒执行那些不能先执行的;延迟加载滚动屏幕。1、简化HTML文档的目的是尽快渲染页面,达到交互状态。方法:1、如果不需要,尽量只生成首屏需要的html数据;2.优先获取资源,提前分析。比如首屏需要的css和js;如果不考虑维护成本,可以将首屏需要的css和js放到文档中;3.发现关键网络资源并优先排序,尽快调度请求和获取页面;4.精简文档最后,服务器端生成程序耗时更少,性能也会更好。比如列表页的页眉、面包屑、品牌区、属性筛选区、60张商品主图数据,这些都是服务端模板渲染输出;其余的是在前端JS中延迟加载或生成的。2.需要用户交互的部分延迟加载对于三级列表页品牌区,服务器只渲染了18个品牌,当用户点击较多时,ajax异步加载其他。对于整个属性,服务器只渲染了筛选区域中的5行。当用户在其他行点击更多时,js从documentembeddedresource中获取数据,渲染成html。这样做可以保证服务器端计算量少,提高服务器性能,减少数据传输。如下图,点击“更多”会加载更多品牌,因为有些三级分类的品牌很多。如果不使用这个方法,整个页面渲染会很慢。因为SEO的需要,京东的三级列表页面无法使用bigpipe等技术进行更好的处理。3.如果不能执行,先不要执行。惰性执行上图是三级列表页最重要的商品专区(商品主图+相关商品的N小图)。每个产品的面积完全相同;如果在服务端拼装了整个产品区,尤其是小图片部分,会出现很多重复的html元素;我们在体验和减少页面内容之间做出了妥协;服务器渲染输出产品的主图部分;小图部分通过json数据嵌入到页面中,然后通过js懒惰的进行渲染。这在缩小页面方面做得很好。而且,缩略图资源是嵌入在页面中的,不是异步加载的;没有网络请求,用户基本感知不到异步带来的渲染闪烁问题。下图是页面内嵌的小图json数据。4.滚动延迟加载当用户向下滚动页面加载当前屏幕的图片和模块时,加载三级列表页60个商品区的图片和页脚。这节省了服务器带宽和压力,并改善了整体页面呈现时间。上面已经介绍了三级列表页优化中最重要的四个原则,但是实际的优化过程中还涉及到很多优化细节,将在后面的章节中进行介绍。将一些JS/CSS资源直接嵌入到页面中将资源嵌入到文档中可以减少请求的数量。比如页面需要的js、css数据。如下图所示:上图中的这些js对象是后端渲染输出的,不适合放在单独的js文件中,直接嵌入到页面输出比较好。slaveWareList是缩略图列表对象。如果放在服务器端模板上渲染输出,则需要对页面进行一些循环组装;此外,页面大小会变得非常大。权衡之后,决定放在前端js渲染输出中。这也带来了一些好处:减轻服务器的压力,提高渲染模板的性能,减少服务器的执行时间;服务器无需生成html,文档减少数百个div,减少页面大小和网络开销;提前放在文档里,没有异步调用;用户基本上不知道渲染过程。对导入的资源进行优先级排序根据自己系统的业务,对每个资源进行优先级排序:需要的资源先加载,低优先级的请求入队列延迟加载或者等待需要的资源加载完成再加载;这样as:热词搜索和推荐,置顶三个热销商品界面,60款主打商品图片,价格优先加载。库存、促销信息、广告词、预售商品、店铺信息等延迟加载。对于点击流,广告统计数据会延迟两秒加载。应用js缓存存储公共属性和商品信息属性。三级列表页面中的每一个商品都是一个对象,存储在一个map中,通过ajax接口异步填充和维护商品的属性。用于后续的用户交互。同时,维护成本也会降低;即页面中使用的每个商品数据都放入一个map中,如果没有则异步加载;如果直接使用;也就是说,这些数据是公共数据。Ajax接口调用页面往往会依赖很多异步接口,因此需要对异步接口进行压测,找出接口的调用方式。比如京东的三级列表页依赖于价格、库存、广告、店铺信息等异步调用接口。有时页面上有300多种产品。如果用get请求将这些sku作为参数,性能很慢,所以需要分组批量调用。例如页面有300个商品,价格界面分为六组,第一组30个,第二组30个,第三组60个,第四组60个,第五组100个组,第六组100人。DNS预解析提前解析可能的域名,避免DNS在以后的HTTP请求中出现延迟。比如价格、库存、图片、单品页面等服务的前期分析。减少HTTP重定向HTTP重定向非常耗时,尤其是不同域名之间的重定向,更耗时;还有额外的DNS查询、TCP握手和其他延迟。***有零重定向。比如三级列表页,以前是http://list.jd.com/12-12-12.html,现在是http://list.jd.com/list.html?cat=12,12,12;在过渡期间可以进行重定向,但在过渡完成后就没有必要了。使用CDN(ContentDeliveryNetwork)将数据放在更靠近用户地理位置的地方,可以显着降低每个TCP连接的网络延迟,提高吞吐量。比如京东的三级列表页,商品详情页,公共JS,CSS。传输压缩内容(Gzip压缩)应在传输前压缩应用程序资源,以尽量减少要传输的字节数:确保对每个要传输的资源使用尽可能最好的压缩方法。在客户端和服务器之间传输之前,应使用Gzip压缩所有文本资源。一般来说,Gzip可以减少文件大小60%~80%。也是比较简单的(在服务器上配置一个选项即可),但是优化效果比较好。(压缩级别,经过不同服务器多次压测,建议Nginx设置为1-4)去除不需要的资源。有请求不如没有请求快。延迟一些非必要的或者异步的,或者可延迟的尽量去问。在客户端缓存资源应该缓存应用程序资源以避免在每个请求上发送相同的内容。无状态域名cookie是很多应用中常见的性能瓶颈,很多开发者会忽略它给每个请求增加的额外负担;减少请求的HTTP标头数据(例如HTTPcookie)节省的时间相当于几个往返延迟时间。例如,列表页依赖的价格和库存接口采用了3.cn无状态域名,从而减少了主域下的cookie传输。请求和响应的并行处理请求和响应的排队会在客户端和服务器端引入延迟。这一点经常被忽视,但会导致不必要的长时间延误。域名分区当页面上很多请求都是同一个域名下的资源时,由于浏览器最多只能同时开启6个连接池,而每个连接池作用于不同的域名,所以同一个域名的很多请求都会排队。如果将这些请求的域名进行分区,则可以将请求并行化,从而加快资源下载速度。例如:页面需要下载上百张图片,为图片调用域名分区。京东页面大多调用图片域名:http://img10.360buyimg.com/http://img11.360buyimg.com/http://img12.360buyimg。com/http://img13.360buyimg.com/http://img14.360buyimg.com/FlattenandConcatenateMergeLinks:将多个JavaScript或CSS文件合并为一个。Flattening:将多个图像组合成一个更大的合成图像(CSSSprites)。服务器将相关信息写入头部,并将服务器IP的最后两位写入头部。如果有问题,方便定位到哪个服务器。ups:后端路由的所有服务器都被检索。去除缓存的***信息或异常,将后端运行状态写入header。头部状态:***、非***、异常等状态。服务器端架构是Nginx+Lua(OpenResty)+golang+redis缓存计算,后面再整理列表页的结构。作者:王向伟,京东商城列表页三级架构师,完成了列表页nodejs版到nginx+lua版的过渡,并对服务端和前端做了很多优化工作三级列表页。【本文来自专栏作者张凯涛微信公众号(凯涛博客),公众号id:kaitao-1234567】
