3 ms·
I'd like to mention the native actor model implementation CAF, the C++ Actor Framework, and share some experiences. (Disclaimer: I've been developing on CAF in
by mavam 4y ago
I'd like to mention the native actor model implementation CAF, the C++ Actor Framework, and share some experiences. (Disclaimer: I've been developing on CAF in the past and have a good relationship with the creator.) CAF (1) provides native actors without an VM layer, (2) type-safe interfaces so that the compiler yells at you when a receiver cannot handle a message, and (3) transparent copy-on-write messaging so that you can still push stuff through pipelines and induce only copies only when a ref count is greater than one.
In our telemetry engine VAST, we've been using CAF successfully for several years for building a distributed system that always has a saturated write path. CAF provides a credit-based streaming abstraction as well, so that you can have backpressure across a chain of actors, making burst-induced OOM issues a blast from the past. You also get all the other benefits of actors, like linking and monitoring, to achieve well-defined failure semantics: either be up and running or collectively fail, but still allowing for local recovery—except for segfaults, this is where "native" has a disadvantage over VM-based actor models.
With CAF's network transparent runtime, a message ender doesn't need to know where receiver lives; the runtime either passes the message as COW pointer to the receiver or serializes it transparently. Other actor model runtimes support that as well, but I'm mentioning it because our experience showed that this is great value: we can can slice and dice our actors based on the deployment target, e.g., execute the application in one single process (e.g., for a beefy box) or wrap actors into single OS processes (e.g., when deploying on container auto-scalers).
The deep integration with the C++ type system allowed us to define very stable RPC-like interfaces. We're currently designing a pub/sub layer as alternate access path, because users are interested in tapping into streaming feeds selectively. This is not easy, because request-response and pub/sub are two ends of a spectrum, but it turns out we can support nicely with CAF.
Resources:
- CAF: https://github.com/actor-framework/actor-framework https://github.com/actor-framework/actor-framework
- VAST: https://tenzir.github.io/vast/docs/understand-vast/actor-model https://tenzir.github.io/vast/docs/understand-vast/actor-mod... (sorry for the incompleteness, we're in migration mode from the old docs, but this page is summarizing the benefits of CAF for us best)
- Good general actor model background: http://dist-prog-book.com/chapter/3/message-passing.html#why-the-actor-model http://dist-prog-book.com/chapter/3/message-passing.html#why...
- tsss 4y agoI still don't get why you need actors. In all my years I've never come across some problem and thought "this would be a great use for actors". Is the kind of applications that I write simply not suited to it? Many other people here in the comments have mentioned how actors can be used to serialize access to mutable state. My applications almost never have mutable state, especially not non-local mutable state that would require locks, except maybe caches. All mutable data is generally in some kind of highly available database and too large to hold in memory anyway. Message queues (like in an actor's message box) can never be in memory or they would lose data when the application crashes. The reasoning in the book that you linked does not make sense to me. Any serious language nowadays has some kind of lightweight thread abstraction which you can spawn thousands or millions of without making a dent into system resources, so this is a non-issue. It goes on to say that > This alleviates the need for the programmer to reason about an entire system. Instead the programmer has a fixed set of concerns, meaning they can ensure behavioral correctness in isolation, rather than having to worry about an interaction they hadn’t anticipated occurring. which is just blatantly false. You can reason about a single actor, yes, but doing so is not helpful when you are interested in the correctness of the entire system. Since actor boundaries are an operational concern, they do not align with the domain logic. To understand the domain logic you always have to look at interactions of multiple actors (a single actor would be pointless) all with their own mutable state, all which can receive messages from anywhere at any time with no guarantees of timely reply to its own messages. A functional programming approach on the other hand is much easier to reason about with a well defined denotational semantics, no peer-to-peer interactions, no mutable state (generally) and composition of components that are aligned with domain boundaries and thus meaningful in isolation. > Without a lightweight process abstraction, users are often forced to write parts of concurrent applications in an event-driven style which obscures control flow, and increases the burden on the programmer. This is exactly the argument _against_ actors in my mind. Because actors communicate only through messages, they enjoy high decoupling but the price for this is horrible cohesion. Any domain logic that is spread across multiple actors will quickly become incomprehensible and unmaintainable. The same is true for event-driven microservices and a great deal of architectural work goes into making the microservices as large as possible to improve cohesion while making them as small as necessary to be independently scalable.