第1页
高性能并发Web服务器实现核心内幕
ideawu
百度服务器研发高级工程师
http://www.ideawu.net/
第2页
内容简介
Not Apache/Lighttpd/Nginx source code
理论, 基础, 通用代码(核心内幕)
如何进化
高性能Web服务器实现核心内幕
高性能网络服务器的实现原理
Web服务器的实现
socket基础, 先学会走再学会飞
第3页
理论结合实践, 实践结合理论
“理论要结合实践”, 是对理论的贬低吗?
Linus不喜欢低级的试错
别告诉我哪个对(错), 告诉我那一个为什么对(错)
理论和实践
理论不结合实践 - 书呆子
实践不结合理论 - 业余者
理论结合实践 - 科学家
实践结合理论 - 专业者
第4页
最原始的网络服务器
网络IO的基础
ssize_t read(int fd, void *buf, size_t count);
ssize_t write(int fd, const void *buf, size_t count);
特点: 阻塞
第5页
网络协议
协议包含两个部分
语法(报文格式)
语义(指令的处理, 交互时序等)
最重要的TCP协议是流式协议, 但几乎所有的应用协议都是基于报文的协议
TCP的”粘包”和”分包”
报文分隔
用连接关闭来表示报文结束. 如, HTTP/1.0的响应
固定长度的报文. 如, TFTP的数据报文.
带自描述长度的固定长度首部的变长报文. 如IP包, TCP分段, nshead(是协议吗?).
带结束符. 如, 行协议, HTTP协议. 逐字节解析和数据转义的影响.
第6页
带有协议的网络服务器
如何读取报文?
尽可能多地读取(read)数据到用户缓冲区中, 即使是固定长度报文, 也不要读取指定长度.
判断用户缓冲区中的数据是否包含至少一个报文
第7页
单个连接的连续服务(长连接)
在一个循环里不断得读取请求, 处理, 然后发送响应.
serv = tcp_socket();
listen(serv);
sock = accept(serv);
while(1){
packet_read(sock, request);
if(request == EXIT){
break;
}
response = handle_packet(request);
packet_write(sock, response);
}
close(sock)
第8页
可以处理多个连接的网络服务器
在外层加一个循环
while(1){
sock = accept(serv);
while(1){
packet_read(sock, request);
if(request == EXIT){
break;
}
response = handle_packet(request);
packet_write(sock, response);
// close(sock); // 短连接
}
close(sock); // 长连接
}
缺点: 必须等一个连接关闭或者退出后, 才能处理下一个连接, 不是并发服务器.
第9页
并发网络服务器
并发服务器是指, 同时处理多个请求的服务器. 并发的原理:
多核(多线程, 多进程)
分片(请求处理的切分)
并发的基本实现 – 避免阻塞(解阻塞)!
使用非阻塞的接口来替代
IO多路复用
找出阻塞的地方, 委托出去.
委托给操作系统内核 sendfile()
委托给多线程/多进程(后面不讨论多进程)
委托给网络服务
委托有时候也叫做"异步".
第10页
阻塞
while(1){
// 可能阻塞
sock = accept(serv);
while(1){
// 可能阻塞
packet_read(sock, request);
if(request == EXIT){
break;
}
// 可能阻塞
response = handle_packet(request);
// 可能阻塞
packet_write(sock, response);
}
close(sock);
}
至少要有一个阻塞, 所以可以在accept()之后进行“解阻塞”.
奇迹 => ...
第11页
原始多线程并发网络服务器
while(1){
// 可能阻塞
sock = accept(serv);
RUN_IN_NEW_THREAD{
while(1){
// 可能阻塞
packet_read(sock, packet);
if(packet == EXIT){
break;
}
// 可能阻塞
response = handle_packet(packet);
// 可能阻塞
packet_write(sock, response);
}
close(sock);
}
}
"RUN_IN_NEW_THREAD"表示创建线程, 这个线程叫做"工作线程".
第12页
原始多线程并发网络服务器(续)
缺点:
线程的数量无法得到控制.
如果是短连接, 创建线程的成本可能相对请求处理的成本更大
要解决的问题:
如何控制线程的数量?
如何避免创建线程对性能的影响
第13页
线程池并发网络服务器
初始化时创建线程池
主进程中accept()之后, 把socket传给工作线程
但又带来了一个问题: 虽然可以不断地接受连接, 但毕竟工作线程有限, 还是会出现连接排队等线程的情况. 当连接数少时是线程等连接, 但当连接数多时是连接等线程.
怎么解决?
调优工作线程的数量.
硬件问题, 不是软件所能解决的, 增加机器.
改变服务器架构, to be continued...
第14页
IO多路复用(IO Multiplex)
前面的架构瓶颈在哪?
把IO委托给操作系统内核
操作系统告知是否可读或者可写
轮询等通知(select, epoll, kqueue)
可读/写表示只能最多成功调用一次read/write而不阻塞
IO多路复用只能解决IO阻塞, 阻塞的类型还有很多种!
第15页
IO多路复用函数介绍
前面的架构瓶颈在哪?
基本IO多路复用函数:
int select(int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
简化:
(rfds_out, wfds_out) = select(rfds_in, wfds_in, timeout);
功能: 判断rfds_in和wfds_in两个列表中的socket连接, 只要有至少一个可读或者可写, 就返回. 或者超时返回.
rfds_in: 要测试的是否可读的socket列表
wfds_in: 要测试的是否可写的socket列表
rfds_out: 返回可读的socket列表
wfds_out: 返回可写的socket列表
timeout: 超时时间, -1表示不永超时
第16页
委托给网络服务
回顾避免阻塞(解阻塞)的方法:
使用非阻塞的接口来替代
IO多路复用
找出阻塞的地方, 委托出去.
委托给操作系统内核 sendfile()
委托给多线程/多进程(后面不讨论多进程)
委托给网络服务
如Apache/Lighttpd/Niginx把请求通过fastcgi(网络)委托给php-cgi进程(网络服务器).
委托给网络服务, 这是一个递归过程
第17页
HTTP服务器(Web服务器)
报文解析: 实现 packet_read()
用抓包工具抓一个HTTP请求报文和一个HTTP响应报文
对照着RFC
上面两步就是理论结合实践, 实践结合理论
语义实现: 实现 handle_packet()
静态文件
大文件
小文件
脚本处理, 以php为例
CGI
FastCGI
Apache mod_php
相对来说, 报文的发送比较通用.
第18页
Web服务器的一般架构
Web服务器将客户端的请求委托给PHP FastCGI进程(是一个独立的网络服务)处理
Web服务器从FastCGI进程读取数据后, 返回给浏览器
如果不是独立的FastCGI服务, 也可以是嵌入到Web服务器内的线程/进程(如Apache mod_php).
第19页
报文解析
http://www.ideawu.net/person/pyhttp/
使用Python的基本socket接口和字符串处理能力, 实现了基本的HTTP协议报文的解析和协议实现.
为IO复用预留了接口
第20页
静态文件请求的处理
文件IO会阻塞
委托给线程
避免文件IO - 内存缓存
委托给操作系统 – sendfile()
第21页
CGI
多进程
用环境变量来传递请求的HTTP报头信息和服务器信息
用stdin传递请求的HTTP报体
用stdout发送响应报头(部分)和报体
缺点:
由于使用环境变量来通信, 扩展性受限
一个进程的生命周期只处理一个请求
第22页
FastCGI
委托给网络
第23页
补充话题
IO多路复用模型中, 为什么不能用标准IO库的行读取函数fgets()来读取HTTP的首部.
因为fgets()调用可能多于一次read(), 是可阻塞的
文本协议和二进制协议如何取舍
报文的格式只是协议的其中一项内容, 语义是另一项更重要的内容.
文本协议总是优于二进制协议(除了少数情况)
应该更关注的是, 报文是定长报文还是变长报文!
参考HTTP, 报头(元数据部分)是文本, 报体可以是二进制数据.
另外, 冒号分隔的key-value行文本报头格式, 是最简单最通用的报文格式.
把"TCP/IP协议详解-卷1", "Unix网络编程-卷1", "计算机网络"这几本书好好看一遍!
第24页
FAQ
IT牛人http://www.udpwork.com/
第25页
FIN
Thanks