Kontraktor:Task、Actor调度的另一个选择

导读:

Java编程中,调度Task、Actor通常采用ExecutorsExecutorService。对无状态的任务,通常可以很好的胜任。但对于大量并发的有状态任务,需要使用Actor模型
Kontraktor是一个Java编写的轻量级高效Actor模型实现。可以直接暴露Actor提供TCP服务、WebService或者WebSockets,从JavaScript客户端调用Actor方法,用JavaScript实现Actor并通过Java调用。

对无状态小任务单元,Executors可以很好的胜任。比如将计算任务分担到多个CPU上。然而,对于运行中的大任务单元Job调度,Executors只能做到次优(sub-optimal)。例如Actor或轻量级进程的消息调度。

许多Actor框架或类似的并发框架使用Executor service批量调度消息。由于Executor service是上下文不敏感的,因此会将单个Actor/Task消息安排多个线程或CPU处理。这会导致访问Actor、Process或Task状态时经常出现缓存未命中(cache miss)的情况。更糟糕的是,因为每个新的“Runnable”会把先前处理的Task缓存冲掉,所以CPU无法维持缓存的稳定。使用忙循环(busy-spin)会带来第二个问题。如果框架使用忙循环读取自己的队列,每个处理线程的CPU负载会升到100%。

借助Kontraktor 2.0,我实现了一种不同的调度机制——使用简单的度量标准测试应用实际需要的CPU资源,再进行水平式扩充。

每个Actor会固定分配到一个Workerthread(“DispatcherThread”)。调度器会定期重新调度Actor,根据信息判断是否需要把它们移动到另一个工作线程。

由于算法过于复杂通常会带来更高的运行时开销,实际调度时采用了一种非常简洁的方式:

  1. 如果消费循环连续处理N个消息没有休息(目前设置N=1000),就认定该线程超载。
  2. 一旦线程标记为“超载”,只要SUM_QUEUED_MSG(线程A上运行的Actor)大于SUM_QUEUED_MSG(新创建线程B上的 Actor),信箱(mailbox)中消息最多的Actor会移动到新的线程(直到#Threads == ThreadMax)。
  3. 如果#Threads == ThreadMax,Actor会根据目前收到的消息和“超载”信息重新分配。

问题:

  • 如果处理消息的时间差别很大,对消息队列的统计会产生误导。一种改进是为每个消息根据定期分析设定加权。可以简单地用每个Actor加权乘以队列大小。
  • 对爆发式负载会有延时,延迟结束后所有可用的CPU才能被真正地使用。
  • JIT真正起效前会有延迟,这会导致错误的分析数据,从而将错误放大(一段时间后能恢复正常,实际情况并没有那么糟糕)。

性能

为了对比自动化调度与Actor线程固定方式的开销,我运行了Computing-Pi测试(可参照前一篇博客)。这些数据并没有展示局部性(locality)带来的影响,只对固定方式与自动化调度进行了比较。

测试1 手动为每个Pi计算Actor分配一个线程,
测试2 一旦监测到实际的负载,总起启动一个worker并且自动进行比例调整。

(注意:示例要求kontraktor2.0-beta-2及更高版本。如果parkNanos选项启用,kontraktor的比例调整会限制在2、3个线程)

该测试运行了8次,每次运行会都会增加thread_max。

测试结果:

Kontraktor Autoscale(通常运行1个线程,然后比例调整到N个线程)

1 threads : 1527
2 threads : 1273
3 threads : 718
4 threads : 630
5 threads : 521
6 threads : 576
7 threads : 619
8 threads : 668

Kontraktor为每个Actor指定固定个数的线程(参见上面源码中被注释的行)

1 threads : 1520
2 threads : 804
3 threads : 571
4 threads : 459
5 threads : 457
6 threads : 534
7 threads : 615
8 threads : 659

结论

运行结果的区别很大程度上可归结与比例调整带来的延迟。对负载明确的情况,预先安排的Actor调度会更有效率。然而,考虑到服务器收到的请求会不断变化,自动化地调度是一种高效的选择。

将Executors/FJ与上面的调度策略进行对比,测试它们各自的缓存(cache)效果是很有意思的。不幸的是,Kontraktor不具备基于ExecutorService的消息分发,也没有针对Akka的调度策略实现。

此外,还需要一个示例Actors管理私有状态才可以观察缓存效果。

原文链接: java-is-the-new-c 翻译: ImportNew.com - 唐尤华
译文链接: http://www.importnew.com/13481.html
[ 转载请保留原文出处、译者和译文链接。]

关于作者: 唐尤华

我喜欢程序员,他们单纯、固执、容易体会到成就感;面对压力,能够挑灯夜战不眠不休;面对困难,能够迎难而上挑战自我。他们也会感到困惑与傍徨,但每个程序员的心中都有一个比尔盖茨或是乔布斯的梦想“用智慧开创属于自己的事业”。我想说的是,其实我是一个程序员。(新浪微博:@唐尤华

查看唐尤华的更多文章 >>



可能感兴趣的文章

发表评论

Comment form

(*) 表示必填项

还没有评论。

跳到底部
返回顶部