nginx 指向一个文件夹_nginx共享内存机制详解

本文详细介绍了nginx如何使用共享内存进行文件缓存,重点关注proxy_cache_path指令的解析和缓存管理。共享内存通过LRU算法进行管理,包括cache manager和cache loader进程的启动与工作原理,以及它们如何处理缓存数据。
摘要由CSDN通过智能技术生成

nginx的共享内存,是其能够实现高性能的主要原因之一,而其主要是用于对文件的缓存。本文首先会讲解共享内存的使用方式,然后会讲解nginx是如何实现共享内存的管理的。

1. 使用示例

nginx声明共享内存的指令为:

proxy_cache_path /Users/Mike/nginx-cache levels=1:2 keys_zone=one:10m max_size=10g inactive=60m use_temp_path=off;

这里只是声明的一个名称为one,最大可用内存为10g的共享内存。这里面各个参数的含义如下:

  • /Users/Mike/nginx-cache:这是一个路径参数,指定了将共享内存所缓存的文件的存储位置。这里为什么会生成文件的原因在于,对于上游服务发出的响应,是可以将其生成一个文件存储在nginx上的,后续如果有同样的请求,就可以直接读取该文件或者读取共享内存中的缓存以响应客户端;
  • levels:在linux操作系统中,如果所有文件都放在一个文件夹中,那么当文件数量非常多的时候,可能一个磁盘驱动就无法读取这么多文件了,如果放置在多个文件夹中,那么就能够利用多个驱动并且读取的优点。这里的levels参数指定的就是如何生成文件夹。假设nginx为上游服务的某个响应数据生成的文件名为e0bd86606797639426a92306b1b98ad9,那么对于上面的levels=1:2,其就会从文件名的最后开始取值,先取1位(也即9)作为一级子目录名,然后取2位(也即ad)作为二级子目录名;
  • keys_zone:该参数指定了当前共享内存的名称,这里为one,后面的10m表示当前共享内存用于存储key的内存大小为10m;
  • max_size:该参数指定了当前共享内存可用的最大内存;
  • inactive:该参数指定了当前共享内存的最长存活时间,如果在这段时间内都没有任何请求访问该内存数据,那么其就会被LRU算法淘汰掉;
  • use_temp_path:该参数指定了是否先将生成的文件放入临时文件夹,后续再移动到指定文件夹下;

2. 工作原理

共享内存的管理工作主要分为如下图所示的几个部分:

4b011fad5ab7ce26b96d29d8876f8941.png

可以看到,其主要分为初始化、共享内存的管理、共享内存的加载和共享内存的使用等几个方面。在初始化的过程中,首先会解析proxy_cache_path指令,然后分别启动cache manager和cache loader进程;这里cache manager进程主要是进行共享内存的管理的,其主要是通过LRU算法清除过期数据,或者当资源紧张时强制删除部分未被引用的内存数据;而cache loader进程的主要工作是在nginx启动之后,读取文件存储目录中已有的文件,将其加载到共享内存中;而共享内存的使用主要是在处理请求完成之后对响应数据的缓存,这一部分的内容将在后面的文章中进行讲解,本文主要讲解前面三部分的工作原理。

按照上面的划分,共享内存的管理主要可以分为三个部分(共享内存的使用将在后面进行讲解)。如下是这三个部分的处理流程的示意图:

811080b1e97c85baee0404901b13943f.png

从上面的流程图中可以看出,在主流程中,主要进行了解析proxy_cache_path指令、启动cache manager进程和启动cache loader进程的工作。而在cache manager进程中,主要工作则分为两部分:1. 检查队列尾部元素是否过期,如果过期并且引用数为0,则删除该元素和该元素对应的文件;2. 检查当前共享内存是否资源紧张,如果资源紧张,则删除所有引用数为0的元素及其文件,无论其是否过期。在cache loader进程的处理流程中,主要是通过递归的方式遍历存储文件的目录及其子目录中的文件,然后将这些文件加载到共享内存中。需要注意的是,cache manager进程在每次遍历完所有的共享内存块之后会进入下一次循环,而cache loader进程在nginx启动之后60s的时刻执行一次,然后就会退出该进程。

3. 源码解读

3.1 proxy_cache_path指令解析

对于nginx各个指令的解析,其都会在相应的模块中定义一个ngx_command_t结构体,该结构体中有一个set方法指定了解析当前指令所使用的方法。如下是proxy_cache_path所对应的ngx_command_t结构体的定义:

static ngx_command_t  ngx_http_proxy_commands[] = {
    
  {
     ngx_string("proxy_cache_path"), // 指定了当前指令的名称
   // 指定了当前指令的使用位置,即http模块,并且指定了当前模块的参数个数,这里是必须大于等于2
      NGX_HTTP_MAIN_CONF|NGX_CONF_2MORE,
   // 指定了set()方法所指向的方法
      ngx_http_file_cache_set_slot,
      NGX_HTTP_MAIN_CONF_OFFSET,
      offsetof(ngx_http_proxy_main_conf_t, caches),
      &ngx_http_proxy_module }
}

可以看到,该指令所使用的解析方法是ngx_http_file_cache_set_slot(),这里我们直接阅读该方法的源码:

