`

用户访问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

分享到:
评论

相关推荐

    关于APP清理缓存

    首先,我们需要了解APP缓存的工作机制。当一个APP首次打开或访问某个网页时,它会下载并存储一些关键信息,如图片、HTML代码、JavaScript等,这些就是缓存。下次再次访问时,APP可以直接从本地缓存读取数据,而无需...

    iOS app 清除缓存

    在iOS应用开发中,缓存是必不可少的一部分,它能够提高应用程序的性能,减少网络请求,为用户提供更快的体验。然而,随着应用的使用,缓存可能会积累过多,占用设备存储空间,甚至可能导致应用运行缓慢。因此,理解...

    zynq用户APP直接访问物理地址示例

    Zynq设计允许用户应用程序(APP)直接访问物理地址,这在进行Linux驱动程序开发时非常有用,特别是在需要与硬件接口直接交互或者实现高性能计算任务时。本示例将深入探讨如何在Zynq Linux环境下,让用户APP直接访问...

    Android13 14系统 app获取第三方应用缓存的方法

    在Android 13和14系统中,获取第三方应用的缓存数据对于开发者来说是一项具有挑战性的任务。这是因为Android系统为了保护用户隐私和安全...在开发过程中,务必谨慎处理数据,尊重用户隐私,确保应用的合法性与安全性。

    IE缓存提取工具

    IE浏览器的缓存通常位于用户个人文件夹的“AppData\Local\Microsoft\Windows\Temporary Internet Files”目录下,其中包含多个子文件夹,每个子文件夹代表一个网站的访问记录,内部有HTML文件、图像和其他多媒体...

    VB 读取IE缓存(查看缓存内容)

    通过VB代码,我们可以访问这些缓存文件,了解用户的浏览历史或进行数据分析。 首先,我们需要了解IE缓存的结构。IE缓存通常位于用户的个人文件夹下,例如在Windows系统中,路径可能是`C:\Users\[用户名]\AppData\...

    缓存提取器

    【缓存提取器】是一款专为了解决用户在浏览网页时遇到无法下载的视频、音乐等问题而设计的工具。它能够帮助用户从浏览器的缓存中提取出这些媒体文件,以便进行保存和离线欣赏。在互联网上,许多在线媒体内容由于版权...

    谷歌浏览器缓存路径修改

    Chrome浏览器默认将缓存文件存储在用户的个人资料目录下,通常位于Windows系统的`%USERPROFILE%\AppData\Local\Google\Chrome\User Data\Default\Cache`路径下。这个位置对于普通用户来说可能不太直观,尤其是当需要...

    数据缓存案例

    如果不存在,系统会从原始数据源获取数据,将其放入缓存并返回给用户,这个过程称为缓存未命中或缓存失效。之后的请求就可以快速地从缓存中获取数据,提高了服务的响应速度。 在“app-mem-nosql”这个标签中,我们...

    tomcat缓存

    根据应用的访问特性,调整缓存策略,比如设置合理的缓存大小、过期时间,或者使用LRU(Least Recently Used)等淘汰算法,以适应不同的场景需求。 #### 3.3 使用CDN 对于静态资源,可以考虑使用内容分发网络(CDN...

    苹果cms影视_APIcloud混合原生APP源码_四端同步 支持选集+缓存+下载

    1. 安全性:源码需要进行安全优化,防止未授权访问和恶意攻击,保护用户数据和服务器资源。 2. 性能优化:对于视频加载和播放,应考虑网络条件,优化数据传输和解码过程,确保视频流畅播放。 3. 用户体验:设计简洁...

    ( 来电了App

    此外,考虑到移动设备的特性,来电了App可能具有离线模式,允许用户在没有网络连接时也能访问部分已缓存的内容。同时,为了节省电量,可能还实现了智能后台管理,自动优化应用的运行状态。 总之,来电了App通过整合...

    JS判断是否安装APP

    在现代Web应用中,开发者经常需要引导用户在访问网页时打开已安装的应用或者引导他们下载应用。这个过程可以通过JavaScript实现,特别是在"JS判断是否安装APP"的场景下。以下是一些关于如何使用JavaScript来检测用户...

    常见APP唤起错误集锦

    文件内容中的`paths`字段应当与你的scheme(如quezhanxz)相对应,这样当用户访问匹配的URL时,设备会尝试打开相应的APP。 需要注意的是,apple-app-site-association文件的修改并不会立即生效,通常需要重新安装...

    清理应哟缓存的方式(包括清理系统应用的缓存)

    在现代的计算机操作系统中,应用程序在运行过程中会产生大量的缓存数据,这些缓存可以提高程序启动速度和运行效率,但随着时间的推移,过多的缓存可能会占用大量存储空间,影响系统的性能。本文将详细讲解如何清理...

    chrome缓存清除扩展程序,通过js清除chrome缓存的api演示

    Chrome浏览器作为一个广泛使用的网络浏览工具,其缓存...这个扩展可以帮助开发者在调试过程中快速清空缓存,也可以供用户根据需要清理个人数据。同时,`1.html`文件可以作为交互界面,让用户更直观地触发缓存清除操作。

    图片缓存清理.zip

    在Windows操作系统中,图片缓存通常包含在系统文件夹的各个部分,例如用户的“AppData”文件夹下的“LocalLow”,“Local”和“Roaming”子目录,以及浏览器的缓存文件夹。这些缓存文件可能包括已打开、预览或下载的...

    AppData文件夹转移工具

    这款工具能够安全地将AppData内容移动到其他磁盘,如D盘或E盘,同时确保应用程序仍能正常访问这些数据。迁移过程需要注意以下几点: 1. **备份数据**:在进行任何重大系统更改之前,都应该先备份重要数据,以防意外...

    IE缓存文件提取器

    在互联网浏览过程中,浏览器会自动下载并存储网页的静态资源,如图片、JavaScript文件和CSS样式表等,以便于快速加载已经访问过的页面,这就是浏览器缓存机制。其中,IE(Internet Explorer)作为曾经广泛使用的...

    一键清IE缓存工具 自动批处理

    IE缓存的主要目的是为了加快网页加载速度,当用户再次访问同一网页时,浏览器可以直接从本地缓存中读取数据,而无需重新从服务器下载,从而提高浏览效率。 **为什么要清理IE缓存** 尽管IE缓存有助于提升浏览体验,...

Global site tag (gtag.js) - Google Analytics