目录
1.关注的3.5个问题
- 连接的建立
- 连接的断开
- 消息到达
- 消息发送完毕
2.服务流程
客户端:socket、connnect、send、recv
服务端:socket、bind、listen、accept、recv、send、close
阻塞IO和非阻塞IO:没有数据的时候是不是立即返回
2.1 优化方案1:为每一条连接,创建一个线程。
2.2 优化方案2:reactor网络模型
1.组成:非阻塞IO+IO多路复用(复用的网络线程)
linux下的IO多路复用模型
select:需要轮询检查每一个IO是否有事件。
poll:将读、写、错误分成三种类型去检测
epoll:将读、写、操作事件集中到一个阻塞中去完成,并且返回有事件的IO。
2.特征:事件循环+事件驱动/回调
3.缺点:容易分裂业务逻辑
4.解决办法:协程:同步非阻塞
2.2.1 单线程reactor结构
//创建监听套接字
listenfd=socket();
//绑定监听套接字
bind(listenfd,addr);
//创建epollIO多路复用
efd=epoll_create(0);
//将listenfd放到efd中管理
epoll_clt(efd,epoll_ctl_add,listenfd,&ev);
while(1){
epoll_event ev[];
int nevents=epoll_wait(efd,ev,events,timeout);
for(int i;i<nevents;i++)
{
epoll_event *e=&ev[i];
if(e->fd==listenfd)
{
//建立连接的事件
}else {//读、写、错误事件
if(e->events & epollin||epollhub){//可读
}
if(e->events & epollout){//可写
}
if(e->events & epollerr){//错误事件
}
}
}
}
epoll原理
epoll_create() 在内核中创建了一个红黑树,去管理注册的事件;还创建了一个就绪队列,这是一个双向队列,如果事件发生了,放到就绪队列里。
epoll_ctl() 对红黑树进行操作。
epoll_wait() 将网络中就绪的事件拷贝到events数组中。
reactor的变种1:nginx去fork出多个进程去处理。
master进程fork()出多个worker进程(CPU核心数) 。通过共享内存+进程锁**,worker进程依次获得处理连接的权利。优化:如果一个worker进程处理了7/8*conntect,就不会去获得进程锁。负载均衡。
问题1:nginx为什么使用多进程?
nginx是静态web服务器,处理http请求,连接之间一般没有关联(除了游戏,玩家之间的交互),隔离性强,使用slab共享内存(在共享内存中加一些数据结构:数组、红黑树),简单数据统一。
运行环境隔离性+数据统一性
问题2:nginx为什么不适用多进程+多线程?
fork()不能在多线程中使用,linux当中只能克隆当前线程,不能克隆其他线程的内容:malloc。其它系统调用,加锁。
reactor的变种2:reactor+队列+线程池:skynet,线程池消耗队列,处理业务逻辑。
2.2.2 one loop per thread模型
每一个线程都有一个循环
重点:1.accept一个线程;2.读、写、错误另起线程。
注:clientfd%io_threads分配;另起的线程需要创建epoll对象。
概述:监听线程和处理线程分开,每个线程创建一个epoll,loop循环就是这个epoll。
one loop per thread的变种1:one loop per thread+队列+线程池(会有一定的延时)
比较
1.reactor:突然大量的连接或流量涌入,会降低吞吐率。
原因:因为epoll_wait的处理能力有限,大量的连接涌入不行;因为大量的流量涌入会导致阻塞操作,会影响其他的连接处理。redis,redis6.0引入多线程,只是解决协议序列化的问题。
2.one loop per thread ,memcached-KV数据库,数据结构比较简单,加锁简单。
问题3:redis为什么选择reactor而不是用one loop per thread:
- redis不仅是KV数据库还是数据结构数据库,支持多种数据结构:如string、hash、set、zset、stream、list,加锁非常麻烦。
- redis运行过程是一个空间和时间均衡的过程,存储数据的时候是变换的。
压缩列表(字节数组)
hash:<=512节点的时候是(节省空间),使用压缩列表;>512节点,使用dict(节省事件)。
zset:<=128节点的时候,使用压缩列表;>128节点使用跳表。
set:数量少时用intset,数量多时用dict存储。
3.例子
银行 epoll
门 accept
柜台 网络IO处理,可能包含业务处理
单线程reactor:一个银行 一个柜台
reactor+线程池:一个银行 多个柜台
one loop per thread:多个银行,每个银行一个柜台
one loop per thread+线程池:one loop per thread,每个银行,每个银行多个柜台
4.知识点
阻塞IO和给阻塞IO
1.阻塞在网络线程;
2.IO操作在没有数据到达时是否立即返回;
3.创建fd的时候,决定了是否阻塞。