quasar
actor模型是用于容错和高度可扩展系统的设计模式。 角色是独立的工作程序模块,它们仅通过消息传递与其他角色进行通信,可以与其他角色隔离而失败,但是可以监视其他角色的故障并在发生这种情况时采取一些恢复措施。 角色是简单,孤立但又协调的并发工作者。
基于演员的设计带来许多好处:
- 自适应行为 :仅通过消息队列进行交互会使参与者松散耦合,并允许他们:
- 隔离故障 :邮箱将消息队列解耦 ,从而允许Actor在不中断服务的情况下重新启动。
- 最大并发容量 :
- Actor在内存消耗和管理开销方面都非常轻巧 ,因此可以在一个盒子中生成甚至数百万个。
- 低复杂度 :
- 每个参与者都可以通过更改其私有状态来实现有状态行为,而不必担心并发修改。
凭借Erlang ,演员模型得到了广泛的认可,并成功实现了关键生产系统中的目标。
这是对JVM的两个参与者库的比较回顾:我们自己的Quasar和Typesafe的Akka 。
类星体
Quasar是一个用于简单,轻量级JVM并发的开源库,该库在JVM上实现了真正的轻量级线程(AKA光纤)。 Quasar光纤的行为类似于普通的Java线程,只是它们几乎没有内存和任务切换开销,因此您可以在单个JVM中轻松产生数十万甚至上百万的光纤。 Quasar还提供了以Go语言提供的模式为模型的光纤间通信通道,并带有通道选择器。 它还包含actor模型的完整实现,该模型以Erlang为蓝本。
尽管本文主要涉及Quasar在Actor模型的实现上建立的,该模型建立在Quasar光纤之上,但请记住,您可以在没有actor的情况下使用Quasar。
类星体演员实现了上面概述的完整的演员范式,其中一些适用于Java 7,Java 8, Clojure和Kotlin 。 Quasar当前不支持Scala。
由于Quasar光纤的工作原理与线程非常相似,因此可以轻松集成现有库,因此可以在不更改代码或进行最少代码更改的情况下使用当前工具和库,同时充分利用轻量级线程的效率。 这样可以保留现有代码,并避免API锁定。 Comsat项目利用Quasar的集成框架,以最少的代码就可以通过光纤实现对几种流行的标准API的移植(它还引入了Web Actors ,这是一种基于Actor的新HTTP,WebSocket和SSE的基于Web的API)。
Quasar光纤是通过创建和调度连续任务来实现的,并且由于JVM还不支持本机连续,因此Quasar通过选择性字节码检测实现了它们:当前可以阻塞光纤的方法需要通过注释进行显式标记,以便Quasar可以插入继续悬挂和恢复挂钩。 但是,可以使用实验性的自动Clojure工具,并且自动工具也将扩展到其他JVM语言。 可以作为附加的构建步骤或在运行时执行检测(通过JVM代理或大多数常见Servlet容器的类加载器)。
阿卡
Akka是用Scala编写的actor框架,除了Scala之外,它还支持Java 7,Java 8(从2.3.10开始的实验)。 它提供了一个基于异步,基于回调的Actor DSL,而不是基于Erlang的基于光纤的Actor系统。 Akka不提供轻量级线程,而是依靠JVM线程来调度参与者。 Akka是一个全方位服务的框架,而不是库,它涵盖了从配置和部署到测试的所有内容。
无阻塞
Akka和Quasar actor之间的主要区别是Akka使用了异步的非阻塞API,而Quasar –例如Erlang,Go,Clojure的core.async –使用了阻塞API:在Akka中,actor 实现了receive
方法,即当actor收到消息时触发的回调,而在Quasar中,actor 调用 receive
方法,该方法将阻塞直到收到消息为止。 从理论上讲,异步和直接(或阻塞)样式是双重的和等效的,因为它们可以相互转换 ,但是在实践中,实现的细节对性能和可伸缩性以及选择哪种样式有很大影响。编程语言可以使一种方法比另一种方法容易。
选择基于异步,基于回调的异步方法的原因是,阻塞普通的OS线程会带来大量开销(就像许多线程的存在一样),而使用非阻塞API可以避免这种情况。 但是,由于Quasar(与Erlang和Go一样)具有真正的轻量级线程,因此阻塞几乎没有开销。
在语言方面,尽管Scala为monad提供了语法支持,这使异步代码的处理变得更加简单,但在对monad没有良好语法支持的语言(如Java)中,阻塞方法要简单得多。 阻塞代码的好处不仅在于更简单,更具可读性和可维护性的 Java代码,还在于更加熟悉和兼容的代码,从而可以集成其他标准Java API。
API比较
Quasar Java API支持Java 7和8。Clojure支持是Pulsar的一部分, Pulsar是Quasar周围的一个薄包装层,非常习惯,并提供了与Erlang类似的actor API。 Kotlin支持是最新添加的功能。 Kotlin是一种非常有前途的针对JVM和JavaScript的静态类型混合编程语言,其设计和构建是由领先的供应商JetBrains的开发工具来实现的。 尽管Kotlin使使用现有Java API的效率比Java本身更有效,更安全,更轻松,更愉快。
Quasar还提供了一个集成工具箱,该工具箱可以添加对其他JVM语言的支持。
Akka在设计时主要考虑了Scala,但一段时间以来它一直在提供其他Java API。
以下是Quasar和Akka Java API之间的比较。 尽管不尽详尽,但它涵盖了关键的差异。 这是一个简短的摘要:
演员定义
Quasar (Kotlin和Java)参与者实现了doRun
方法(或Clojure中的函数),就像在Erlang中一样,该方法可以使用任意语言控制流构造,并且只要开发人员认为合适就可以阻塞操作。 通常它将至少使用receive
(正常或选择性)和send
:
class MyActor extends BasicActor<String, MyActorResult> {private final Logger log = LoggerFactory.getLogger(MyActor.class);@Suspendable@Overrideprotected MyActorResult doRun() throws InterruptedException, SuspendExecution {// ...Arbitrary code here...final String msg = receive(m -> {if ("test".equals(m)) return "testMsg";else return null; // Defer});// ...Arbitrary code here...return new MyActorResult();}
}
由Pulsar提供的Clojure API更加简洁:
(def log (LoggerFactory/getLogger (class *ns*)))(spawn#(; ...Arbitrary code here...(receive ; Single, fiber-blocking, selective receive"test" (do (. log info "received test") "testMsg")) ; Other messages will stay in the mailbox; ...Arbitrary code here...))
类星体参与者(如Erlang进程)使用阻塞接收,并被允许执行阻塞IO(尽管阻塞IO操作最终不会阻塞OS线程,而是阻塞光纤,这使它们具有很高的可伸缩性)。
Akka actor被实现为receive
事件的回调,并且不允许阻塞:
public class MyUntypedActor extends UntypedActor {LoggingAdapter log = Logging.getLogger(getContext().system(), this);// "receive" must be toplevelpublic void onReceive(Object message) throws Exception {if ("test".equals(message))log.info("received test");elselog.info("received unknown message")}
}
演员生命周期和监督层次
类星体 actor的创建和启动就像在光纤上一样容易:
ActorRef myActor = new MyActor().spawn();
虽然Quasar和Akka都支持监视(也可以监视 )其他参与者的故障,但Akka强制将参与者添加到适当的监督层次结构中,因此必须始终通过指定参与者的类和适当的构造函数参数的配方来实例化参与者。 顶级演员必须由演员系统生成,子演员必须由父母的上下文生成:
ActorRef myActor = system.actorOf(Props.create(MyActor.class), "myactor");
监督是一种分层的故障管理模式,提供了故障隔离的良好实践:当子角色终止时,主管将根据其监督策略进行操作。 受监督的儿童演员可能由于永久性故障,暂时性故障或仅完成工作而终止。 终止时,主管通常可以选择使自己失败( 升级 ),仅重新启动失败的孩子或重新启动所有孩子。
在Quasar中,就像在Erlang中一样,监督是可选的,而supervisor只是一个预先构建的(尽管是可配置的)actor,它通过内部使用原始actor监视操作(监视和链接)来提供监督。 当使用主管时,Quasar也需要指定创建演员的方法(以及其他信息,例如主管在放弃之前应尝试多少次重试等):
ChildSpec actorSpec = new ChildSpec("myactor", TRANSIENT, 1, 1, MILLISECONDS, 100, MyActor::new);
Supervisor mySupervisor = new SupervisorActor(ALL_FOR_ONE, actorSpec).spawn();
Quasar还允许通过可重写的Actor.reinstantiate
方法来监督和重新启动预先构建的本地actor实例,因此它可以与任何依赖项引擎一起使用。
Akka提供了几种关闭参与者的方法。 Quasar和Erlang一样,只是鼓励一条简单的消息来表示关闭请求(尽管这种通用机制已经是所有行为的一部分–参见下文); 通过中断演员的基础线 (线或纤维),可以突然终止。
行为举止
Quasar遵循Erlang OTP库的示例,为常见的actor类型(称为behaviors)提供可配置的actor模板。 行为都实现通用,有用的消息传递模式,但是在Quasar中,它们还为参与者的引用添加了便捷方法。 Quasar的行为均以OTP建模:
-
EventSourceActor
(模仿Erlang的gen_event
建模)可以动态注册和取消注册处理程序,这些处理程序将仅对收到的消息作出React。 如果您认为这种特定类型的Quasar actor与仅React的异步Akka actor非常接近,那么您就走对了。 -
ServerActor
(根据Erlang的gen_server
建模)对公开请求-响应API的服务进行建模。 -
ProxyServerActor
允许编写基于接口的服务器:它通过传递任何接口实现而构建,并将产生ActorRef
,该ActorRef
将代理接口并将与接口方法相对应的消息发送到基础服务器actor。 当然,这只是一个用例,但是我认为当将传统的API移植到Quasar actor时,这种行为actor会有所帮助。 - 在即将到来的Quasar 0.7.0(模仿Erlang的
gen_fsm
建模)中添加了FiniteStateMachineActor
,可以很轻松地将actor编写为显式的有限状态机。
Akka不包括这种预建的actor模板。 相反,各种常见行为内置于标准API。
演员系统
Akka可以作为独立部署的容器或库运行; 它是通过引用多个参与者系统的配置文件来设置的,每个参与者系统由一个主管监督。 该配置包括日志记录,调度(AKA作为调度),网络,消息序列化和平衡(AKA路由)。 还提供了合理的默认值,因此配置是可选的。
在Akka中,actor系统是重量级的对象,对应于逻辑应用程序。 Quasar是一个库,而不是一个框架,它根本没有参与者系统的概念,因为它不需要包含整个应用程序。 当然,各种特定的配置是可能的:
- 光纤的默认调度程序是fork-join(偷工),但甚至可以为每个光纤选择它。 参与者只是继承了用于它们运行的链的调度机制,这意味着他们自己不需要调度/调度设置。
- 监督层次结构是可选的,因此不需要“根”主管。
- 可以使用任何日志记录机制,但是(可选)行为为此使用“标准”日志记录API SLF4J 。
- Quasar提供了现成的Galaxy群集中的网络演员和演员迁移功能,但可以提供更多支持。 群集功能是在群集提供程序的配置(例如Galaxy)中设置的,而不是在Quasar本身中设置的。
- 类星体与部署无关。 对于适用于所有JVM应用程序(也适用于采用Quasar的应用程序)的酷部署解决方案,我们建议您看看Capsule 。
内部角色API
Quasar演员的默认内部API仅包括以下内容:
-
receive
/tryReceive
方法和一个可filterMessage
用于在接收到消息之前将其丢弃。 - 对参与者的外部,不透明引用。
- 基本的actor监视构造
link
,watch
和可handleLifecycleMessage
。
更多功能,例如默认嵌入的发件人引用,日志记录,终止请求处理,请求服务,事件处理和监督,可以通过扩展预建行为来获得或由您添加。 而且,由于有了Quasar光纤,发送和接收操作可以同时被阻塞并且效率很高,因此不需要异步的,返回Future
send
变量,例如Akka的ask
。
始终为所有参与者启用Akka功能(例如监视和监督),因此内部API广泛:
-
receive
方法。 - 对参与者的外部,不透明引用。
- 对最后一个消息发件人的引用(如果有)。
- 可覆盖的生命周期方法。
- 使用的主管策略。
- 具有附加功能的
context
属性,例如:- 创建受监管儿童的工厂方法。
Akka还提供了一个可选的Stash
特性,可以管理第二条已收到但应延迟处理的消息队列。 相反,Quasar与Erlang一样,允许选择性接收,因此它不需要开发人员仅出于延迟消息处理的目的来管理其他消息队列。
热升级
Quasar通过在JMX或指定的“模块”目录中加载新类,允许在运行时完全自动地升级actor。 Quasar还允许通过@OnUpgrade
注释的方法以受控方式升级@OnUpgrade
。
换一个演员的部分功能用一个新的运行时虽然阿卡支持become
方法,但提供了一流的重新定义不支持,所以无论是演员的行为必须以字节码已经存在于正在运行的JVM或新代码来替换必须通过一些其他的加载工具。
联网,远程处理,可靠性和集群
Quasar作为Galaxy上的集群集成的一部分,开箱即用地支持远程参与者,但是可以添加更多的远程处理和集群提供程序。 Akka提供了类似的功能以及预先构建的功能,可以直接在远程节点上生成角色,并在单独节点上的角色之间进行负载平衡消息。
Quasar还通过实验支持actor 迁移 -挂起正在运行的actor并将其恢复到另一台计算机上的能力。
邮箱持久性
Akka包括一个基于其基础事件源引擎的实验性邮箱持久性支持,并且要求actor扩展PersistentActor
特性并为正常行为和恢复提供两个单独的事件处理程序,以及显式调用persist
。
Quasar目前不支持actor邮箱持久性。
积分
Quasar不会强迫开发人员使用actor系统的所有功能,也不会完全使用actor。 实际上,Quasar为第三方技术提供了易于使用的集成框架,这些框架具有异步,基于未来的API或阻塞API ,因此它们可以与轻量级线程(“光纤”)配合使用,而不能与常规的重量级线程一起使用。 Quasar允许您自由地混合actor和非actor代码,或使用actor代码中的任何集成库,而无需专门的API。
Comsat使用此框架来集成标准和流行的Java和Clojure技术:
- Dropwizard
- 泽西岛 JAX-RS (客户端和服务器)
- Spring Boot (Web控制器)
- OkHttp
- HTTP客户端
- 翻新
- 小程序
- Clojure戒指
Comsat还包括Web Actors ,这是一个新的actor API,用于处理HTTP,WebSocket和SSE交换。
目前, Akka项目提供:
- Apache Camel消息传递集成。
- 基于HTTP actor的API( Spray )。
- 基于ZeroMQ actor的API。
- 基于TCP参与者的API。
- 基于UDP actor的API。
- 基于文件IO actor的API。
Akka与不基于消息传递的系统的集成必然是新的参与者API。
测试中
Quasar不包括专用的测试工具包,因为它是一个支持临时参与者的阻塞框架,临时参与者的链可以在终止时产生价值,因此任何常规测试工具(如JUnit)都可以与常规多线程测试实践一起使用。
Akka是一个异步框架,因此它必须以阻止单角色测试调用( TestActorRef
, TestFSMRef
)的形式提供专用的API。 它还提供了支持ScalaTest断言的特殊参与者,以对整个参与者子系统( TestKit
mixin或TestProbe
)执行外部集成测试。 支持定时断言,监督测试,消息交换限制,消息交换和故障跟踪。
系统监控与管理
Quasar通过标准的JMX MBean公开了丰富的actor监视数据(邮箱,堆栈跟踪),可以监视启用JMX的工具(例如JDK的免费提供的JVisualVM和JConsole),或者使用REST API使用Jolokia 。 此外,Quasar还提供工具来微调仪器并记录详细的光纤执行轨迹。
可以通过专有软件(Typesafe Console)监视和管理Akka应用程序,该软件需要生产系统的商业许可。
新的Relic和App Dynamics支持Akka和Quasar(通过JMX)。
完整应用比较:类比股票和React型股票
要了解Akka和Quasar之间的异同,没有比查看使用两者编写的相同应用程序的代码更好的方法了。 Quasar Stocks是Reactive Stocks Play / Akka激活器模板的Java端口,用于Quasar参与者和Comsat Web参与者。
在385行代码中,纯Java Quasar应用程序几乎与半Scala Typesafe应用程序(285个位置)一样紧凑,考虑到actor和Web Actor只能做一件事,这特别好:一切都可以与JSON库无关,因此您不必被迫仅使用一个Web框架并接受其对Web开发问题的意见。
而且我仍然认为Quasar一个人比较容易理解,因为它是普通的Java命令式样式,仅在效率更高的轻量级线程实现上运行:没有声明性/功能性/ monadic / async强制您解决JVM问题线程的占用空间大。
例如,可以将Typesafe版本中基于“未来情绪”的Web服务替换为同等高效且完全传统的JAX-RS Jersey版本,仅使用光纤阻塞而不是线程阻塞。 因此,与其使用异步操作Future
以及专用的非标准DSL来组成它们,就像在Typesafe版本中那样:
object StockSentiment extends Controller {case class Tweet(text: String)implicit val tweetReads = Json.reads[Tweet]def getTextSentiment(text: String): Future[WSResponse] =WS.url(Play.current.configuration.getString("sentiment.url").get) post Map("text" -> Seq(text))def getAverageSentiment(responses: Seq[WSResponse], label: String): Double = responses.map { response =>(response.json \\ label).head.as[Double]}.sum / responses.length.max(1) // avoid division by zerodef loadSentimentFromTweets(json: JsValue): Seq[Future[WSResponse]] =(json \ "statuses").as[Seq[Tweet]] map (tweet => getTextSentiment(tweet.text))def getTweets(symbol:String): Future[WSResponse] = {WS.url(Play.current.configuration.getString("tweet.url").get.format(symbol)).get.withFilter { response =>response.status == OK}}def sentimentJson(sentiments: Seq[WSResponse]) = {val neg = getAverageSentiment(sentiments, "neg")val neutral = getAverageSentiment(sentiments, "neutral")val pos = getAverageSentiment(sentiments, "pos")val response = Json.obj("probability" -> Json.obj("neg" -> neg,"neutral" -> neutral,"pos" -> pos))val classification =if (neutral > 0.5)"neutral"else if (neg > pos)"neg"else"pos"response + ("label" -> JsString(classification))}def get(symbol: String): Action[AnyContent] = Action.async {val futureStockSentiments: Future[Result] = for {tweets <- getTweets(symbol) // get tweets that contain the stock symbolfutureSentiments = loadSentimentFromTweets(tweets.json) // queue web requests each tweets' sentimentssentiments <- Future.sequence(futureSentiments) // when the sentiment responses arrive, set them} yield Ok(sentimentJson(sentiments))futureStockSentiments.recover {case nsee: NoSuchElementException =>InternalServerError(Json.obj("error" -> JsString("Could not fetch the tweets")))}}
}
可以编写一个完全标准的,熟悉的JAX-RS服务,唯一的区别是附加的@Suspendable
注释和生成光纤,而不是用于并行操作的线程:
@Singleton
@Path("/")
public class Sentiment {final CloseableHttpClient client = FiberHttpClientBuilder.create(Runtime.getRuntime().availableProcessors()).setMaxConnPerRoute(1000).setMaxConnTotal(1000000).build();@GET@Path("{sym}")@Produces(MediaType.APPLICATION_JSON)@Suspendablepublic JsonNode get(@PathParam("sym") String sym) throws IOException, ExecutionException, InterruptedException {List<Fiber<JsonNode>> agents = new ArrayList<>();List<JsonNode> sentiments = new ArrayList<>();for (JsonNode t : getTweets(sym).get("statuses"))agents.add(sentimentRetriever(t.get("text").asText())); // spawn worker fibersfor (Fiber<JsonNode> f : agents) // join fiberssentiments.add(f.get());return sentimentJson(sentiments);}private JsonNode sentimentJson(List<JsonNode> sentiments) {Double neg = getAverageSentiment(sentiments, "neg");Double neutral = getAverageSentiment(sentiments, "neutral");Double pos = getAverageSentiment(sentiments, "pos");ObjectNode ret = Application.Conf.mapper.createObjectNode();ObjectNode prob = Application.Conf.mapper.createObjectNode();ret.put("probability", prob);prob.put("neg", neg);prob.put("neutral", neutral);prob.put("pos", pos);String c;if (neutral > 0.5)c = "neutral";else if (neg > pos)c = "neg";elsec = "pos";ret.put("label", c);return ret;}private Double getAverageSentiment(List<JsonNode> sentiments, String label) {Double sum = 0.0;final int size = sentiments.size();for (JsonNode s : sentiments)sum += s.get("probability").get(label).asDouble();return sum / (size > 0 ? size : 1);}private Fiber<JsonNode> sentimentRetriever(String text) throws IOException {return new Fiber<> (() -> {HttpPost req = new HttpPost(Application.Conf.sentimentUrl);List<NameValuePair> urlParameters = new ArrayList<>();urlParameters.add(new BasicNameValuePair("text", text));req.setEntity(new UrlEncodedFormEntity(urlParameters));return Application.Conf.mapper.readTree(EntityUtils.toString(client.execute(req).getEntity()));}).start();}@Suspendableprivate JsonNode getTweets(String sym) throws IOException {return Application.Conf.mapper.readTree (EntityUtils.toString(client.execute(new HttpGet(Application.Conf.tweetUrl.replace(":sym:", sym))).getEntity()));}
}
阻塞样式还有另一个好处:Quasar API 更小 , 更简单 。 例如,完全不需要Akka对计划消息的特定支持,因为在Quasar中,演员主体可以使用常规的控制流构造。 所以代替:
// Fetch the latest stock value every 75ms
val stockTick = context.system.scheduler.schedule(Duration.Zero, 75.millis, self, FetchLatest)
在消息处理循环中定期进行光纤阻塞定时接收已足够:
for(;;) {Object cmd = receive(75, TimeUnit.MILLISECONDS);if (cmd != null) {// ...} else self().send(new FetchLatest());// ...
}
另外,默认情况下,Quasar Web Actor会自动将新的actor分配给新的HTTP会话或WebSocket连接,因此Quasar完全不需要Typesafe版本的基于回调的应用程序控制器 ,因为一切都由actor直接处理,它将浏览器(或移动客户端)简单地视为另一个参与者,可以监视以监视客户端终止。
关于应用程序的Typesafe的教程提到了几种类型的设计模式:
- 响应式推送基本上意味着将线程有效地分配给参与者以处理WebSocket交换。 通过使用Quasar的基于光纤的actor,可以有效地完成此操作,而不会限制常规控制流构造的使用。
- React性请求和React性组合基本上是指使用异步构造(例如
Future
的单子组合,以实现Web服务中有效的线程使用。 在光纤上运行时,这种复杂性是完全没有必要的:您可以使用常规的,直接的阻塞调用和控制流,并且光纤调度程序可以为您处理线程以达到相同的效果和性能。 - React式用户界面基本上已经被复制到Quasar Stocks中。
最后,Web Actor是100%Servlet兼容的,因此,如果您不想运行非标准嵌入式服务器,则无需运行。 相反,Play必须独立运行2 。
性能比较
环形测试JMH基准测试套件基于Fiber-Test ,并从Fiber-Test派生而来,比较了基于Akka,Quasar Actors,Java Threads和Quasar光纤的几种消息传递实现,带有或不带有不同类型的通道。
基准将工人参与者安排成一个环形并执行消息传递循环。 变量是:
- 工作者角色的数量(默认= 503)
- 循环的长度(默认= 1E + 06消息交换)
- 响铃次数(默认= 1)
- 在每次消息交换及其参数之前执行的业务逻辑(默认=无)。
所有测试均在2008年底的MacBook Pro铝制版,8GB RAM,Mint Linux(Linux 3.13.0-49通用)下使用Core 2 Duo P8600 2.4Ghz,JDK 1.8.0_45-b14进行了积极的优化和分层编译的情况下进行。已启用。 使用的JMH版本是1.8,带有5个fork,5个热身迭代和10个迭代。
我们首先来看一下带有默认参数的内存占用量:
与光纤和Quasar actor相比,Akka具有最高的堆使用率,最高的GC事件数和最高的GC总时间,因此Quasar总体上具有较低的内存占用量。
关于速度,首先要注意的是,改变工人角色的数量,甚至达到数百万人,都不会改变Quasar和Akka的单环性能数据:这证实了角色(和光纤)确实非常轻巧。
然后进行了两组测量:第一个测量具有固定数量的1E + 04消息交换和变化的业务工作量,显示Quasar的启动速度稍快,但是随着工作量开始占主导地位,Akka和Quasar的执行性能非常相似:
在没有工作负载和数量不等的消息交换的情况下,我们测量的是纯框架开销 。 Quasar再次启动得更快,但是Akka领先,Quasar的额外开销达到并稳定在Akka的80%左右:
由于缺少JVM中的本机连续性,JMH perfasm
分析回合突出显示了Quasar中与用户堆栈管理相关的真正轻量级线程的额外成本。 Akka不提供真正的轻量级线程,因此没有开销。
当然, 任何开销(无论多么小)都比没有开销要大得多。 要了解实际开销是否重大,我们必须将其与实际工作负载进行比较。 每条消息的业务工作量相当于对224个元素的int数组进行排序,或者等效地,在1700字节的纯alpha文本上匹配预编译的仅6个数字的正则表达式(失败)匹配项(仅3到4微秒)基准系统),Quasar比Akka 慢1% 。
这意味着在最坏的情况下, 对于平均每个消息交换平均在1700字节文本上至少完成6字符正则表达式匹配的应用程序,性能差异将小于1% 。 由于大多数应用程序的功能远不止这些,因此实际上,您可以获得Quasar光纤和actor可以提供的与Akka 3 相同性能的大量额外编程功能 。
结论
Quasar是Java,Clojure和Kotlin的快速,精简和实用的并发库,提供了真正的轻量级线程和许多成熟的并发范例,包括与Erlang几乎相同的actor模型的实现。 Quasar的集成,采用和退出成本也很低。 它的功能集中,并且确实提供了诸如日志记录和监视之类的额外功能,它使用标准的API(SLF4J,JMX等)。
Akka是一个应用程序框架,与Play等其他Typesafe框架一样,它是一个全面的选择,涵盖了整个应用程序,引入了自己的丰富API(甚至用于日志记录),测试工具,监视和部署。
Akka –甚至是其Java API –受Scala的影响很大,并且Java开发人员可能会觉得陌生。 无论您是编写Java,Clojure还是Kotlin,类星体演员都非常熟悉和习惯。
Akka的API是基于回调的。 Quasar提供了真正的光纤(例如Erlang或Go),因此阻塞是免费的,并且Quasar的actor API更简单,更熟悉并且与其他代码兼容。 像基于Erlang一样,基于阻塞和基于光纤允许使用很少的基本概念,而Akka引入了许多不熟悉和多余的概念。 例如,要解决缺少简单的阻塞选择性接收(由Erlang和Quasar提供)的问题,Akka必须引入消息存储。 其他概念(如单子期货)与业务逻辑或参与者模型无关,但纯粹是偶然的复杂性。
如果发生以下情况,Akka无疑是必经之路:
- 您已经接受了Scala,并喜欢它的编程风格。
- 您既不用担心押在框架上并坚持下去,也不必为选择退出而付出可能很高的重新设计/重写价格。
- 您准备为生产监视付费,或者愿意编写自己的监视解决方案。
否则,我建议您尝试一下Quasar:它可用于生产环境,轻量级,快速,基于标准,易于集成,完全免费且开源,并且比Akka的异步参与者提供的功能更多,而且复杂性更低。
- 有几种参与者系统不支持选择性接收,但是Erlang可以。 乌尔夫·维格(Ulf Wiger)的演讲“偶然复杂性导致的死亡”显示了如何使用选择性接收避免实现完整,复杂且易于出错的转换矩阵。 在另一场演讲中 ,Wiger将非选择性(FIFO)接收与俄罗斯方块游戏进行了比较,在俄罗斯方块游戏中,您必须在拼图中放入每一块,而选择性接收则将问题变成了拼图游戏,您可以在其中寻找一块你知道会适合的。
- 除非您使用带有某些限制的第三方插件 。
- 2048字节以上的Quasar变得比Akka快,但到目前为止的原因尚不清楚,这可能与更有利的内联有关。
翻译自: https://www.javacodegeeks.com/2015/05/quasar-and-akka-a-comparison.html
quasar