Loading [MathJax]/jax/output/CommonHTML/config.js
前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >线程框架模型总结

线程框架模型总结

作者头像
章鱼carl
发布于 2022-03-31 03:28:01
发布于 2022-03-31 03:28:01
83800
代码可运行
举报
文章被收录于专栏:章鱼carl的专栏章鱼carl的专栏
运行总次数:0
代码可运行

本篇对笔者接触过的线程框架模型做一个概括性的总结。

主要介绍三种模型:

1. Disruptor:Apache Storm底层应用了Disruptor来实现worker内部的线程通信;

2. Reactor:Apache Netty整体架构基于Reactor模式;

3. Actor:Akka是在JVM上的Actor模型的实现。而Apache Flink的RPC框架是基于Akka实现的,之后任务执行框架修改为基于Actor的Mailbox模型;

Disruptor

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
https://lmax-exchange.github.io/disruptor/user-guide/index.html
https://github.com/LMAX-Exchange/disruptor

LMAX Disruptor 是一个高性能的线程间消息库。它起源于 LMAX 对并发性、性能和非阻塞算法的研究,如今已成为 Exchange 基础设施的核心部分。

Disruptor 是一个提供并发环形缓冲区数据结构的库。它被设计为在异步事件处理架构中提供低延迟、高吞吐量的工作队列。

核心抽象

1. RingBuffer——Disruptor底层数据结构实现,核心类,是线程间交换数据的中转地;

2. Sequencer——序号管理器,生产同步的实现者,负责消费者/生产者各自序号、序号栅栏的管理和协调,Sequencer有单生产者,多生产者两种不同的模式,里面实现了各种同步的算法;

3. Sequence——序号,声明一个序号,用于跟踪RingBuffer中任务的变化和消费者的消费情况,Disruptor里面大部分的并发代码都是通过对Sequence的值同步修改实现的,而非锁,这是Disruptor高性能的一个主要原因;

4. SequenceBarrier——序号栅栏,管理和协调生产者的游标序号和各个消费者的序号,确保生产者不会覆盖消费者未来得及处理的消息,确保存在依赖的消费者之间能够按照正确的顺序处理

5. EventProcessor——事件处理器,监听RingBuffer的事件,并消费可用事件,从RingBuffer读取的事件会交由实际的生产者实现类来消费;它会一直侦听下一个可用的序号,直到该序号对应的事件已经准备好。

6. EventHandler——业务处理器,是实际消费者的接口,完成具体的业务逻辑实现,第三方实现该接口;代表着消费者。

7. Producer——生产者接口,第三方线程充当该角色,producer向RingBuffer写入事件。

8. Wait Strategy——Wait Strategy决定了一个消费者怎么等待生产者将事件(Event)放入Disruptor中。

Java内置了几种内存消息队列,如下所示:

我们知道CAS算法比通过加锁实现同步性能高很多,而上表可以看出基于CAS实现的队列都是无界的,而有界队列是通过同步实现的。在系统稳定性要求比较高的场景下,为了防止生产者速度过快,如果采用无界队列会最终导致内存溢出,只能选择有界队列。

而有界队列只有ArrayBlockingQueue,该队列是通过加锁实现的,在请求锁和释放锁时对性能开销很大,这时候基于有界队列的高性能的Disruptor就应运而生。

Disruptord的高性能之道

1. 环形数据结构

为了避免垃圾回收,采用数组而非链表,本质是对象资源复用技术。同时,数组对处理器的缓存机制更加友好。

2. 元素位置定位

数组长度2^n,通过位运算,加快定位的速度。下标采取递增的形式。不用担心index溢出的问题。index是long类型,即使100万QPS的处理速度,也需要30万年才能用完。

3. 无锁设计

每个生产者或者消费者线程,会先申请可以操作的元素在数组中的位置,申请到之后,直接在该位置写入或者读取数据。整个过程通过原子变量CAS,保证操作的线程安全。

Reactor


代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
https://netty.io/
https://github.com/netty/netty

这个模式从Java NIO中来,是一种基于事件驱动的设计模式。Doug Lea(JUC并发包的作者)的"Scalable IO in Java"中阐述了Reactor模式。

Scalable IO in Java 地址:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
http://gee.cs.oswego.edu/dl/cpjslides/nio.pdf

演进过程

