设为首页 收藏本站
查看: 1108|回复: 0

[经验分享] Nginx Proxy Cache分析

[复制链接]

尚未签到

发表于 2018-11-13 06:06:58 | 显示全部楼层 |阅读模式
  本文从几个部分来详细介绍Nginx的proxy cache功能。第一部分,主要介绍proxy cache的过期、空间管理等。第二部分,主要介绍在Nginx(作为反向代理服务器)收到请求之后,如何检查本地的缓存来确定是否要向后端服务器发起请求。第三部分,主要介绍Nginx向后端服务器发起请求并收到回复的情况下,如何把响应回复缓存到本地。
  第一部分
  在 Nginx中,如果启用了proxy cache功能,master process会在启动的时候启动管理缓存的两个子进程(区别于处理请求的子进程)来管理内存和磁盘的缓存个体。第一个进程的功能是定期检查缓存,并将过 期的缓存删除;第二个进程的作用是在启动的时候将磁盘中已经缓存的个体映射到内存中(目前Nginx设定为启动以后60秒),然后退出。
  具体的,在 这两个进程的ngx_process_events_and_timers()函数中,会调用ngx_event_expire_timers()。 Nginx的ngx_event_timer_rbtree(红黑树)里面按照执行的时间的先后存放着一系列的事件。每次取执行时间最早的事件,如果当前 时间已经到了应该执行该事件,就会调用事件的handler。两个进程的handler分别是 ngx_cache_manager_process_handler和ngx_cache_loader_process_handler。因为 ngx_process_events_and_timers()是被循环调用的,所以上面两个handler也会被调用多次。
  1. ngx_cache_manager_process_handler
  这个函数调用了每个磁盘缓存路径对应的manager()函数,即ngx_http_file_cache_manager()函数。这个函数很简单,就是检查缓存队列,看里面的索引信息有没有过期,如果过期,就把缓存的文件从磁盘删掉,并把索引从内存中释放。
C代码   

  • static time_t ngx_http_file_cache_manager(void *data)
  • {

  •     ngx_http_file_cache_t  *cache = data;
  •     //先删过期的缓存
  •     next = ngx_http_file_cache_expire(cache);

  •      for ( ;; ) {
  •         //获取最更新的缓存空间的大小
  •         ngx_shmtx_lock(&cache->shpool->mutex);
  •         size = cache->sh->size;
  •         ngx_shmtx_unlock(&cache->shpool->mutex);

  •         //如果空间在指定范围内,不用再删了。return
  •         //...

  •         //如果size超过磁盘的使用空间,即size >= cache->max_size
  •         //强制把部分缓存删除,以保证缓存使用的空间在指定范围内
  •         next = ngx_http_file_cache_forced_expire(cache);

  •         //休息一下以后继续删
  •         //...
  •     }
  • }
  2. ngx_cache_loader_process_handler
  这个函数跟 ngx_cache_manager_process_handler差不多,不过在里面调用了对应路径的loader()函数,即 ngx_http_file_cache_loader()。这个函数的作用是给磁盘缓存路径下面子路径下面的所有缓存文件建立内存索引,并根据文件名得 到key来插入红黑树,并放入过期删除队列。
