ti共享内存技术_nginx共享内存机制详解

本文深入探讨了nginx的共享内存机制,它是实现高性能的关键。文章详细解释了如何使用proxy_cache_path指令配置共享内存,包括参数意义,如levels、keys_zone、max_size等。接着,介绍了共享内存的工作原理,包括初始化、管理、加载和使用流程,特别是cache manager和cache loader进程的角色。最后,通过源码分析解析proxy_cache_path指令的过程和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. 工作原理

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

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

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

从上面的流程图中可以看出,在主流程中,主要进行了解析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) == 0) {

s.len = value[i].len - 9;

s.data = value[i].data + 9;

// 对时间进行解析,最终将转换为以秒为单位的时间长度 inactive = ngx_parse_time(&s, 1);

if (inactive == (time_t) NGX_ERROR) {

ngx_conf_log_error(NGX_LOG_EMERG, cf, 0,

"invalid inactive value\"%V\"", &value[i]);

return NGX_CONF_ERROR;

}

continue;

}

// 如果参数是以"max_size="开头,

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值