点赞
评论
收藏
分享
举报
nginx源码分析之缓存设计
发表于2020-08-31 14:35

浏览 1.9k

nginx一向以高性能著称,缓存是其中一大利器,本文将分享nginx中各个级别缓存的原理和内部是如何实现的。


0.什么是缓存

缓存存在计算机中的各个领域,其目的都是为了提升处理速度,可以说是以空间换取了时间。但事实并不是这么简单,因为引入了缓存,就要处理一致性、过期处理、缓存策略和命中率等。例如CPU有几级缓存,memcached作为内存缓存服务器,redis也是,还有文件缓存服务器,CDN等。总而言之,访问目标后‘备份’到某中间层,下次再访问时,直接从中间层获取,加快了访问速度。所以目标越往后,处理速度越慢,程序要做的就是尽量命中在前面,不要让访问穿透到后面目标。


1.nginx缓存

nginx属于web应用服务器,是典型的request/response模式。整体而言,nginx的缓存可以总结为3个:304响应、静态级别缓存、动态级别缓存。下面一一介绍:

*304响应

这个其实不算真正意义上的缓存,之所以提出来,是因为它的重要性,也因为涉及客户端的缓存。当客户端访问某URL时,如果服务器响应304状态码,其意义是告诉客户端您访问的东西没有变化过,内容从自己本地获取。浏览器就是典型的应用。当你第一次访问某静态资源时,响应200,当按F5时,会响应304。实现原理是nginx根据头部信息if_modified_since与静态资源的时间戳比较,如果一样,则返回304。做web开发应该清楚这个状态码。

*静态级别缓存

location /test {

    open_file_cache  max=100;

}

这个级别的缓存是缓存静态资源文件的句柄。nginx处理一个请求时,会打开一个文件句柄,然后在请求处理结束后关闭它。加入open_file_cache后,就会将文件句柄保存在工作进程中(不是共享内存),只要工作进程存在,当后面的请求访问同样的文件时,就不用重新打开文件。这样的好处是节省了文件打开关闭的开销。

*动态级别缓存

http {

    ...

    fastcgi_cache_path  /tmp/abc keys_zone=c1:10M;

    server {

        location ~ \.php$ {

            ...

            fastcgi_cache  c1;

            fastcgi_cache_valid  200 30s;

        }

    }

}

这个级别的缓存是缓存动态请求的内容。当客户端访问某动态文件时(http://example.com/test.php),nginx将请求重新封装成fastcgi协议,然后转发给php-fpm处理,当php-fpm正常响应后,nginx将响应内容保存到某目录的文件里。下次同样的请求过来,就直接从缓存目录的文件中读取,比第一次快且省事很多。

聪明的你可能已经想到动态级别缓存可以再加上静态级别缓存,没错的。这3种响应是独立的,可以灵活配合使用。现在您已经明白缓存的原理,接下来看下如何实现的

2.304响应

ngx_http_not_modified_filter_module处理了这个功能,属于http request header filter模块。源码非常简单。

static ngx_int_t ngx_http_not_modified_header_filter(ngx_http_request_t *r)

{

    ...

    if (r->headers_in.if_modified_since) {


        if (r->headers_in.if_modified_since

            && ngx_http_test_if_modified(r))

        {

            return ngx_http_next_header_filter(r);

        }


        /* not modified */

        r->headers_out.status = NGX_HTTP_NOT_MODIFIED;

        return ngx_http_next_header_filter(r);

    }


    return ngx_http_next_header_filter(r);

}


static ngx_uint_t ngx_http_test_if_modified(ngx_http_request_t *r)

{

    ...


    if (r->headers_out.last_modified_time == (time_t) -1) {

        return 1;

    }


    clcf = ngx_http_get_module_loc_conf(r, ngx_http_core_module);


    if (clcf->if_modified_since == NGX_HTTP_IMS_OFF) {

    ims = ngx_parse_http_time(r->headers_in.if_modified_since->value.data,

                              r->headers_in.if_modified_since->value.len);


    if (ims == r->headers_out.last_modified_time) {

        return 0;

    }

    if (clcf->if_modified_since == NGX_HTTP_IMS_EXACT

        || ims < r->headers_out.last_modified_time)

    {

        return 1;

    }

    return 0;

}


3.静态级别缓存

typedef struct {

    ngx_rbtree_t             rbtree;

    ngx_rbtree_node_t        sentinel;

    ngx_queue_t              expire_queue;

    ngx_uint_t               current;

    ngx_uint_t               max;

    time_t                   inactive;

} ngx_open_file_cache_t;


记得这个只在工作进程中处理,不涉及共享内存。看的出来ngx_open_file_cache_t是一个红黑树,也是一个队列。每个文件的文件(根据完整文件名)对应一个节点。这样就完整的维护了所有的可能要缓存的文件。又因为缓存需要处理过期,所以最近访问的文件会保持在expire_queue队列的前面,每次访问将从队列中删除,然后移到最前面。整个逻辑封装在一个函数里:

ngx_int_t ngx_open_cached_file(ngx_open_file_cache_t *cache, ngx_str_t *name, ngx_open_file_info_t *of, ngx_pool_t *pool) { ... }


4.动态级别缓存

ngx_http_file_cache_t {

    ngx_http_file_cache_sh_t {

        tree

        queue

        ...

    }

}

ngx_http_cache_t {

    keys

    key

    ngx_http_file_cache_node_t

}

如果简化点处理过程就是:根据url算出md5,然后将动态请求响应内容存储到文件名为某md5的文件里。

http://example.com/a.php -> d41d8cd98f00b204e9800998ecf8427e -> /tmp/abc/d41d8cd98f00b204e9800998ecf8427e

http://example.com/b.php -> d41d8cd98f00b204e9800998ecf8432d -> /tmp/abc/d41d8cd98f00b204e9800998ecf8432d

http://example.com/c.php -> d41d8cd98f00b204e9800998ecf84a32 -> /tmp/abc/d41d8cd98f00b204e9800998ecf84a32

因为文件存储在硬盘里,所以这里用到共享内存,而不是工作进程的内存中,来保存这种对应关系。

我们可以将每个请求(r)当作各个缓存节点,对应ngx_http_cache_t。ngx_http_file_cache_t就是缓存的集合。明白这个关系后,我们解释下整个流程是如何从开始到结束的。

a)根据fastcgi_cache_path的keys_zone,nginx创建了多个 ngx_http_file_cache_t。根据fastcgi_cache指定了某个location将用到哪个ngx_http_file_cache_t。

b)为每个请求创建一个ngx_http_cache_t(在ngx_http_file_cache_new函数里),然后继续产生一个key,作为本节点的索引。你可以将请求、请求响应内容、内容存储文件都当作同一个东西,最终都是节点,对应ngx_http_cache_t。所以需要有       key这个东西。