最最原始的网络编程思路就是服务器用一个while循环,不断监听端口是否有新的套接字连接,如果有,那么就调用一个处理函数处理,类似:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
while(true){
socket = accept();
    handle(socket)
}

这种方法的最大问题是无法并发,效率太低,如果当前的请求没有处理完,那么后面的请求只能被阻塞,服务器的吞吐量太低。

之后,想到了使用多线程,也就是很经典的connection per thread,每一个连接用一个线程处理,tomcat服务器的早期版本确实是这样实现的。

优点:

一定程度上极大地提高了服务器的吞吐量,因为之前的请求在read阻塞以后,不会影响到后续的请求,因为他们在不同的线程中。

缺点:

缺点在于资源要求太高,系统中创建线程是需要比较高的系统资源的,如果连接数太高,系统无法承受,而且,线程的反复创建-销毁也需要代价。

单线程Reactor

抽象出来两个组件——Reactor和Handler两个组件:

(1) Reactor:负责响应IO事件,当检测到一个新的事件,将其发送给相应的Handler去处理;新的事件包含连接建立就绪、读就绪、写就绪等。

(2) Handler:将自身(handler)与事件绑定,负责事件的处理,完成channel的读入,完成处理业务逻辑后,负责将结果写出channel。

缺点:

当其中某个 handler 阻塞时,会导致其他所有的client 的 handler 都得不到执行,并且更严重的是,handler 的阻塞也会导致整个服务不能接收新的 client 请求(因为 acceptor 也被阻塞了)。 因为有这么多的缺陷, 因此单线程Reactor 模型用的比较少。这种单线程模型不能充分利用多核资源,所以实际使用的不多。因此,单线程模型仅仅适用于handler 中业务处理组件能快速完成的场景。

多线程Reactor

在单线程Reactor模式基础上,做如下改进:

1. 将Handler处理器的执行放入线程池,多线程进行业务处理。

2. 对于Reactor而言,可以仍为单个线程。如果服务器为多核的CPU,为充分利用系统资源,可以将Reactor拆分为两个线程。

Reactor优缺点

优点:

(1) 响应快,不必为单个同步时间所阻塞,虽然Reactor本身依然是同步的;

(2) 编程相对简单,可以最大程度的避免复杂的多线程及同步问题,并且避免了多线程/进程的切换开销;

(3) 可扩展性,可以方便的通过增加Reactor实例个数来充分利用CPU资源;

可复用性,reactor框架本身与具体事件处理逻辑无关,具有很高的复用性;

缺点:

(1) 相比传统的简单模型,Reactor增加了一定的复杂性,因而有一定的门槛,并且不易于调试。

(2) Reactor模式需要底层的SynchronousEvent Demultiplexer支持,比如Java中的Selector支持,操作系统的select系统调用支持,如果要自己实现Synchronous Event Demultiplexer可能不会有那么高效。

(3) Reactor模式在IO读写数据时还是在同一个线程中实现的,即使使用多个Reactor机制的情况下,那些共享一个Reactor的Channel如果出现一个长时间的数据读写,会影响这个Reactor中其他Channel的相应时间,比如在大文件传输时,IO操作就会影响其他Client的相应时间,因而对这种操作,使用传统的Thread-Per-Connection或许是一个更好的选择,或者此时使用改进版的Reactor模式如Proactor模式。

Reactor vs Proactor模型:

Reactor模型:

1 向事件分发器注册事件回调

2 事件发生

3 事件分发器调用之前注册的函数

4 在回调函数中读取数据,对数据进行后续处理

Proactor模型:

1 向事件分发器注册事件回调

2 事件发生

3 操作系统读取数据,并放入应用缓冲区,然后通知事件分发器

4 事件分发器调用之前注册的函数

5 在回调函数中对数据进行后续处理

以下是Netty中的Reactor模型:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
https://www.jianshu.com/p/0d0eece6d467

Actor

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
https://akka.io/
https://github.com/akka/akka

Carl Hewitt 在1973年对Actor模型进行了如下定义:"Actor模型是一个把'Actor'作为并发计算的通用原语". Actor是异步驱动,可以并行和分布式部署及运行的最小颗粒。也就是说,它可以被分配,分布,调度到不同的CPU,不同的节点,乃至不同的时间片上运行,而不影响最终的结果。因此Actor在空间(分布式)和时间(异步驱动)上解耦的。而Akka是Lightbend(前身是Typesafe)公司在JVM上的Actor模型的实现。我们在了解actor模型之前,首先来了解actor模型主要是为了解决什么样的问题。

