ergo

package module
v1.1.225 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Oct 12, 2023 License: MIT Imports: 10 Imported by: 1

README

Ergo Framework

GoDoc MIT license Telegram Community Discord Community Twitter

Technologies and design patterns of Erlang/OTP have been proven over the years. Now in Golang. Up to x5 times faster than original Erlang/OTP in terms of network messaging. The easiest way to create an OTP-designed application in Golang.

https://ergo.services

Purpose

The goal of this project is to leverage Erlang/OTP experience with Golang performance. Ergo Framework implements DIST protocol, ETF data format and OTP design patterns gen.Server, gen.Supervisor, gen.Application which makes you able to create distributed, high performance and reliable microservice solutions having native integration with Erlang infrastructure

Cloud

Distributed Cloud is coming. With Ergo Framework you can join your services into a single cluster with transparent networking using our Cloud Overlay Network where they can connect to each other smoothly, no matter where they run - AWS, Azure or GCP, or anywhere else. All these connections are secured with end-to-end encryption. Read more in this article https://blog.ergo.services/cloud-overlay-network-3a133d47efe5.

Quick start

First, you need to install the boilerplate code generation tool ergo - https://github.com/ergo-services/tools using command below

go install ergo.services/tools/ergo@latest

And then, you can create your project with just one command. Here is example:

Supervision tree

