Q:Nginx惊群现象是什么?如何解决?


A:在 多线程 / 多进程下,多个worker等待同一个socket事件,当事件发生时,这些线程 / 进程同时被内核调度唤醒,但只有一个worker处理事件,其他的worker在accept( )返回失败后重新休眠(或其他选择)。这种性能浪费现象就是惊群。

    ( 如果用一个单独的进程处理accept,在多核环境下实际上是对cpu的浪费 )


Nginx如何解决惊群现象:

1. 判断(flag)ngx_use_accept_mutex 是否为1;

    ( nginx worker进程数大于1且配置文件中打开accept mutex )

2. 判断ngx_accept_disabled 是否为正;

    ( ngx_accept_disabled = 单进程所有连接数 1/8  - 剩余空闲连接数 )

    ( disabled大于0,表示当前worker满负荷 )

    ( 通过disabled变量控制是否去竞争accpet_mutex,当disabled越大,让出机会越大 )

3. 获取锁ngx_trylock_accept_mutex( ) ,监听句柄放入当前worker的epoll中;

4. 拿到锁后,置(flag) NGX_POSTED_EVENTS 为1,把accpet时间放入ngx_posted_accept_events 链表中,EPOLLIN | EPOLLOUT 事件放入ngx_posted_events 链表中;

    ( 读写事件耗时长,将在释放锁后延迟处理 )

5. 没拿到锁的worker,timer修改为ngx_accept_mutex_delay ,将以更短的超时时间返回,使得没获得锁的worker去拿锁的频率更高。


/*************************************************************************************/


Q:讲一下epoll的内核实现,都涉及哪些数据结构?


A:


epoll部分源码剖析总结http://blog.csdn.net/chen892704067/article/details/74837962


/*************************************************************************************/


Q:select 和 epoll 的区别?


A:

select 特点:

select 中FD_SET是有限的,在内核2.6中,该值是1024;

include/linux/posix_types.h:
#define __FD_SETSIZE         1024


所以它无法监听1024以上个句柄;

select 内核中实现是用轮询方法,即每次检测都会遍历所有 FD_SET 中的句柄;


epoll 特点:

它所支持的FD上限是最大可打开的文件数目,这个数字与系统内存有关,在1GB的内存上大约为10w左右;

epoll只对活跃的FD进行操作,基于每个FD上的回调函数实现的,只有活跃的socket才会出现在等待队列中;

/*************************************************************************************/


Q:fork( ) 都会做哪些复制?


A:共享 .text  (指令) 

      复制 .data ( 已初始化的全局变量和静态变量 )

             .bss   ( 未初始化的全局变量和静态变量 )

                堆   ( malloc ) 

                栈   ( 局部变量 )

              PCB  ( 打开的文件 )        

            【PCB结构】http://blog.csdn.net/jnu_simba/article/details/11724277


/*************************************************************************************/


Q:什么是写时拷贝?fork( ) 以后,父进程打开的文件指针位置在子进程里面是否一样?


A:写时拷贝(COW): fork( ) 后父进程的虚拟空间( VMA )拷贝给子进程,在虚拟空间与物理页面建立映射的过程中使用了写时拷贝技术,使得子进程共享父进程的 (.data / .bss / 堆 / 栈) 等段,当父子进程其中一个对该区域进行写入时,子进程复制一个新的物理页面并建立映射,使得父子进程相互独立(具体操作是按页为单位复制),同时节省了很多物理内存。

       父进程打开的文件指针存放在PCB中,复制到子进程中后,子进程对应描述符也能对文件进行操作,该描述符指向同一个文件表项,文件表项引用计数加一。/* 两个进程打开文件的读写位置是否一样? */

                                             

两个描述符指向两个磁盘上的文件


                                                            

两个描述符指向同一个磁盘上的文件



子进程继承父进程的打开文件


Linux进程管理——fork()和写时复制 :http://www.cnblogs.com/wuchanming/p/4495479.html

/*************************************************************************************/


Q:进程池和线程池的优缺点,不同场合如何选择?


A:进程池:进程之间处理任务相互独立(某个进程挂掉了,不影响其他进程),但进程间通信需要管道 / 共享内存 / 消息队列等机制实现,而且进程切换带来的开销大;

      线程池:线程之间共享资源且切换代价小,有利于利用多核机器资源,但对于线程共享的临界资源需要使用锁 / 信号量等机制保护,同时一个线程的崩溃会导致整个进程的崩溃;

      进程池适用于需要频繁创建 / 销毁进程,且进程存在时间短的业务。如web服务器开发

      线程池适用于大量并发的场景,如游戏服务器后台任务(定时同时给100w个用户发送邮件)


