eRPC

eRPC is an efficient, extensible and easy-to-use RPC framework.

Suitable for RPC, Microservice, Peer-to-Peer, IM, Game and other fields.

Install

  • go vesion ≥ 1.11

  • install

GO111MODULE=on go get -u -v -insecure github.com/henrylee2cn/erpc/v6
  • import
import "github.com/henrylee2cn/erpc/v6"

Feature

HeaderBodyHeaderBodyrawprotojsonprotopbprotothriftprotohttprototcptcp4tcp6unixunixpacketkcpquic

Benchmark

Self Test

  • A server and a client process, running on the same machine

  • CPU: Intel Xeon E312xx (Sandy Bridge) 16 cores 2.53GHz

  • Memory: 16G

  • OS: Linux 2.6.32-696.16.1.el6.centos.plus.x86_64, CentOS 6.4

  • Go: 1.9.2

  • Message size: 581 bytes

  • Message codec: protobuf

  • Sent total 1000000 messages

  • erpc

client concurrency mean(ms) median(ms) max(ms) min(ms) throughput(TPS)
100 1 0 16 0 75505
500 9 11 97 0 52192
1000 19 24 187 0 50040
2000 39 54 409 0 42551
5000 96 128 1148 0 46367
  • erpc/socket
client concurrency mean(ms) median(ms) max(ms) min(ms) throughput(TPS)
100 0 0 14 0 225682
500 2 1 24 0 212630
1000 4 3 51 0 180733
2000 8 6 64 0 183351
5000 21 18 651 0 133886

Comparison Test

Environment Throughputs Mean Latency P99 Latency
  • Profile torch of erpc/socket
  • Heap torch of erpc/socket

Example

server.go

package main

import (
	"fmt"
	"time"

	"github.com/henrylee2cn/erpc/v6"
)

func main() {
	defer erpc.FlushLogger()
	// graceful
	go erpc.GraceSignal()

	// server peer
	srv := erpc.NewPeer(erpc.PeerConfig{
		CountTime:   true,
		ListenPort:  9090,
		PrintDetail: true,
	})
	// srv.SetTLSConfig(erpc.GenerateTLSConfigForServer())

	// router
	srv.RouteCall(new(Math))

	// broadcast per 5s
	go func() {
		for {
			time.Sleep(time.Second * 5)
			srv.RangeSession(func(sess erpc.Session) bool {
				sess.Push(
					"/push/status",
					fmt.Sprintf("this is a broadcast, server time: %v", time.Now()),
				)
				return true
			})
		}
	}()

	// listen and serve
	srv.ListenAndServe()
}

// Math handler
type Math struct {
	erpc.CallCtx
}

// Add handles addition request
func (m *Math) Add(arg *[]int) (int, *erpc.Status) {
	// test meta
	erpc.Infof("author: %s", m.PeekMeta("author"))
	// add
	var r int
	for _, a := range *arg {
		r += a
	}
	// response
	return r, nil
}

client.go

package main

import (
	"time"

	"github.com/henrylee2cn/erpc/v6"
)

func main() {
	defer erpc.SetLoggerLevel("ERROR")()

	cli := erpc.NewPeer(erpc.PeerConfig{})
	defer cli.Close()
	// cli.SetTLSConfig(&tls.Config{InsecureSkipVerify: true})

	cli.RoutePush(new(Push))

	sess, stat := cli.Dial(":9090")
	if !stat.OK() {
		erpc.Fatalf("%v", stat)
	}

	var result int
	stat = sess.Call("/math/add",
		[]int{1, 2, 3, 4, 5},
		&result,
		erpc.WithAddMeta("author", "henrylee2cn"),
	).Status()
	if !stat.OK() {
		erpc.Fatalf("%v", stat)
	}
	erpc.Printf("result: %d", result)
	erpc.Printf("Wait 10 seconds to receive the push...")
	time.Sleep(time.Second * 10)
}

// Push push handler
type Push struct {
	erpc.PushCtx
}

// Push handles '/push/status' message
func (p *Push) Status(arg *string) *erpc.Status {
	erpc.Printf("%s", *arg)
	return nil
}

Usage

Peer(server or client) Demo