mynode
|- myapp
|   |
|    `- mysup
|        |
|         `- myactor
|- myweb
`- myactor2

To generate project for this design use the following command:

ergo -init MyNode -with-app MyApp -with-sup MyApp:MySup -with-actor MySup:MyActor -with-web "MyWeb{port:8000,handlers:3}" -with-actor MyActor2

as a result you will get generated project:

   mynode/
   |-- apps/
   |   `-- myapp/
   |       |-- myactor.go
   |       |-- myapp.go
   |       `-- mysup.go
   |-- cmd/
   |   |-- myactor2.go
   |   |-- mynode.go
   |   |-- myweb.go
   |   `-- myweb_handler.go
   |-- README.md
   |-- go.mod
   `-- go.sum

to try it:

$ cd mynode
$ go run ./cmd/

You may also read our article about this tool with a great example https://blog.ergo.services/quick-start-1094d56d4e2

Features

image

  • Support Erlang 25 - allows you connect your node to (and accept connection from) any Erlang/Elixir node within a cluster
  • Spawn Erlang-like processes
  • Register/unregister processes with simple atom
  • Set of ready-to-use disign patterns (behaviors)
    • gen.Server behavior with atomic state and Erlang's gen_server support to make sync request ServerProcess.Call, async - ServerProcess.Cast or Process.Send in fashion of gen_server:call, gen_server:cast, erlang:send accordingly
    • gen.Supervisor behavior with all known restart strategies (One For One, One For All, Rest For One, Simple One For One)
    • gen.Application behavior with all known starting types (Permanent, Temporary, Transient)
    • gen.Pool a basic design pattern with a pool of workers. All messages/requests received by the pool process are forwarded to the workers using the "Round Robin" algorithm. The worker process is automatically restarting on termination
    • gen.TCP - socket acceptor pool for TCP protocols. This behavior aims to provide everything you need to accept TCP connections and process packets with a small code base and low latency while being easy to use.
    • gen.UDP - acceptor pool for UDP protocols. This behavior provides the same feature set as TCP but for handling UDP packets using pool of handlers
    • gen.Web - Web API Gateway behavior. This behavior allows you to listen HTTP port and handle HTTP-request using pool of workers.
    • gen.Stage behavior support (originated from Elixir's GenStage). This is abstraction built on top of gen.Server to provide a simple way to create a distributed Producer/Consumer architecture, while automatically managing the concept of backpressure. This implementation is fully compatible with Elixir's GenStage. Example is here examples/genstage or just run go run ./examples/genstage to see it in action
    • gen.Saga behavior support. It implements Saga design pattern - a sequence of transactions that updates each service state and publishes the result (or cancels the transaction or triggers the next transaction step). gen.Saga also provides a feature of interim results (can be used as transaction progress or as a part of pipeline processing), time deadline (to limit transaction lifespan), two-phase commit (to make distributed transaction atomic). Here is example examples/gensaga.
    • gen.Raft behavior support. It's improved implementation of Raft consensus algorithm. The key improvement is using quorum under the hood to manage the leader election process and make the Raft cluster more reliable. This implementation supports quorums of 3, 5, 7, 9, or 11 quorum members. Here is an example of this feature examples/genraft
  • Monitor processes/nodes, local/remote with Erlang support
  • Link processes local/remote with Erlang support
  • embedded EPMD (in order to get rid of erlang' dependencies) with Erlang support
  • Unmarshalling terms into the struct using etf.TermIntoStruct, etf.TermProplistIntoStruct or to the string using etf.TermToString including custom marshaling/unmarshaling via Marshal and Unmarshal interfaces. But it's highly recommended to use etf.RegisterType so you will be receiving messages in a native Golang-type
  • Encryption (TLS 1.3) support (including autogenerating self-signed certificates)
  • Compression support (with customization of compression level and threshold). It can be configured for the node or a particular process.
  • Proxy support with end-to-end encryption, includeing compression/fragmentation/linking/monitoring features.
  • Tested and confirmed support Windows, Darwin (MacOS), Linux, FreeBSD.
  • Zero dependencies. All features are implemented using the standard Golang library.

Requirements

  • Go 1.17.x and above

Versioning

Golang introduced v2 rule a while ago to solve complicated dependency issues. We found this solution very controversial and there is still a lot of discussion around it. So, we decided to keep the old way for the versioning, but have to use the git tag with v1 as a major version (due to "v2 rule" restrictions). Since now we use git tag pattern 1.999.XYZ where X - major number, Y - minor, Z - patch version.

Changelog

Here are the changes of latest release. For more details see the ChangeLog

v2.2.4 2023-05-01 [tag version v1.999.224]

This release includes fixes:

  • Fixed incorrect handling of gen.SupervisorStrategyRestartTransient restart strategy in gen.Supervisor
  • Fixed missing ServerBehavior in [gen.Pool, gen.Raft, gen.Saga, gen.Stage, gen.TCP, gen.UDP, gen.Web] behavior interfaces
  • Introduced the new tool for boilerplate code generation - ergo https://github.com/ergo-services/tools. You may read more information about this tool in our article with a great example https://blog.ergo.services/quick-start-1094d56d4e2

Benchmarks

Here is simple EndToEnd test demonstrates performance of messaging subsystem

Hardware: workstation with AMD Ryzen Threadripper 3970X (64) @ 3.700GHz

❯❯❯❯ go test -bench=NodeParallel -run=XXX -benchtime=10s
goos: linux
goarch: amd64
pkg: github.com/ergo-services/ergo/tests
cpu: AMD Ryzen Threadripper 3970X 32-Core Processor
BenchmarkNodeParallel-64                 4738918              2532 ns/op
BenchmarkNodeParallelSingleNode-64      100000000              429.8 ns/op

PASS
ok      github.com/ergo-services/ergo/tests  29.596s

these numbers show almost 500.000 sync requests per second for the network messaging via localhost and 10.000.000 sync requests per second for the local messaging (within a node).

Compression

This benchmark shows the performance of compression for sending 1MB message between two nodes (via a network).

❯❯❯❯ go test -bench=NodeCompression -run=XXX -benchtime=10s
goos: linux
goarch: amd64
pkg: github.com/ergo-services/ergo/tests
cpu: AMD Ryzen Threadripper 3970X 32-Core Processor
BenchmarkNodeCompressionDisabled1MBempty-64         2400           4957483 ns/op
BenchmarkNodeCompressionEnabled1MBempty-64          5769           2088051 ns/op
BenchmarkNodeCompressionEnabled1MBstring-64         5202           2077099 ns/op
PASS
ok      github.com/ergo-services/ergo/tests     56.708s

It demonstrates more than 2 times improvement.

Proxy

This benchmark demonstrates how proxy feature and e2e encryption impact a messaging performance.

❯❯❯❯ go test -bench=NodeProxy -run=XXX -benchtime=10s
goos: linux
goarch: amd64
pkg: github.com/ergo-services/ergo/tests
cpu: AMD Ryzen Threadripper 3970X 32-Core Processor
BenchmarkNodeProxy_NodeA_to_NodeC_direct_Message_1KB-64                     1908477       6337 ns/op
BenchmarkNodeProxy_NodeA_to_NodeC_via_NodeB_Message_1KB-64                  1700984       7062 ns/op
BenchmarkNodeProxy_NodeA_to_NodeC_via_NodeB_Message_1KB_Encrypted-64        1271125       9410 ns/op
PASS
ok      github.com/ergo-services/ergo/tests     45.649s

Ergo Framework vs original Erlang/OTP

Hardware: laptop with Intel(R) Core(TM) i5-8265U (4 cores. 8 with HT)

benchmarks

sources of these benchmarks are here

EPMD

Ergo Framework has embedded EPMD implementation in order to run your node without external epmd process needs. By default, it works as a client with erlang' epmd daemon or others ergo's nodes either.

The one thing that makes embedded EPMD different is the behavior of handling connection hangs - if ergo' node is running as an EPMD client and lost connection, it tries either to run its own embedded EPMD service or to restore the lost connection.

Examples

Code below is a simple implementation of gen.Server pattern examples/genserver

package main

import (
	"fmt"
	"time"

	"github.com/ergo-services/ergo/etf"
	"github.com/ergo-services/ergo/gen"
)

type simple struct {
	gen.Server
}

func (s *simple) HandleInfo(process *gen.ServerProcess, message etf.Term) gen.ServerStatus {
	value := message.(int)
	fmt.Printf("HandleInfo: %#v \n", message)
	if value > 104 {
		return gen.ServerStatusStop
	}
	// sending message with delay 1 second
	fmt.Println("increase this value by 1 and send it to itself again")
	process.SendAfter(process.Self(), value+1, time.Second)
	return gen.ServerStatusOK
}

here is output of this code

$ go run ./examples/simple
HandleInfo: 100
HandleInfo: 101
HandleInfo: 102
HandleInfo: 103
HandleInfo: 104
HandleInfo: 105
exited

See https://github.com/ergo-services/examples for more details

Elixir Phoenix Users

Users of the Elixir Phoenix framework might encounter timeouts when trying to connect a Phoenix node to an ergo node. The reason is that, in addition to global_name_server and net_kernel, Phoenix attempts to broadcast messages to the pg2 PubSub handler

To work with Phoenix nodes, you must create and register a dedicated pg2 GenServer, and spawn it inside your node. The spawning process must have "pg2" as a process name:

type Pg2GenServer struct {
    gen.Server
}

func main() {
    // ...
    pg2 := &Pg2GenServer{}
    node1, _ := ergo.StartNode("node1@localhost", "cookies", node.Options{})
    process, _ := node1.Spawn("pg2", gen.ProcessOptions{}, pg2, nil)
    // ...
}

Development and debugging

There are options already defined that you might want to use

  • -ergo.trace - enable debug info (logging via lib.Log(...))
  • -ergo.debug - enable extended debug info (logging via lib.Log(...) and lib.Warning(...))
  • -ergo.norecover - disable panic catching
  • -ergo.warning - enable/disable warnings (logging via lib.Warning(...). Default: enable)

To enable Golang profiler just add --tags debug in your go run or go build like this:

go run --tags debug ./examples/genserver/demoGenServer.go

Now golang' profiler is available at http://localhost:9009/debug/pprof

To check test coverage:

go test -coverprofile=cover.out ./...
go tool cover -html=cover.out -o coverage.html

To run tests with cleaned test cache:

go vet
go clean -testcache
go test -v ./...

To run benchmarks:

go test -bench=Node -run=X -benchmem

Companies are using Ergo Framework

Kaspersky RingCentral LilithGames

is your company using Ergo? add your company logo/name here

Commercial support

please, contact ceo@ergo.services for more information

Documentation

Index

Constants

View Source
const (
	Version           = "2.2.4" // Ergo Framework version
	VersionPrefix     = "ergo"  // Prefix using for the full version name
	VersionOTP    int = 25      // Erlang version support
)

Variables

View Source
var (
	DefaultNode node.Node
)

Functions

func AddProxyRoute added in v1.1.225

func AddProxyRoute(proxy node.ProxyRoute) error

func AddStaticRoute added in v1.1.225

func AddStaticRoute(node string, host string, port uint16, options node.RouteOptions) error

AddStaticRoute adds static route for the given name

func AddStaticRouteOptions added in v1.1.225

func AddStaticRouteOptions(node string, options node.RouteOptions) error

AddStaticRouteOptions adds static route options for the given node name which does regular port resolving but applies static options

func AddStaticRoutePort added in v1.1.225

func AddStaticRoutePort(node string, port uint16, options node.RouteOptions) error

AddStaticRoutePort adds static route for the given node name which makes node skip resolving port process

func ApplicationInfo added in v1.1.225

func ApplicationInfo(name string) (gen.ApplicationInfo, error)

func ApplicationLoad added in v1.1.225

func ApplicationLoad(app gen.ApplicationBehavior, args ...etf.Term) (string, error)

func ApplicationStart added in v1.1.225

func ApplicationStart(appName string, args ...etf.Term) (gen.Process, error)

func ApplicationStartPermanent added in v1.1.225

func ApplicationStartPermanent(appName string, args ...etf.Term) (gen.Process, error)

func ApplicationStartTransient added in v1.1.225

func ApplicationStartTransient(appName string, args ...etf.Term) (gen.Process, error)

func ApplicationStop added in v1.1.225

func ApplicationStop(appName string) error

func ApplicationUnload added in v1.1.225

func ApplicationUnload(appName string) error

func Connect added in v1.1.225

func Connect(node string) error

Connect sets up a connection to node

func Disconnect added in v1.1.225

func Disconnect(node string) error

Disconnect close connection to the node

func Env added in v1.1.225

func Env(name gen.EnvKey) interface{}

Env returns value associated with given environment name.

func IsAlias added in v1.1.225

func IsAlias(alias etf.Alias) bool

IsAlias checks whether the given alias is belongs to the alive process on this node. If the process died all aliases are cleaned up and this function returns false for the given alias. For alias from the remote node always returns false.

func IsAlive added in v1.1.225

func IsAlive() bool

IsAlive returns true if node is still alive

func IsMonitor added in v1.1.225

func IsMonitor(ref etf.Ref) bool

IsMonitor returns true if the given references is a monitor

func Links(process etf.Pid) []etf.Pid

func ListEnv added in v1.1.225

func ListEnv() map[gen.EnvKey]interface{}

ListEnv returns a map of configured Node environment variables.

func LoadedApplications added in v1.1.225

func LoadedApplications() []gen.ApplicationInfo

func MakeRef added in v1.1.225

func MakeRef() etf.Ref

MakeRef creates an unique reference within this node

func MonitoredBy added in v1.1.225

func MonitoredBy(process etf.Pid) []etf.Pid

func Monitors added in v1.1.225

func Monitors(process etf.Pid) []etf.Pid

func MonitorsByName added in v1.1.225

func MonitorsByName(process etf.Pid) []gen.ProcessID

func Name added in v1.1.225

func Name() string

Name returns node name

func NetworkStats added in v1.1.225

func NetworkStats(name string) (node.NetworkStats, error)

NetworkStats returns network statistics of the connection with the node. Returns error ErrUnknown if connection with given node is not established.

func Nodes added in v1.1.225

func Nodes() []string

Nodes returns the list of connected nodes

func NodesIndirect added in v1.1.225

func NodesIndirect() []string

NodesIndirect returns the list of nodes connected via proxies

func ProcessByAlias added in v1.1.225

func ProcessByAlias(alias etf.Alias) gen.Process

ProcessByAlias returns Process for the given alias. Returns nil if it doesn't exist (not found) or terminated

func ProcessByName added in v1.1.225

func ProcessByName(name string) gen.Process

ProcessByName returns Process for the given name. Returns nil if it doesn't exist (not found) or terminated.

func ProcessByPid added in v1.1.225

func ProcessByPid(pid etf.Pid) gen.Process

ProcessByPid returns Process for the given Pid. Returns nil if it doesn't exist (not found) or terminated.

func ProcessInfo added in v1.1.225

func ProcessInfo(pid etf.Pid) (gen.ProcessInfo, error)

ProcessInfo returns the details about given Pid

func ProcessList added in v1.1.225

func ProcessList() []gen.Process

ProcessList returns the list of running processes

func ProvideRemoteSpawn added in v1.1.225

func ProvideRemoteSpawn(name string, object gen.ProcessBehavior) error

func ProxyRoute added in v1.1.225

func ProxyRoute(name string) (node.ProxyRoute, bool)

ProxyRoute returns proxy route added using AddProxyRoute

func ProxyRoutes added in v1.1.225

func ProxyRoutes() []node.ProxyRoute

ProxyRoutes returns list of proxy routes added using AddProxyRoute

func RegisterBehavior added in v1.1.225

func RegisterBehavior(group, name string, behavior gen.ProcessBehavior, data interface{}) error

RegisterBehavior

func RegisterName added in v1.1.225

func RegisterName(name string, pid etf.Pid) error

func RegisteredBehavior added in v1.1.225

func RegisteredBehavior(group, name string) (gen.RegisteredBehavior, error)

RegisteredBehavior

func RegisteredBehaviorGroup added in v1.1.225

func RegisteredBehaviorGroup(group string) []gen.RegisteredBehavior

RegisteredBehaviorGroup

func Registrar added in v1.1.225

func Registrar() node.Registrar

Returns Registrar interface

func RemoveProxyRoute added in v1.1.225

func RemoveProxyRoute(name string) bool

func RemoveStaticRoute added in v1.1.225

func RemoveStaticRoute(name string) bool

Remove static route removes static route with given name

func Resolve added in v1.1.225

func Resolve(node string) (node.Route, error)

Resolve

func ResolveProxy added in v1.1.225

func ResolveProxy(node string) (node.ProxyRoute, error)

ResolveProxy resolves proxy route. Checks for the proxy route added using AddProxyRoute. If it wasn't found makes request to the registrar.

func RevokeRemoteSpawn added in v1.1.225

func RevokeRemoteSpawn(name string) error

func SetEnv added in v1.1.225

func SetEnv(name gen.EnvKey, value interface{})

SetEnv set node environment variable with given name. Use nil value to remove variable with given name. Ignores names with "ergo:" as a prefix.

func Spawn added in v1.1.225

func Spawn(name string, opts gen.ProcessOptions, object gen.ProcessBehavior, args ...etf.Term) (gen.Process, error)

Spawn spawns a new process

func StartNode

func StartNode(name string, cookie string, opts node.Options) (node.Node, error)

StartNode create new node with name and cookie string

func StartNodeWithContext

func StartNodeWithContext(ctx context.Context, name string, cookie string, opts node.Options) (node.Node, error)

StartNodeWithContext create new node with specified context, name and cookie string

func StaticRoute added in v1.1.225

func StaticRoute(name string) (node.Route, bool)

StaticRoute returns Route for the given name. Returns false if it doesn't exist.

func StaticRoutes added in v1.1.225

func StaticRoutes() []node.Route

StaticRoutes returns list of routes added using AddStaticRoute

func Stats added in v1.1.225

func Stats() node.NodeStats

func Stop added in v1.1.225

func Stop()

func UnregisterBehavior added in v1.1.225

func UnregisterBehavior(group, name string) error

UnregisterBehavior

func UnregisterName added in v1.1.225

func UnregisterName(name string) error

func Uptime added in v1.1.225

func Uptime() int64

Uptime returns node uptime in seconds

func Wait added in v1.1.225

func Wait()

func WaitWithTimeout added in v1.1.225

func WaitWithTimeout(d time.Duration) error

func WhichApplications added in v1.1.225

func WhichApplications() []gen.ApplicationInfo

Types

This section is empty.

Directories

Path Synopsis
apps
examples
lib
proto

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL