nginx笔记(杂乱版一)
网址:http://tengine.taobao.org/book/chapter_02.html#id1
阅读源码,调试代码是一个非常重要的手段,非常快
一般我们会设置与机器cpu核数一致
主要是因为这样不容易发生CPU迁移,CPU绑核是常用服务器优化手段。
MASTER+WORKER=NUM(CPU)
从容地重启,这个是一个比较神奇的特性,IPVS并没有
从容重启原理:master进程在接收到HUP信号后是怎么做的呢?首先master进程在接到信号后,会先重新加载配置文件,然后再启动新的worker进程,并向所有老的worker进程发送信号,告诉他们可以光荣退休了。(Q:已经负载的链接按照这个说法没法读取啊,那么重复的。。岂不是要重新连接?不过连接也可以写配置文件,如果把连接写入配置文件,就可以通过读取配置文件,获取现有的链接,保证不掉线。。。)
不过确实大多数是这么做
处理请求的机会也是一样的
所有worker进程的listenfd会在新连接到来时变得可读,为保证只有一个进程处理该连接,所有worker进程在注册listenfd读事件前抢accept_mutex,抢到互斥锁的那个进程注册listenfd读事件,在读事件里调用accept接受该连接。
nginx进程模型的好处。:
首先,对于每个worker进程来说,独立的进程,不需要加锁,所以省掉了锁带来的开销,同时在编程以及问题查找时,也会方便很多。其次,采用独立的进程,可以让互相之间不会影响,一个进程退出后,其它进程还在工作,服务不会中断,master进程则很快启动新的worker进程。当然,worker进程的异常退出,肯定是程序有bug了,异常退出,会导致当前worker上的所有请求失败,不过不会影响到所有请求,所以降低了风险。当然,好处还有很多,大家可以慢慢体会。
nginx采用了异步非阻塞的方式来处理请求:poll/epoll的方式
apache的常用工作方式:多线程(并发多吃不消)每个请求会独占一个工作线程,当并发数上到几千时,就同时有几千的线程在处理请求了。这对操作系统来说,是个不小的挑战,线程带来的内存占用非常大,线程的上下文切换带来的cpu开销很大
go服务器:goroutine轻量级线程,所以现在外国服务器端以Go搭建居多(还有一个原因,写的快啊)
为什么nginx可以采用异步非阻塞的方式来处理呢:
因为03年那时候,没有轻量级线程,只有epoll/poll比较好用(C++/C):P
在nginx里面,最忌讳阻塞的系统调用
(建议查看select/poll/epoll原理,这样容易理解。)
有趣的数据:epoll更多的并发数,只是会占用更多的内存而已。
我(作者)之前有对连接数进行过测试,在24G内存的机器上,处理的并发请求数达到过200万。
像这种小的优化在nginx中非常常见,同时也说明了nginx作者的苦心孤诣:所以应该仔细学习这种小优化(md实际上效果并不小,光这个性能可以提升3/2倍。。。)
nginx在做4个字节的字符串比较时,会将4个字符转换成一个int型,再作比较,以减少cpu的指令数等等。
对于一个基本的web服务器来说,事件通常有三种类型,网络事件、信号、定时器。
信号看什么书????
定时器数据结构一般不是最小堆么???nginx里面的定时器事件是放在一颗维护定时器的红黑树里面,每次在进入epoll_wait前,先从该红黑树里面拿到所有定时器事件的最小时间
(代码确认一下)
在写nginx代码时,在处理网络事件的回调函数时,通常做的第一个事情就是判断超时,然后再去处理网络事件。
我们可以用一段伪代码来总结一下nginx的事件处理模型:
while (true) {
for t in run_tasks:
t.handler();
update_time(&now);
timeout = ETERNITY;
for t in wait_tasks: /* sorted already */
if (t.time <= now) {
t.timeout_handler();
} else {
timeout = t.time - now;
break;
}
nevents = poll_function(events, timeout);
for i in nevents:
task t;
if (events[i].type == READ) {
t.handler = read_handler;
} else { /* events[i].type == WRITE */
t.handler = write_handler;
}
run_tasks_add(t);
}