在nginx的配置文件中设置了proxy_path xxxxxx 的命令后,客户端针对nginx发出的请求就会被转向后端服务器。如果此时nginx开启了缓存的功能并且后端服务器为其发出的包指明过期时间,nginx就成为了一个缓存服务器,以缓解后端服务器的压力。可是当大量的用户同时请求nginx上的某一过期或是不存在的数据时,这些请求会被nginx全部转到上游服务器,这样就会给源服务器很大压力。我开始阅读相关源代码,试图解决这个问题。
在nginx的配置文件中,有deamon off,master_process off两个指令,默认值均为on,如果显式设置成off,仅仅是为了方便调试。前者在生产过程中可以使用;后者不可!
如果我们在配置文件中加入pass_proxy http://xxxxxx;指令,则HTTPPROXYMODULE就会起作用。在nginx中,模块的的入口是handler函数。对于此模块,其对应的文件是ngx_http_proxy_module.c/
一 需要用到的数据结构以及操作
(1) ngx_http_file_cache_node_t
(2) slab内存操作
内存的分配:
ngx_slab_alloc_locked(ngx_slab_pool_t *pool, size_t size);
内存的释放
ngx_slab_free_locked(ngx_slab_pool_t *pool, void *p);
内存分配失败(内存池耗尽啦)的判断方法:
fcn = ngx_slab_alloc_locked……;
if (fcn == NULL) {
//内存池耗尽,内存分配失败!!!!
}
(3) 队列操作
- 基础数据结构
在core/ngx_queue.h中
typedef struct ngx_queue_s ngx_queue_t;
struct ngx_queue_s {
ngx_queue_t *prev;
ngx_queue_t *next;
};
基础结构仅包含两个指针,没有实际的数据成员。使用的时候需要自己定义
- 实际的队列结点
eg.
typedef struct {
ngx_rbtree_node_t node;
ngx_queue_t queue;
……
} ngx_http_file_cache_node_t
除了队列结点中的数据外,还有一个ngx_queue_t字段,用于将结点串联起来
- 获取队列结点数据
#define ngx_queue_data(q, type, link) (type*)((u_char*)q – offsetof(type, link))
q是指向当前队列结构中的基础队列字段的指针
type 是队列结构的类型名
link 是队列结构中基础队列字段的字段名
eg.
ngx_http_file_cache_node_t *fcn;
fcn -> queue 是 q;
ngx_http_file_cache_node_t 是 type;
queue 是 link.
- 初始化队列 ngx_queue_init(ngx_queue_t *q), 这个宏的作用是令结点自己指向自己
- 插入队列 ngx_queue_insert_head(ngx_queue_t *head, ngx_queue_t *q), 将q插入到head->next的位置。
- 遍历队列
for (q = ngx_queue_last(&head);q != ngx_queue_sentinal(&head);q = ngx_queue_prev(q)) {
//每个队列都有个哨兵结点,用以定位头结点和尾结点,sentinal
}
- 删除结点
ngx_queue_remove(q)
- 判断队列是否为空
ngx_queue_empty(h)
(4)自定义的数据结构
ngx_http.h,我需要修改的文件都include了此文件,我将把自定义的数据结构暂时放在这里。
二 http proxy模块
ngx_http_proxy_module.h,在ngx_http_proxy_module.c文件中有ngx_http_proxy_handler函数的定义。
ngx_http_proxy_handler (ngx_http_request_t *r) {
…….
rc = ngx_http_read_client_request_body(r,ngx_http_upstream_init);
……
}
在ngx_http_request_body.c中
ngx_http_read_client_request_body(ngx_http_request_t *r, ngx_http_client_body_handler_pt post_handler) {
……
if (r -> headers_in.content_lenth_n == 0) {
post_handler(r);//这个函数其实是调用了ngx_http_upstream_init
}
……
}
在ngx_http_upstream.c中
ngx_http_upstream_init(ngx_http_request_t *r) {
……
ngx_http_upstream_init_request(r);
}
在ngx_http_upstream.c中
ngx_http_upstream_init_request(ngx_http_request_t *r) {
……
if (u -> conf -> cache) {
rc = ngx_http_upstream_cache(r,u);//这个函数对缓存进行读取
……
if (rc != NGX_DECLINED) {
ngx_http_finalize_request(r,rc);
return ;
}
}
//如果没有从缓存中读取到合适的数据,就会从这里继续向下执行,向上游服务器请求数据
}
三 读取cache的部分
(1)联系源服务器前,缓存的读取从函数: ngx_http_upstream_cache开始。
在ngx_http_upstream.c中
ngx_http_upstream_cache(ngx_http_request_t *r, ngx_http_upstream_t *u) {
……
c = r->cache
if ( c == NULL) {
……//这里边的代码看不懂啊,有待研究,似乎和生成cache的key值有关
}
rc = ngx_http_file_cache_open(r);//这个函数是读取缓存的主要函数
…….//rc的值代表了读取缓存的状态,根据rc的值来决定下一步需要做的事情
}
在ngx_http_file_cache.c中,
ngx_http_file_cache_open(ngx_http_request_t *r) {//修改源码点:被挂起的用户应当在这个函数中被插入队列
……
rc = ngx_http_file_cache_exists(cache,c);//判断内存中是否有缓存结点
……
if (!test) {
return NGX_DECLINED;
}
}
在ngx_http_file_cache.c中,
ngx_http_file_cache_exists(ngx_http_file_cache_t *cache, ngx_http_cache_t *c) {
ngx_int_t rc;
ngx_shmtx_lock(&cache->shpool->mutex);
ngx_http_file_cache_node_t *fcn; //内存结点
ngx_shmtx_lock()
fcn = ngx_http_file_cache_lookup(cache,c->key);//在树中寻找结点
if(fcn) {//如果找到结点
ngx_queue_remove(&fcn -> queue);
fcn -> uses ++;
fcn -> count++;
……
}
//如果运行到这里,说明结点没有找到,需要创建。
fcn = ngx_slab_alloc_locked(cache->shpool,sizeof(ngx_http_file_cache_node_t));
…..
done://跳转点,查找完毕,善后
….
c->node = fcn;
}
(2)联系源服务器后,对缓存进行更新
在ngx_http_upstream.c中,
ngx_http_upstream_process_request() {
if (u->cacheable) {
//对缓存进行更新
}
//从保存被hold住的队列中取出所有结点,响应所有客户端
}
四 修改和添加的内容
1. 数据结构
数据结构暂时全部放在http/ngx_http.h中
(1)我们将部分客户端挂起,这样就需要一个队列类型的数据结构来存储客户端信息。
(2)内存中都会有相应的内存结点(ngx_http_file_cache_node_t),它与客户端请求的文件相对应。
我需要在这个数据结构中加一个队列的哨兵结点,用以挂载(1)中提到的队列。
在http/ngx_http_cache.h中:
typedef struct {
……
ngx_queue_t my_request_wait_queue;
} ngx_http_file_cache_node_t;
2. 在两个地方对ngx_http_file_cache_node_t中的my_request_wait_queue进行初始化。
(1) 在ngx_http_file_cache.c中,
ngx_http_file_cache_add() {
……
if (fcn == NULL) {
……
ngx_queue_init(&fcn->my_request_wait_queue);//对my_request_wait_queue初始化;
}
}
(2) 在ngx_http_file_cache.c中,
ngx_http_file_cache_exists() {
if (fcn == NULL) {}
ngx_queue_init(&fcn->my_request_wait_queue);//对my_request_wait_queue初始化;
}
3. 在确定缓存是否需要更新的地方进行修改(NGX_HTTP_CACHE_UPDATING,此处略)
4. 在NGX_HTTP_CACHE_UPDATING的情况下,将自定义结点加入my_request_wait_queue.
ngx_http_file_cache_node_t *fcn;
my_ngx_request_wait_queue *waitq;
if (fcn->updating) {
waitq = ngx_slab_alloc_locked(cache->shpool, sizeof(my_ngx_request_wait_queue_t));
if (waitq == NULL) {
printf(” Error accures\n”);
return 999//自定义错误号
}
ngx_queue_init(&waitq->queue);
ngx_queue_insert_head(&fcn->my_request_wait_queue,&waitq->queue);
}