nginx的cookie缓存问题及配置文件研究

 其实我很早就在考虑这个问题,nginx既然能缓存,为什么用户和用户间的缓存不会串呢?

直到OpenCDN的用户反馈上来存在用户和用户间的缓存互串问题,我才去研究。

首先,要注意的一点,nginx默认的缓存是不会考虑到cookie的,只根据URI,从配置文件的这条就一目了然了。

proxy_cache_key "$host$uri$is_args$args";

用于存储的key是用根据URL然后进行散列化生成的,而这个key和cookie无关。 这种配置下,两个cookie不同的用户缓存的key是一样的。于是我们就会想,应该如何让缓存和cookie相关呢?

1.proxy_cache_key加入cookie

然后去nginx的wiki上了这么一段配置文件

proxy_cache_key "$host$request_uri$cookie_user";

这个配置文件还存在一定的猫腻,这个cookie_user我一开始就简单的是用户的cookie,后来我才发现,我太天真了。原来user是字段名,这个指的是cookie中key为user那段对应的value。天呐,我怎么会知道一个CDN用户的网站的cookie中有哪些cookie字段? 所以,这个$cookie_xxx变量是必须要事先知道cookie的字段的,而$http_ccokie则不是,它里面存放的是完整的cookie值,而不是其中一个。

proxy_cache_key "$host$request_uri$http_cookie";


那么。是不是配置文件这样写就OK了呢?很好地让cookie成为了影响key的变量,这样两个cookie不用的用户缓存就不同了。其实,这才是麻烦的开始。

1.一旦采用这种方式来存放缓存,会把整个网站内的所有缓存全部割裂,包括图片(图片也携带cookie),一张图片100用户就因为cookie的不同要存放100份。

2.一些捣蛋份子会影响cookie,比如google分析,google分析会在网站中加入cookie来方便他追踪用户的访问记录。而浏览器在发包的时候,这个cookie也被携带着发给了nginx。于是一个静态站点也可能因为站点统计的原因,变成一个像携带着cookie的动态站,并且如果采用上面这条配置,所以的静态文件都会因为cookie而被割裂N份。

当然,似乎google统计还是为我们考虑过一些的,遵循一个自己YY的小规范:所有的key全部用下划线开头,以示区别。一开始我还以为用这种方式可以将cookie进行清理,只留下网站中真正影响登录状态的cookie,但是百度统计的BAIDUID的出现让我发现这只是一种美好的幻想,唉。而且还有第1点在,让整个cookie缓存方案陷入僵局。

小结:

一般情况下不推荐用cookie作为cache key进行存储,代价太大,简直让CDN服务器成了浏览器的后花园。而我们要做的是公园!!

不过,如果一个站点如果已经把图片、css这类的静态资源和主站分离,倒可以考虑用下这种方法。


2.通过mime类型判断是否缓存

好,让我们继续探索之旅,那么对于存在cookie的,到底要如何来做呢?贝奇哥给了我一种方案,所有的mime类型为image、text/css等等的这些静态文件,可以进行忽略cookie的缓存,而其他的全部回源。虽然我对于这种方案并不是很满意,但是还是去试了一下。结果不试不知道,一试却发现好多东西。

首先我写了这样一段来进行判断。

set $a 1;
if ($upstream_http_content_type ~* "image/"){
	set $a 0;
}
proxy_cache_bypass $a;


结果这段配置整整调了一个下午还是没有生效。后来发现$upstream_http_content_type居然是空的,百思不得其解。后来google groups上有tengine的文景大牛做了很言简意赅的解释,恍然大悟。

