zoukankan      html  css  js  c++  java
  • 关于高并发的几个基础问题

    什么是C10K?

    C10K 就是 Client 10000 问题,即

    在同时连接到服务器的客户端数量超过 10000 个的环境中,即便硬件性能足够, 依然无法正常提供服务。”,

    简而言之,就是单机1万个并发连接问题。

    这个概念最早由 Dan Kegel 提出并发布于其个人站点

    解决方案就是IO多路复用机制(select、poll、epoll等)。

    最弱连接(Weakest link)

    如果往两端用力拉一条由很多环 (连接)组成的锁链,其中最脆弱的一个连接会先断掉。

    因此,锁链整体的强度取决于其中最脆弱的一环。

    select和epoll模型的区别是什么?

    1. 不同点一:文件描述符限制
    select单个进程能够监视的文件描述符的数量存在最大限制。
    epoll没有文件描述符限制。
    
    2. 不同点二:监听方式
    select调用会阻塞,直到有描述符就绪(有数据可读、可写、或者有except),或者超时(timeout指定等待时间),函数返回。
    当select函数返回后,需要通过遍历fdset,才能找到就绪的描述符。
    epoll事先通过epoll_ctl()来注册一个文件描述符,一旦某个文件描述符就绪时,内核会采用类似callback的回调机制,迅速激活这个文件描述符,当进程调用epoll_wait() 时便得到通知。
    即此处去掉了遍历文件描述符,而是通过监听回调的的机制。通过每个fd定义的回调函数来实现的。只有就绪的fd才会执行回调函数。
    
    3. 相同点一:实现机制
    select和epoll都是IO多路复用的机制。
    I/O多路复用就是通过一种机制,一个进程可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。
    
    4. 相同点二:同步I/O
    select和epoll都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的。
    而异步I/O则无需自己负责进行读写,异步I/O的实现会负责把数据从内核拷贝到用户空间。

    水平触发(level triggered)和边缘触发(edge triggered)

    LT模式
    当epoll_wait检测到描述符事件发生并将此事件通知应用程序,应用程序可以不立即处理该事件;下次调用epoll_wait时,会再次响应应用程序并通知此事件。
    关注点是数据(读操作缓冲区不为空,写操作缓冲区不为满),epoll_wait 总会返回就绪。
    LT(level triggered)是缺省的工作方式,并且同时支持block和no-block socket。
    在这种模式中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作。
    如果你不作任何操作,内核还是会继续通知你的。
    
    
    ET模式
    当epoll_wait检测到描述符事件发生并将此事件通知应用程序,应用程序必须立即处理该事件;如果不处理,下次调用epoll_wait时,不会再次响应应用程序并通知此事件。
    关注点是变化,只有监视的文件上有数据变化发生(读操作关注有数据写进缓冲区,写操作关注数据从缓冲区取走),epoll_wait 才会返回。
    
    ET(edge-triggered)是高速工作方式,只支持no-block socket。
    在这种模式下,当描述符从未就绪变为就绪时,内核通过epoll告诉你。
    然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知,
    直到你做了某些操作导致那个文件描述符不再为就绪状态了(比如,你在发送,接收或者接收请求。
    但是请注意,如果一直不对这个fd作IO操作(从而导致它再次变成未就绪),内核不会发送更多的通知(only once)。
    
    ET模式在很大程度上减少了epoll事件被重复触发的次数,因此效率要比LT模式高。
    epoll工作在ET模式的时候,必须使用非阻塞套接字接口,
    以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死。

    协程解决的是什么问题?

    在高并发场景上,协程解决了c10k的问题(c10k的一个表现就是系统CPU高,因为操作系统要调度工作线程)。
    这是由于IO的使用方式是一个连接fd对应一个线程,fd采用阻塞方式调用,当fd不可读、写时,线程不可调度。
    当连接过高时,线程数量也大量增长,线程不仅占用了大量内存,
    而且调度线程也需要大量的cpu,所以并发到10k的时候就达到瓶颈。
    
    操作系统为解决这种问题,提供了多路复用的接口。
    一个线程可以处理多个连接,当一个连接不可读写时,线程不会阻塞,线程检查是否有其他可读写的连接;
    这样操作系统节省了大部分内存和线程调度所需要的的cpu,基于这种技术单机并发可以达到百万甚至更高。
    
    这个工作方式解决了并发的问题,但是这种方式操作复杂,当连接不可用时,应用程序需要保存连接的上下文,待连接可用时在继续之前的操作。
    
    协程解决了这种问题,协程内部帮应用程序保存了连接的上下文,
    开发者不用关心IO多路复用的实现,可以认为IO操作是阻塞的调用,极大方便的开发者;
    比如openresty就是在lua层面实现了协程,它不仅可以保证高并发,对开发者而言编程也特别简单。

    golang协程及其调度

    golang不仅支持IO的协程处理,还提供了事件、管道等阻塞调用的组件,对于使用者是阻塞的,

    对于程序而言只是不再处理当前的逻辑,转而去执行其他可执行的逻辑,将cpu利用率最大化,线程调度最小化。

    golang抽象了P、M、G三种对象实现了协程的调度

    G(goroutine)是协程(用户线程),执行应用程序逻辑代码,数量动态增减。主要有以下几种状态:

      - 初始

      - 待运行(G处在运行队列中,等待M取出并运行)

      - 运行中

      - 等待(G在等待某些条件完成,比如执行了一个不可读的channel,这时G既不在运行中也不再运行队列中)

      - 系统调用(M正在运行这个G发起的系统调用)

      - 已终止。

    P(process)是逻辑cpu,就是计算资源,在程序启动时创建,P的数量默认等于cpu核心数, 但可以通过环境变量GOMAXPROC修改,配置后不可变更,主要有两种状态空闲、运行。

    M(machine)是内核线程,用于在P上调度G,数量动态增加,只增不减,主要有以下几种状态:

      - 自旋(即M正在从运行队列中获取G,这时M拥有一个P)

      - 运行G

      - 等待(找不到可运行的G,就要从自旋变成等待状态,这时M并不拥有P;因为自旋也是占用CPU的,等待就让出CPU了;如果之后有可运行的时,可以通过futex去唤醒等待中的M去执行)

      - 系统调用(阻塞状态)

    G的调度就是M调度G在P上运行,让最少的M将P的利用率最大化。

    M=P是最完美的状态(openresty),但是当M由于系统调用变成不可用时(阻塞),P不能被利用,如果有待运行的G时,就要考虑新建M运行待运行的G。

    M是工作线程,用最少的M在P上运行G,这是golang设计的目标,因为M多了,操作系统要调度M。

    golang的GMP原理

    GMP模型

    - Go需要保证有足够的M可以运行G, 不让CPU闲着, 也需要保证M的数量不能过多,避免过度的CPU调度消耗。

    - M(内核线程)是运行goroutine的实体,goroutine调度器的功能是把可运行的goroutine分配到内核线程(即工作线程)上。

    - Goroutine调度器和OS调度器是通过M结合起来的,每个M都代表了1个内核线程,OS调度器负责把内核线程分配到物理CPU的核上执行。

    - P可以理解为控制go代码并行度的机制。

    go func()调度流程

    参考:Linux IO模式及 select、poll、epoll详解

    参考:Golang调度器GMP原理与调度全分析

    作者:Standby一生热爱名山大川、草原沙漠,还有妹子
    出处:http://www.cnblogs.com/standby/

    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

  • 相关阅读:
    hiveserver2 with kerberos authentication
    python Basic usage
    python Quicksort demo
    Python HeapSort
    mrunit for wordcount demo
    CCDH证书
    Hadoop question list
    Hadoop Yarn core concepts
    Hadoop Resource
    Hadoop could not find or load main class
  • 原文地址:https://www.cnblogs.com/standby/p/14827733.html
Copyright © 2011-2022 走看看