char *ngx_http_file_cache_set_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
    
    char  *confp = conf;

    off_t                   max_size;
    u_char                 *last, *p;
    time_t                  inactive;
    ssize_t                 size;
    ngx_str_t               s, name, *value;
    ngx_int_t               loader_files, manager_files;
    ngx_msec_t              loader_sleep, manager_sleep, loader_threshold,
                            manager_threshold;
    ngx_uint_t              i, n, use_temp_path;
    ngx_array_t            *caches;
    ngx_http_file_cache_t  *cache, **ce;

    cache = ngx_pcalloc(cf->pool, sizeof(ngx_http_file_cache_t));
    if (cache == NULL) {
    
        return NGX_CONF_ERROR;
    }

    cache->path = ngx_pcalloc(cf->pool, sizeof(ngx_path_t));
    if (cache->path == NULL) {
    
        return NGX_CONF_ERROR;
    }

    // 初始化各个属性的默认值
    use_temp_path = 1;

    inactive = 600;

    loader_files = 100;
    loader_sleep = 50;
    loader_threshold = 200;

    manager_files = 100;
    manager_sleep = 50;
    manager_threshold = 200;

    name.len = 0;
    size = 0;
    max_size = NGX_MAX_OFF_T_VALUE;

    // 示例配置:proxy_cache_path /Users/Mike/nginx-cache levels=1:2 keys_zone=one:10m max_size=10g inactive=60m use_temp_path=off;

    // 这里的cf->args->elts中存储了解析proxy_cache_path指令时,其包含的各个token项,
    // 所谓的token项,指的就是使用空格分隔的字符片段
    value = cf->args->elts;

    // value[1]就是配置的第一个参数,也即cache文件会保存的根路径
    cache->path->name = value[1];

    if (cache->path->name.data[cache->path->name.len - 1] == '/') {
    
        cache->path->name.len--;
    }

    if (ngx_conf_full_name(cf->cycle, &cache->path->name, 0) != NGX_OK) {
    
        return NGX_CONF_ERROR;
    }

    // 从第三个参数开始进行解析
    for (i = 2; i < cf->args->nelts; i++) {
    

        // 如果第三个参数是以"levels="开头,则解析levels子参数
        if (ngx_strncmp(value[i].data, "levels=", 7) == 0) {
    

            p = value[i].data + 7;  // 计算开始解析的其实位置
            last = value[i].data + value[i].len;    // 计算最后一个字符的位置

            // 开始解析1:2
            for (n = 0; n < NGX_MAX_PATH_LEVEL && p < last; n++) {
    

                if (*p > '0' && *p < '3') {
    

                    // 获取当前的参数值,比如需要解析的1和2
                    cache->path->level[n] = *p++ - '0';
                    cache->path->len += cache->path->level[n] + 1;

                    if (p == last) {
    
                        break;
                    }

                    // 如果当前字符是冒号,则继续下一个字符的解析;
                    // 这里的NGX_MAX_PATH_LEVEL值为3,也就是说levels参数后最多接3级子目录
                    if (*p++ == ':' && n < NGX_MAX_PATH_LEVEL - 1 && p < last) {
    
                        continue;
                    }

                    goto invalid_levels;
                }

                goto invalid_levels;
            }

            if (cache->path->len < 10 + NGX_MAX_PATH_LEVEL) {
    
                continue;
            }

        invalid_levels:

            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                               "invalid "levels" "%V"", &value[i]);
            return NGX_CONF_ERROR;
        }

        // 如果当前的参数是以"use_temp_path="开头,则解析use_temp_path参数,该参数值为on或者off,
        // 表示当前缓存文件是否首先存入临时文件夹中,最后再写入到目标文件夹中,如果为off则直接存入目标文件夹
        if (ngx_strncmp(value[i].data, "use_temp_path=", 14) == 0) {
    

            // 如果为on,则标记use_temp_path为1
            if (ngx_strcmp(&value[i].data[14], "on") == 0) {
    
                use_temp_path = 1;

                // 如果为off,则标记use_temp_path为0
            } else if (ngx_strcmp(&value[i].data[14], "off") == 0) {
    
                use_temp_path = 0;

                // 如果都不止,则返回解析异常
            } else {
    
                ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                                   "invalid use_temp_path value "%V", "
                                   "it must be "on" or "off"",
                                   &value[i]);
                return NGX_CONF_ERROR;
            }

            continue;
        }

        // 如果参数是以"keys_zone="开头,则解析keys_zone参数。该参数的形式如keys_zone=one:10m,
        // 这里的one是一个名称,以供给后续的location配置使用,而10m则是一个大小,
        // 表示供给存储key的缓存大小
        if (ngx_strncmp(value[i].data, "keys_zone=", 10) == 0) {
    

            name.data = value[i].data + 10;

            p = (u_char *) ngx_strchr(name.data, ':');

            if (p) {
    
                // 计算name的长度,name记录了当前的缓存区的名称,也即这里的one
                name.len = p - name.data;

                p++;

                // 解析所指定的size大小
                s.len = value[i].data + value[i].len - p;
                s.data = p;

                // 对大小进行解析,会将指定的大小最终转换为字节数,这里的字节数必须大于8191
                size = ngx_parse_size(&s);
                if (size > 8191) {
    
                    continue;
                }
            }

            ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,
                               "invalid keys zone size "%V"", &value[i]);
            return NGX_CONF_ERROR;
        }

        // 如果参数是以"inactive="开头,则解析inactive参数。该参数的形式如inactive=60m,
        // 表示缓存的文件在多长时间没有访问之后将会过期
        if (ngx_strncmp(value[i].data, "inactive=", 9)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值