Code Monkey home page Code Monkey logo

gam's Introduction

Go Actor Model

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 over one million messages per second between nodes using only two actors, while still preserving message order! This is three times more the new super advanced UDP based Artery transport for Scala Akka.

:> node1.exe
2016/11/17 14:57:26 [REMOTING] Started EndpointManager
2016/11/17 14:57:26 [REMOTING] Starting GAM server on 127.0.0.1:8081.
2016/11/17 14:57:26 [REMOTING] Started EndpointWriter for host 127.0.0.1:8080
2016/11/17 14:57:26 [REMOTING] Connecting to host 127.0.0.1:8080
2016/11/17 14:57:26 [REMOTING] Connected to host 127.0.0.1:8080
2016/11/17 14:57:26 [REMOTING] Getting stream from host 127.0.0.1:8080
2016/11/17 14:57:26 [REMOTING] Got stream from host 127.0.0.1:8080
2016/11/17 14:57:26 Starting to send
2016/11/17 14:57:26 50000
2016/11/17 14:57:26 100000
2016/11/17 14:57:26 150000
... snip ...
2016/11/17 14:57:28 900000
2016/11/17 14:57:28 950000
2016/11/17 14:57:28 1000000
2016/11/17 14:57:28 Elapsed 1.8954982s
2016/11/17 14:57:28 Msg per sec 1055131 <---

Why Actors

batman

  • Decoupled Concurrency
  • Distributed by default
  • Fault tolerance

For a more indepth description of the differences, see this thread Actors vs. CSP

Building

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
go get github.com/gogo/protobuf/protoc-gen-gofast
go get google.golang.org/grpc
#go get github.com/gogo/protobuf/protoc-gen-gogofast
#go get github.com/gogo/protobuf/protoc-gen-gogofaster
#go get github.com/gogo/protobuf/protoc-gen-gogoslick

Finally, run the make tool in the package's root to generate the protobuf definitions and build the packages.

Windows users can use Cygwin to run make: www.cygwin.com

Hello world

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()
}

State machines / Become and Unbecome

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()
}

Lifecycle events

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()
}

Supervision

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

Example

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()
}

Networking / Remoting

GAM's networking layer is built as a thin wrapper ontop of gRPC and message serialization is built on Protocol Buffers

Example

Node 1

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()
}

Node 2

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()
}

Message Contracts

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.

gam's People

Contributors

charignon avatar dtravin avatar mattias-lundell avatar pavelsmejkal avatar rogeralsing avatar samanbarghi avatar utrack avatar

Watchers

 avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.