大家好,欢迎来到IT知识分享网。
C10K问题及常见的解决方案
瓶颈分析
文件句柄
linux系统默认支持的文件描述符大小是1024,可通过ulimit -n
查看.我的wsl中默认是.
系统内存
通过如下shell代码,可查询一个socket连接对应的缓冲区大小,单位byte
xyf@Xiong-New:~$ cat /proc/sys/net/ipv4/tcp_wmem 4096 16384 xyf@Xiong-New:~$ cat /proc/sys/net/ipv4/tcp_rmem 4096
相当于
xyf@Xiong-New:~$ cat /proc/sys/net/ipv4/tcp_wmem 4KB 16KB 4MB xyf@Xiong-New:~$ cat /proc/sys/net/ipv4/tcp_rmem 4KB 128KB 6MB
三个值分别代表最小分配值、默认分配值和最大分配值, 按默认分配值计算,10K个连接需要1.4GB内存.
网络带宽
- 第一个层面,应用程序如何和操作系统配合,感知 I/O 事件发生,并调度处理在上万个套接字上的 I/O 操作?前面讲过的阻塞 I/O、非阻塞 I/O 讨论的就是这方面的问题。
- 第二个层面,应用程序如何分配进程、线程资源来服务上万个连接?这在接下来会详细讨论。
阻塞io+多进程
来一个连接, 服务器主进程就创建一个子进程进行客户端连接的处理, 此方法开销非常大, 但实现起来比较简单.
do{
accept connections fork for conneced connection fd process_run(fd) }while(true)
有没有知名的开源框架使用此方案?
阻塞io+多线程/线程池
do{
accept connections pthread_create for conneced connection fd thread_run(fd) }while(true)
来一个连接, 服务器主线程就创建一个子线程进行客户端连接的处理, 此方法开销略小, 子线程共享主线程资源,实现起来也比较简单. 但是线程的创建与销毁对资源的占用仍然不可忽视.
可以通过创建线程池进一步减小切换线程导致的开销, 以下是线程池的示意图及伪代码:
thread_func() {
while(1){
1. 上锁 2. 从连接队列中取出一个连接 if(连接队列空) 解锁, 阻塞等待队列非空条件变量为真, 返回时加锁 else 取出连接队列中的连接 3. 解锁 4. 处理io事件 } } PushToList(fd) {
1.上锁 2. 修改队列 if(队列非满) 将新的连接加入到队列中 else 扩展队列容量, 并将新连接加入队列中 3.解锁 4.队列非空条件变量为真 } thread_create(...,thread_func,...);// 首先创建线程池, 每个线程执行thread_func函数 while(1) {
int fd = accept(); connectio_list.PushToList(fd); //将连接存到连接队列中 }
队列已满时, 需要扩容, 可采用链表的形式,动态增加连接大小,不存在扩容问题,理论上可以存无限多连接. 示意图如下:
线程池能扩展吗, 需要扩展吗?
非阻塞 I/O + readiness notification + 单线程
本质上是通过selector事件分发器维护一个事件列表, 可通过系统内核来判断哪些事件就绪, 然后对就绪事件进行io处理. 相比于阻塞io+多进程,多线程模型, 此模型实现了单个线程进行多路io处理, 又称io多路复用. 结合非阻塞io, 可以高效的处理并发问题.
do {
poller.dispatch() for fd in registered_fdset{
if(is_readable(fd) == true){
handle_read(fd) }else if(is_writeable(fd)==true){
handle_write(fd) } }while(ture)
上述方案需要遍历注册的套接字,如果直接返回有事件发生的套接字,则是epoll方案
do {
poller.dispatch() for fd_event in active_event_set{
if(is_readable_event(fd_event) == true){
handle_read(fd_event) }else if(is_writeable_event(fd_event)==true){
handle_write(fd_event) } }while(ture)
为什么IO多路复用需要结合非阻塞IO使用?
- 如果采用阻塞IO, 线程有可能阻塞在recv/read/write/send处, 不能重复利用单线程处理多个IO事件的能力.
Reactor模式本质上就是IO多路复用+非阻塞IO, 只不过换了一种写法, 将fd,缓冲区,回调函数包装在一起定义为一个连接对象, 有可读事件发生时调用recv_cb回调函数, 有可写事件发生时调用send_cb回调函数.
这样写的好处,我理解有两点:
- 首先代码结构更加清晰
- 更加面向业务, 每个连接有自己的缓冲区, 业务程序员不关心如何通过系统IO函数收发数据, 只关心数据从哪里取,处理好后放在哪里. 关于从内核拷贝到用户,从用户拷贝到内核,业务程序员并不关心.
以下是Reactor模式示意图:
并发量增加时,会产生一些常见文件, 见下文:
并发量增加时常见的三个问题
非阻塞 I/O + readiness notification + 多线程
主从reactor模型
异步 I/O+ 多线程
免责声明:本站所有文章内容,图片,视频等均是来源于用户投稿和互联网及文摘转载整编而成,不代表本站观点,不承担相关法律责任。其著作权各归其原作者或其出版社所有。如发现本站有涉嫌抄袭侵权/违法违规的内容,侵犯到您的权益,请在线联系站长,一经查实,本站将立刻删除。 本文来自网络,若有侵权,请联系删除,如若转载,请注明出处:https://yundeesoft.com/109874.html