Why AsyncFP 引起的一场争论

基于Scala的函数式编程高性能Actor开源框架AsyncFP发布后,在Scala领域引起了一场争论,争论关键还是如何更好地利用底层硬件或操作系统机制,以及如何更好地切合JVM。

争论文章地址:Why AsyncFP? Does Scala really need another type of actor? 我们是否真的还需要另外一种Actor呢?

在Why AsyncFP这个PDF中,作者谈到:
● Computers are getting fatter, not faster. An i9 chip has
12 hardware threads. A Spark T4 has 64 hardware
threads.
计算机越来越胖,而不是越来越快。i9有12个硬件线程,而Spark T4有64个硬件线程。

● Code that can use all that hardware is very difficult to
write. Often it is better to use that extra hardware for
something like EJB when its performance is good
enough
试图使用所有的硬件线程的编程代码是难以编写的,较好的办法使用额外硬件(水平扩展Scalable)。(banq注:这也是为什么我经常提出进行一些宏观解决方案的原因。)

● Even when multi-threaded code is correct, it often runs
slower than single-threaded code.
即使多线程代码编写正确了,它也经常会比单线程代码运行慢(banq注:共享锁可能带来的)

● Single-threaded servers written using Node.js are
popular because they are fast and easy to write. But
then what do we do with really fat computers?
这也就是为什么使用Node.js编写的单线程服务器受到如此欢迎的原因,因为他们快而且易于编写,但是我们为什么又需要这些有很多线程的胖计算机呢?

待续...

2011年12月28日 09:48 "@banq"的内容
但是我们为什么又需要这些有很多线程的胖计算机呢? ...

需要这么多硬件线程的原因是:在基于流的编程范式中Flow-Based Programming,线程之间需要交换大量数据,而只有当存在很多线程的情况下,才具有很好的扩展性,也就是性能线性增长,比如两个线程忙于交换数据,这时就无法腾出手来处理业务逻辑,而多线程有几十个线程,这样其他空闲的线程可以继续处理从客户端请求过来的业务。

But high throughput is achieved by passing large blocks of data, not by passing one message at a time.
这是指Scala中现有的Actor模型问题,要获得高吞吐量,必须在线程之间通过传递大块的数据才能获得,而不是一次只传递一个消息,这样就无法获得很高的数据吞吐量。

所以,Passing data between threads is slow! 在Scala中线程之间传递数据是慢的,实际上,如果软件线程和硬件线程共享一个VM操作系统进程时传递数据是快的,很显然Scala的Actor模型没有很好地利用这些操作系统和硬件特性,所以,Scala需要一种全新的Actor模型。

现在,我们面对这样的现实:提供很多硬件线程的胖计算机(双核直至多核);这时你的系统访问量要求很高,比如需要并发1万或更多;而应用非常复杂,是中大型项目;数据库采取的无容量限制的NoSQL。

在这样条件下,我们有三种架构选择,Why AsyncFP一文提出的第一种选择如下图,也就是我们通常的水平扩展Scalable架构,通过增加额外计算机方式实现分布式直至云计算架构,回避内部多线程问题,每个应用服务器甚至都可以是使用Node.js这样单线程服务器,这样的架构部署容易,技术方案成熟,但是开销很大。



[该贴被banq于2011-12-28 10:23修改过]


2011年12月28日 10:20 "@banq"的内容
我们有三种架构选择 ...

第二种解决方案是:每次客户端Http请求分配一个线程为指服务,直至其完成响应,典型的如JavaEE服务器如tomcat等等,每个线程都有自己的堆栈,但是问题是扩展性难。

多线程情况下,存在共享内存资源争夺的情况,比如Java每个线程有自己堆栈,通过共享Heap实现状态共享,引入同步锁机制避免资源争夺,造成了性能扩展性差。


2011年12月28日 10:30 "@banq"的内容
引入同步锁机制避免资源争夺,造成了性能扩展性差 ...

Scala的Actor模型克服了第二种资源争夺的问题,但是Scala的Actor也存在以下问题:
Actors虽然很scalable. 但是对用于服务一个用户http请求的消息数量是有限制的,因为这涉及到线程的切换和非批量化处理。

Actors模型相对来说是重量的对象,当需要服务一个用户请求时,Actors有初始化数量上的限制。

随着Actor增加,其代码容易变成意大利面条,搅拌在一起。



[该贴被banq于2011-12-28 10:37修改过]


2011年12月28日 10:36 "@banq"的内容
Scala的Actor也存在以下问题 ...

AsyncFP的Actors是轻量的,当一个用户请求产生时,能够立即初始化为之服务;AsyncFP Actors缺省其实是一个单线程,一个Actor能够被一个异步mailbox初始化,当它有消息需要处理时就会运行在另外一个分离的线程中(异步), 这样就能够利用硬件多线程机制打破传统的I/O堵塞和费时费力计算(banq注:感觉类似Disruptor单线程高性能框架)

邮箱mailbox之间的消息传递是异步的且很快,AsyncFP认为消息应该就如同方法调用一样快,在200 nanoseconds以内。而且消息传递是批量性的,这样会增加吞吐量。

一个消息系统的消息发送不但要象方法调用一样快,而且还要有很高的数据吞吐量。


AsyncFP的Actor和Scala的Actor区别见:Scala vs AsyncFP actors

AsyncFP提出在异步消息并发模式下的数据完整性Data integrity:一些业务活动被第一个消息初始化启动后,真正完成却只有在后面消息到达后才完成。维护这样的业务数据完整性,banq注:这也许就是我们之前讨论的事件与事务性

AsyncFP和Scala两个Actor在处理数据完整性上方式是不同的:Scala采取的是使用回复reply,当Actor在处理一个消息时,会发送一个请求给另外一个Actor等待其回复,这样,在等待回复然后处理完成前,没有任何新消息被再接受处理,通过这种方式强制保证数据完整性。

在等待回复时,被Actor使用的线程将被堵塞,这就降低整个系统的可伸缩性Scalable了。回复应该被谨慎使用,Scala提供了一个替代机制message guards,这将不会堵塞线程。

当然,也有人认为,等待回复只是等待,不会堵塞线程。而且认为AsyncFP actor只有在共享同样mailbox情况下会比Scala的actor快大约100倍。

AsyncFP actors 只有等待所有消息接收完成后才进行处理,除非消息类被编码时绑定到特定的查询或修改。

查询和修改在接受时被放入一个事务性查询中,按照如下次序处理::一个查询能够在其他查询完成前进行,但是一个修改只能在所有先前到达的查询修改完成前才能处理。

其他类型的消息只要它的回复不被在等待或处理就能够随时处理。

当一个AsyncFP actor发送一个消息,它总是通过一个 callback 句柄获得回应,而不是让线程堵塞在那里等待响应,这样在回复完成前其他消息也可以被处理(使用Disruptor的jdonFramework也是这么处理响应的)。

Callback 功能是典型无名的,这样他们在被定义时能够访问其方法的本地变量,这些本地变量在其他消息没有处理时是无法访问的。

AsyncFP能够共享一个mailbox,而Scala不行。

最近出的JActor好像也是AsyncFP的,banq老大研究过没