网站管理公司排名,广西桂林公司,物流公司网站怎么做,阿里买域名 电脑做网站转载:闲话高并发的那些神话#xff0c;看京东架构师如何把它拉下神坛 高并发也算是这几年的热门词汇了#xff0c;尤其在互联网圈#xff0c;开口不聊个高并发问题#xff0c;都不好意思出门。高并发有那么邪乎吗#xff1f;动不动就千万并发、亿级流量#xff0c;听上去… 转载:闲话高并发的那些神话看京东架构师如何把它拉下神坛 高并发也算是这几年的热门词汇了尤其在互联网圈开口不聊个高并发问题都不好意思出门。高并发有那么邪乎吗动不动就千万并发、亿级流量听上去的确挺吓人。但仔细想想这么大的并发与流量不都是通过路由器来的吗 0x00 一切源自网卡 高并发的流量通过低调的路由器进入我们系统第一道关卡就是网卡网卡怎么抗住高并发这个问题压根就不存在千万并发在网卡看来一样一样的都是电信号网卡眼里根本区分不出来你是千万并发还是一股洪流所以衡量网卡牛不牛都说带宽从来没有并发量的说法。 网卡位于物理层和链路层最终把数据传递给网络层IP层在网络层有了IP地址已经可以识别出你是千万并发了所以搞网络层的可以自豪的说我解决了高并发问题可以出来吹吹牛了。谁没事搞网络层呢主角就是路由器这玩意主要就是玩儿网络层。 0x01 一头雾水 非专业的我们一般都把网络层IP层和传输层TCP层放到一起操作系统提供对我们是透明的很低调、很靠谱以至于我们都把他忽略了。 吹过的牛是从应用层开始的应用层一切都源于Socket那些千万并发最终会经过传输层变成千万个Socket那些吹过的牛不过就是如何快速处理这些Socket。处理IP层数据和处理Socket究竟有啥不同呢 0x02 没有连接就没用等待 最重要的一个不同就是IP层不是面向连接的而Socket是面向连接的IP层没有连接的概念在IP层来一个数据包就处理一个不用瞻前也不用顾后而处理Socket必须瞻前顾后Socket是面向连接的有上下文的读到一句我爱你激动半天你不前前后后地看看就是瞎激动了。 你想前前后后地看明白就要占用更多的内存去记忆就要占用更长的时间去等待不同连接要搞好隔离就要分配不同的线程或者协程。所有这些都解决好貌似还是有点难度的。 0x03 感谢操作系统 操作系统是个好东西在Linux系统上所有的IO都被抽象成了文件网络IO也不例外被抽象成Socket但是Socket还不仅是一个IO的抽象它同时还抽象了如何处理Socket最著名的就是select和epoll了知名的nginx、netty、redis都是基于epoll搞的这仨家伙基本上是在千万并发领域必备神技。 但是多年前Linux只提供了select的这种模式能处理的并发量非常小而epoll是专为高并发而生的感谢操作系统。不过操作系统没有解决高并发的所有问题只是让数据快速地从网卡流入我们的应用程序如何处理才是老大难。 操作系统的使命之一就是最大限度的发挥硬件的能力解决高并发问题这也是最直接、最有效的方案其次才是分布式计算。前面我们提到的nginx、netty、redis都是最大限度发挥硬件能力的典范。如何才能最大限度的发挥硬件能力呢 0x04 核心矛盾 要最大限度的发挥硬件能力首先要找到核心矛盾所在。我认为这个核心矛盾从计算机诞生之初直到现在几乎没有发生变化就是CPU和IO之间的矛盾。 CPU以摩尔定律的速度野蛮发展而IO设备磁盘网卡却乏善可陈。龟速的IO设备成为性能瓶颈必然导致CPU的利用率很低所以提升CPU利用率几乎成了发挥硬件能力的代名词。 0x05 中断与缓存 CPU与IO设备的协作基本都是以中断的方式进行的例如读磁盘的操作CPU仅仅是发一条读磁盘到内存的指令给磁盘驱动之后就立即返回了此时CPU可以接着干其他事情读磁盘到内存本身是个很耗时的工作等磁盘驱动执行完指令会发个中断请求给CPU告诉CPU任务已经完成CPU处理中断请求此时CPU可以直接操作读到内存的数据。 中断机制让CPU以最小的代价处理IO问题那如何提高设备的利用率呢答案就是缓存。 操作系统内部维护了IO设备数据的缓存包括读缓存和写缓存读缓存很容易理解我们经常在应用层使用缓存目的就是尽量避免产生读IO。 写缓存应用层使用的不多操作系统的写缓存完全是为了提高IO写的效率。操作系统在写IO的时候会对缓存进行合并和调度例如写磁盘会用到电梯调度算法。 0x06 高效利用网卡 高并发问题首先要解决的是如何高效利用网卡。网卡和磁盘一样内部也是有缓存的网卡接收网络数据先存放到网卡缓存然后写入操作系统的内核空间内存我们的应用程序则读取内存中的数据然后处理。 除了网卡有缓存外TCP/IP协议内部还有发送缓冲区和接收缓冲区以及SYN积压队列、accept积压队列。 这些缓存如果配置不合适则会出现各种问题。例如在TCP建立连接阶段如果并发量过大而nginx里面socket的backlog设置的值太小就会导致大量连接请求失败。 如果网卡的缓存太小当缓存满了后网卡会直接把新接收的数据丢掉造成丢包。当然如果我们的应用读取网络IO数据的效率不高会加速网卡缓存数据的堆积。如何高效读取网络数据呢目前在Linux上广泛应用的就是epoll了。 操作系统把IO设备抽象为文件网络被抽象成了SocketSocket本身也是一个文件所以可以用read/write方法来读取和发送网络数据。在高并发场景下如何高效利用Socket快速读取和发送网络数据呢 要想高效利用IO就必须在操作系统层面了解IO模型在《UNIX网络编程》这本经典著作里总结了五种IO模型分别是阻塞式IO非阻塞式IO多路复用IO信号驱动IO和异步IO。 0x07 阻塞式IO 我们以读操作为例当我们调用read方法读取Socket上的数据时如果此时Socket读缓存是空的没有数据从Socket的另一端发过来操作系统会把调用read方法的线程挂起直到Socket读缓存里有数据时操作系统再把该线程唤醒。 当然在唤醒的同时read方法也返回了数据。我理解所谓的阻塞就是操作系统是否会挂起线程。 0x08 非阻塞式IO 而对于非阻塞式IO如果Socket的读缓存是空的操作系统并不会把调用read方法的线程挂起而是立即返回一个EAGAIN的错误码在这种情景下可以轮询read方法直到Socket的读缓存有数据则可以读到数据这种方式的缺点非常明显就是消耗大量的CPU。 0x09 多路复用IO 对于阻塞式IO由于操作系统会挂起调用线程所以如果想同时处理多个Socket就必须相应地创建多个线程线程会消耗内存增加操作系统进行线程切换的负载所以这种模式不适合高并发场景。有没有办法较少线程数呢 非阻塞IO貌似可以解决在一个线程里轮询多个Socket看上去可以解决线程数的问题但实际上这个方案是无效的原因是调用read方法是一个系统调用系统调用是通过软中断实现的会导致进行用户态和内核态的切换所以很慢。 但是这个思路是对的有没有办法避免系统调用呢有就是多路复用IO。 在Linux系统上select/epoll这俩系统API支持多路复用IO通过这两个API一个系统调用可以监控多个Socket只要有一个Socket的读缓存有数据了方法就立即返回然后你就可以去读这个可读的Socket了如果所有的Socket读缓存都是空的则会阻塞也就是将调用select/epoll的线程挂起。 所以select/epoll本质上也是阻塞式IO只不过他们可以同时监控多个Socket。 0x0A select和epoll的区别 为什么多路复用IO模型有两个系统API我分析原因是select是POSIX标准中定义的但是性能不够好所以各个操作系统都推出了性能更好的API如Linux上的epoll、Windows上的IOCP。 至于select为什么会慢大家比较认可的原因有两点一点是select方法返回后需要遍历所有监控的Socket而不是发生变化的Ssocket还有一点是每次调用select方法都需要在用户态和内核态拷贝文件描述符的位图通过调用三次copy_from_user方法拷贝读、写、异常三个位图。epoll可以避免上面提到的这两点。 0x0B Reactor多线程模型 在Linux操作系统上性能最为可靠、稳定的IO模式就是多路复用我们的应用如何能够利用好多路复用IO呢经过前人多年实践总结搞了一个Reactor模式目前应用非常广泛著名的Netty、Tomcat NIO就是基于这个模式。 Reactor的核心是事件分发器和事件处理器事件分发器是连接多路复用IO和网络数据处理的中枢核心就是监听Socket事件select/epoll_wait然后将事件分发给事件处理器事件分发器和事件处理器都可以基于线程池来做。 需要重点提一下的是在Socket事件中主要有两大类事件一个是连接请求另一个是读写请求连接请求成功处理之后会创建新的Socket读写请求都是基于这个新创建的Socket。 所以在网络处理场景中实现Reactor模式会稍微有点绕但是原理没有变化。具体实现可以参考Doug Lea的《Scalable IO in Java》http://gee.cs.oswego.edu/dl/cpjslides/nio.pdf Reactor原理图 0x0C Nginx多进程模型 Nginx默认采用的是多进程模型Nginx分为Master进程和Worker进程真正负责监听网络请求并处理请求的只有Worker进程所有的Worker进程都监听默认的80端口但是每个请求只会被一个Worker进程处理。 这里面的玄机是每个进程在accept请求前必须争抢一把锁得到锁的进程才有权处理当前的网络请求。每个Worker进程只有一个主线程单线程的好处是无锁处理无锁处理并发请求这基本上是高并发场景里面的最高境界了。参考http://www.dre.vanderbilt.edu/~schmidt/PDF/reactor-siemens.pdf 数据经过网卡、操作系统、网络协议中间件Tomcat、Netty等重重关卡终于到了我们应用开发人员手里我们如何处理这些高并发的请求呢我们还是先从提升单机处理能力的角度来思考这个问题。 0x0D 突破木桶理论 据经过网卡、操作系统、中间件Tomcat、Netty等重重关卡终于到了我们应用开发人员手里我们如何处理这些高并发的请求呢 我们还是先从提升单机处理能力的角度来思考这个问题在实际应用的场景中问题的焦点是如何提高CPU的利用率谁叫它发展的最快呢木桶理论讲最短的那根板决定水位那为啥不是提高短板IO的利用率而是去提高CPU的利用率呢 这个问题的答案是在实际应用中提高了CPU的利用率往往会同时提高IO的利用率。当然在IO利用率已经接近极限的条件下再提高CPU利用率是没有意义的。我们先来看看如何提高CPU的利用率后面再看如何提高IO的利用率。 0x0E 并行与并发 提升CPU利用率目前主要的方法是利用CPU的多核进行并行计算并行和并发是有区别的在单核CPU上我们可以一边听MP3一边Coding这个是并发但不是并行因为在单核CPU的视野听MP3和Coding是不可能同时进行的。 只有在多核时代才会有并行计算。并行计算这东西太高级工业化应用的模型主要有两种一种是共享内存模型另外一种是消息传递模型。 0x0F 多线程设计模式 对于共享内存模型其原理基本都来自大师Dijkstra在半个世纪前1965的一篇论文《Cooperating sequential processes》这篇论文提出了大名鼎鼎的概念信号量Java里面用于线程同步的wait/notify也是信号量的一种实现。 大师的东西看不懂学不会也不用觉得丢人毕竟大师的嫡传子弟也没几个。东洋有个叫结城浩的总结了一下多线程编程的经验写了本书叫《JAVA多线程设计模式》这个还是挺接地气能看懂的。下面简单介绍一下。 1. Single Threaded Execution 这个模式是把多线程变成单线程多线程在同时访问一个变量时会发生各种莫名其妙的问题这个设计模式直接把多线程搞成了单线程于是安全了当然性能也就下来了。最简单的实现就是利用synchronized将存在安全隐患的代码块方法保护起来。在并发领域有个临界区criticalsections的概念我感觉和这个模式是一回事。 2. Immutable Pattern 如果共享变量永远不变那就多个线程访问就没有任何问题永远安全。这个模式虽然简单但是用的好能解决很多问题。 3. Guarded Suspension Patten 这个模式其实就是等待-通知模型当线程执行条件不满足时挂起当前线程等待当条件满足时唤醒所有等待的线程通知在Java语言里利用synchronizedwait/notifyAll可以很快实现一个等待通知模型。结城浩将这个模式总结为多线程版的If我觉得非常贴切。 4. Balking 这个模式和上个模式类似不同点是当线程执行条件不满足时直接退出而不是像上个模式那样挂起。这个用法最大的应用场景是多线程版的单例模式当对象已经创建了不满足创建对象的条件就不用再创建对象退出。 5. Producer-Consumer 生产者-消费者模式全世界人都知道。我接触的最多的是一个线程处理IO如查询数据库一个或者多个线程处理IO数据这样IO和CPU就都能成分利用起来。如果生产者和消费者都是CPU密集型再搞生产者-消费者就是自己给自己找麻烦了。 6. Read-Write Lock 读写锁解决的读多写少场景下的性能问题支持并行读但是写操作只允许一个线程做。如果写操作非常非常少而读的并发量非常非常大这个时候可以考虑使用写时复制copy on write技术我个人觉得应该单独把写时复制单独作为一个模式。 7. Thread-Per-Message 就是我们经常提到的一请求一线程。 8. Worker Thread 一请求一线程的升级版利用线程池解决线程的频繁创建、销毁导致的性能问题。BIO年代Tomcat就是用的这种模式。 9. Future 当你调用某个耗时的同步方法很心烦想同时干点别的事情可以考虑用这个模式这个模式的本质是个同步变异步的转换器。同步之所以能变异步本质上是启动了另外一个线程所以这个模式和一请求一线程还是多少有点关系的。 10. Two-Phase Termination 这个模式能解决优雅地终止线程的需求。 11. Thread-Specific Storage 线程本地存储避免加锁、解锁开销的利器C#里面有个支持并发的容器ConcurrentBag就是采用了这个模式这个星球上最快的数据库连接池HikariCP借鉴了ConcurrentBag的实现搞了个Java版的有兴趣的同学可以参考。 12. Active Object这个不讲也罢 这个模式相当于降龙十八掌的最后一掌综合了前面的设计模式有点复杂个人觉得借鉴的意义大于参考实现。 最近国人也出过几本相关的书但总体还是结城浩这本更能经得住推敲。基于共享内存模型解决并发问题主要问题就是用好锁但是用好锁还是有难度的所以后来又有人搞了消息传递模型这个后面再聊。 基于共享内存模型解决并发问题主要问题就是用好锁但是用好锁还是有难度的所以后来又有人搞了消息传递模型。 0x10 消息传递模型 共享内存模型难度还是挺大的而且你没有办法从理论上证明写的程序是正确的我们总一不小心就会写出来个死锁的程序来每当有了问题总会有大师出来于是消息传递Message-Passing模型横空出世发生在上个世纪70年代消息传递模型有两个重要的分支一个是Actor模型一个是CSP模型。 0x11 Actor模型 Actor模型因为Erlang声名鹊起后来又出现了Akka。在Actor模型里面没有操作系统里所谓进程、线程的概念一切都是Actor我们可以把Actor想象成一个更全能、更好用的线程。 在Actor内部是线性处理单线程的Actor之间以消息方式交互也就是不允许Actor之间共享数据没有共享就无需用锁这就避免了锁带来的各种副作用。 Actor的创建和new一个对象没有啥区别很快、很小不像线程的创建又慢又耗资源Actor的调度也不像线程会导致操作系统上下文切换主要是各种寄存器的保存、恢复所以调度的消耗也很小。 Actor还有一个有点争议的优点Actor模型更接近现实世界现实世界也是分布式的、异步的、基于消息的、尤其Actor对于异常失败的处理、自愈、监控等都更符合现实世界的逻辑。 但是这个优点改变了编程的思维习惯我们目前大部分编程思维习惯其实是和现实世界有很多差异的这个回头再细说一般来讲改变我们思维习惯的事情阻力总是超乎我们的想象。 0x12 CSP模型 Golang在语言层面支持CSP模型CSP模型和Actor模型的一个感官上的区别是在CSP模型里面生产者消息发送方和消费者消息接收方是完全松耦合的生产者完全不知道消费者的存在但是在Actor模型里面生产者必须知道消费者否则没办法发送消息。 CSP模型类似于我们在多线程里面提到的生产者-消费者模型核心的区别我觉得在于CSP模型里面有类似绿色线程green thread的东西绿色线程在Golang里面叫做协程协程同样是个非常轻量级的调度单元可以快速创建而且资源占用很低。 Actor在某种程度上需要改变我们的思维方式而CSP模型貌似没有那么大动静更容易被现在的开发人员接受都说Golang是工程化的语言在Actor和CSP的选择上也可以看到这种体现。 0x13 多样世界 除了消息传递模型还有事件驱动模型、函数式模型。事件驱动模型类似于观察者模式在Actor模型里面消息的生产者必须知道消费者才能发送消息而在事件驱动模型里面事件的消费者必须知道消息的生产者才能注册事件处理逻辑。 Akka里消费者可以跨网络事件驱动模型的具体实现如Vertx里消费者也可以订阅跨网络的事件从这个角度看大家都在取长补短。 作者meng_philip123链接https://www.jianshu.com/p/51523ecae414來源简书简书著作权归作者所有任何形式的转载都请联系作者获得授权并注明出处。转载于:https://www.cnblogs.com/DreamRecorder/p/9242911.html