京东列表页是什么
•京东列表页是商品属性高度聚合的关键词或索引组成的目录树,从京东首页下拉菜单里可以直接进入。
列表页HTML页面渲染
价格服务
促销服务
库存状态和配送至服务
广告词服务
预售和店铺信息
推荐商品
热卖商品
快报
脚印等等
架构
性能数据
双11当天访问量过亿,双11当天服务器端响应时间<80ms。此处我们用的是第1000次中第99次排名的时间。 (页面200-300KB,逻辑复杂,渲染元素多)
列表页流量特点
数据离散
热点少
各种爬虫、比价软件抓取。
架构特点
能迅速响瞬变的需求,和各种变态需求;
支持各种垂直化页面改版;
页面模块化;
开关可控制模块功能;
一分钟降级;
AB测试;
高性能、水平扩容;
多机房多活、异地多活;
静态化;
各种异常和兜底数据;
防爬虫和恶意刷列表页;
业务优化思想
使用Nginx+Lua技术获取数据并渲染模板输出
页面逻辑分散到各个阶段
数据结构优化
算法优化
页面html瘦身
Js等前端优化
Nginx+lua的优势
Lua是一个可以嵌入到Nginx配置文件中的动态脚本语言。
Nginx的看家本领就是速度,Lua的拿手好戏亦是速度,这两者的结合在速度上无疑有基因上的优势。应对高并发nginx更有出色的表现。
Lua简单易懂,适合快速开发。
Lua模板渲染性能不错。支持随时变更模板需求。
Nginx秒级重启,重启不丢失本地缓存数据。
模板引擎:lua-resty-template
https://github.com/bungle/lua-resty-template
数据结构优化
Lua和go直接是http调用,返回的数据是json串,需要反序列化。
反序列化非常消耗cpu。
对象》数组》字符串
变量用0,1代替字符串
数据层级最好不要超过三层
算法优化
模板输出变量严重消耗性能。一个变量,在模板上只输出一次。多余的不输出。
for循环用for i=1,#evParamsArr do end 方式。
数组用table t={};t[#t+1]=“”;取代table.insert(t,””);
对于需要遍历的数据结构尽量用key-value,不要用数组。
lua-resty-http模块中keepalive默认是true。调用的服务不支持的话,要改源码为false。
Lua中类型转换,提前把类型转换好,然后再参与运算。
字符串拼接用数组先存起来,最后concat
不用嵌套循环模板中不要重复输出同一个变量,并且输出前要转成str类型
提升速度的优化方向
负载均衡:DNS+CDN,减少DNS查找,每一次主机名解析都需要一次网络往返,从而增加请求的延迟时间,同时还会阻塞后续请求。
减少页面连接数 和并行处理请求和响应:减少浏览器http并发连接,合并js,合并css,合并图标。请求和响应的排队都会导致延迟,无论是客户端还是服务器端
减少页面大小:带宽有限,gzip压缩
页面静态化:同一时间内,查下相同分类的结果页面都是一样。
前端缓存:应该缓存应用资源,从而避免每次请求都发送相同的内容。
前端页面模板数据结构和后端数据结构优化
异步化、非实时变化数据,可以异步请求。
前端js分担一部分模板非必须的逻辑处理(考虑seo)。如ajax、瀑布流、bigpipe等。
后端服务尽量不调用其他系统接口。如果调用,可用定时任务把数据缓存起来,然后自己取。
重用TCP连接:尽可能使用持久连接,以消除TCP 握手和慢启动延迟;
减少HTTP重定向:HTTP 重定向极费时间,特别是不同域名之间的重定向,更加费时;这里面既有额外的DNS 查询、TCP 握手,还有其他延迟。最佳的重定向次数为零。
消除不必要的请求字节:如cookie太大。
用其他三级域名做异步调用。
降级开关前置
开关前置
1、Init_by_lua_file初始化开关数据
2、共享字典存储开关
3、提供API进行开关切换
开关控制
秒级切换开关
功能切换开关
第三方服务降级开关
异常、超时等自动降级
JS相关优化
1、异步方法先后执行顺序要控制。
2、对于非必须的业务,做成wait2000毫秒。
3、对于点击流做成页面加载完再调用wl.js
4、考虑各个接口性能,分批调用。
5、回调函数的优化,不要全局find,把需要的元素缓存到全局变量中,从缓存中查找比全局查找快。
6、js面向对象编程,不要因为某个方法报错影响后面的js功能执行。同时维护成本降低。
7、需要缓存的数据,尽量缓存起来,减少调用次数。
8、埋点的onclick不要写在html里。动态绑定。
缓存架构
1、缓存的存在一是提高访问速度和高并发,二是上游服务器挂了,还有缓存数据给用户展示,相当于兜底了,虽然内容旧一点,但是不会形成事故,影响用户体验和无法浏览商品。
2、这里的缓存还可以理解为一个小型cdn服务器功能。为后端应用挡流量。
3、每台应用服务器都有自己的缓存(HttpLuaModule模块的shared dict),缓存中放当前时段内最热的数据。如果缓存不命中,从go服务取。(reload不丢失)
4、当出现异常时,从本地缓存取,如果没有从Redis缓存取(独立全量的缓存,请看下图)。
读取分布式Redis缓存
服务异常读取。
异步收集数据,定时执行写缓存。
使用本地Twemproxy进行分片
减少redis连接数
1 、缓存的内容是什么
缓存的是调用上游服务器,lua渲染模板后的html数据(页面在200Kb左右)。
2、Key值是什么
Key值是对用户访问的url进行过滤和排序后md5值。(url中多余的非白名单参数丢弃)
3、缓存失效时间设置
缓存的失效时间在上图key缓存存入时指定,这个时间在配置文件中,可随时调整。
4、缓存的数据不设置过期时间
上图第一页缓存和其他页缓存存入时,不指定失效时间。通过nginx的共享缓存自己来管理,LRU策略淘汰冷数据。(Least recently used. 可以理解为, 最少使用的被淘汰。)
5、第一页和其他页缓存为什么是10片
Nginx的shared_dict缓存读取是有锁的,如果放在一片上,读时间会长,高并发下,容易形成排队严重的瓶颈。
6、为什么第一页和其他页缓存分开
因为服务器的内存是有限的,第一页是每个三级分类的默认页,从首页菜单直接进入的页面。这块缓存不能被丢弃,所以单独分配了存储空间,这个空间合理计算好,不能用完。同时,如果后端接口有问题、数据解析有问题、各种异常等,都需要用这块数据做最后的兜底。
7、如何区分第一页和其他页
根据自己的业务,通过判断url上的参数值来确定。
8、开关控制
整个缓存模块有五个开关来控制,总开关、第一页写开关、第一页读开关、其他页写开关、其他页读开关。
10、500、501、502、503、504、error 、timeout等异常处理
对上面后台服务运行时错误,在nginx做了一层递归跳转逻辑。跳转时,加了一个header值,标记是服务器错误,直接在程序中判断static=1,返回共享字典缓存或redis缓存数据
11、缓存命中率问题
由于列表页用户筛选的属性千变万化,第一页缓存命中100%,但是其他页缓存命中只有12%,所以要引入redis来全量存数据。提高命中率。Redis缓存是一个写异步的work,异常时读取。
12、缓存和cdn技术结合使用
对于上cdn,是程序根据不同的分类来设置header头的。Cdn回源时,命中缓存后,取缓存的flag值,来决定是否写header头。对于cdn异常回源,会根据If-Modified-Since和缓存生成时间比较,直接返回304.
13、缓存失效风暴
防爬虫
日志分析(统计ip,UA,referer)
通过cookie(js去写key,value值在html和图片)
Referer非法
UA黑名单
IP黑名单
拦截率95%
工具
1、Oprofile
http://www.cnblogs.com/bangerlee/archive/2012/08/30/2659435.html
2、apache性能测试工具ab
3、 apache性能测试工具 Jmeter
4、nginx-systemstap-tools(栈调用的火焰图)
一个性能检测和调试跟踪的工具,最开始是为了调试内核被做出来的,后来添加了用户态跟踪的功能。
https://github.com/openresty/nginx-systemtap-toolkit
5、stap++(nginx性能调优)
https://github.com/openresty/stapxx
6、nginx-gdb-utils(nginx调试)
https://github.com/openresty/nginx-gdb-utils
作者:
王向维