// Start a server
var peer1 = erpc.NewPeer(erpc.PeerConfig{
    ListenPort: 9090, // for server role
})
peer1.Listen()

...

// Start a client
var peer2 = erpc.NewPeer(erpc.PeerConfig{})
var sess, err = peer2.Dial("127.0.0.1:8080")

Call-Struct API template

type Aaa struct {
    erpc.CallCtx
}
func (x *Aaa) XxZz(arg *<T>) (<T>, *erpc.Status) {
    ...
    return r, nil
}
  • register it to root router:
// register the call route
// HTTP mapping: /aaa/xx_zz
// RPC mapping: Aaa.XxZz
peer.RouteCall(new(Aaa))

// or register the call route
// HTTP mapping: /xx_zz
// RPC mapping: XxZz
peer.RouteCallFunc((*Aaa).XxZz)

Service method mapping

erpc.SetServiceMethodMapper(erpc.HTTPServiceMethodMapper)
erpc.SetServiceMethodMapper(erpc.RPCServiceMethodMapper)

Call-Function API template

func XxZz(ctx erpc.CallCtx, arg *<T>) (<T>, *erpc.Status) {
    ...
    return r, nil
}
  • register it to root router:
// register the call route
// HTTP mapping: /xx_zz
// RPC mapping: XxZz
peer.RouteCallFunc(XxZz)

Push-Struct API template

type Bbb struct {
    erpc.PushCtx
}
func (b *Bbb) YyZz(arg *<T>) *erpc.Status {
    ...
    return nil
}
  • register it to root router:
// register the push handler
// HTTP mapping: /bbb/yy_zz
// RPC mapping: Bbb.YyZz
peer.RoutePush(new(Bbb))

// or register the push handler
// HTTP mapping: /yy_zz
// RPC mapping: YyZz
peer.RoutePushFunc((*Bbb).YyZz)

Push-Function API template

// YyZz register the handler
func YyZz(ctx erpc.PushCtx, arg *<T>) *erpc.Status {
    ...
    return nil
}
  • register it to root router:
// register the push handler
// HTTP mapping: /yy_zz
// RPC mapping: YyZz
peer.RoutePushFunc(YyZz)

Unknown-Call-Function API template

func XxxUnknownCall (ctx erpc.UnknownCallCtx) (interface{}, *erpc.Status) {
    ...
    return r, nil
}
  • register it to root router:
// register the unknown call route: /*
peer.SetUnknownCall(XxxUnknownCall)

Unknown-Push-Function API template

func XxxUnknownPush(ctx erpc.UnknownPushCtx) *erpc.Status {
    ...
    return nil
}
  • register it to root router:
// register the unknown push route: /*
peer.SetUnknownPush(XxxUnknownPush)

Plugin Demo

// NewIgnoreCase Returns a ignoreCase plugin.
func NewIgnoreCase() *ignoreCase {
    return &ignoreCase{}
}

type ignoreCase struct{}

var (
    _ erpc.PostReadCallHeaderPlugin = new(ignoreCase)
    _ erpc.PostReadPushHeaderPlugin = new(ignoreCase)
)

func (i *ignoreCase) Name() string {
    return "ignoreCase"
}

func (i *ignoreCase) PostReadCallHeader(ctx erpc.ReadCtx) *erpc.Status {
    // Dynamic transformation path is lowercase
    ctx.UriObject().Path = strings.ToLower(ctx.UriObject().Path)
    return nil
}

func (i *ignoreCase) PostReadPushHeader(ctx erpc.ReadCtx) *erpc.Status {
    // Dynamic transformation path is lowercase
    ctx.UriObject().Path = strings.ToLower(ctx.UriObject().Path)
    return nil
}

Register above handler and plugin

// add router group
group := peer.SubRoute("test")
// register to test group
group.RouteCall(new(Aaa), NewIgnoreCase())
peer.RouteCallFunc(XxZz, NewIgnoreCase())
group.RoutePush(new(Bbb))
peer.RoutePushFunc(YyZz)
peer.SetUnknownCall(XxxUnknownCall)
peer.SetUnknownPush(XxxUnknownPush)

Config