C代码   

  • static void ngx_http_file_cache_loader(void *data)
  • {
  •     ngx_http_file_cache_t  *cache = data;
  •     ngx_tree_ctx_t  tree;

  •     //进入loading状态
  •     //...

  •     //初始化tree
  •     tree.init_handler = NULL;
  •     tree.file_handler = ngx_http_file_cache_manage_file;
  •     tree.pre_tree_handler = ngx_http_file_cache_noop;
  •     tree.post_tree_handler = ngx_http_file_cache_noop;
  •     tree.spec_handler = ngx_http_file_cache_delete_file;
  •     tree.data = cache;
  •     tree.alloc = 0;
  •     tree.log = ngx_cycle->log;

  •     //ngx_walk_tree是递归函数,打开每层路径 (dir)直到每个文件(file),根据其路径和文件名得到key,在缓存的rbtree(红黑树)里面找这个key(部分),如果没有找到的话,就在 内存中分配一个映射这个文件的node(但是不会把文件的内容进行缓存),然后插入到红黑树中和加入队列。
  •     //ctx->file_handler=>
  •     //ngx_http_file_cache_manage_file=>
  •     //ngx_http_file_cache_add_file=>
  •     //ngx_http_file_cache_add
  •     //从n = ngx_read_file(...)函数可以看出,每个磁盘缓存文件的开头的sizeof(ngx_http_file_cache_header_t)个byte存放了跟缓存相关的信息
  •     ngx_walk_tree(&tree, &cache->path->name);

  •     //...
  • }
    第二部分
  在http 的请求发送给有proxy_cache和proxy_pass定义的location的时候,会调用到 ngx_http_proxy_handler()。其中在决定是否要向后端发送请求的时候,先检查一下本地是否有缓存一个copy。具体的位置是在 ngx_http_upstream_init_request()函数的一开始,在发起向后的连接之前。
C代码   

  • static void ngx_http_upstream_init_request(ngx_http_request_t *r)
  • {
  •     //在"proxy_cache"的set()即ngx_http_proxy_cache()中设置
  •     ////就是plcf->upstream.cache
  •     //这儿cache是ngx_shm_zone_t类型的
  •     if (u->conf->cache) {
  •         //寻找缓存
  •         //如果返回NGX_DECLINED就是说缓存中没有,请向后端发送请求
  •         rc = ngx_http_upstream_cache(r, u);
  •         if (rc == NGX_BUSY) {
  •             //重新试一次
  •             r->write_event_handler = ngx_http_upstream_init_request;
  •             return;
  •         }
  •         r->write_event_handler = ngx_http_request_empty_handler;
  •         if (rc == NGX_DONE) {
  •             //在缓存中找到了
  •             return;
  •         }

  •         if (rc != NGX_DECLINED) {
  •             ngx_http_finalize_request(r, rc);
  •             return;
  •         }
  •     }

  •     //向后端发起请求
  •     //...
  • }
  
  
  第三部分
  在Nginx收到后端服务器的响应之后,会把这个响应发回给用户。而如果缓存功能启用的话,Nginx就会把响应存入磁盘里。
  
C代码   

  • static void ngx_http_upstream_send_response(ngx_http_request_t *r, ngx_http_upstream_t *u)
  • {
  •     rc = ngx_http_send_header(r);

  •     //...

  •     #if (NGX_HTTP_CACHE)
  •     //是否要缓存,即proxy_no_cache指令
  •     //...

  •     //如果要缓存,设置有效时间等
  •     //...
  •     #endif

  •     //...

  •     //ngx_http_upstream_process_upstream ==> 1. ngx_event_pipe ==>
  •     //ngx_event_pipe_read_upstream ==> ngx_event_pipe_write_chain_to_temp_file ==>
  •     //ngx_write_chain_to_temp_file ==> ngx_create_temp_file --> ngx_write_chain_to_file
  •     //==>2. ngx_http_upstream_process_request ==> ngx_http_file_cache_update
  •     //在这儿把从后端获取的文件写到磁盘
  •     ngx_http_upstream_process_upstream(r, u);
  • }
  
  附:proxy cache的配置、set()函数和初始化函数
  在Nginx配置文件里面设置proxy_cache_path指令,指定缓存内存的大小和在disk文件系统的路径。如:
Nginx config代码   

  • #...

  • http {

  •     #...

  •     proxy_cache_path /var/proxy_cache_dir levels=1:2 keys_zone=cache_one:500m max_size=30g;

  •     server {
  •         #...
  •         location / {
  •             proxy_cache cache_one;
  •             #...
  •         }
  •         #...
  •     }

  •     #...
  • }
  在解析配置文件的时候,"proxy_cache_path"指令的set()函数是ngx_http_file_cache_set_slot()。
  
