• 谈谈多路转接实现的服务器


    单线程服务器

    对单线程阻塞的服务器,每个连接和相关处理动作会阻塞下一个请求的接收

    只有上一个请求处理完成,才能处理下一个请求

    通过多路转接,使得服务器能够并发处理多个请求,这些请求与事件处理函数绑定并注册到事件循环上

    每次循环,从多路转接函数获得触发事件的请求(即套接字),根据触发事件类型顺序调用相应的处理函数

    以上就要求这些处理函数本身不能阻塞,或者说通过某些手段实现非阻塞

    常规的实现方式有:
    1. 通过线程池提交任务,执行CPU阻塞调用并提供回调
    2. 通过中间件实现任务异步化
    3. 通过再次注册事件

    所谓的高速服务器一般指处理请求本身,不包括服务器自身状态维护
    如Nginx、Redis,若要对服务器状态进行维护,一般会通过子进程或子线程等方式进行

    为什么多路转接速度快?

    首先从阻塞说起,网络IO造成的阻塞时间相比CPU处理速度来说,过于漫长,大部分时间都在等待网络IO

    通过多路转接,一旦发生IO阻塞,可以立即切换到其他事件处理函数中,使得处理效率提升

    其次是开销,传统通过进程、线程实现请求处理,让系统自动调度,小型应用完全可以接受

    一旦请求量提升或者说遇到高并发,进程/线程执行栈切换开销就很明显,另外进程/线程本身内存开销也很大

    早期解决方案是通过池化技术免去创建和销毁部分的开销,但依然无法解决高并发

    毕竟一个系统能创建的进程或线程数量不会太多,池化后可使用的量也不会很多,并发量上来问题还是明显

    一个解决的思路是分布式,利用LB做多机,但又引入了额外的分布式问题

    另一个解决思路是降低开销,将内核上的切换引入到用户态,降低切换开销,通过协程实现单线程并发

    单线程(协程)其实本质是一种由用户控制的切换,遇到IO就切换,让系统去等待数据并通知

    只要是遇到网络IO,基本离不开多路转接,因为没有比它更高效的轮询机制了

    用上了多路转接和协程,就能实现内存占用低的同时保证效率的提升(毕竟单线程实现),当然不使用协程也能得到足够的效率提升

    毕竟实现协程还要连带实现一系列的相关机制,往往线程池就足以

    另外谈谈Golang的goroutine

    虽然也叫协程,但Go语言本身的设计架构是M:N的

    有一个runtime负责逻辑线程到物理线程的映射,在Go中,提交一个G(goroutine),会被添加到P(processor)的一个队列中,P从队列中循环取出goroutine执行,而P又与M进行映射,P就是逻辑线程,M则是物理线程

    等于说,Go从语言层面实现了多核心并发,并且因为执行栈都是在用户态管理(半用户态,实际是runtime维护了一个内存池),遇到网络IO阻塞,goroutine会被runtime从P队列中取出挂起,通过专门负责网络IO的G去多路转接监听事件,当事件到达则将这个goroutine按某个策略重新添加到某个P的队列(runtime的调度策略)

    从执行流的角度理解,每个goroutine相当于一个执行流,一个P对应一组执行流,物理线程M上运行P,运行过程中遇到网络IO阻塞或其他调度权转让(从goroutine到goroutine),由runtime负责执行流的跳转,对物理线程M是无感知的

  • 相关阅读:
    Spring4
    Mysql索引优化、JMM内存模型
    深入浅出JVM
    ZGC垃圾搜集器
    Hibernate4基础
    红楼梦 各版本及资料
    JavaME环境配置
    5.1 Intent
    4.3 异步任务
    4.2.2 网络编程之Socket
  • 原文地址:https://www.cnblogs.com/ikct2017/p/13143446.html
Copyright © 2020-2023  润新知