Nginx中缓存过期队列定时清理的实现,是通过event_timer红黑树来实现的。
相关数据结构先行:
|
|
struct ngx_event_s {
…
…
ngx_event_handler_pt handler; //事件处理函数
…
ngx_rbtree_node_t timer; //包含有超时时间的红黑树节点
…
…
}
|
|
nginx中对cache的管理是通过一个单独的进程进行的,下面直接对源码进行分析:
1.事件处理方法注册
|
|
ngx_process_cycle.c
static ngx_cache_manager_ctx_t ngx_cache_manager_ctx = {
ngx_cache_manager_process_handler, "cache manager process", 0
};
//注册ngx_event_handler_pt为ngx_cache_manager_process_handler
//后面会将给方法赋值给event-handler
|
|
2.启动cache manager 进程
|
|
ngx_process_cycle.c
static void
ngx_start_cache_manager_processes(ngx_cycle_t *cycle, ngx_uint_t respawn)
{
…
…
//启动cache manager 进程
//ngx_cache_manager_process_cycle是进程执行方法体,ngx_cache_manager_ctx是方法体的传入参数
//ngx_cache_manager_ctx是ngx_cache_manager_ctx-ngx_event_handler_pt将会赋值给event-handler用于处理定时事件
ngx_spawn_process(cycle, ngx_cache_manager_process_cycle,
&ngx_cache_manager_ctx, "cache manager process",
respawn ? NGX_PROCESS_JUST_RESPAWN : NGX_PROCESS_RESPAWN);
…
…
}
|
|
3.cache manager进程执行方法体:
|
|
ngx_process_cycle.c
//cache manager进程的执行方法体
static void
ngx_cache_manager_process_cycle(ngx_cycle_t *cycle, void *data)
{
…
…
//将ngx_cache_manager_ctx_t-handler赋值给event-handler,这里ngx_cache_manager_ctx_t-handler的赋值是在1处进行的
ev.handler = ctx->handler;
…
…
for ( ;; ) {
…
//处理事件和超时
ngx_process_events_and_timers(cycle);
}
}
|
|
---------------------------------------------------------------------- 华丽的函数分割线------------------------------------------------------------------
|
|
ngx_event.c
//处理超时和事件
void
ngx_process_events_and_timers(ngx_cycle_t *cycle)
{
…
….
if (delta) {
//查找timer红黑树,如果有超时的节点,则执行节点对应的event-handler方法
ngx_event_expire_timers();
}
…
…
}
}
|
|
4.遍历timer红黑树,并执行相应过期节点的回调函数
ngx_process_cycle.c
static void
ngx_cache_manager_process_handler(ngx_event_t *ev)
{
…
…
for (i = 0; i < ngx_cycle->pathes.nelts; i++) {
if (path[i]->manager) {
n = path[i]->manager(path[i]->data);//实际上就是ngx_http_file_cache_manager,在ngx_http_file_cache_set_slot函数中完成的manager的注册
next = (n <= next) ? n : next;
ngx_time_update();
}
}
if (next == 0) {
next = 1;
}
ngx_add_timer(ev, next * 1000);
}
|
|
5.超时事件处理函数ngx_cache_manager_process_handler
--------------------------------------------------- --------华丽的函数分割线------------------------------------------------------------------
ngx_http_file_cache.c
//"proxy_cache_path"指令的set()函数
char *
ngx_http_file_cache_set_slot(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
…
…
…
cache->path->manager = ngx_http_file_cache_manager;//注册manager
cache->path->loader = ngx_http_file_cache_loader;
cache->path->data = cache;
…
…
}
----------------------------------------------------------------------华丽的函数分割线------------------------------------------------------------------
ngx_http_file_cache.c
//遍历缓存过期队列,从而删除过期的缓存
static time_t
ngx_http_file_cache_manager(void *data)
{
…
…
//先删除过期的缓存
next = ngx_http_file_cache_expire(cache);
cache->last = ngx_current_msec;
cache->files = 0;
for ( ;; ) {
ngx_shmtx_lock(&cache->shpool->mutex);
//获取删除过期缓存后的缓存队列的大小
size = cache->sh->size;
ngx_shmtx_unlock(&cache->shpool->mutex);
ngx_log_debug1(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0,
"http file cache size: %O", size);
//如果空间在指定范围内,不用再删了。return
//如果size超过磁盘的使用空间,即size >= cache->max_size
//强制把部分缓存删除,以保证缓存使用的空间在指定范围内
if (size < cache->max_size) {
return next;
}
wait = ngx_http_file_cache_forced_expire(cache);
if (wait > 0) {
return wait;
|
|
}
if (ngx_quit || ngx_terminate) {
return next;
}
}
}
----------------------------------------------------------- 华丽的函数分割线------------------------------------------------------------------------
//用于清理cache的缓存队列中过期的节点对应的缓存数据
static time_t
ngx_http_file_cache_expire(ngx_http_file_cache_t *cache)
{
....
....
path = cache->path;
//len表示缓存文件对应的全路径名称的长度
/* 全路径的名称形式为:proxy_cache_path+'/'+根据level生成的子路径+16进制表示的MD5码
path->name.len + 1 :表示proxy_cache_path+'/'的长度
path->len 表示根据 level生成的子路径的长度
2 * NGX_HTTP_CACHE_KEY_LEN 表示16进制表示的MD5码的长度,之所以是2 * NGX_HTTP_CACHE_KEY_LEN 是因此MD5码是16个字节,一个字节是8位,而一个16进制数字只需要4位表示
因此MD5码所占的位数为16*8,转换成16进制的形式,所表示的字节的个数为16*8/2=16*2=NGX_HTTP_CACHE_KEY_LEN*2
*/
len = path->name.len + 1 + path->len + 2 * NGX_HTTP_CACHE_KEY_LEN;
name = ngx_alloc(len + 1, ngx_cycle->log);
if (name == NULL) {
return 10;
}
ngx_memcpy(name, path->name.data, path->name.len);
now = ngx_time();
ngx_shmtx_lock(&cache->shpool->mutex);
for ( ;; ) {
if (ngx_queue_empty(&cache->sh->queue)) {
wait = 10;
break;
}
//取得过期队列的最后一个节点
q = ngx_queue_last(&cache->sh->queue);
//获得过期队列节点对应的ngx_http_file_cache_node_t节点的地址
fcn = ngx_queue_data(q, ngx_http_file_cache_node_t, queue);
wait = fcn->expire - now;
//表示当前的cache文件没有过期,则直接跳出循环,因为过期队列越是最新的就越靠前存放,最新的缓存存在队列头部
if (wait > 0) {
wait = wait > 10 ? 10 : wait;
break;
}
//取得过期队列的最后一个节点
|
|
q = ngx_queue_last(&cache->sh->queue);
//获得过期队列节点对应的ngx_http_file_cache_node_t节点的地址
fcn = ngx_queue_data(q, ngx_http_file_cache_node_t, queue);
wait = fcn->expire - now;
//表示当前的cache文件没有过期,则直接跳出循环,因为过期队列越是最新的就越靠前存放,最新的缓存存在队列头部
if (wait > 0) {
wait = wait > 10 ? 10 : wait;
break;
}
ngx_log_debug6(NGX_LOG_DEBUG_HTTP, ngx_cycle->log, 0,
"http file cache expire: #%d %d %02xd%02xd%02xd%02xd",
fcn->count, fcn->exists,
fcn->key[0], fcn->key[1], fcn->key[2], fcn->key[3]);
if (fcn->count == 0) {
//删除磁盘中缓存的文件
ngx_http_file_cache_delete(cache, q, name);
continue;
}
if (fcn->deleting) {
wait = 1;
break;
}
//将node中字符表示的MD5码,key转换为16进制表示的MD5码
//并将转换后的16进制表示形式存储在key中,方法执行完后
//返回转换后的字符串的最后一个字符
p = ngx_hex_dump(key, (u_char *) &fcn->node.key,
sizeof(ngx_rbtree_key_t));
len = NGX_HTTP_CACHE_KEY_LEN - sizeof(ngx_rbtree_key_t);
//将fcn->key转换成16进制表示的形式
//fcn->key中存储的是url的MD5码的后12个字符
(void) ngx_hex_dump(p, fcn->key, len);
//通过上面的两部转换,就将URL的MD5吗转换成了16进制表示的形式
//并且存储在了key中
/*
* abnormally exited workers may leave locked cache entries,
* and although it may be safe to remove them completely,
* we prefer to remove them from inactive queue and rbtree
* only, and to allow other leaks
*/
ngx_queue_remove(q);
ngx_rbtree_delete(&cache->sh->rbtree, &fcn->node);
....
}
....
....
}
|
|
//取得过期队列的最后一个节点
q =ngx_queue_last(&cache->sh->queue);
//获得过期队列节点对应的ngx_http_file_cache_node_t节点的地址
fcn = ngx_queue_data(q,ngx_http_file_cache_node_t, queue);
wait = fcn->expire - now;
宗上所述,过期队列中的删除过期节点的过程如下:
6.流程描述
综上所述:nginx通过timer红黑树完成过期缓存定时清理的工作流程为:
1. 首先注册超时处理函数:ngx_cache_manager_process_handler和缓存管理函数ngx_http_file_cache_manager
2. 在cache manager执行进程中进入无限循环,反复调用ngx_process_events_and_timers方法。
3. 在ngx_process_events_and_timers方法中,调用ngx_event_expire_timers方法。
4. 在ngx_event_expire_timers方法中,找到超时的节点,并执行相应event中的handler。在超时时间中的handler就是前面注册的ngx_cache_manager_process_handler
5. 在ngx_cache_manager_process_handler方法中,遍历ngx_cycle中的所有path,并执行每个path的manger回到函数,在cache中的path的manager回调函数就是前面注册的ngx_http_file_cache_manager。因此在ngx_cache_manager_process_handler中会执行ngx_http_file_cache_manager函数。
6. 在ngx_http_file_cache_manager中完成对缓存过期队列的遍历,并删除过期缓存占用的内存(红黑节点、过期队列节点等)。