C代码   

  • char * ngx_http_file_cache_set_slot(...)
  • {
  •     //分配内存给cache结构
  •     ngx_http_file_cache_t  *cache;
  •     cache = ngx_pcalloc(cf->pool, sizeof(ngx_http_file_cache_t));
  •     cache->path = ngx_pcalloc(cf->pool, sizeof(ngx_path_t));

  •     //获取缓存在disk文件系统的路径
  •     value = cf->args->elts;
  •     cache->path->name = value[1];//第一个arg

  •     //去掉path最后的"/"
  •     //如果path不是以"/"开头,即是一个相对路径,就在前面加上$nginx_home的路径
  •     //...

  •     //解析其他的参数
  •     for(;;){
  •         //"level"
  •         //设置cache->path->level,cache->path->len

  •         //"keys_zone"
  •         //设置name.data和name.len,即缓存的名称

  •         //"inactive"
  •         //设置缓存驻留时间

  •         //"max_size"
  •         //设置硬盘大小
  •     }

  •     cache->path->manager = ngx_http_file_cache_manager;
  •     cache->path->loader = ngx_http_file_cache_loader;
  •     cache->path->data = cache;

  •     //检查cache->path是否已经存在
  •     //如果不存在,则添加到cf->cycle->pathes
  •     ngx_add_path(cf, &cache->path);

  •     //name是缓存区名称,size是内存缓存大小
  •     //遍历cf->cycle->shared_memory的part链表寻找是否已经存在同名的内存空间。如果不存在,就分配一个大小为ngx_shm_zone_t的内存单元,放在part链表最后一个单元的elts。
  •     //shared_memory为ngx_list_t结构,part为ngx_list_part_s结构
  •     cache->shm_zone = ngx_shared_memory_add(cf, &name, size, cmd->post);

  •     //注册回调函数等
  •     cache->shm_zone->init = ngx_http_file_cache_init;
  •     cache->shm_zone->data = cache;
  •     cache->inactive = inactive;
  •     cache->max_size = max_size;
  • }
  
  在cycle初始化的时候,会初始化内存索引的空间
  
C代码   

  • ngx_cycle_t * ngx_init_cycle(...)
  • {
  •     //...

  •     part = &cycle->shared_memory.part;
  •     shm_zone = part->elts;

  •     //遍历链表中每个ngx_list_part_t的每个单元
  •     for(;;){

  •         //检查当前ngx_list_part_t是否已经遍历完成。若是,则跳到下个ngx_list_part_t
  •         //...

  •         //检查每个单元,即ngx_shm_zone_t的shm的size是否为0
  •         //检查shm的init函数是否有注册,如果没有就说明这个shm没有被用到
  •         //...

  •         shm_zone.shm.log = cycle->log;
  •         //old_cycle的shared_memory
  •         opart = &old_cycle->shared_memory.part;
  •         oshm_zone = opart->elts;

  •         //遍历old_cycle的shared_memory每个ngx_list_part_t的每个单元
  •         for(;;){
  •             //检查当前ngx_list_part_t的所有单元是否已经遍历完成。若是,则跳到下个ngx_list_part_t
  •             //...

  •             //检查是否同名,不同名则跳到下一个单元
  •             //...

  •             //找到同名且shm的size和addr匹配
  •             //调用init函数,并跳到下一个new cycle的shm_zone
  •             shm_zone.init(&shm_zone, oshm_zone[n].data);

  •             //同名但size和addr不匹配,说明old_cycle的oshm_zone无效,把它释放
  •             ngx_shm_free(&oshm_zone[n].shm);
  •             break;
  •         }

  •         //在old_cycle没有找到跟新cycle的shm_zone匹配的shm
  •         //分配共享内存给&shm_zone.shm,用mmap或者shmget/shmat
  •         ngx_shm_alloc(&shm_zone.shm);

  •         //把 &shm_zone.shm初始化一个ngx_slab_pool_t结构的变量sp
  •         //对sp成员赋值,调用ngx_shmtx_create()给sp->mutex初始化锁
  •         //ngx_slab_init(),对分配的共享内存进行初始化
  •         ngx_init_zone_pool(cycle, &shm_zone);
  •         //ngx_http_file_cache_init()
  •         shm_zone.init(&shm_zone, NULL);
  •     }
  • }
  
  
