Nginx是当前最流行的HTTP Server之一,与Apache相比,Nginx在高并发情况下具有巨大的性能优势。
Apache工作方式:每个请求会独占一个工作线程,当并发数上到几千时,就同时有几千的线程在处理请求了。这对操作系统来说,是个不小的挑战,线程带来的内存占用非常大,线程的上下文切换带来的cpu开销很大,自然性能就上不去了,而这些开销完全是没有意义的。
Nginx工作模式:nginx在启动后,会有一个master进程和多个worker进程。master进程主要用来管理worker进程,work进程是用来工作的进程。模型如下图所示:
master进程会向worker进程发送各种信号,监控worker进程的运行状态,当worker进程异常退出后,会自动重启新的worker进程。
多个worker进程之间是对等的,他们同等竞争来自客户端的请求,各进程相互之间是独立的。一个请求,只可能放在一个worker进程中处理,一个worker进程,不可能处理其它进程的请求。
worker进程的个数是可以设置的,通常会设置于机器cpu核数一致。
Nginx进程模型的好处:首先,独立的进程,不需要加锁,所以省掉了锁带来的开销;其次,采用独立的进程,可以让互相之间不会影响,一个进程退出后,其他进程还在工作,服务不会中断,master进程很快会启动新的worker进程。
Nginx如何处理高并发:nginx采用异步非阻塞的方式来处理请求,可以同时处理成千上万个请求。具体到系统调用就是像select/poll/epoll/kqueue这样的系统调用。它们提供一种机制,让你可以同时监控多个事件,调用它们是阻塞的,但可以设置超时时间,在超时时间之内,如果有事情准备好了,就返回。以epoll为例,当事件没准备好时,放到epoll里面,事件准备好了,就去读写,当读写返回EAGAIN时,将它再次加入到epoll里面。这样,只要有事件准备好了,我们就去处理它,只有当所有事件都没准备好时,才在epoll里面等着。
这样我们就可以并发处理大量的并发,当然这里的并发请求,是指未处理完的请求,线程只有一个,所以同时能处理的请求只有一个,只是在请求间进行不断切换而已,切换也是因为异步事件为准备好,而主动让出的,这里的切换是没有任何代价的,可以理解为循环处理多个准备好的事件。
epoll只遍历活跃的socket描述符,这是因为在内核实现中,epoll是根据每个fd上面的callback函数实现的。
location /echo{
echo "hello world!!!!";
}
要实现这个功能需要三步:1、读入配置文件中echo指令及其参数;2、进行HTTP包装(添加HTTP头等工作);3、将结果返回给客户端。
typedef struct{
ngx_str_t ed;
}ngx_http_echo_loc_conf_t;
struct ngx_command_s {
ngx_str_t name;
ngx_uint_t type;
char *(*set)(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
ngx_uint_t conf;
ngx_uint_t offset;
void *post;
};
其中name是词条指令的名称,type使用掩码标志位方式配置指令参数。set是一个函数指针,用于指定一个参数转化函数,这个函数一般是将配置文件中相关指令的参数转化成需要的格式并存入配置结构体。Nginx预定义了一些转换函数,可以方便我们调用。这些函数一般以"_slot"结尾,例如ngx_conf_set_flag_slot将"on"或"off"转为1或0,ngx_conf_set_str_slot将裸字符串转化为ngx_str_t。
static ngx_command_t ngx_http_echo_commands[] = {
{
ngx_string("echo"),
NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
ngx_http_echo,
NGX_HTTP_LOC_CONF_OFFSET,
offsetof(ngx_http_echo_loc_conf_t, ed),
NULL
},
ngx_null_command
};
参数转化函数的代码为:
static char * ngx_http_echo(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
ngx_http_core_loc_conf_t *clcf;
clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
clcf->handler = ngx_http_echo_handler;
ngx_conf_set_str_slot(cf, cmd, conf);
return NGX_CONF_OK;
}
这个函数除了调用ngx_conf_set_str_slot转化为echo指令的参数外,还将修改了核心模块配置(也就是这个location的配置),将其handler替换为我们编写的ngx_http_echo_handler。这样就屏蔽了此location的默认handler,使用ngx_http_echo_handler产生HTTP响应。
static ngx_http_module_t ngx_http_echo_module_ctx = {
NULL, /* preconfiguration */
NULL, /* postconfiguration */
NULL, /* create main configuration */
NULL, /* init main configuration */
NULL, /* create server configuration */
NULL, /* merge server configuration */
ngx_http_echo_create_loc_conf, /* create location configration */
ngx_http_echo_merge_loc_conf /* merge location configration */
};
可以看到一共有8个Hook注入点,分别会在不同的时刻被Nginx调用,由于我们的模块仅仅用于location域,这里将不需要注入点设为NULL即可。 其中create_loc_conf用于初始化一个配置体,如为配置结构分配内存等工作;merge_loc_conf用于将其父block的配置信息合并到此结构体中,也就是实现配置的继承。这两个函数会被Nginx自动调用。这里的命名规则:ngx_http_[module-name]_[create|merge]_[main|srv|loc]_conf。下面分别是这两个函数的代码:
static void *
ngx_http_echo_create_loc_conf(ngx_conf_t *cf)
{
ngx_http_echo_loc_conf_t *conf;
conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_echo_loc_conf_t));
if (conf == NULL) {
return NGX_CONF_ERROR;
}
conf->ed.len = 0;
conf->ed.data = NULL;
return conf;
}
static char *
ngx_http_echo_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
{
ngx_http_echo_loc_conf_t *prev = parent;
ngx_http_echo_loc_conf_t *conf = child;
ngx_conf_merge_str_value(conf->ed, prev->ed, "");
return NGX_CONF_OK;
}
其中ngx_pcalloc用于在Nginx内存池中分配一块空间,是pcalloc的一个包装。使用ngx_pcalloc分配的内存空间不必手工free,Nginx会自行管理,在适当时候释放。
#define ngx_conf_merge_str_value(conf, prev, default) \
if (conf.data == NULL) { \
if (prev.data) { \
conf.len = prev.len; \
conf.data = prev.data; \
} else { \
conf.len = sizeof(default) - 1; \
conf.data = (u_char *) default; \
} \
}
可以看到,core/ngx_conf_file.h还定义了很多merge value的宏用于merge各种数据。它们的行为比较相似:使用prev填充conf,如果prev的数据为空则使用default填充。
static ngx_int_t
ngx_http_echo_handler(ngx_http_request_t *r)
{
ngx_int_t rc;
ngx_buf_t *b;
ngx_chain_t out;
ngx_http_echo_loc_conf_t *elcf;
elcf = ngx_http_get_module_loc_conf(r, ngx_http_echo_module);
if(!(r->method & (NGX_HTTP_HEAD|NGX_HTTP_GET|NGX_HTTP_POST)))
{
return NGX_HTTP_NOT_ALLOWED;
}
r->headers_out.content_type.len = sizeof("text/html") - 1;
r->headers_out.content_type.data = (u_char *) "text/html";
r->headers_out.status = NGX_HTTP_OK;
r->headers_out.content_length_n = elcf->ed.len;
if(r->method == NGX_HTTP_HEAD)
{
rc = ngx_http_send_header(r);
if(rc != NGX_OK)
{
return rc;
}
}
b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));
if(b == NULL)
{
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "Failed to allocate response buffer.");
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
out.buf = b;
out.next = NULL;
b->pos = elcf->ed.data;
b->last = elcf->ed.data + (elcf->ed.len);
b->memory = 1;
b->last_buf = 1;
rc = ngx_http_send_header(r);
if(rc != NGX_OK)
{
return rc;
}
return ngx_http_output_filter(r, &out);
}
handler会接收一个ngx_http_request_t指针类型的参数,这个参数指向一个ngx_http_request_t结构体,此结构体存储了这次HTTP请求的一些信息,定义如下:
struct ngx_http_request_s {
uint32_t signature; /* "HTTP" */
ngx_connection_t *connection;
void **ctx;
void **main_conf;
void **srv_conf;
void **loc_conf;
ngx_http_event_handler_pt read_event_handler;
ngx_http_event_handler_pt write_event_handler;
#if (NGX_HTTP_CACHE)
ngx_http_cache_t *cache;
#endif
ngx_http_upstream_t *upstream;
ngx_array_t *upstream_states;
/* of ngx_http_upstream_state_t */
ngx_pool_t *pool;
ngx_buf_t *header_in;
ngx_http_headers_in_t headers_in;
ngx_http_headers_out_t headers_out;
ngx_http_request_body_t *request_body;
time_t lingering_time;
time_t start_sec;
ngx_msec_t start_msec;
ngx_uint_t method;
ngx_uint_t http_version;
ngx_str_t request_line;
ngx_str_t uri;
ngx_str_t args;
ngx_str_t exten;
ngx_str_t unparsed_uri;
ngx_str_t method_name;
ngx_str_t http_protocol;
ngx_chain_t *out;
ngx_http_request_t *main;
ngx_http_request_t *parent;
ngx_http_postponed_request_t *postponed;
ngx_http_post_subrequest_t *post_subrequest;
ngx_http_posted_request_t *posted_requests;
ngx_http_virtual_names_t *virtual_names;
ngx_int_t phase_handler;
ngx_http_handler_pt content_handler;
ngx_uint_t access_code;
ngx_http_variable_value_t *variables;
/* ... */
}
ngx_http_request_s定义比较长,里面有诸如uri,args和request_body等HTTP常用信息。这里需要特别注意的几个字段是headers_in、headers_out和chain,它们分别表示request header、response header和输出缓冲区链表(缓冲区链表是Nginx I/O中的重要内容)。
typedef struct {
ngx_list_t headers;
ngx_uint_t status;
ngx_str_t status_line;
ngx_table_elt_t *server;
ngx_table_elt_t *date;
ngx_table_elt_t *content_length;
ngx_table_elt_t *content_encoding;
ngx_table_elt_t *location;
ngx_table_elt_t *refresh;
ngx_table_elt_t *last_modified;
ngx_table_elt_t *content_range;
ngx_table_elt_t *accept_ranges;
ngx_table_elt_t *www_authenticate;
ngx_table_elt_t *expires;
ngx_table_elt_t *etag;
ngx_str_t *override_charset;
size_t content_type_len;
ngx_str_t content_type;
ngx_str_t charset;
u_char *content_type_lowcase;
ngx_uint_t content_type_hash;
ngx_array_t cache_control;
off_t content_length_n;
time_t date_time;
time_t last_modified_time;
} ngx_http_headers_out_t;
设置好头信息后使用ngx_http_send_header就可以将头信息输出,ngx_http_send_header接受一个ngx_http_request_t类型的参数。
struct ngx_chain_s {
ngx_buf_t *buf;
ngx_chain_t *next;
};
其中ngx_chain_t是ngx_chain_s的别名,buf为某个数据缓冲区的指针,next指向下一个链表节点。ngx_buf_t中比较重要的是pos和last,分别表示缓冲区数据在内存中的起始地址和结尾地址,last_buf是一个位域,设为1表示此缓冲区是链表中最后一个元素,为0表示后面还有元素。因为我们只有一组数据,所以缓冲区链表中只有一个节点,如果需要输入多组数据可将各组数据放入不同的缓冲区后插入链表。下图展示了Nginx缓冲链表的结构:
ngx_module_t ngx_http_echo_module = {
NGX_MODULE_V1,
&ngx_http_echo_module_ctx, /* module context */
ngx_http_echo_commands, /* module directives */
NGX_HTTP_MODULE, /* module type */
NULL, /* init master */
NULL, /* init module */
NULL, /* init process */
NULL, /* init thread */
NULL, /* exit thread */
NULL, /* exit process */
NULL, /* exit master */
NGX_MODULE_V1_PADDING
};
#include
#include
#include
typedef struct{
ngx_str_t ed;
}ngx_http_echo_loc_conf_t;
static char *ngx_http_echo(ngx_conf_t *cf, ngx_command_t *cmd, void *conf);
static void *ngx_http_echo_create_loc_conf(ngx_conf_t *cf);
static char *ngx_http_echo_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child);
static ngx_command_t ngx_http_echo_commands[] = {
{
ngx_string("echo"),
NGX_HTTP_LOC_CONF|NGX_CONF_TAKE1,
ngx_http_echo,
NGX_HTTP_LOC_CONF_OFFSET,
offsetof(ngx_http_echo_loc_conf_t, ed),
NULL
},
ngx_null_command
};
static ngx_http_module_t ngx_http_echo_module_ctx = {
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
ngx_http_echo_create_loc_conf,
ngx_http_echo_merge_loc_conf
};
ngx_module_t ngx_http_echo_module = {
NGX_MODULE_V1,
&ngx_http_echo_module_ctx,
ngx_http_echo_commands,
NGX_HTTP_MODULE,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NULL,
NGX_MODULE_V1_PADDING
};
static ngx_int_t ngx_http_echo_handler(ngx_http_request_t *r)
{
ngx_int_t rc;
ngx_buf_t *b;
ngx_chain_t out;
ngx_http_echo_loc_conf_t *elcf;
elcf = ngx_http_get_module_loc_conf(r, ngx_http_echo_module);
if (!(r->method & (NGX_HTTP_HEAD|NGX_HTTP_GET|NGX_HTTP_POST)))
{
return NGX_HTTP_NOT_ALLOWED;
}
r->headers_out.content_type.len = sizeof("text/html")-1;
r->headers_out.content_type.data = (u_char *)"text/html";
r->headers_out.status = NGX_HTTP_OK;
r->headers_out.content_length_n = elcf->ed.len;
if (r->method == NGX_HTTP_HEAD)
{
rc = ngx_http_send_header(r);
if (rc != NGX_OK)
{
return rc;
}
}
b = ngx_pcalloc(r->pool, sizeof(ngx_buf_t));
if ( b == NULL )
{
ngx_log_error(NGX_LOG_ERR, r->connection->log, 0, "Failed to allocate response buffer.");
return NGX_HTTP_INTERNAL_SERVER_ERROR;
}
out.buf = b;
out.next = NULL;
b->pos = elcf->ed.data;
b->last = elcf->ed.data + (elcf->ed.len);
b->memory = 1;
b->last_buf = 1;
rc = ngx_http_send_header(r);
if (rc != NGX_OK)
{
return rc;
}
return ngx_http_output_filter(r, &out);
}
static char * ngx_http_echo(ngx_conf_t *cf, ngx_command_t *cmd, void *conf)
{
ngx_http_core_loc_conf_t *clcf;
clcf = ngx_http_conf_get_module_loc_conf(cf, ngx_http_core_module);
clcf->handler = ngx_http_echo_handler;
ngx_conf_set_str_slot(cf, cmd, conf);
return NGX_CONF_OK;
}
static void * ngx_http_echo_create_loc_conf(ngx_conf_t *cf)
{
ngx_http_echo_loc_conf_t *conf;
conf = ngx_pcalloc(cf->pool, sizeof(ngx_http_echo_loc_conf_t));
if (conf == NULL){
return NGX_CONF_ERROR;
}
conf->ed.len = 0;
conf->ed.data = NULL;
return conf;
}
static char * ngx_http_echo_merge_loc_conf(ngx_conf_t *cf, void *parent, void *child)
{
ngx_http_echo_loc_conf_t *prev = parent;
ngx_http_echo_loc_conf_t *conf = child;
ngx_conf_merge_str_value(conf->ed, prev->ed, "");
return NGX_CONF_OK;
}
/* vim: set expandtab sw=4 ts=4 sts=4: */
Nginx模块的安装
ngx_addon_name=ngx_http_echo_module
HTTP_MODULES="$HTTP_MODULES ngx_http_echo_module"
NGX_ADDON_SRCS="$NGX_ADDON_SRCS $ngx_addon_dir/ngx_http_echo_module.c"
2.进入Nginx源代码,使用下面的命令安装编译
./configure --prefix=/home/wangzhilong/mysoft/ --add-module=./myfile/ngx_http_echo/ --with-pcre=../pcre-8.32
make
make install
其中--prefix使用绝对路径,如果使用到了正则,需要经prce编译进去。
正向代理的概念:也就是传说中的代理,它的工作原理就像一个跳板,简单的说,我是一个用户,我访问不了某网站,但是我能访问一个代理服务器,这个代理服务器呢,他能访问那个我不能访问的网站,于是我先连上代理服务器,告诉他我需要那个无法访问网站的内容,代理服务器去取回来,然后返回给我。从网站的角度,只在代理服务器来取内容的时候有一次记录,有时候并不知道是用户的请求,也隐藏了用户的资料,这取决于代理告不告诉网站。
反向代理的概念:对于客户端而言它就像是原始服务器,并且客户端不需要进行任何特别的设置。客户端向反向代理发送请求,反向代理将请求转交给后台的服务端,并将获得的内容返回给客户端,就像这些内容原本就是它自己的一样。如下图所示:
好处:正向代理的典型用途为在防火墙内的局域网客户端提供方位Internet的途径。正向代理还可以使用缓冲特性减少网络使用率。方向代理的典型用途是将防火墙后面的服务器提供给Internet用户访问。反向代理还可以为后端的多台服务器提供负载平衡,或为后端较慢的服务器提供缓冲服务。另外,反向代理还可以启用高级URL策略和管理技术,从而使处于不同web服务器系统的web页面同时存在于同一个URL空间下。
反向代理的例子:
用python搭建一个简单服务端,代码如下:
#!/usr/bin/env python
# -*- encoding: utf-8 -*-
import tornado.httpserver
import tornado.ioloop
import tornado.options
import tornado.web
from tornado.options import define, options
define("port", default=8000, help="run on the given port", type=int)
class MainHandler(tornado.web.RequestHandler):
def get(self):
self.write("hello, world\n")
def main():
tornado.options.parse_command_line()
application = tornado.web.Application([ \
(r"/", MainHandler), \
])
http_server = tornado.httpserver.HTTPServer(application)
http_server.listen(options.port)
tornado.ioloop.IOLoop.instance().start()
if __name__ == '__main__':
main()
# vim: set expandtab sw=4 ts=4 sts=4:
分别启动两个端口的服务
python test.py --port=8001
python test.py --port=8002
设定负载均衡服务列表
upstream test_1{
#ip_hash;
server 127.0.0.1:8001 down;
server 127.0.0.1:8002 max_fails=3 fail_timeout=20s;
}
upstream是Nginx的HTTP Upstream模块,这个模块通过一个简单的调度算法来实现客户端IP到后台服务端的负载均衡。Nginx的负载均衡模块目前支持4种调度算法,下面分别进行介绍:
(1)轮询(默认)。每个请求按时间顺序逐一分配到不同的后端服务器,如果后端某台服务器宕机,故障系统被自动剔除,使用户访问不受影响。Weight指定轮询权值,Weight值越大,分配到的访问几率越高,主要用于后端每个服务器性能不均的情况下。
(2)ip_hash。每个请求按访问IP的hash结果分配,这样来自同一个IP的访客固定访问一个后端服务器,有效解决了动态网页存在的session共享的问题。
(3)fair。这是比上面两个更加智能的负载均衡算法。此种算法可以依据页面大小和加载时间长短智能地进行负载均衡,也就是根据后端服务器的响应时间来分配请求,响应时间短的优先分配。Nginx本身是不支持fair的,如果需要使用这种调度算法,必须下载Nginx的upstream_fair模块。
(4)url_hash。此方法按访问url的hash结果来分配请求,使每个url定向到同一个后端服务器,可以进一步提高后端缓存服务器的效率。Nginx本身是不支持url_hash 的,如果需要使用这种调度算法,必须安装Nginx的hash软件包。
upstream支持的状态参数
在HTTP Upstream模块中,可以通过server指令指定后端服务器的IP地址和端口,同时还可以设定每个后端服务器在负载均衡调度中的状态。常用的状态有:
down,表示当前的server暂时不参与负载均衡。
backup,预留的备份机器。当其他所有的非backup机器出现故障或者忙的时候,才会请求backup机器,因此这台机器的压力最轻。
max_fails,允许请求失败的次数,默认为1.当超过最大次数时,返回proxy_next_upstream模块定义的错误。
fail_timeout,在经历了max_fails次失败后,暂停服务的时间。max_fails可以和fail_timeout一起使用。
注,当负载调度算法为ip_hash时,后端服务器在负载调度中的状态不能是weight和backup。
在server模块中的配置:
location / {
#root html;
#index index.html index.htm;
proxy_pass http://test_1;
proxy_read_timeout 1800;
proxy_pass_header Server;
proxy_set_header Host $http_host;
proxy_redirect off;
proxy_set_header X-Real-IP $remote_addr; #指令允许将发送到被代理服务器的请求头重新定义或者增加一些字段
proxy_set_header X-Scheme $scheme;
}
通过查看日志,可以看到对Nginx的请求,被分配到不同的服务器上。