c)处理缓存目录和文件名

d)nginx处理动态请求是在ngx_event_pipe里的,当需要缓存里,pipe会有个temp_path将响应内容存储到这个文件里

e)最后将pipe的temp_path重命名成前面产生的文件名,结束了。


5.小结

动态请求的缓存处理代码还是有点复杂的,有兴趣的同学研究下其如何实现很有好处,可以体会下nginx的代码精致程度。如一惯风格,写作主要以白话方式分享原理,细节点到即止,细节上有疑问欢迎交流。

已修改于2023-03-09 02:05
创作不易,留下一份鼓励
洪志道

暂无个人介绍

关注



写下您的评论
发表评论
全部评论(0)

按点赞数排序

按时间排序

关于作者
洪志道
这家伙很懒还未留下介绍~
11
文章
0
问答
54
粉丝
相关文章
上一篇文章介绍了HTTP请求匹配server{}配置块的过程,接着请求会继续匹配location{}配置块,并最终决定哪些指令及Nginx模块处理请求。本文将介绍location的匹配规则,以及rewrite指令与location匹配顺序的关系。 生产环境中的nginx.conf往往含有上百条location,这是因为Nginx常常身兼多职:充当提供静态资源CDN、作为负载均衡为分布式集群提供扩展性、作为APIgateway提供接口服务等等。location一旦配置错误,Nginx上巨大的并发连接数会将错误放大上万倍,很容易导致严重的线上事故。 而location也很容易配置错误,它既支持前缀匹配,也支持正则表达式匹配,当二者同时出现时,为了获得更高的性能,Nginx设计了复杂的location匹配优先级。这是因为前缀匹配是对静态的location多叉树检索完成的,它的性能要比正则表达式高得多,唯有搞清楚具体的匹配流程,我们才能设计出匹配速度更快的location。 而且rewrite指令修改URL的功能也让location匹配变得更为复杂。特别是r
点赞 12
浏览 2k
本文已收录在前端食堂同名仓库Github github.com/Geekhyt,欢迎光临食堂,如果觉得酒菜还算可口,赏个Star对食堂老板来说是莫大的鼓励。历史背景互联网的全球化导致了互联网的数据量快速增长,加上在本世纪初摩尔定律在单核CPU上的失效,CPU朝着多核方向发展,而Apache显然并没有做好多核架构的准备,它的一个进程同一时间只能处理一个连接,处理完一个请求后才能处理下一个,这无疑不能应对如今互联网上海量的用户。况且进程间切换的成本是非常高的。在这种背景下,Nginx应运而生,可以轻松处理数百万、上千万的连接。Nginx优势高并发高性能可扩展性好高可靠性热部署开源许可证Nginx主要应用场景静态资源服务,通过本地文件系统提供服务反向代理服务、负载均衡API服务、权限控制,减少应用服务器压力Nginx配置文件和目录通过 rpm-qlnginx 可以查看Nginx安装的配置文件和目录。如图是我在某某云上安装的最新稳定版本的Nginx的配置文件及目录。/etc/nginx/nginx.conf核心配置文件/etc/ng
点赞 2
浏览 756
TableofContentsNameSynopsisDescriptionDirectivesxss_getxss_callback_argxss_override_statusxss_check_statusxss_input_typesLimitationsTroubleShootingInstallationCompatibilityTODOAuthorCopyright&LicenseSeeAlsoSynopsis#accessing/foo?callback=processgivestheresponse #body"process(...);"(withoutquotes)where"..." #istheoriginalresponsebodyofthe/foolocation. server{ location/foo{ #yourcontenthandlergoeshere... xss_geton; xss_callbac
点赞 1
浏览 1.3k