C代码   

  • ngx_http_file_cache_init(ngx_shm_zone_t *shm_zone, void *data)
  • {
  •     ngx_http_file_cache_t  *ocache = data;
  •     cache = shm_zone->data;
  •     //如果ocache不是NULL,即有old cache,就比较缓存路径和level等,如果match的话就继承ocache的sh、shpool、bsize等
  •     //...

  •     //如果没有old cache
  •     cache->shpool = (ngx_slab_pool_t *) shm_zone->shm.addr;
  •     //ngx_slab_alloc_locked()
  •     cache->sh = ngx_slab_alloc(cache->shpool, sizeof(ngx_http_file_cache_sh_t));

  •     cache->shpool->data = cache->sh;
  •     //初始化红黑树
  •     //把rbtree的insert函数设为ngx_http_file_cache_rbtree_insert_value()
  •     ngx_rbtree_init(&cache->sh->rbtree, &cache->sh->sentinel, ngx_http_file_cache_rbtree_insert_value);
  •     //初始化一个queue
  •     ngx_queue_init(&cache->sh->queue);
  •     cache->sh->cold = 1;
  •     cache->sh->loading = 0;
  •     cache->sh->size = 0;
  •     //获取文件系统的block size
  •     cache->bsize = ngx_fs_bsize(cache->path->name.data);
  •     //max_size成了总共的block数量
  •     cache->max_size /= cache->bsize;
  •     len = sizeof(" in cache keys zone \"\"") + shm_zone->shm.name.len;
  •     cache->shpool->log_ctx = ngx_slab_alloc(cache->shpool, len);
  • }
  
  索引缓存文件ngx_http_file_cache_lookup
DSC0000.png

  文件缓存格式
DSC0001.png

  
  
  缓存过期队列
DSC0002.png

  


运维网声明 1、欢迎大家加入本站运维交流群:群②:261659950 群⑤:202807635 群⑦870801961 群⑧679858003
2、本站所有主题由该帖子作者发表,该帖子作者与运维网享有帖子相关版权
3、所有作品的著作权均归原作者享有,请您和我们一样尊重他人的著作权等合法权益。如果您对作品感到满意,请购买正版
4、禁止制作、复制、发布和传播具有反动、淫秽、色情、暴力、凶杀等内容的信息,一经发现立即删除。若您因此触犯法律,一切后果自负,我们对此不承担任何责任
5、所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其内容的准确性、可靠性、正当性、安全性、合法性等负责,亦不承担任何法律责任
6、所有作品仅供您个人学习、研究或欣赏,不得用于商业或者其他用途,否则,一切后果均由您自己承担,我们对此不承担任何法律责任
7、如涉及侵犯版权等问题,请您及时通知我们,我们将立即采取措施予以解决
8、联系人Email:admin@iyunv.com 网址:www.yunweiku.com

所有资源均系网友上传或者通过网络收集,我们仅提供一个展示、介绍、观摩学习的平台,我们不对其承担任何法律责任,如涉及侵犯版权等问题,请您及时通知我们,我们将立即处理,联系人Email:kefu@iyunv.com,QQ:1061981298 本贴地址:https://www.iyunv.com/thread-634221-1-1.html 上篇帖子: nginx 源码安装 下篇帖子: Nginx为什么比Apache Httpd高效
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

扫码加入运维网微信交流群X

扫码加入运维网微信交流群

扫描二维码加入运维网微信交流群,最新一手资源尽在官方微信交流群!快快加入我们吧...

扫描微信二维码查看详情

客服E-mail:kefu@iyunv.com 客服QQ:1061981298


QQ群⑦:运维网交流群⑦ QQ群⑧:运维网交流群⑧ k8s群:运维网kubernetes交流群


提醒:禁止发布任何违反国家法律、法规的言论与图片等内容;本站内容均来自个人观点与网络等信息,非本站认同之观点.


本站大部分资源是网友从网上搜集分享而来,其版权均归原作者及其网站所有,我们尊重他人的合法权益,如有内容侵犯您的合法权益,请及时与我们联系进行核实删除!



合作伙伴: 青云cloud

快速回复 返回顶部 返回列表