Merge pull request #41 from dapr/actorConcepts

Enhancing Actor Docs and Adding Actor Concepts
This commit is contained in:
Shalabh Mohan Shrivastava 2019-10-04 16:00:55 -07:00 committed by GitHub
commit f19da65b18
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 190 additions and 182 deletions

View File

@ -0,0 +1,72 @@
# Introduction to Actors
Dapr runtime provides an actor implementation which is based on Virtual Actor pattern. The Dapr Actors API provides a single-threaded programming model leveraging the scalability and reliability guarantees provided by underlying platform on which Dapr is running.
## What are Actors?
An actor is an isolated, independent unit of compute and state with single-threaded execution.
The [actor pattern](https://en.wikipedia.org/wiki/Actor_model) is a computational model for concurrent or distributed systems in which a large number of these actors can execute simultaneously and independently of each other. Actors can communicate with each other and they can create more actors.
### When to use Actors
Dapr Actors is an implementation of the actor design pattern. As with any software design pattern, the decision whether to use a specific pattern is made based on whether or not a software design problem fits the pattern.
Although the actor design pattern can be a good fit to a number of distributed systems problems and scenarios, careful consideration of the constraints of the pattern and the framework implementing it must be made. As general guidance, consider the actor pattern to model your problem or scenario if:
* Your problem space involves a large number (thousands or more) of small, independent, and isolated units of state and logic.
* You want to work with single-threaded objects that do not require significant interaction from external components, including querying state across a set of actors.
* Your actor instances won't block callers with unpredictable delays by issuing I/O operations.
## Actors in Dapr
Every actor is defined as an instance of an actor type, identical to the way an object is an instance of a class. For example, there may be an actor type that implements the functionality of a calculator and there could be many actors of that type that are distributed on various nodes across a cluster. Each such actor is uniquely identified by an actor ID.
## Actor Lifetime
Dapr actors are virtual, meaning that their lifetime is not tied to their in-memory representation. As a result, they do not need to be explicitly created or destroyed. The Dapr Actors runtime automatically activates an actor the first time it receives a request for that actor ID. If an actor is not used for a period of time, the Dapr Actors runtime garbage-collects the in-memory object. It will also maintain knowledge of the actor's existence should it need to be reactivated later. For more details, see Actor lifecycle and garbage collection.
This virtual actor lifetime abstraction carries some caveats as a result of the virtual actor model, and in fact the Dapr Actors implementation deviates at times from this model.
An actor is automatically activated (causing an actor object to be constructed) the first time a message is sent to its actor ID. After some period of time, the actor object is garbage collected. In the future, using the actor ID again, causes a new actor object to be constructed. An actor's state outlives the object's lifetime as state is stored in configured state provider for Dapr runtime.
## Distribution and failover
To provide scalability and reliability, actors instances are distributed throughout the cluster and automatically migrates them from failed nodes to healthy ones as required.
Actors are distributed across the pods of the Actor Service, and those pods are distributed across the nodes in a cluster. Each service instance contains a set of actors.
The Dapr Actor runtime manages distribution scheme and key range settings for you. This simplifies some choices but also carries some consideration:
* By default, actors are randomly placed into pods resulting in uniform distribution.
* Because actors are randomly placed, it should be expected that actor operations will always require network communication, including serialization and deserialization of method call data, incurring latency and overhead.
## Actor communication
You can interact with Dapr to invoke the actor method by calling Http/gRPC endpoint
```
POST/GET/PUT/DELETE http://localhost:3500/v1.0/actors/<actorType>/<actorId>/method/<method>
```
You can provide any data for actor method in the request body and response for the request would be in response body which is data from actor call.
Refer [dapr spec](/dapr/spec/blob/master/actors.md) for more details.
### Concurrency
The Dapr Actors runtime provides a simple turn-based access model for accessing actor methods. This means that no more than one thread can be active inside an actor object's code at any time. Turn-based access greatly simplifies concurrent systems as there is no need for synchronization mechanisms for data access. It also means systems must be designed with special considerations for the single-threaded access nature of each actor instance.
A single actor instance cannot process more than one request at a time. An actor instance can cause a throughput bottleneck if it is expected to handle concurrent requests.
Actors can deadlock on each other if there is a circular request between two actors while an external request is made to one of the actors simultaneously. The Dapr actor runtime will automatically time out on actor calls and throw an exception to the caller to interrupt possible deadlock situations.
![](../../images/actors_communication.png)
### Turn-based access
A turn consists of the complete execution of an actor method in response to a request from other actors or clients, or the complete execution of a timer/reminder callback. Even though these methods and callbacks are asynchronous, the Dapr Actors runtime does not interleave them. A turn must be fully finished before a new turn is allowed. In other words, an actor method or timer/reminder callback that is currently executing must be fully finished before a new call to a method or callback is allowed. A method or callback is considered to have finished if the execution has returned from the method or callback and the task returned by the method or callback has finished. It is worth emphasizing that turn-based concurrency is respected even across different methods, timers, and callbacks.
The Dapr Actors runtime enforces turn-based concurrency by acquiring a per-actor lock at the beginning of a turn and releasing the lock at the end of the turn. Thus, turn-based concurrency is enforced on a per-actor basis and not across actors. Actor methods and timer/reminder callbacks can execute simultaneously on behalf of different actors.
The following example illustrates the above concepts. Consider an actor type that implements two asynchronous methods (say, Method1 and Method2), a timer, and a reminder. The diagram below shows an example of a timeline for the execution of these methods and callbacks on behalf of two actors (ActorId1 and ActorId2) that belong to this actor type.
![](../../images/actors_concurrency.png)

View File

@ -1,65 +0,0 @@
# Actor Pattern with Dapr
Dapr is a new programming model that is based on RPC communication between the framework and the user code, namely HTTP. However, it does support developers to write their code using the Actor Pattern. Specifically, Dapr allows a contextual Id to be associated with messages. Dapr provides:
1) Routing by a contextual Id.
2) Ensuring single-threaded access per contextual Id.
3) Setting and getting state by a contextual Id is serialized.
The combination of above behaviors delivers essential characteristics of an Actor: uniquely identified, single-threaded, with isolated state.
## Authoring an Actor
In Dapr, authoring Actor code is no difference with writing any other service code. An Actor in this case is a web server that listens to any number of messages the actor expects to handle.
When a request is routed to a handler of the web server, a contextual Id may present in the request header. Dapr guarantees that requests with the same contextual Id are dispatched to the web server sequentially.
Handler code can set or retrieve state from the Dapr sidecar with the contextual Id attached.
## Talking to an Actor
Just like invoking any other Dapr instances, you can send a request to an Actor by doing a POST to the sidecar. The **to** address of your message will be in the format of:
```yaml
<Actor name>.<Contextual Id>
```
> Dapr uses **virtual actors** pattern. You don't need to explicitly create or destroy Actor instances.
For example, the following POST request to **/set-temperature** sends a temperature payload to a **theromstat** with id **123**:
```json
{
"to": [
"theromstat.123"
],
"data": {
"temperature": 72
}
}
```
Your code can access the response from the actor through the HTTP response body.
Dapr also allows you to make direct calls to actors. The Dapr sidecar provides a special **/actor** route that can be used to route requests to actor instance. For example, to invoke the above actor directly, send a POST request to:
```bash
http://localhost:<sidecarport>/actors/theromstat/123
```
## Key Differences From a Full Actor Framework
Dapr programming model is not an Actor programming model. Although it provides certain actor characteristics, Dapr differ from common Actor programming model in several ways.
### Single Activation
Many Actor frameworks requires single activation of an actor at any time. Dapr doesnt offer such guarantees. There should be a single activation for the most of time. However, multiple activations may exist during failovers, scaling, and network partitions. Dapr will converge back to single activation when such conditions are resolved.
Dapr offers exact-once delivery within a configurable window. Dapr delivers requests for the same actor id to the same service instance, while serializing client requests as well as state access of an instance. The combination of these features offers a high-fidelity simulation of the single activation behavior. However, there could be corner cases that cause problems when multiple activations do occur.
### State Transaction Scope
Some Actor frameworks allow wrapping multiple state operations into an atomic transaction. In Dapr, each state operation is a separate transaction. Because Dapr doesn't dictate how user code is written, a user may trigger multiple state transactions in the code. If the code crashes between transactions, the state is left at the last committed transaction.
## More Information
To learn more about Dapr Actor Pattern support, consult the following topics:
* [Enabling the Actor Pattern](../../topics/enable_actor_pattern.md)

View File

@ -0,0 +1,118 @@
# Dapr Actors Runtime
Dapr Actors runtime provides following capabilities:
## Actor State Management
Actors can save state reliably using state management capability.
You can interact with Dapr through Http/gRPC endpoints for state management.
### Save the Actor State
You can save the Actor state of a given key of actorId of type actorType by calling
```
POST/PUT http://localhost:3500/v1.0/actors/<actorType>/<actorId>/state/<key>
```
Value of the key is passed as request body.
```
{
"key": "value"
}
```
If you want to save multiple items in a single transaction, you can call
```
POST/PUT http://localhost:3500/v1.0/actors/<actorType>/<actorId>/state
```
### Retrieve the Actor State
Once you have saved the actor state, you can retrived the saved state by calling
```
GET http://localhost:3500/v1.0/actors/<actorType>/<actorId>/state/<key>
```
### Remove the Actor State
You can remove state permanently from the saved Actor state by calling
```
DELETE http://localhost:3500/v1.0/actors/<actorType>/<actorId>/state/<key>
```
Refer [dapr spec](/dapr/spec/blob/master/actors.md) for more details.
## Actor Timers and Reminders
Actors can schedule periodic work on themselves by registering either timers or reminders.
### Actor timers
You can register a callback on actor to be executed based on
timer.
Dapr Actor runtime ensures that the callback methods respect the turn-based concurrency guarantees.This means that no other actor methods or timer/reminder callbacks will be in progress until this callback completes execution.
The next period of the timer starts after the callback completes execution. This implies that the timer is stopped while the callback is executing and is started when the callback finishes.
The Dapr Actors runtime saves changes made to the actor's state when the callback finishes. If an error occurs in saving the state, that actor object will be deactivated and a new instance will be activated.
All timers are stopped when the actor is deactivated as part of garbage collection. No timer callbacks are invoked after that. Also, the Dapr Actors runtime does not retain any information about the timers that were running before deactivation. It is up to the actor to register any timers that it needs when it is reactivated in the future.
You can create a timer for an actor by calling the Http/gRPC request to Darp.
```
POST,PUT http://localhost:3500/v1.0/actors/<actorType>/<actorId>/timers/<name>
```
You can provide the timer due time and callback in the request body.
You can remove the actor timer by calling
```
DELETE http://localhost:3500/v1.0/actors/<actorType>/<actorId>/timers/<name>
```
Refer [dapr spec](/dapr/spec/blob/master/actors.md) for more details.
### Actor reminders
Reminders are a mechanism to trigger persistent callbacks on an actor at specified times. Their functionality is similar to timers. But unlike timers, reminders are triggered under all circumstances until the actor explicitly unregisters them or the actor is explicitly deleted. Specifically, reminders are triggered across actor deactivations and failovers because the Dapr Actors runtime persists information about the actor's reminders using Dapr actor state provider.
You can create a persistent reminder for an actor by calling the Http/gRPC request to Darp.
```
POST,PUT POST,PUT http://localhost:3500/v1.0/actors/<actorType>/<actorId>/reminders/<name>
```
You can provide the reminder due time and period in the request body.
#### Retrieve Actor Reminder
You can retrieve the actor reminder by calling
```
GET http://localhost:3500/v1.0/actors/<actorType>/<actorId>/reminders/<name>
```
#### Remove the Actor Reminder
You can remove the actor reminder by calling
```
DELETE http://localhost:3500/v1.0/actors/<actorType>/<actorId>/reminders/<name>
```
Refer [dapr spec](/dapr/spec/blob/master/actors.md) for more details.