Nginx有请求的各个处理阶段,依次执行 (http://tengine.taobao.org/book/chapter_02.html#id10):


Rewrite
Access
Content
Log


if对应的是Rewrite阶段。$upstream_xxx这个变量是在Content执行阶段拿到后端响应的内容才可以使用,所以它在 Rewrite阶段是拿不到变量的,一般只能在记录访问日志的Log阶段才能用得到。


唉,我又天真了一次,我在rewrite阶段 YY Content阶段才有的东西,这种判断的写法是不行的。但是真的没有办法了吗?我们现在要解决的问题是如何在content阶段执行判断,而if是不能依靠。我找啊找啊,居然还真的被我找到了一个可以用的东西,他叫map,直接上春哥写的文档的片段 http://openresty.org/download/agentzh-nginx-tutorials-zhcn.html


在上面的例子中,我们还应当注意到 map 指令是在 server 配置块之外,也就是在最外围的 http 配置块中定义的。很多读者可能会对此感到奇怪,毕竟我们只是在 location /test 中用到了它。这倒不是因为我们不想把 map 语句直接挪到 location 配置块中,而是因为 map 指令只能在 http 块中使用!

很多 Nginx 新手都会担心如此“全局”范围的 map 设置会让访问所有虚拟主机的所有 location 接口的请求都执行一遍变量值的映射计算,然而事实并非如此。前面我们已经了解到 map 配置指令的工作原理是为用户变量注册 “取处理程序”,并且实际的映射计算是在“取处理程序”中完成的,而“取处理程序”只有在该用户变量被实际读取时才会执行(当然,因为缓存的存在,只在请求生命期中的第一次读取中才被执行),所以对于那些根本没有用到相关变量的请求来说,就根本不会执行任何的无用计算。

这种只在实际使用对象时才计算对象值的技术,在计算领域被称为“惰性求值”(lazy evaluation)。提供“惰性求值” 语义的编程语言并不多见,最经典的例子便是 Haskell. 与之相对的便是“主动求值” (eager evaluation)。我们有幸在 Nginx 中也看到了“惰性求值”的例子,但“主动求值”语义其实在 Nginx 里面更为常见。


于是,我们可以先写一个map在外面,然后利用惰性求值的特性再让他在content阶段才进行执行,同时由于map的缓存特性,在第一次求值完就可以固化住,我们也很容易验证map的判断效果,只要在配置文件里面加一条add_header把它打印出来就可以了。

http{
	...
	map $upstream_http_content_type $a{
		default			"1";
		"^*image/"		"0";
	}
}

location {
	...
	proxy_cache_bypass $a;
}

本来以为这样配置就搞定了,但是这次研究真心一点都不顺利啊。$upstream_http_content_type居然还是空的!这个时候我已经很淡定了,对这个空值也不惊慌,继续找文章。果然,找到了。http://nginx.2469901.n2.nabble.com/Cache-by-mime-type-td7580925.html

原来要使用proxy_no_cache来替代proxy_cache_bypass,那么试试吧。居然真的奏效了!

http{
	...
	map $upstream_http_content_type $a{
		default			"1";
		"^*image/"		"0";
	}
}

location {
	...
	proxy_no_cache $a;
}

然后我开始思考,为什么两个功能几乎一样的函数却不一样,唯一的猜想是,一个函数(proxy_cache_bypass)是反代前执行的,一个函数(proxy_no_cache)是反代后执行的,只有反代后才有$upstream_xxx变量,而并不是简单地在content阶段都有。但是让人郁闷的是,在nginx执行的时候会出警告,说proxy_no_cache功能被改变,建议和proxy_cache_bypass一起执行,但是如果两个一起执行的话,由于nginx惰性求值的缓存特性,必然是proxy_cache_bypass先被执行,然后被固化到了一个空值,然后proxy_no_cache再执行也是空值了,这个还待请教大牛。

小结:

通过map的惰性求值,使得content阶段也可以执行判断,解决问题。

关键点:必须要这个函数是在upstream后执行才能有效,如果这个函数在upstream前执行的话,那么一定是无解的。

大哭我才不会告诉你我本来想用mime类型来控制cache key的,在mime类型是image的时候,cache key不加cookie,而除此之外,cache key加入cookie。于是最要命的一点是prxoy_cache_key这个函数是upstream前执行的(因为毕竟要查缓存是否命中么)。然后这个逻辑就变成了颠倒了因果的逻辑:只有发出包才能获得mime类型,而如果不用cache_key来查缓存就发不出包。


3.奥卡姆剃刀

但是如果是只缓存image等类型的静态文件,这样的话cache-control和expires不就几乎没有用了吗?这个方案还是无法满足我。于是我继续探寻。这个时候才发现,似乎已经无路可走。要么做后花园(方案1),要么做大公园(方案2),根据cache-control等header头等来缓存的自助公园呢?(我自己杜撰的名词,不过大家应该可以理解我的意思吧)。

于是开始思路逆行,什么情况下才需要这么纠结于cookie呢?

有用户登录的情况。那么有用户登录的网站又有几类呢?

1.直接用现成的整站方案(Discuz!等)。2.找人或者自己开发的。

如果是第一类网站,需要我们担心吗?不需要!这种网站稳健地很!各种header头一应俱全,不该缓存的页面nginx也是绝对不会缓存的。

如果是第二类网站,需要我们担心吗?需要。那么要那担心什么呢?担心header头不标准吗?不担心!因为这类网站常常什么header都没有,反倒也不会被缓存。那么哪些是需要我们担心的?那些自己做的网站,又输出了错误了的header头,比如在私人页面上面输出缓存标记的。这样的网站多吗?不多!那么既然网站是你写的,解铃还须系铃人。自己乖乖地到CDN上来配你的缓存规则!

那么,还有什么cookie的缓存问题呢?没有了!

那我配置文件需要做什么改动吗?不用做任何改动!直接按照默认的来就行!

那你还让我读到这里,不会一开始就告诉我啊?发火  

好吧。。我错了。。。




评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值