学习Nginx的HTTPPROXYMODULE的部分代码

在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);

}