一、配置
指令 | 含义 |
---|---|
proxy_cache_path | /data/nginx/tmp-test levels=1:2 keys_zone=tmp-test:100m inactive=7d max_size=1000g; |
proxy_cache_path | 缓存文件路径 |
levels | 设置缓存文件目录层次;levels=1:2 表示两级目录 |
keys_zone | 设置缓存名字和共享内存大小 |
inactive | 在指定时间内没人访问则被删除 |
max_size | 最大缓存空间,如果缓存空间满,默认覆盖掉缓存时间最长的资源。每一个proxy_cache_path对应一个ngx_http_file_cache_t结构体。 |
proxy_cache tmp-test | 使用名为tmp-test的缓存配置 |
proxy_cache_key $uri | 定义缓存唯一key,通过唯一key来进行hash存取 |
proxy_cache_methods | 设置缓存哪些HTTP方法 |
proxy_cache_min_uses | 指定请求至少被发送了多少次以上时才缓存,可以防止低频请求被缓存 |
proxy_cache_bypass | 如果指定的任何一个变量值不为空,或者不等于0,nginx就不会查找缓存,直接进行代理转发 |
proxy_cache_lock/proxy_cache_lock_timeout | 当多个客户端同时请求同一份内容时,如果开启proxy_cache_lock(默认off)则只有一个请求被发送至后端;其他请求将等待该内容返回;当第一个请求返回时,其他请求将从缓存中获取内容返回;当第一个请求超过了proxy_cache_lock_timeout超时时间(默认5s),则其他请求将同时请求到后端来获取响应,且响应不会被缓存;启用proxy_cache_lock可以应对雪崩效应。 |
二、数据结构和变量
-
节点红黑树(cache->sh->rbtree):核心数据结构,以key的md5值前四位作为索引形成红黑树,表示所有存在的资源节点。
-
节点超时队列(cache->sh->queue):LRU类型数据结构,manager进程定时触发,根据fcn->expire = now + cache->inactive判断节点是否超时,超时则对节点进行释放。
-
cache->watermark:当缓存节点分配失败时,将对cache->sh->watermark设置为当前节点总数的7/8,当manager进程触发后,将根据LRU算法对超过watermark数量的节点进行释放。
-
c->lock_timeout:通常情况下,当前请求的缓存不存在,而其它请求正在更新该缓存时,当前缓存启动c->wait_event定时器,定时时间为c->lock_timeout,等待其它请求完成缓存。
-
c->lock_age:当某请求执行节点缓存更新操作时,会记录fcn->lock_time = now+c->lock_age,在该事件范围内,其它对该资源访问的请求会被阻塞等待,而超过该事件范围后,后续对该资源节点的请求将直接溯源并缓存。
-
c->stale_updating/c->stale_error:首先需要说明HTTP响应中stale-while-revalidate和stale-if-error的含义,当用户访问的资源过期时(超过cache-control:max-age),为了避免过高响应延迟,如果当前时间仍在max-age + stale-while-revalidate范围内,那么代理将返回给用户过期资源,同时后台发起资源更新。stale-if-error的含义也是类似,只是对于过期资源,将优先溯源,在溯源失败的情况下,如果当前时间仍在max-age + stale-if-error范围内,那么允许返回给用户过期资源。即使服务端响应中不携带这两种字段,通过使能nginx配置项proxy_cache_use_stale也可以起到同样的效果。
-
fcn->valid_sec:当响应到来时,将根据响应头的cache-control:max-age、proxy_cache_valid配置等信息对资源的有效期进行设置,当资源失效后,通常需对资源进行溯源更新。
三、请求处理
-
ngx_http_upstream_cache
/*根据proxy_cache,查找全局的缓存配置proxy_cache_path,找到相应配置*/ rc = ngx_http_upstream_cache_get(r, u, &cache); /*创建ngx_http_cache_t结构体(r->cache)*/ if (ngx_http_file_cache_new(r) != NGX_OK) { return NGX_ERROR; } /*实际调用ngx_http_proxy_create_key函数,创建key值(r->cache->keys)*/ if (u->create_key(r) != NGX_OK) { return NGX_ERROR; } /*根据r->cache->keys的值,生成16字节长的crc32值(r->cache->crc32)和md5值(r->cache->key和r->cache->main)*/ ngx_http_file_cache_create_key(r); /*查询proxy_cache_bypass的配置,确认该请求是否不从缓存获取而是从后端获取*/ switch (ngx_http_test_predicates(r, u->conf->cache_bypass)) rc = ngx_http_file_cache_open(r); /* 缓存已过期(valid_sec)情况下,可能返回NGX_HTTP_CACHE_STALE或NGX_HTTP_CACHE_UPDATING。 如果返回NGX_HTTP_CACHE_STALE,表示需要本请求去后端更新。但在cache_use_stale为updating和cache_background_update使能的情况下,允许发送过期缓存(返回NGX_OK),同时创建子请求用于缓存更新。 如果返回NGX_HTTP_CACHE_UPDATING,表示已经有请求去后端更新,但在cache_use_stale为updating情况下,允许发送过期缓存(返回NGX_OK)。*/ switch (rc){ }
-
ngx_http_file_cache_open
/*判断是否已经存在缓存*/ rc = ngx_http_file_cache_exists(cache, c); /*生成缓存文件路径:/path/levels/key*/ ngx_http_file_cache_name(r, cache->path) /* 1. 节点不存在。如果load进程已经加载过缓存,则不需要再读取,否则需要尝试读取缓存文件 2. 节点已存在。如果缓存文件不存在(如cache_lock情况),则不需要 读取。*/ if (!test) { goto done; } /*缓存文件存在的流程*/ return ngx_http_file_cache_read(r, c); /*缓存文件不存在的流程*/ /*1. 对于未向后端请求(updating)或后端请求超时(lock_age)的节点,返回NGX_DECLINED,表示重新发起请求并缓存。 2.否则,使能lock_timeout定时器,返回NGX_AGAIN,处理结束,等待节点可用并获取缓存文件。*/ done: return ngx_http_file_cache_lock(r, c);
-
ngx_http_file_cache_exists
/*加锁*/ ngx_shmtx_lock(&cache->shpool->mutex); /*根据请求生成r->cache->key值,从红黑树查找相应节点*/ fcn = c->node; if (fcn == NULL) { fcn = ngx_http_file_cache_lookup(cache, c->key); } if (fcn) { /*代表着cache文件已经存在,可以直接获取,否则返回NGX_AGAIN, 上层返回NGX_HTTP_CACHE_SCARCE,表示该节点的请求次数未达到min_uses, 不被cache,需从后端获取。*/ if (fcn->exists || fcn->uses >= c->min_uses) { c->exists = fcn->exists; if (fcn->body_start) { c->body_start = fcn->body_start; } rc = NGX_OK; goto done; } rc = NGX_AGAIN; goto done; } /*如果不存在缓存,则分配一个ngx_http_file_cache_node_t*/ fcn = ngx_slab_calloc_locked(cache->shpool, sizeof(ngx_http_file_cache_node_t)); /*1. r->cache->key的前4个字节,作为红黑树的key。后8个字节 ,作为fcn的key。 2. 节点加入红黑树。 3. 根据inactive设置节点超时时间,加入超时队列。 4. 返回NGX_DECLINED代表节点新创建。*/
- NGX_OK
-缓存正常命中
-设置 cache_status 为 NGX_HTTP_CACHE_HIT,然后向客户端发送缓存内容 - NGX_HTTP_CACHE_STALE
-缓存内容过期,当前请求需要向后端请求新的响应数据。
-设置 cache_status为 NGX_HTTP_CACHE_EXPIRED,并返回 NGX_DECLINED
以继续请求处理 (r->cached = 0; c->valid_sec = 0)。 - NGX_HTTP_CACHE_UPDATING
-缓存内容过期,同时己有同样使用该缓存节点的其它请求正在请求新的响应数据。
-如果 fastcgi_cache_use_stale 启用了 “updating”,设置 cache_status 为
NGX_HTTP_CACHE_UPDATING,然后向客户端发送过期缓存内容。否则,将返回
值重设为 NGX_HTTP_CACHE_STALE。 - NGX_HTTP_CACHE_SCARCE
-因缓存节点被查询次数还未达 min_uses,对此请求禁用缓存机制
-继续请求处理,但是不再缓存其响应数据 (u->cacheable = 0)。 - NGX_DECLINED
-缓存内容因为不存在 (c->exists == 0)、缓存内容未通过校验、或者当前请
求正在更新缓存等原因,暂时无法使用缓存。
-继续请求处理,并尝试对其响应数据进行缓存。 - NGX_AGAIN
-缓存内容过期,并且当前缓存节点正在被其它请求更新,或者 还未能从缓存文
件中读到足够的数据 (aio 模块下)。
-返回 NGX_BUSY,Nginx 会再次尝试读取缓存。 - NGX_ERROR
-内存相关、文件系统等系统错误。
-返回 NGX_ERROR,Nginx 会调用 ngx_http_finalize_request 终止此请求。 - NGX_HTTP_SPECIAL_RESPONSE
-打开 fastcgi_intercept_errors配置情况下,直接返回缓存的错误码。
-设置 cache_status 为 NGX_HTTP_CACHE_HIT 并返回错误码。
- NGX_OK
-
返回值处理
NGX_BUSY:请求cache_lock,等待缓存生成,返回。
NGX_OK:缓存文件获取成功,发送缓存。
NGX_DECLINED:需要向后端发送请求,可能缓存也可能不缓存。
四、响应体接收
-
ngx_http_upstream_send_response
valid = r->cache->valid_sec; //获取该节点的有效时间,如果valid为0,则不缓存,在查询时如果valid_sec已超时,则需重新从后端获取。 if (valid == 0) { valid = ngx_http_file_cache_valid(u->conf->cache_valid, u->headers_in.status_n); if (valid) { r->cache->valid_sec = now + valid; } } /*将文件部分内容(ngx_http_file_cache_header_t + ngx_http_file_cache_key + r->cache->keys)写入u->buffer*/ ngx_http_file_cache_set_header(r, u->buffer.start) /*如果需要缓存,那么上面所保存的结构体、响应头等内容也将写入文件,而不只是响应包体*/ if (u->cacheable) { p->buf_to_file = ngx_calloc_buf(r->pool); p->buf_to_file->start = u->buffer.start; p->buf_to_file->pos = u->buffer.start; }
-
ngx_event_pipe_read_upstream
/*响应包体通过pipe机制写入临时文件*/ if (p->cacheable && (p->in || p->buf_to_file)) { rc = ngx_event_pipe_write_chain_to_temp_file(p); if (rc != NGX_OK) { return rc; } }
四、响应体接收结束(ngx_http_upstream_process_request)
-
ngx_http_file_cache_update
/*将tmp文件拷贝成缓存文件*/ rc = ngx_ext_rename_file(&tf->file.name, &c->file.name, &ext); /*更新节点的uniq、exists、updating等信息,更新文件大小(c->node->fs_size)、整体缓存大小(cache->sh->size)等信息*/
-
manager进程(ngx_http_file_cache_manager)
/* 1.清理未被引用的节点; 2.刷新被引用的节点; 3.处理文件数超过manager_files或处理时间超过manager_threshold,则返回。*/ next = (ngx_msec_t) ngx_http_file_cache_expire(cache) * 1000; for ( ;; ){ /*缓存文件大小超过最大值或个数超过watermark,则对老的节点进行强制释放。*/ } /*返回一个时间估计值,用于设置下一次manager超时时间。*/