rogeralsing / gam
- среда, 4 мая 2016 г. в 03:12:29
Go
Go Actor Model framework - Ultra fast distributed actors for Golang
GAM is a MVP Actor Model framework for Go.
Design principles:
Minimalistic API - In the spirit of Go, the API should be small and easy to use. Avoid enterprisey JVM like containers and configurations.
Build on existing technologies - There are already a lot of great tech for e.g. networking and clustering, build on those. e.g. gRPC streams for networking, Consul.IO for clustering.
Pass data, not objects - Serialization is an explicit concern, don't try to hide it. Protobuf all the way.
Be fast - Do not trade performance for magic API trickery.
Ultra fast remoting, GAM currently manages to pass 800k+ messages per second between nodes using only two actors, while still preserving message order!
:> node1.exe
2016/04/30 20:33:48 Host is 127.0.0.1:55567
2016/04/30 20:33:48 Started EndpointManager
2016/04/30 20:33:48 Starting GAM server on 127.0.0.1:55567.
2016/04/30 20:33:48 Started EndpointWriter for host 127.0.0.1:8080
2016/04/30 20:33:48 Connecting to host 127.0.0.1:8080
2016/04/30 20:33:48 Connected to host 127.0.0.1:8080
2016/04/30 20:33:48 Getting stream from host 127.0.0.1:8080
2016/04/30 20:33:48 Got stream from host 127.0.0.1:8080
2016/04/30 20:33:48 Starting to send
2016/04/30 20:33:48 50000
2016/04/30 20:33:48 100000
...snip...
2016/04/30 20:33:50 950000
2016/04/30 20:33:50 1000000
2016/04/30 20:33:50 Elapsed 2.4237125s
2016/04/30 20:33:50 Msg per sec 825180 <---
For a more indepth description of the differences, see this thread Actors vs. CSP
You need to ensure that your $GOPATH
variable is properly set.
Next, install the standard protocol buffer implementation and run the following commands to get all the neccessary tooling:
go get github.com/gogo/protobuf/proto
go get github.com/gogo/protobuf/protoc-gen-gogo
go get github.com/gogo/protobuf/gogoproto
Finally, run the make
tool in the package's root to generate the protobuf definitions and build the packages.
type Hello struct{ Who string }
type HelloActor struct{}
func (state *HelloActor) Receive(context actor.Context) {
switch msg := context.Message().(type) {
case Hello:
fmt.Printf("Hello %v\n", msg.Who)
}
}
func main() {
props := actor.FromInstance(&HelloActor{})
pid := actor.Spawn(props)
pid.Tell(Hello{Who: "Roger"})
console.ReadLine()
}
type Become struct{}
type Hello struct{ Who string }
type BecomeActor struct{}
func (state *BecomeActor) Receive(context actor.Context) {
switch msg := context.Message().(type) {
case Hello:
fmt.Printf("Hello %v\n", msg.Who)
context.Become(state.Other)
}
}
func (state *BecomeActor) Other(context actor.Context) {
switch msg := context.Message().(type) {
case Hello:
fmt.Printf("%v, ey we are now handling messages in another behavior", msg.Who)
}
}
func NewBecomeActor() actor.Actor {
return &BecomeActor{}
}
func main() {
props := actor.FromProducer(NewBecomeActor)
pid := actor.Spawn(props)
pid.Tell(Hello{Who: "Roger"})
pid.Tell(Hello{Who: "Roger"})
console.ReadLine()
}
Unlike Akka, GAM uses messages for lifecycle events instead of OOP method overrides
type Hello struct{ Who string }
type HelloActor struct{}
func (state *HelloActor) Receive(context actor.Context) {
switch msg := context.Message().(type) {
case actor.Started:
fmt.Println("Started, initialize actor here")
case actor.Stopping:
fmt.Println("Stopping, actor is about shut down")
case actor.Stopped:
fmt.Println("Stopped, actor and it's children are stopped")
case actor.Restarting:
fmt.Println("Restarting, actor is about restart")
case Hello:
fmt.Printf("Hello %v\n", msg.Who)
}
}
func main() {
props := actor.FromInstance(&HelloActor{})
actor := actor.Spawn(props)
actor.Tell(Hello{Who: "Roger"})
//why wait?
//Stop is a system message and is not processed through the user message mailbox
//thus, it will be handled _before_ any user message
//we only do this to show the correct order of events in the console
time.Sleep(1 * time.Second)
actor.Stop()
console.ReadLine()
}
Root actors are supervised by the actor.DefaultSupervisionStrategy()
, which always issues a actor.RestartDirective
for failing actors
Child actors are supervised by their parents.
Parents can customize their child supervisor strategy using gam.Props
type Hello struct{ Who string }
type ParentActor struct{}
func (state *ParentActor) Receive(context actor.Context) {
switch msg := context.Message().(type) {
case Hello:
props := actor.FromProducer(NewChildActor)
child := context.Spawn(props)
child.Tell(msg)
}
}
func NewParentActor() actor.Actor {
return &ParentActor{}
}
type ChildActor struct{}
func (state *ChildActor) Receive(context actor.Context) {
switch msg := context.Message().(type) {
case actor.Started:
fmt.Println("Starting, initialize actor here")
case actor.Stopping:
fmt.Println("Stopping, actor is about shut down")
case actor.Stopped:
fmt.Println("Stopped, actor and it's children are stopped")
case actor.Restarting:
fmt.Println("Restarting, actor is about restart")
case Hello:
fmt.Printf("Hello %v\n", msg.Who)
panic("Ouch")
}
}
func NewChildActor() actor.Actor {
return &ChildActor{}
}
func main() {
decider := func(child *actor.PID, reason interface{}) actor.Directive {
fmt.Println("handling failure for child")
return actor.StopDirective
}
supervisor := actor.NewOneForOneStrategy(10, 1000, decider)
props := actor.
FromProducer(NewParentActor).
WithSupervisor(supervisor)
pid := actor.Spawn(props)
pid.Tell(Hello{Who: "Roger"})
console.ReadLine()
}
GAM's networking layer is built as a thin wrapper ontop of gRPC and message serialization is built on Protocol Buffers
type MyActor struct{
count int
}
func (state *MyActor) Receive(context actor.Context) {
switch msg := context.Message().(type) {
case *messages.Response:
state.count++
fmt.Println(state.count)
}
}
func main() {
remoting.StartServer("localhost:8090")
pid := actor.SpawnTemplate(&MyActor{})
message := &messages.Echo{Message: "hej", Sender: pid}
//this is the remote actor we want to communicate with
remote := actor.NewPID("localhost:8091", "myactor")
for i := 0; i < 10; i++ {
remote.Tell(message)
}
console.ReadLine()
}
type MyActor struct{}
func (*MyActor) Receive(context actor.Context) {
switch msg := context.Message().(type) {
case *messages.Echo:
msg.Sender.Tell(&messages.Response{
SomeValue: "result",
})
}
}
func main() {
remoting.StartServer("localhost:8091")
pid := actor.SpawnTemplate(&MyActor{})
//register a name for our local actor so that it can be discovered remotely
actor.ProcessRegistry.Register("myactor", pid)
console.ReadLine()
}
syntax = "proto3";
package messages;
import "actor.proto"; //we need to import actor.proto, so our messages can include PID's
//this is the message the actor on node 1 will send to the remote actor on node 2
message Echo {
actor.PID Sender = 1; //this is the PID the remote actor should reply to
string Message = 2;
}
//this is the message the remote actor should reply with
message Response {
string SomeValue = 1;
}
For more examples, see the example folder in this repository.