在akka系统的官网上主要介绍了现代并发编程模型所遇到的问题,里面主要提到了三个点

(1) 在面向对象的语言中一个显著的特点是封装,然后通过对象提供的一些方法来操作其状态,但是共享内存的模型下,多线程对共享对象的并发访问会造成并发安全问题。一般会采用加锁的方式去解决

加锁会带来一些问题:

1. 加锁的开销很大,线程上下文切换的开销大

2. 加锁导致线程block,无法去执行其他的工作,被block无法执行的线程,其实也是占据了一种系统资源

3. 加锁在编程语言层面无法防止隐藏的死锁问题

(2) Java中并发模型是通过共享内存来实现,cpu中会利用cache来加速主存的访问,为了解决缓存不一致的问题,在java中一般会通过使用volatile来标记变量,让jmm的happens before机制来保障多线程间共享变量的可见性。因此从某种意义上来说是没有共享内存的,而是通过cpu将cache line的数据刷新到主存的方式来实现可见。因此与其去通过标记共享变量或者加锁的方式,依赖cpu缓存更新,倒不如每个并发实例之间只保存local的变量,而在不同的实例之间通过message来传递。

(3) call stack的问题 当我们编程模型异步化之后,还有一个比较大的问题是调用栈转移的问题,如下图中主线程提交了一个异步任务到队列中,worker thread 从队列提取任务执行,调用栈就变成了workthread发起的,当任务出现异常时,处理和排查就变得困难。

那么akka 的actor的模型是怎样处理这些问题的?

actor通过消息传递的方式与外界通信。消息传递是异步的。每个actor都有一个邮箱,该邮箱接收并缓存其他actor发过来的消息,actor一次只能同步处理一个消息,处理消息过程中,除了可以接收消息,不能做任何其他操作。

Actor模型的另一个好处就是可以消除共享状态,因为它每次只能处理一条消息,所以actor内部可以安全的处理状态,而不用考虑锁机制

(1) actor之间可以互相发送message。

(2) actor在收到message之后会将其存入其绑定的Mailbox中。

(3) Actor中Mailbox中提取消息,执行内部方法,修改内部状态。

(4) 继续给其他actor发送message。

可以看到下图,actor内部的执行流程是顺序的,同一时刻只有一个message在进行处理,也就是actor的内部逻辑可以实现无锁化的编程。actor和线程数解耦,可以创建很多actor绑定一个线程池来进行处理,no lock,no block的方式能减少资源开销,并提升并发的性能

参考

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
1  https://developer.aliyun.com/article/616952?spm=a2c6h.13262185.0.0.4ce163f8Bh85tc

2  https://zhuanlan.zhihu.com/p/404668883

3  https://www.jianshu.com/p/e48d83e39a2f

