大家都知道利用缓存技术是提升网站性能和稳定性的利器,比如利用redis集群来缓冲mysql的压力,利用opcache缓存编译后的bytecode提升php的性能等等,同样对于网站的整体架构而言,如果一些数据更新频率不高的页面,没有必要每次都从数据库里读取数据,一来调用数据库,响应速度慢;二来增加数据库服务器的负荷,在大促时尤其明显;三,依赖过多的底层组件,如solr,mysql,redis,当出现单点故障的时候,会导致页面的可用性急剧下降。因此在上半年,我们对蘑菇街App端商品详情页做了页面静态化的改造,经过差不多半年的线上运行和大促的考验,效果比较明显,mobile_goods_pool集群的压力明显降低,商品详情页的平均RT由原来的200ms下降到现在的100ms不到,整体稳定性达到了99.99%。
在项目初期,我们选型了varnish作为缓存系统,但随着开发过程中引入了ESI,发现varnish对ESI的支持非常弱,比如不支持querystring和cookie的参数传递,而例如详情页上的一些实时促销信息需要传递iid到detail_dyn的请求中,这时varnish就不适用了。因此我们经过调研,选择了apache traffic server(简称ATS)作为我们最终的缓存系统,ATS是一款新兴的高性能、模块化的http代理和缓存系统,提供了对ESI full feature的支持,而且支持磁盘存储。
首先对于ESI的支持是刚需,另外支持磁盘存储对我们而言也是非常适配的特性,大家都知道varnish是基于内存的,假设活跃商品的量级足够大的时候,内存不可能放下所有的商品数据,当需要做数据的分布式存储时,会涉及到proxy路由规则改动,数据垂直切片,服务器水平扩容,服务器容灾等等问题,运维和开发的成本也会上升不少,ATS支持磁盘存储,因此我们在架构上选择了单机全量缓存的方案,用第一层nginx反向代理做负载均衡和健康检测,并且对现有架构的几乎没有侵入,运维成本的降低保证了项目的快速上线。
cache很重要的一点是保证数据一致性,我们做了Get method的缓存,但是当数据库中的数据被更新时,ATS如何感知到,并且刷新数据是很重要的。因此在项目中我们开发了一个失效中心的java application来做这件事。
不能在业务代码做update,delete操作的时候嵌入一行代码,首先,这样对业务的倾入性太大,成本太高,其次对业务系统性能也会有影响。因此我们采用异步监听mysql binlog的方案来感知数据变化,将失效中心的数据源接入Pigeon系统(蘑菇街内部使用的mysql binlog订阅系统)中。
当需要移除ATS cache中某个object时,ATS提供了自定义的http请求:Purge。Purge基于Http协议,性能非常好。
curl -X PURGE -H 'Host: mogujie.com' -v "http://www.mogujie.com/nmapi/goods/v9/goods/detail?iid= 17q45cw&itemInfoId= 17q45cw"
* About to connect() to localhost port 80 (#0)
* Trying 127.0.0.1...
* connected
* Connected to localhost (127.0.0.1) port 80 (#0)
> PURGE /nmapi/goods/v9/goods/detail?iid=17q45cw&itemInfoId=17q45cw HTTP/1.1
> User-Agent: curl/7.28.1
> Host: mogujie.com
> Accept: */*
>
< HTTP/1.1 200 Ok
< Date: Mon, 27 Jul 2015 09:43:07 GMT
< Connection: keep-alive。
当下次浏览器访问这个商品时,ATS将不会命中,请求会透传到web容器进行回源。
静态化的方案部署上线之后,detail的命中率达到80%左右,商品详情页接口的访问深度缩短,系统响应速度得到了很大提升。对于ATS这样的工具来说,它的作用是将第一次动态请求的页面缓存起来,在过期时间内,后续的访问请求返回的都是缓存里的数据,不再向后端服务器发起请求,适用场景是一些高PV,且更新频率不高的页面。