跟我学代码架构设计模式之--高并发高吞吐模型演变

高并发、高吞吐是我们追求的目标,最大化压榨单机的性能来达到更多更快的服务。

首先说下,为什么会有这么多模型演变,本质原因是因为操作系统底层提供的线程模型不够牛逼,不能满足我们的服务需求!

模型演变如下:

第一代:操作系统线程无限开模型

基本原理是业务请求来了以后直接开启一个线程进行后台处理,业务中包含阻塞等待的时候,直接挂起操作系统线程。

这种模型的缺点是明显的,本质原因是操作系统的线程开销很大: 一个操作系统线程占用内存比较大,单机内存量有限,无边界无限开很快会导致内存溢出、服务崩溃。如果业务操作中有阻塞操作API,那么占用的操作系统线程得不到释放,后续的业务请求会继续开新线程,导致同一时间范围内会有巨量操作系统线程存在,很快就会导致内存溢出,即使内存不溢出,大量的线程会导致操作系统忙于线程上下文切换,效率急剧下降!

第二代:线程池模型

基本原理是不再像第一代模型一样无限多开线程,而是采用包含有限个线程的线程池来处理业务请求,业务来了以后先投入线程池的任务队列中排队等待池中线程执行,但是业务中包含阻塞等待API的时候,也是直接挂起操作系统线程。

这种模型解决了第一代模型中线程无限开导致的服务崩溃、服务不可用问题,但是由于业务阻塞同样会挂起操作系统线程,如果同一时间段内业务请求比较多,池中线程都阻塞在业务上无法及时释放,后续的业务请求会在队列中排队,导致系统吞吐量急剧下降甚至客户端超时不可用!

第三代:业务拆分+事件发生器模型

基本原理是同样采用线程池+任务队列的"模型核心",但是与第二代模型不同的是避免了"模型核心"线程池中的线程执行业务阻塞API调用时直接阻塞操作系统线程。但是这个方案的"模型核心"非阻塞需要我们开发人员通过精心设计业务代码来保证,具体实现方案是:

1 "模型核心"提供业务线程池+任务队列供业务使用

2 我们业务实现的时候要负责把业务拆分多个无阻塞的业务片段

3 提供"事件发生器"组件,事件来耦合一个业务的多个片段

4 业务编程模型或者说编程思想要变成对API可能发生的事件注册事件处理器的模式(也可以说是注册回调的模式),而不是传统的同步等待返回模式的API。

一个需要注意的点是:对于业务拆分,由于历史原因,我们引用的自己系统边界外的依赖库API、甚至操作系统API可能没有实现事件通知机制(或者说API没有提供了异步事件通知回调,而是同步阻塞API),即没有自带"事件发生器",这就导致我们可能无法把阻塞的操作片段从业务中去除!所以,我们应该设计一个"事件发生器兼容层"来实现我们的"模型核心"不受阻塞,进而模型核心不会阻塞操作系统线程,进而保证了高吞吐量!如何引入兼容层说明如下:

1 如果我们系统边界外的API提供了事件发生器(或者说API提供了异步事件通知回调),那么我们可以直接利用这些API的消息回调耦合自己的业务片段来达到我们的"模型核心"不阻塞操作系统线程!比如netty、javaNIO等框架实现了自己的事件发生器,可以直接提供给我们"模型核心"IO事件通知,那么我的"模型核心"依赖netty做IO的时候就不用封装nettyIO这块的"事件通知了"。

2 如果我们系统边界外的API没有提供事件发生器来发出事件通知(或者说API是同步阻塞API),这时候我们系统内就要提供一个自己的“兼容层事件通知发生器”,通常是设计一个执行阻塞任务的线程池+额外的超时机制Timer来实现"事件发生器",这个发生器和我们的"模型核心"线程池进行消息交互,保证了我们的"模型核心"不受阻塞,进而模型核心不会阻塞操作系统线程,进而一定程度上(事件发生器兼容层有阻塞操作系统线程)保证了高吞吐量!一个例子就是JDBC是阻塞的非事件的API,我们需要把JDBC相关的操作放到兼容层封装为事件API来和"模型核心"进行事件通知。

架构图:

该模型的总结:该模型核心必须完全执行非阻塞的事件通知模式的API的业务片段,最理想的情况也就是吞吐量最高的情况就是该模型核心所执行的业务片段所依赖的外部API都是非阻塞的事件通知形式的API!

第四代:在操作系统线程之上再封装应用层"线程调度"机制的模型

基本原理是在应用层实现"线程调度",解决了操作系统线程调度的痛点,即应用层线程调度应该实现内存占用少,上下文切换开销少等功能,此外应用层线程调度器来保证不阻塞对应的底层操作系统线程。

我们在这里把应用层线称为"协程"。我们的业务层在编写业务代码的时候就应该以协程作为并发单位,并且在协程中允许业务代码中包含阻塞的API调用,业务请求到达后要封装成协程任务,然后交给协程调度器进行调度执行。

实际上这种编程模式就退化到了第一代模型了,只是由于协程相比操作系统线程内存和切换开销大大减少,我们就可以不用担心协程数量过多导致的内存溢出和调度开销以及协程中业务阻塞导致的整体吞吐量下降了!一句话:想并发和高吞吐,随心所欲的开协程就好了~

最主要的好处还是:我们再也不用像第三代模型一样在业务代码层做非阻塞业务片段拆分了,直接使用人类易于理解的同步编程模型即可。

第五代:操作系统线程调度重新实现,满足巨量线程同时调度,编程模型退化为第一代、第二代~

总结:

第一代模型有操作系统线程扩张和线程阻塞问题

第二代模型解决了操作系统线程扩展问题,没有解决线程阻塞问题

第三代模型解决了操作系统线程阻塞问题,带来了业务非阻塞化拆分导致的编程复杂度提升

第四代解决了操作系统线程阻塞问题,也允许业务中存在阻塞代码,解决了第三代的编程复杂性问题

(完)

发布了63 篇原创文章 · 获赞 25 · 访问量 8万+

猜你喜欢

转载自blog.csdn.net/w1857518575/article/details/86663465