iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 6
1
自我挑戰組

學學WEB開發的各種技術及框架系列 第 6

(拜讀) 比較Scala 的 ZIO vs Monix vs Akka vs Akka-Type 優劣及風格

今天想來拜讀三篇在medium上的高質量系列文
Scalaz 8 IO vs Akka (typed) actors vs Monix

一次學習在scala上的最有名的三個併發庫(ZIO 、 Monix 、 Akka)

There’s a couple of hot development areas in the Scala ecosystem, and the competition between the various side-effect wrappers is one of the most interesting. We have the bifunctor IO from Scalaz 8 (which is now a standalone project, ZIO), we have cats-effect and its IO, which is a simpler version of Monix’s Task, and finally we have the good old Akka actors.

Some people say that the IO/Task wrappers offer a viable replacement for Akka’s actors. But is that so? Can you really replace an Actor with an IO or Task? If yes, is it practical to do so? Does it offer any advantages? Let’s find out on some concrete examples!

First of all, we have to answer the question: what are we actually comparing? Akka is much more than just actors; there’s streaming, persistence and clustering, just to name the three most popular modules. Monix is a smaller library, but apart from the core concurrency library it also provides a reactive streaming implementation.

ZIO (Scalaz 8 IO), on the other hand, as well as cats-effect, is clearly focused only on encapsulating side effects and providing a concurrency library. And that’s what we’ll be comparing: akka-actor (just the base module), monix-task and scalaz-zio. This quite radically constraints the reasonable use-cases, and naturally rules out any problems which would be best solved e.g. with a streaming solution (ZIO doesn’t have a streaming library; for a purely functional streaming implementation, see fs2).

As a side-note: you might be wondering why we’ll be using Monix instead of cats-effect; this blog might provide some hints, but the exact relationship between the two has yet to play out. As for now, cats-effect is a poorer version of what Monix’s Task offers, having very similar semantics and design — which isn’t surprising, as both are lead by the same person, Alex Nedelcu. Will the IO from cats-effect replace Monix’s Task? Will Monix be totally replaced by cats-* projects? Time will tell.

A common complaint against Akka’s actors is their lack of type safety; however, there’s an ongoing effort to address this issue with the akka-typed module, hence we’ll add akka-typed-actors to the mix.

We’ll be using the following versions of the projects:

Akka: 2.5.12
Monix: 3.0.0-RC1 (2.3.3 should mostly work as well)
ZIO: 0.1-SNAPSHOT (no release yet)

What’s special about actors

Before we dive into technical comparisons, let’s first write down what is so special about actors, which might hint on the use-cases which are most naturally implemented with a “raw” actor, instead of e.g. using streams.

First of all, an actor encapsulates and manages state (statless actors are considered an anti-pattern). Access to the state is guaranteed to be serialized, so that the state is always accessed and changed by a single thread. An actor provides a “safe haven” for the data it manages.

Secondly, actors define a way to communicate between concurrently running processes, via message passing. Each actor is associated with a mailbox, to which incoming messages are enqueued and processed by the actor one-by-one in a first-in-first-out fashion. In other words, there’s a queue in front of each actor.

Finally, actors provide a way to manage errors. Not all errors have to be handled inside an actor; instead, errors can (and should!) be propagated to parent actors. The parent actor might have more context and decide what’s the best course of action: re-creating the child actor, stopping it, escalating, etc. This is known as supervisor hierarchies.
Action plan

We’ll go over some use-cases for actors and see if & potentially how they can be implemented using Monix / ZIO in three installments. This part covers the basics and state encapsulation. The next part will cover communication, and the final one error handling.

The goal is to keep the disussion close to “real life”, so each example is backed by runnable code, available on GitHub, implemented using all four approaches. The crucial code snippets are embedded in the article, but to test things interactively, it’s always useful to simply browse the code.
Rate limiter

Let’s start with an example which requires protected access to some non-trivial state. The goal will be to implement a rate limiter: we want to run a side-effecting computation (e.g. sending an HTTP request) so that in any perMillis time window, at most maxRuns computations are started. (Note that we only count the start of the computation, not the when the computation finishes; due to e.g. network latencies the target system might see at times a slightly different rate of requests.)

All of the implementations will use the same data structure to store a queue of waiting computations and calculate when they can be run. The RateLimiterQueue case class includes:

waiting: a FIFO queue of computations waiting until the rate limit allows them to be started
lastTimestamp: a list of timestamps at which computations in the current time window (which is perMillis wide) have been started

Apart from enqueueing a new request, we can compute which requests (if any) should be run given the current timestamp. The result of the def run(now: Long): (List[RateLimiterTask[F]], RateLimiterQueue[F]) method is:

a list of tasks, where each task can be either running a computation, or scheduling an invocation of run in the future (when the rate limit will allow computations to be started)

as the RateLimiterQueue is immutable, an updated copy of the data structure, with modified lastTimestamps and waiting queues

View source
Using Akka

First, let’s use the above RateLimiterQueue data structure to implement a rate limiter using pure, “traditional” Akka. As we are in the Akka ecosystem, computations will be represented as Future values. However, as a Future is a computation that is already running (eagerly), we have to make it lazy and make sure that it’s only constructed once the rate limiter allows it to be run.

This also requires attention from the user of the code, so that instead of passing an already created Future instance (which is a running


上一篇
認真來了解一下quarkus這個Java mix K8s框架
下一篇
Java的快速全端開發框架vaadin 14
系列文
學學WEB開發的各種技術及框架10
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言