type PeerConfig struct {
    Network            string        `yaml:"network"              ini:"network"              comment:"Network; tcp, tcp4, tcp6, unix, unixpacket, kcp or quic"`
    LocalIP            string        `yaml:"local_ip"             ini:"local_ip"             comment:"Local IP"`
    ListenPort         uint16        `yaml:"listen_port"          ini:"listen_port"          comment:"Listen port; for server role"`
    DialTimeout time.Duration `yaml:"dial_timeout" ini:"dial_timeout" comment:"Default maximum duration for dialing; for client role; ns,µs,ms,s,m,h"`
    RedialTimes        int32         `yaml:"redial_times"         ini:"redial_times"         comment:"The maximum times of attempts to redial, after the connection has been unexpectedly broken; Unlimited when <0; for client role"`
	RedialInterval     time.Duration `yaml:"redial_interval"      ini:"redial_interval"      comment:"Interval of redialing each time, default 100ms; for client role; ns,µs,ms,s,m,h"`
    DefaultBodyCodec   string        `yaml:"default_body_codec"   ini:"default_body_codec"   comment:"Default body codec type id"`
    DefaultSessionAge  time.Duration `yaml:"default_session_age"  ini:"default_session_age"  comment:"Default session max age, if less than or equal to 0, no time limit; ns,µs,ms,s,m,h"`
    DefaultContextAge  time.Duration `yaml:"default_context_age"  ini:"default_context_age"  comment:"Default CALL or PUSH context max age, if less than or equal to 0, no time limit; ns,µs,ms,s,m,h"`
    SlowCometDuration  time.Duration `yaml:"slow_comet_duration"  ini:"slow_comet_duration"  comment:"Slow operation alarm threshold; ns,µs,ms,s ..."`
    PrintDetail        bool          `yaml:"print_detail"         ini:"print_detail"         comment:"Is print body and metadata or not"`
    CountTime          bool          `yaml:"count_time"           ini:"count_time"           comment:"Is count cost time or not"`
}

Optimize

func SetMessageSizeLimit(maxMessageSize uint32)
func SetSocketKeepAlive(keepalive bool)
func SetSocketKeepAlivePeriod(d time.Duration)
func SetSocketNoDelay(_noDelay bool)
func SetSocketReadBuffer(bytes int)
func SetSocketWriteBuffer(bytes int)

Extensions

Codec

"github.com/henrylee2cn/erpc/v6/codec""github.com/henrylee2cn/erpc/v6/codec""github.com/henrylee2cn/erpc/v6/codec""github.com/henrylee2cn/erpc/v6/codec""github.com/henrylee2cn/erpc/v6/codec""github.com/henrylee2cn/erpc/v6/codec"

Plugin

"github.com/henrylee2cn/erpc/v6/plugin/auth""github.com/henrylee2cn/erpc/v6/plugin/binder""github.com/henrylee2cn/erpc/v6/plugin/heartbeat""github.com/henrylee2cn/erpc/v6/plugin/proxy""github.com/henrylee2cn/erpc/v6/plugin/secure""github.com/henrylee2cn/erpc/v6/plugin/overloader"

Protocol

"github.com/henrylee2cn/erpc/v6/proto/rawproto"github.com/henrylee2cn/erpc/v6/proto/jsonproto""github.com/henrylee2cn/erpc/v6/proto/pbproto""github.com/henrylee2cn/erpc/v6/proto/thriftproto""github.com/henrylee2cn/erpc/v6/proto/httproto"

Transfer-Filter

"github.com/henrylee2cn/erpc/v6/xfer/gzip""github.com/henrylee2cn/erpc/v6/xfer/md5"

Mixer

"github.com/henrylee2cn/erpc/v6/mixer/multiclient""github.com/henrylee2cn/erpc/v6/mixer/websocket""github.com/henrylee2cn/erpc/v6/mixer/evio"html "github.com/xiaoenai/tp-micro/helper/mod-html"

Projects based on eRPC

project description
TP-Micro is a simple, powerful micro service framework based on eRPC
Pholcus is a distributed, high concurrency and powerful web crawler software

Business Users

  
  

License

eRPC is under Apache v2 License. See the LICENSE file for the full license text