/*************************************************************************************/


Q:TCP/IP 四层协议,为什么会有传输层和网络层?


A:传输层:对应用层提供网络连接中提供可靠的传输服务(源主机到目的主机),提供进程间的通信、流量控制、差错控制;

      网络层:负责点到点的传输,相邻计算机(路由)之间的通信,处理网络上流动的数据报;

      TCP/IP是把传输中的任务逐层抽象出来,便于完成复杂的跨网络的不同主机进程间通信


      分层的作用:

      设计相对简单,每层只需要考虑它本身的功能;

      某个地方需要改变设计,只需要把变动的层替换掉。


/*************************************************************************************/


Q:三次握手四次挥手信令流程


A:



/*************************************************************************************/


Q:TCP三次握手哪个阶段会抛出异常?为什么不是两次握手?


A:TCP第三次握手若失败(ACK丢失),那么服务器将重传SYN;

      若客户端此时发送数据(客户端发送ACK后,认为连接已经建立),那么数据包中自带的ACK将得到服务器确认,服务器进入ESTABLISH状态,双方可以正常通信;

      若ACK后的数据包也丢失,服务器收不到客户端的ACK,则重传一定次数后将发送RST报文结束连接。



为什么不是两次握手:

       因为信道不稳定,而通信双方要在不可靠的信道上完成可靠的传输,三次是理论上的最小值;要想确定双通道通畅,至少要三个包,服务器和客户端都要收到对方发来的ACK,以确认通信的可靠性。

       两次握手的情况:C 给 S 发送SYN,S 收到SYN返回ACK,按照两次握手的协定,S 进入ESTABLISH状态而开始发送数据;但如果 S 返回给 C 的ACK丢失,那么 C 不知道 S 是否准备好,将忽略所有到来的数据包,而 S 在分组超时后将不断重传,从而形成死锁。

       四次握手的情况:其实三次握手是双方各握一次手,然后各确认一次,只是中间的SYN和ACK合成了一个包


/*************************************************************************************/


Q:Linux下进程有哪些通信方式?全双工和半双工的区别?


A:IPC:信号量、消息队列、共享内存、管道、socket

全双工:迟延小、速度快,双方可以同时收发数据,无需切换

半双工:同时只能有一方发送 / 一方收


/*************************************************************************************/


Q:进程和线程的区别?


A:

进程:操作系统进行资源分配和调度的基本单位,每个进程都有自己的虚拟空间,大小取决于计算机的位数;

线程:是cpu调度和分派的基本单位,是进程里的一条执行路径,一个进程里至少有一个线程;


任务的执行需要计算机的资源(计算机上下文环境),要实现同时执行就必须不断轮换,为了实现多任务系统引出了进程(管理分配资源);

多任务之间切换需要不断保存、调入上下文,当切换频繁时造成的开销就很大,为了提高Cpu的利用率引入了线程(较小的切换开销);


进程和线程都可以看成Cpu执行的一个时间段,一个进程的执行需要:Cpu加载上下文 + Cpu执行 + Cpu保存上下文,而同一个进程内的线程切换开销相对进程极小(仅需要切换线程栈:约4k大小,包括栈指针、程序计数器、一组寄存器),因为同一个进程里的线程共享该进程的资源,是颗粒度更小的Cpu执行时间段。


线程与进程的区别-知乎:https://www.zhihu.com/question/25532384

进程底层构成与调度-简书:http://www.jianshu.com/p/2b993a4b913e


/*************************************************************************************/


Q:什么是同步/异步、阻塞/非阻塞、半同步半异步?


A:同步:调用不会立即返回,调用者主动等待调用结果;

      异步:调用立即返回,通过信号、状态通知调用者结果,或通过回调函数处理;


      阻塞:等待直到调用返回结果


      非阻塞:循环检测状态直到调用返回结果(同步),处理别的任务直到得到通知结果(异步)


      半同步/半异步:事件异步,读写同步

/*************************************************************************************/


Q:Epoll的ET/LT模式在实现上有什么区别?内核上两种模式是如何实现的?