View File

@ -1,41 +0,0 @@
# Integration with Actor Frameworks
This article introduces how Dapr integrate with existing Actor Frameworks such as [Orleans](https://github.com/dotnet/orleans), [Akka](https://akka.io/), and [Service Fabric Reliable Actors](https://docs.microsoft.com/en-us/azure/service-fabric/service-fabric-reliable-actors-introduction). With Dapr integration, these Actor frameworks will allow developers to write Actor code in any programming languages, and to plug their Actors into Dapr eventing pipeline that enables reliable messaging, pub-sub, binding to event sources and many other features.
## Client-side integration
On the client side, Dapr acts as a shim in front of an Actor proxy. This allows Dapr to bring reliable messaging, binding and other features to the connected Actor framework.
![Actors client](../imgs/actors_client.png)
## Server-side integration
On the server side, Dapr leverages Actor framework capabilities such as state management and single activation guarantee to offer complete Actor framework capabilities.
Dapr defines an **Actor API**, which the integrated Actor frameworks are supposed to implement/support.
> Dapr comes with a built-in Actor framework implemenation through the same Actor API.
The following diagram illustrates two possible execution paths. ![Actors server](../imgs/actors_server.png)
### Client calling an Actor (green path)
1. A client makes a call to an Actor instance.
2. Dapr calls an Actor proxy to forward the request.
3. As the Actor runtime activates/invokes an Actor, it calls Dapr through the Actor API to invoke the user method.
4. Dapr forwards the request to user code through Dapr API.
5. As the response is returned, it was sent back to client as a response to the original request.
### A message triggering an Actor (yellow path)
1. A message is routed to the Dapr instance that is hosting the destination Actor.
2. Dapr dispatches the message to the Actor runtime when possible.
3. The rest of the steps are the same as the green path.
## Actor API
At a high level, the Actor API shall contain the following methods:
* Dispatch to Actor when possible
* Call method on an Actor now
* Load/save state
* Create/remove
* Locate & send message to an Actor
## Custom Programming Experience
Custom programming experiences can be built on top of Dapr through a custom library. The Custom library adapts Dapr API to a custom API shape that the user code consumes, as shown in the following diagram:
![custom_experience](../imgs/programming_experience.png)

View File

@ -1,76 +0,0 @@
# Enabling the Actor Pattern
The Actor Pattern has been broadly used in modeling complex distributed systems in which a large number of individual agents work together. For example, in many IoT solutions, actors are often used as digital presences of physical devices. Dapr supports the Actor Pattern by offering key characteristics of an actor. This document introduces architecturally how Dapr enables the Actor Pattern. To learn about how to use the Actor Pattern with Dapr, please read [this document](../concepts/actor/actor_pattern.md).
## Overview
Dapr uses an off-path **partition table** to assign actor ids to service instances so that requests to the same actor id is always routed to the same instance. The partition table is dynamically adjusted when re-partitioning or failover happens. Changes to the table are broadcasted to Dapr sidecars to update their own settings to match with the global table.
A client can talk to an actor through either reliable messaging or direct invocation, as illustrated by the following diagram:
![actor pattern](../imgs/actor_pattern.png)
### Calling an actor through messaging
1. When a process pair of user code and Dapr sidecar comes up, the sidecar registers the pair (such as a Pod on Kubernetes) with a **partition table** hosted on the Dapr control plane.
2. The partition table updates itself to reflect the topology change.Then, it broadcasts a change notification to all Dapr sidecars.
3. The Dapr sidecar updates its own settings to poll messages from corresponding partitions of a reliable queue.
4. The client code sends requests to the reliable queue, which puts the requests in corresponding partitions based on the associated contextual ids. Then, the messages are picked up and consumed during the next poll.
### Calling an actor through direct invocation
1. The client sidecar reads the partition table to find actor id assignments.
2. The sidecar figures out a direct route to the target service instance and sends the request direct to the destination sidecar.
## Messaging
When enabled, each Dapr sidecar has a designated reliable message queue. This allows the sidecar to sequentialize requests to a specific actor id. Dapr supports at-least-once delivery and exact-once delivery within a configurable time window. Please see [reliable messaging](TBD) for more details.
Reliable messing is not used for direct invocations. In such a case, user code is expected to handle response errors and retry the request as needed.
## State Management
All state access are encapsulated in a **state provider**. Dapr expects a state provider to offer state partitioning for scale and replication for high availability and reliability.
Because all requests to an actor instance are routed to the same Dapr service instance, the user code can use local variables as local caches of its state.
When Dapr routes an actor instance to a new Dapr service instance, it invokes the **/state** endpoint so that the actor instance can restore state from the state store.
## Partition Table
Dapr uses different strategies to manage and use the partition table for different scenarios. Although partition table doesnt sit on active data path, Dapr tries to employ any possible optimizations to minimize the overheads of table management and lookups.
### Skipping the table
If an Dapr service is stateless, and if only direct invocation is needed, the partition table is skipped. In this case, requests to actor instances are distributed by the service load balancer.
If an Dapr service is stateless, and is known to have light traffic so that messaging system partition is not needed, the table can also be skipped even when reliable messaging is used, because all Dapr instances will simply compete for the latest messages from the same partition.
If Dapr services have stable identifiers (i.e. doesnt change when restarted or relocated), and both the possible range of actor Ids and all Dapr service ids are known, a predefined algorithm can be used to decide actor partitions instead of looking up the partition table. However, in such a case, dynamic partitioning is not allowed.
### Range-only table
Range-only table contains rows of actor id ranges only. When the service replica number changes, the table is updated so that the possible range is evenly distributed across all service replicas.
The partition table can change its assignments based on partition loads. For example, when it finds the first partition is heavily used than others, it can reassign multiple service instances to the partition through **dynamic assignments**, or re-partition through **dynamic ranges**, as shown by the following diagram:
![dynamic range](../imgs/dynamic_range.png)
Dynamic assignments requires less sidecar configuration changes, but it requires a sidecar being able to handle multiple partitions.
### Per-actor table
Per-actor table maintains a detailed mapping between actor ids and service instances. Per-actor table is quite expensive to maintain. Its most useful when the actor code fluctuates in resource consumption when given different workloads. In such case, a more sophisticated scheduling algorithm, such as a greedy algorithm or a dynamic programming algorithm, can be used to place the actor instances.
In addition to the increased maintenance burden, per-actor table also triggers sidecar configuration updates much more frequently. And the sidecar needs to be able to handle scattered id ranges, when could be hard to be implemented against certain messaging systems.
In the above discussion, it's been assumed that service instances have stable identifiers. If this is not the case, a check-in-check-out mechanism is needed for a service instance to associate itself to a given partition. In such as case, the partition table defines partition ranges but doesnt define assignments. Instead, all sidecars come in as competing consumers and attempt to check out partitions. A partition can be explicitly checked in for scenarios such controlled upgrades. Otherwise, a checkout expires after certain time window and the partition is made available for checkouts again.
## Sequential Access
When configured to run as actors, Dapr sidecar dispatches messages to user code. The user code is free to use whatever threading model of choice to handle these messages. When the user code tries to preserve state, the state write requests are again dispatched to the underlying state store sequentially.
Dapr allows read-only requests (signified by the GET verb) to be routed to any service instances. This allows the single-writer-multiple-reader pattern, in which readers get eventual consistent (but almost always up-to-date - the delta is the single in-flight transaction) reads on actor states.
Such sequential access pattern is broken when multiple activations occur. For example, due to network partition, two service instances get different partition table data and try to poll data from the same partition. One message for an actor goes to intance A and the subsquent message for the same actor goes to instance B. This may lead to consistency issues.
One way to partially mitigate this is to add a auto-increment version tag on state data so that the user code can check for conflicting updates while submitting its changes.
## Summary
Dapr can be used as building blocks to build a complete Actor framework but itself isnt one. As a developer, you should be aware that when you use Dapr by itself, you can configure it to offer some characteristics of an Actor, but you should not assume it gives a complete feature set as a complete Actor framework.

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB