`

用户访问app缓存的过程

阅读更多

       在web项目中,大家都已经非常熟悉其架构流程了。都说Cache是万金油,哪里不舒服抹哪里。这些流程中,几乎每个环节都会进行cache。从浏览器到webserver,到cgi程序,到DB数据库,会进行浏览器cache,数据cache,SQL查询的cache等等。对于fastcgi这里的cache,很少被使用。去年年底,我对nginx的fastcgi_cache进行摸索使用。在我的测试过程中,发现一些WIKI以及网络上没被提到的注意点,这里分享一下。

Nginx模块fastcgi_cache的几个注意点
这里是我的NGinx配置信息

#增加调试信息
add_header X-Cache-CFC "$upstream_cache_status - $upstream_response_time";
fastcgi_temp_path /dev/shm/nginx_tmp;
 
#cache设置
fastcgi_cache_path   /dev/shm/nginx_cache  levels=1:2 keys_zone=cfcache:10m inactive=50m;
fastcgi_cache_key "$request_method://$host$request_uri";
fastcgi_cache_methods GET HEAD;
fastcgi_cache   cfcache;
fastcgi_cache_valid   any 1d;
fastcgi_cache_min_uses  1;
fastcgi_cache_use_stale error  timeout invalid_header http_500;
fastcgi_ignore_client_abort on;

 

配置这些参数时,注意每个参数的作用域,像fastcgi_cache_path参数,只能在http配置项里配置,而fastcgi_cache_min_uses这个参数,可以在http、server、location三个配置项里配置。这样更灵活的会每个域名、每个匹配的location进行选择性cache了。具体的参数作用域,参考FASTCGI模块的官方WIKI。我为了调试方便,添加了一个X-Cache-CFC的http响应头,$upstream_cache_status 变量表示此请求响应来自cache的状态,分别为:

  1. ISS 未命中
  2. EXPIRED – expired, request was passed to backend Cache已过期
  3. UPDATING – expired, stale response was used due to proxy/fastcgi_cache_use_stale updating Cache已过期,(被其他nginx子进程)更新中
  4. STALE – expired, stale response was used due to proxy/fastcgi_cache_use_stale Cache已过期,响应数据不合法,被污染
  5. HIT 命中cache

Nginx模块fastcgi_cache的几个注意点

程序代码是Discuz!论坛, 随便开启测试了几下,发现/dev/shm/nginx_cache/下没有任何目录建立,也没有文件创建。调试的http header响应头里的X-Cache-CFC 结果一直是MISS。从服务器进程上来看,Nginx cache manager process 跟Nginx cache loader process 进程也正常运行:

root 3100 1 0 14:52 ? 00:00:00 nginx: master process /usr/sbin/nginx
www-data 3101 3100 0 14:52 ? 00:00:00 nginx: worker process
www-data 3102 3100 0 14:52 ? 00:00:00 nginx: cache manager process
www-data 3103 3100 0 14:52 ? 00:00:00 nginx: cache loader process

不知道为何会这样,为何没有cache成功,我以为我配置参数有问题,只好阅读WIKI。发现fastcgi_ignore_headers 参数下解释有这么一段

fastcgi_ignore_headers
Syntax: fastcgi_ignore_headers field …
Default:
Context: http
server
location
Reference: fastcgi_ignore_headers

This directive forbids processing of the named headers from the FastCGI-server reply. It is possible to specify headers like “X-Accel-Redirect”, “X-Accel-Expires”, “Expires” or “Cache-Control”.

也就是说这个参数的值,将会被忽略掉,同样被忽略掉的响应头比如X-Accel-Redirect, X-Accel-Expires, Expires or Cache-Control,而nginx配置中并没有fastcgi_ignore_headers参数的设定,那么问题会不会出现在FASTCGI响应结果里包含了类似X-Accel-Redirect, X-Accel-Expires, Expires or Cache-Control这几个响应头呢?用strace抓包,看了下nginx与fpm进程通讯的数据

####为了确保准确抓到处理该http请求的进程,我把nginx 、fpm都只开启了一个进程处理。
//strace -ff -tt -s 1000 -o xxx.log -p PHPFPM-PID
14:52:07.837334 write(3, "\1\6\0\1\0\343\5\0X-Powered-By: PHP/5.3.10-1ubuntu3.5\r\nExpires: Thu, 19 Nov 1981 08:52:00 GMT\r\nCache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0\r\nPragma: no-cache\r\nContent-type: text/html\r\n\r\nHello cfc4n1362034327\0\0\0\0\0\1\3\0\1\0\10\0\0\0\0\0\0\0\0\0\0", 256) = 256
 
//strace -ff -tt -s 1000 -o xxx.log -p Nginx-PID
15:05:13.265663 recvfrom(12, "\1\6\0\1\0\343\5\0X-Powered-By: PHP/5.3.10-1ubuntu3.5\r\nExpires: Thu, 19 Nov 1981 08:52:00 GMT\r\nCache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0\r\nPragma: no-cache\r\nContent-type: text/html\r\n\r\nHello cfc4n1362035113\0\0\0\0\0\1\3\0\1\0\10\0\0\0\0\0\0\0\0\0\0", 4023, 0, NULL, NULL) = 256

从抓取的数据包里可以看到,fpm确实返回了包含ExpiresCache-Control头的http 响应头信息。那么疑问来了:

  1. nginx的fastcgi_cache没缓存这条http响应,是因为响应头里包含“Expires”、“Cache-Control”的原因吗?
  2. 程序里并没有输出“Expires”、“Cache-Control” http header的代码,这是谁输出的呢?
  3. 既然是fpm响应的时候,就已经有了,那么是php的core模块,还是其他拓展模块输出的?
  4. “Expires:”时间为何是“Thu, 19 Nov 1981 08:52:00 GMT”?

疑问比较多,一个一个查起,先从Nginx的fastcgi_cache没缓存这条http响应查起。我根据测试环境nginx版本1.1.9(ubuntu 12.04默认的),到nginx官方下了对应版本的源码,搜索了fastcgi参数使用的地方,在http\ngx_http_upstream.c找到了。虽然不能很流程的读懂nginx的代码,但粗略的了解,根据了解的情况加以猜测,再动手测试实验,也得出了结论,确定了nginx的fastcgi_cache的规则。

//ngx_http_upstream.c
//line 3136  当fastcgi响应包含set-cookie时,不缓存
static ngx_int_t
ngx_http_upstream_process_set_cookie(ngx_http_request_t *r, ngx_table_elt_t *h,
    ngx_uint_t offset)
{
#if (NGX_HTTP_CACHE)
    ngx_http_upstream_t  *u;
 
    u = r->upstream;
 
    if (!(u->conf->ignore_headers & NGX_HTTP_UPSTREAM_IGN_SET_COOKIE)) {
        u->cacheable = 0;
    }
#endif
 
    return NGX_OK;
}
 
//line 3242 当响应头包含Expires时,如果过期时间大于当前服务器时间,则nginx_cache会缓存该响应,否则,则不缓存
static ngx_int_t
ngx_http_upstream_process_expires(ngx_http_request_t *r, ngx_table_elt_t *h,
    ngx_uint_t offset)
{
    ngx_http_upstream_t  *u;
 
    u = r->upstream;
    u->headers_in.expires = h;
 
#if (NGX_HTTP_CACHE)
    {
    time_t  expires;
 
    if (u->conf->ignore_headers & NGX_HTTP_UPSTREAM_IGN_EXPIRES) {
        return NGX_OK;
    }
 
    if (r->cache == NULL) {
        return NGX_OK;
    }
 
    if (r->cache->valid_sec != 0) {
        return NGX_OK;
    }
 
    expires = ngx_http_parse_time(h->value.data, h->value.len);
 
    if (expires == NGX_ERROR || expires < ngx_time()) {         u->cacheable = 0;
        return NGX_OK;
    }
 
    r->cache->valid_sec = expires;
    }
#endif
 
    return NGX_OK;
}
 
//line 3199  当响应头包含Cache-Control时,#####如果####这里有如果啊。。。
//【注意】如果Cache-Control参数值为no-cache、no-store、private中任意一个时,则不缓存...不缓存...
//【注意】如果Cache-Control参数值为max-age时,会被缓存,且nginx设置的cache的过期时间,就是系统当前时间 + mag-age的值
    if (ngx_strlcasestrn(p, last, (u_char *) "no-cache", 8 - 1) != NULL
        || ngx_strlcasestrn(p, last, (u_char *) "no-store", 8 - 1) != NULL
        || ngx_strlcasestrn(p, last, (u_char *) "private", 7 - 1) != NULL)
    {
        u->cacheable = 0;
        return NGX_OK;
    }
 
    p = ngx_strlcasestrn(p, last, (u_char *) "max-age=", 8 - 1);
 
    if (p == NULL) {
        return NGX_OK;
    }
    ...
    r->cache->valid_sec = ngx_time() + n;

也就是说,fastcgi响应http请求的结果中,响应头包括ExpiresCache-ControlSet-Cookie三个,都会可能不被cache,但不只有这些,别忘了nginx配置中fastcgi_ignore_headers参数设定的部分。以及ngxin的X-ACCEL X-Accel-RedirectX-Accel-ExpiresX-Accel-CharsetX-Accel-Buffering等nginx自定义的响应头。由于这几个不常用,我也没深入研究。通过对nginx的ngx_http_upstream模块代码模糊理解,加猜测,以及写了脚本测试验证,可以得到结论是正确的。即Nginx fastcgi_cache在缓存后端fastcgi响应时,当响应里包含set-cookie时,不缓存;当响应头包含Expires时,如果过期时间大于当前服务器时间,则nginx_cache会缓存该响应,否则,则不缓存;当响应头包含Cache-Control时,如果Cache-Control参数值为no-cacheno-storeprivate中任意一个时,则不缓存,如果Cache-Control参数值为max-age时,会被缓存,且nginx设置的cache的过期时间,就是系统当前时间 + mag-age的值。

Nginx模块fastcgi_cache的几个注意点
nginx fastcgi_cache 响应expired
Nginx模块fastcgi_cache的几个注意点
nginx fastcgi_cache hit命中
Nginx模块fastcgi_cache的几个注意点
FASTCGI_CACHE $upstream_cache_status 结果为miss,一次也没命中。

//逐个测试,测试时,注释其他的
header("Expires: ".gmdate("D, d M Y H:i:s", time()+10000).' GMT');
header("Expires: ".gmdate("D, d M Y H:i:s", time()-99999).' GMT');
header("X-Accel-Expires:30");
header("Cache-Control: no-cache");
header("Cache-Control: no-store");
header("Cache-Control: private");
header("Cache-Control: max-age=10");
setcookie('cfc4n',"testaaaa");
echo 'Hello cfc4n',time();

到了这里,疑问1解决了。那么疑问2、3呢?程序里并没有输出ExpiresCache-Control http header的代码,这是谁输出的呢?既然是fpm响应的时候,就已经有了,那么是php的core模块,还是其他拓展模块输出的?我精简了代码,只输出一个“hello world”,发现也确实被缓存了。显然,php脚本程序中并没输出http header 的ExpiresCache-Control,多次测试,最终定位到session_start函数,翻阅源码找到了这些代码:

//ext/session/session.c  line:1190 左右
// ...
CACHE_LIMITER_FUNC(private) /* {{{ */
{
    ADD_HEADER("Expires: Thu, 19 Nov 1981 08:52:00 GMT");
    CACHE_LIMITER(private_no_expire)(TSRMLS_C);
}
/* }}} */
//再到这里3 或者上面几个 ##默认是nocache
CACHE_LIMITER_FUNC(nocache) /* {{{ */
{
    ADD_HEADER("Expires: Thu, 19 Nov 1981 08:52:00 GMT");
 
    /* For HTTP/1.1 conforming clients and the rest (MSIE 5) */
    ADD_HEADER("Cache-Control: no-store, no-cache, must-revalidate, post-check=0, pre-check=0");
 
    /* For HTTP/1.0 conforming clients */
    ADD_HEADER("Pragma: no-cache");
}
/* }}} */
//这里2
static php_session_cache_limiter_t php_session_cache_limiters[] = {
    CACHE_LIMITER_ENTRY(public)
    CACHE_LIMITER_ENTRY(private)
    CACHE_LIMITER_ENTRY(private_no_expire)
    CACHE_LIMITER_ENTRY(nocache)
    {0}
};
 
static int php_session_cache_limiter(TSRMLS_D) /* {{{ */
{
    php_session_cache_limiter_t *lim;
 
    if (PS(cache_limiter)[0] == '\0') return 0;
 
    if (SG(headers_sent)) {
        const char *output_start_filename = php_output_get_start_filename(TSRMLS_C);
        int output_start_lineno = php_output_get_start_lineno(TSRMLS_C);
 
        if (output_start_filename) {
            php_error_docref(NULL TSRMLS_CC, E_WARNING, "Cannot send session cache limiter - headers already sent (output started at %s:%d)", output_start_filename, output_start_lineno);
        } else {
            php_error_docref(NULL TSRMLS_CC, E_WARNING, "Cannot send session cache limiter - headers already sent");
        }
        return -2;
    }
 
    for (lim = php_session_cache_limiters; lim->name; lim++) {
        if (!strcasecmp(lim->name, PS(cache_limiter))) {
            lim->func(TSRMLS_C);   //这里1
            return 0;
        }
    }
 
    return -1;
}
// ...

到了这里,知道原因了,是程序调用session_start时,php的session拓展自己输出的。session.cache_limit参数来决定输出包含哪种Expires的header,默认是nocache,修改php.ini的session.cache_limit参数为“none”即可让session模块不再输出这些http 响应头。或在调用session_start之前,使用session_cache_limiter函数来指定下该参数值。那为什么要在使用session时,发ExpiresCache-Control的http response header呢?我猜测了下,需要session时,基本上是用户跟服务器有交互,那么,既然有交互,就意味着用户的每次交互结果也可能不一样,就不能cache这个请求的结果,给返回给这个用户。同时,每个用户的交互结果都是不一样的,nginx也就不能把包含特殊Cache-Control的个人响应cache给其他人提供了。

还有一个无聊的问题“Expires:时间为何是Thu, 19 Nov 1981 08:52:00 GMT”?我翻阅了session.c这段代码的添加时间,版本,作者信息,在php官方版本库中找到了这次提交的信息

Revision 17092 – (view) (download) (as text) (annotate) – [select for diffs]
Modified Sun Dec 12 14:16:55 1999 UTC (13 years, 2 months ago) by sas
File length: 28327 byte(s)
Diff to previous 16964
Add cache_limiter and cache_expire options. Rename extern_referer_check
to referer_check.

对比session.c两个版本的变更,果然是这块代码。作者是sas,也就是Sascha Schumannhttp://php.net/credits.php里可以看到他的大名。关于这个expires过期时间的问题,有人在stackoverflow也提问过,Why is “Expires” 1981?,别人说那天是他生日。这是真的么?如果那天是他生日的话,而他增加session.cache_limiter时是1999年,他才17岁,17岁呀。我17岁时在干嘛?还不知道电脑长啥样,正在玩『超级玛丽』呢。

好奇的不是我一个人,还有个帖子是epoch date — Expires: Thu, 19 Nov 1981 08:52:00也问了。另外两个地址虽然没问,也有人提到那天是他生日了。http://boinc.berkeley.edu/dev/forum_thread.php?id=2514https://github.com/codeguy/Slim/issues/157,这些帖子都提到说原帖是http://www.phpbuilder.com/lists/php3-list/199911/3159.php ,我无法访问,被跳转到首页了。用http://web.archive.org找到了历史快照,发现上下文关系不大,也不能证明是他生日。 我更是好奇的发了两封邮件到他的不同邮箱里问他,不过,目前他还没回复。或许他没收到、没看到,或许懒得回了。N年后,“Expires:时间为何是Thu, 19 Nov 1981 08:52:00 GMT”这个日期,会不会又成了一段奇闻佳话了呢?

 

转自:http://blog.hackroad.com/operations-engineer/linux_server/12916.html

分享到:
评论

相关推荐

    Java毕业设计-基于SpringBoot和Redis学校生活服务app平台(源码+数据库)

    在整个开发过程中,记得合理利用Redis来缓存一些频繁访问的数据,以提高系统的性能和响应速度。 听起来你计划开发一个基于SpringBoot和Redis的学校生活服务App平台,这是一个很棒的想法!SpringBoot是一个流行的...

    工程硕士学位论文 基于Android+HTML5的移动Web项目高效开发探究

    综上所述,“认我测”在线认证检测系统,率先填补了认证检测领域移动端的空缺,提供了Web浏览器+移动端的双端访问模式,给用户提供了多种访问途径,真正实现了用户和检测机构的随时随地在线下单检测。 关键词:...

    基于vuejs+koa2+mysql的高仿饿了么移动APP.zip

    MySQL提供了一系列安全措施,如用户账户管理、访问权限控制、SSL/TLS加密连接、审计日志等功能,确保数据的安全性和合规性。同时,MySQL附带了一系列管理工具,如MySQL Server、MySQL Workbench、MySQL Shell等,...

    Windows Server 2008系统管理视频教程csdn.txt

    8-7授予用户访问网络资源 关机 更改系统时间08:37 8-8拒绝本地登录03:06 8-9安全选项10:35 8-10软件限制策略08:56 8-11禁用自动播放和使用注册表编辑工具05:08 8-12禁止用户运行指定的程序02:07 8-13跟踪用户登录...

    基于ZigBee+ESP32+MQTT+EMQX+TomCat+Servlet接口+MySQL+安卓app的物联网课设.zip

    MySQL提供了一系列安全措施,如用户账户管理、访问权限控制、SSL/TLS加密连接、审计日志等功能,确保数据的安全性和合规性。同时,MySQL附带了一系列管理工具,如MySQL Server、MySQL Workbench、MySQL Shell等,...

    ASP.NET4高级程序设计第4版 带目录PDF 分卷压缩包 part1

    11.2.7 缓存用户配置 11.2.8 缓存配置 11.2.9 输出缓存扩展 11.3 数据缓存 11.3.1 向缓存添加项目 11.3.2 简单的缓存测试 11.3.3 缓存优先级 11.3.4 使用数据源控件的缓存 11.4 缓存依赖 11.4.1...

    ASP.NET4高级程序设计(第4版) 3/3

    11.2.7 缓存用户配置 367 11.2.8 缓存配置 368 11.2.9 输出缓存扩展 369 11.3 数据缓存 373 11.3.1 向缓存添加项目 373 11.3.2 简单的缓存测试 375 11.3.3 缓存优先级 376 11.3.4 使用数据源控件的...

    IIS6.0 IIS,互联网信息服务

    除了匿名访问用户(Anonymous)外,IIS中的FTP将使用Windows 2000自带的用户库(可在“开始→程序→管理工具→计算机管理”中找到“用户”一项来进行用户库的管理)。 最后,关键一步还有就是将你的电脑变为网络中的...

    ASP.NET2.0高级编程(第4版)1/6

    21.3.5 SQL存储过程的调试734 21.4 异常和错误处理735 21.4.1 处理页面上的异常735 21.4.2 处理应用程序异常736 21.4.3 HTTP状态码737 21.5 小结738 第22章 文件I/O和流739 22.1 使用驱动器、目录和文件739 22.1.1 ...

    ebsite for net4.0网站建设系统 v3.0 正式版.zip

    一般情况下压缩率高达80%,意味着在你带宽不足的情况下能大大提高访问速度,同时也能提高用户访问速度,js与css的合并能减少大量没有必要的iis请求,并且可以缓存输出,没有必要每次请求都进行压缩处理。 13.缩略图...

    智能客服 基于springboot+swaggger+elasticsearch+mysql+源代码+文档说明

    更详细的使用过程: 1.安装elasticsearch服务,安装es-ik插件(直接拷贝到plugin/ik目录下),安装es-head插件(需要安装nodejs,npm install后使用grunt server启动),启动es,启动es-head 2.安装mysql服务,(初始化...

    ASP.NET的网页代码模型及生命周期

    在客户端浏览器访问该页面时,浏览器会给IIS发送请求消息,IIS则会开始执行ASP.NET编译过程,如果不存在编译过后的DLL文件,则加载编译的类并创建对象。当创建对象完成,生成创建对象后的代码并生成一个ASPX页面代码...

    自定义统计SDKTcStatInterface.zip

    APP常规数据统计本节主要介绍如何设置数据上报策略,如何记录页面访问行为和用户自定义事件。本节也介绍了通过集成测试模块验证SDK是否集成成功的方法。3.1. 数据上报策略统计服务SDK会先把数据记录缓存在本地,然后...

    All-In-One Toolbox Pro 8.1.5.5.8.apk

    *快速设置上的所有可用传感器:快速访问系统设置 *音量设置,QR和条形码扫描仪,手电筒,指南针...... - 为什么多功能一体机工具箱是您必备的工具应用程序? ♥功能强大:如此多的有用工具,如此紧凑的尺寸,增加了...

    彩虹cms v1.4.zip

    大家在安装的时候请直接输入www.******.com/install,如果开始输入了www.******.com显示过"Access denied for user 'admin'@'localhost' (using password: YES)"后,需要删除caihong_App\Temp中的缓存才能访问前台

    2.ASP.NET.2.0.高级编程(第4版) [1/7]

    5.11.1 允许用户选择多项 133 5.11.2 使用ListBox控件的例子 133 5.11.3 给集合添加条目 136 5.12 CheckBox服务器控件 136 5.12.1 确定复选框是否被选中 138 5.12.2 给复选框赋值 138 5.12.3 排列复选框的文本...

    新一代存储

    b、轻松设定企业网络访问规则,规范员工上网行为(如允许访问百度,禁止访问微博等) c、可为公司领导层提供专属的线路,在网络负荷时确保领导层工作 5)多媒体功能,提供培训和企业文化的平台 a、Video Station...

    ASP.NET 控件的使用

    9.5 使用SqlDataSource控件缓存数据库数据 291 9.6 小结 293 第10章 使用List控件 294 10.1 List控件概述 294 10.1.1 声明列表项 294 10.1.2 绑定到数据源 296 10.1.3 确定被选中的列表项 299 10.1.4 追加数据项 ...

    浩辰CAD2008标准版part1(1/2)

     HDI驱动有其灵活的挂接上也明显提高,HDI驱动没有繁索的安装过程,且所有的设置参数与原有的系统设置并存,可以进行预览,笔宽,线型,纸张等等参数的设置。  3、智能化打印优化驱动  浩辰ICAD2008i新加入...

Global site tag (gtag.js) - Google Analytics