4  https://zhuanlan.zhihu.com/p/229338771
本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2022-02-24,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 章鱼沉思录 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
暂无评论
推荐阅读
编辑精选文章
换一批
分布式系统模式8-Singular Update Queue
来源: https://martinfowler.com/articles/patterns-of-distributed-systems/
java达人
2021/01/05
6670
分布式系统模式8-Singular Update Queue
Disruptor学习笔记
以前一直听说有Disruptor这个东西,都是性能很强大,所以这几天自己也看了一下。 下面是自己的学习笔记,另外推荐几篇自己看到写的比较好的博客: Disruptor——一种可替代有界队列完成并发线程间数据交换的高性能解决方案 Disruptor3.0的实现细节
一枝花算不算浪漫
2018/12/24
8070
disruptor (史上最全)[通俗易懂]
文章很长,而且持续更新,建议收藏起来,慢慢读!疯狂创客圈总目录 语雀版 | 总目录 码云版| 总目录 博客园版 为您奉上珍贵的学习资源 :
全栈程序员站长
2022/09/07
1.7K0
disruptor (史上最全)[通俗易懂]
Spring Boot + Disruptor = 王炸!!
工作中遇到项目使用Disruptor做消息队列,对你没看错,不是Kafka,也不是rabbitmq;Disruptor有个最大的优点就是快,还有一点它是开源的哦,下面做个简单的记录.
Java技术栈
2023/02/27
6100
Spring Boot + Disruptor = 王炸!!
springboot 项目使用 Disruptor 做内部消息队列
点击上方“芋道源码”,选择“设为星标” 管她前浪,还是后浪? 能浪的浪,才是好浪! 每天 10:33 更新文章,每天掉亿点点头发... 源码精品专栏 原创 | Java 2021 超神之路,很肝~ 中文详细注释的开源项目 RPC 框架 Dubbo 源码解析 网络应用框架 Netty 源码解析 消息中间件 RocketMQ 源码解析 数据库中间件 Sharding-JDBC 和 MyCAT 源码解析 作业调度中间件 Elastic-Job 源码解析 分布式事务中间件 TCC-Transaction
芋道源码
2022/03/21
1K0
高性能无锁并发框架Disruptor,太强了
Disruptor是一个开源框架,研发的初衷是为了解决高并发下队列锁的问题,最早由LMAX提出并使用,能够在无锁的情况下实现队列的并发操作,并号称能够在一个线程里每秒处理6百万笔订单
愿天堂没有BUG
2022/09/08
1.5K0
高性能无锁并发框架Disruptor,太强了
闲话高并发的那些神话,看京东架构师如何把它拉下神坛
高并发也算是这几年的热门词汇了,尤其在互联网圈,开口不聊个高并发问题,都不好意思出门。高并发有那么邪乎吗?动不动就千万并发、亿级流量,听上去的确挺吓人。但仔细想想,这么大的并发与流量不都是通过路由器来的吗?
京东技术
2018/07/30
1.9K1
闲话高并发的那些神话,看京东架构师如何把它拉下神坛
鸟瞰 Java 并发框架
几年前 NoSQL 开始流行的时候,像其他团队一样,我们的团队也热衷于令人兴奋的新东西,并且计划替换一个应用程序的数据库。 但是,当深入实现细节时,我们想起了一位智者曾经说过的话:“细节决定成败”。最终我们意识到 NoSQL 不是解决所有问题的银弹,而 NoSQL vs RDMS 的答案是:“视情况而定”。 类似地,去年RxJava 和 Spring Reactor 这样的并发库加入了让人充满激情的语句,如异步非阻塞方法等。为了避免再犯同样的错误,我们尝试评估诸如 ExecutorService、 RxJava、Disruptor 和 Akka 这些并发框架彼此之间的差异,以及如何确定各自框架的正确用法。
芋道源码
2019/06/21
1.1K0
鸟瞰 Java 并发框架
高性能无锁并发框架 Disruptor,太强了!
Disruptor是一个开源框架,研发的初衷是为了解决高并发下队列锁的问题,最早由LMAX提出并使用,能够在无锁的情况下实现队列的并发操作,并号称能够在一个线程里每秒处理6百万笔订单
Java技术栈
2020/09/22
3.5K0
高性能无锁并发框架 Disruptor,太强了!
一种高效无锁内存队列的实现
Disruptor是LMAX公司开源的一个高效的内存无锁队列。这两天看了一下相关的设计文档和博客,下面尝试进行一下总结。 第一部分。引子 谈到并发程序设计,有几个概念是避免不了的。 1.锁:锁是用来做并发最简单的方式,当然其代价也是最高的。内核态的锁的时候需要操作系统进行一次上下文切换,等待锁的线程会被挂起直至锁释放。在上下文切换的时候,cpu之前缓存的指令和数据都将失效,对性能有很大的损失。用户态的锁虽然避免了这些问题,但是其实它们只是在没有真实的竞争时才有效。下面是一个计数实验中不加锁、使用锁、使用CA
李海彬
2018/03/23
4.5K1
一种高效无锁内存队列的实现
高吞吐框架Disruptor应用场景
多年前在并发编程网http://ifeve.com/disruptor了解到了自认为是黑科技的并发框架DISRUPTOR, 我当时在想NETTY为什么没有和它整合。后来了解过的log4j2, jstorm也慢慢有用到, 而一直以来也并没有机会去使用和了解细节, 大多时候觉得Doug Lea的JDK并发包也足够使用。而近期业务需要基于NETTY简单裹了一个类似vertx的luoying-server https://github.com/zealzeng/luoying-server 业务处理线程不适合在event loop中处理, 简单用有界的ThreadPoolExecutor作为worker pool, 想考虑把disruptor整合进来, 看了两天发觉对disruptor的使用场景产生了误解。
Zeal
2020/11/11
5K0
高性能队列——Disruptor
背景 Disruptor是英国外汇交易公司LMAX开发的一个高性能队列,研发的初衷是解决内存队列的延迟问题(在性能测试中发现竟然与I/O操作处于同样的数量级)。基于Disruptor开发的系统单线程能支撑每秒600万订单,2010年在QCon演讲后,获得了业界关注。2011年,企业应用软件专家Martin Fowler专门撰写长文介绍。同年它还获得了Oracle官方的Duke大奖。 目前,包括Apache Storm、Camel、Log4j 2在内的很多知名项目都应用了Disruptor以获取高性能。在美团
美团技术团队
2018/03/12
1.8K0
高性能队列——Disruptor
深入java多线程与高并发:JMH与Disruptor,确定能学会?
今天我们讲两个内容,第一个是JMH,第二个是Disruptor。这两个内容是给大家做更进一步的这种多线程和高并发的一些专业上的处理。生产环境之中我们很可能不自己定义消息队列,而是使用
愿天堂没有BUG
2022/10/28
7280
深入java多线程与高并发:JMH与Disruptor,确定能学会?
Disruptor简单使用
  Disruptor从功能上来说,可以实现队列的功能,也可以把它当成单机版的JMS来看待。从性能上来说,它比ArrayBlockingQueue有更好的性能表现,对于生产者消费者模型的业务,Disruptor是一个更好的选择可以很好的实现业务的分离。
