服务器核心性能,高性能并发Web服务器实现核心内幕

第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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值