A:LT模式:当socket缓冲区可读 / 可写时,提醒应用程序处理,若数据未一次处理完成,则会反复提醒;

      ET模式:当socket缓冲区可读 / 可写时,只提醒应用程序一次,应用程序必须一次处理完所有数据,直到返回

                    EAGAIN,否则就会丢失数据。

内核实现:

调用epoll_wait( )后,循环检测rdlist是否为空,当rdlist中有就绪的fd时,将fd复制到txlist(清空rdlist),然后再从

txlist复制到用户空间,应用程序得到epoll_wait( )返回后处理数据;

若应用程序一次未处理完所有的就绪的fd,在LT模式下,未处理的fd将会被放回rdlist( )调用时返回;

而ET模式下,这些未处理的fd将被忽略;

(具体细节请移步 http://blog.csdn.net/chen892704067/article/details/74837962


/*************************************************************************************/


Q:vi 的基本命令?


A:i        Insert模式

     f        Find

     u       撤销

 (n)dd     删除

 (n)yy      拷贝

 (n)pp     粘贴


/*************************************************************************************/


Q:Linux上查看系统内存使用的命令,查看Cpu的命令


A:top   /* 类似任务管理器,可以动态的查看内存和CPU */


/*************************************************************************************/


Q:Linux上查看系统版本的命令?进程状态的命令?系统所启动服务的命令?


A:uname -a   查看用户名   内核版本  时间等等信息

      ps              查看进程状态

      netstat      查看端口

      chkconfig --list      查看服务


/*************************************************************************************/


Q:Linux 调试核心转储文件,程序断点是如何实现的?


A:调试核心转储文件:

      $ unlimit -c unlimited    # 设置core大小为无限

       程序运行后,若出现segmentation fault( core dump ),将会产生 core 文件

      $ gdb test core              # 调试core文件

      编译出带调试符号表的可执行文件(-g),然后和core文件一起用gdb调试,可得到出错的源代码


     程序断点实现:

     通过CPU特殊指令——int 3来实现的(软中断:模拟一个中断,保存当前状态然后跳至处理例程中执行)

     当进程执行到 int 3 指令,操作系统就会将它暂停,在Linux上会给进程发送一个SIGTRAP信号


     调试器工作原理:实现断点:http://www.cnblogs.com/xiaoerhei/p/4460285.html


/*************************************************************************************/


Q:fwrite和write的区别,sendfile的内部实现?


A:fwrite是库函数,write是系统调用,fwrite内部也是通过调用write而实现的;


write 每次写入调用者要求大小的数据至内核缓冲区,每次调用都涉及到内核态到用户态的转换,操作系统会由一个守护进程定期的把这些数据写入磁盘中;

fwrite则是每次把数据先写入一个应用进程的缓冲区,等到该缓冲区满或调用fflush这类刷新缓冲区的函数,调用write把缓冲区中的数据一次写入内核缓冲区中,减少了中断次数。


sendfile:

传统文件传输方式 (read( ) / write)读取传输数据,需要经过多次上下文的切换;

1. 调用 read( ),文件数据从磁盘中读入内核缓冲区

2. read( ) 返回,数据从内核缓冲区copy到用户缓冲区

3. 调用 write( ),数据从用户缓冲区copy到 socket 的内核缓冲区

4. 数据从内核缓冲区copy到相关协议栈


在此过程中,数据经过了:磁盘 -> 内核buf -> 用户buf -> 内核buf -> 协议栈


使用 sendfile 提升文件传输性能:

sendfile 通过DMA(Direct Memory Access,直接从内存读取)把硬盘数据拷贝到kernel buf,然后数据将被直接拷贝到 socket 相关的kernel buf,中间没有 user mode  到 kernel mode 的转换,最后直接将数据拷贝给协议栈


 

Linux网络编程--sendfile零拷贝高效率发送文件

http://blog.csdn.net/hnlyyk/article/details/50856268


/*************************************************************************************/


Q:描述符对于服务器有什么用?


A:内核为了不让某一进程消耗掉所有的文件资源,会对用户进程打开的文件数做限制(用户级限制),默认值是1024(unlimit -n);

而在web服务器中,一个连接(socket)就要占一个文件描述符(反向代理则占2个),通过更改默认值文件描述符的最大值来优化服务器是常见的方式之一;

以 Nginx 为例,Nginx对一个连接使用最多2个描述符,如果系统有很多连接请求,就需要提高sys.fs.file_max,以增加系统对文件描述符整体数量的限制,这样才能支持不断增加的负载需求。




Logo

更多推荐