良辰美景TT
2019/03/29
8680
Disruptor简单使用
别看唐探了,Q(ueue)的真相在这里
注:虚线部分为对 cobar 中间件的改造,业务调用是无感知的如图示,主要步骤如上图所示
kunge
2021/07/16
5230
并发编程之Disruptor
一、Disruptor是什么 Disruptor是一个高性能的异步处理框架,或者可以认为是最快的消息框架(轻量的JMS),也可以认为是一个观察者模式实现,或者事件-监听模式的实现,直接称disruptor模式。 Disruptor最大特点是高性能,它被设计用于在生产者—消费者问题(producer-consumer problem,简称PCP)上获得尽量高的吞吐量(TPS,Transaction Per Second))和尽量低的延迟。Disruptor是LMAX在线交易平台的关键组成部分,LMAX平台使
lyb-geek
2018/03/27
2.6K1
并发编程之Disruptor
深入浅出生产者-消费者模式
生产者-消费者模式是一个经典的多线程设计模式,它为多线程间的协作提供了良好的解决方案。也经常有面试官会让手写一个生产者消费者,从代码细节可以看出你对多线程编程的熟练程度,今天我们来详细看一下如何写出一个生产者消费者模式,并且逐步对其优化争取做到高性能。
beifengtz
2019/06/03
3.6K0
深入浅出生产者-消费者模式
并发编程 | 并发编程框架 - Disruptor - 深入理解高性能异步处理框架
在并发编程的世界中,对效率的追求从未停止过。我们尝试用各种方式来提高程序的执行效率,包括使用更高级的并发控制结构,如锁和线程池,以及采用更先进的并发设计模式。然而,有一种工具在许多高性能系统中得到了广泛的应用,那就是Disruptor。Disruptor是一个高性能的异步处理框架,它利用了Ring Buffer、CAS等高效的并发策略,使得在处理高并发、低延迟的需求时,表现出了惊人的性能。
kfaino
2023/11/04
1.4K0
并发编程 | 并发编程框架 - Disruptor - 深入理解高性能异步处理框架
还在用BlockingQueue?读这篇文章,了解下Disruptor吧
听到队列相信大家对其并不陌生,在我们现实生活中队列随处可见,去超市结账,你会看见大家都会一排排的站得好好的,等待结账,为什么要站得一排排的,你想象一下大家都没有素质,一窝蜂的上去结账,不仅让这个超市崩溃,还会容易造成各种踩踏事件,当然这些事其实在我们现实中也是会经常发生。
用户5397975
2019/10/14
1.6K0
还在用BlockingQueue?读这篇文章,了解下Disruptor吧
Disruptor框架学习(1)--怎么实现
1 Disruptor学习 在上一篇文章中,笔者提到了log4j2中的异步logger。通过测试数据来看,在使用异步logger后,打印日志的时间明显缩短,系统响应时间得到了巨大的提升。 那么,dis
贾博岩
2018/05/11
1.2K0
相关推荐
分布式系统模式8-Singular Update Queue
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档
本文部分代码块支持一键运行,欢迎体验
本文部分代码块支持一键运行,欢迎体验