Create & Init Project...

This commit is contained in:
2019-04-22 18:49:16 +08:00
commit fc4fa37393
25440 changed files with 4054998 additions and 0 deletions

78
library/net/rpc/BUILD Normal file
View File

@@ -0,0 +1,78 @@
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
package(default_visibility = ["//visibility:public"])
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//library/net/rpc/context:all-srcs",
"//library/net/rpc/interceptor:all-srcs",
"//library/net/rpc/liverpc:all-srcs",
"//library/net/rpc/warden:all-srcs",
],
tags = ["automanaged"],
)
go_library(
name = "go_default_library",
srcs = [
"balancer.go",
"client.go",
"client2.go",
"server.go",
"trace.go",
],
importpath = "go-common/library/net/rpc",
tags = ["automanaged"],
deps = [
"//library/conf/dsn:go_default_library",
"//library/conf/env:go_default_library",
"//library/ecode:go_default_library",
"//library/log:go_default_library",
"//library/naming:go_default_library",
"//library/naming/discovery:go_default_library",
"//library/net/metadata:go_default_library",
"//library/net/netutil/breaker:go_default_library",
"//library/net/rpc/context:go_default_library",
"//library/net/rpc/interceptor:go_default_library",
"//library/net/trace:go_default_library",
"//library/stat:go_default_library",
"//library/sync/errgroup:go_default_library",
"//library/time:go_default_library",
"//vendor/github.com/pkg/errors:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = [
"breaker_test.go",
"client2_test.go",
"server_test.go",
],
embed = [":go_default_library"],
rundir = ".",
tags = ["automanaged"],
deps = [
"//library/conf/env:go_default_library",
"//library/ecode:go_default_library",
"//library/log:go_default_library",
"//library/naming:go_default_library",
"//library/naming/discovery:go_default_library",
"//library/net/rpc/context:go_default_library",
"//library/time:go_default_library",
"//vendor/github.com/smartystreets/goconvey/convey:go_default_library",
],
)

105
library/net/rpc/balancer.go Normal file
View File

@@ -0,0 +1,105 @@
package rpc
import (
"context"
"reflect"
"sync/atomic"
"go-common/library/log"
"go-common/library/sync/errgroup"
)
const (
_clientsPool = 3
)
// balancer interface.
type balancer interface {
Boardcast(context.Context, string, interface{}, interface{}) error
Call(context.Context, string, interface{}, interface{}) error
}
// wrr get avaliable rpc client by wrr strategy.
type wrr struct {
pool []*Client
weight int64
server int64
idx int64
}
// Boardcast broad cast to all Client.
// NOTE: reply must be ptr.
func (r *wrr) Boardcast(ctx context.Context, serviceMethod string, args interface{}, reply interface{}) (err error) {
if r.weight == 0 {
log.Error("wrr get() error weight:%d server:%d idx:%d", len(r.pool), r.server, r.idx)
return ErrNoClient
}
rtp := reflect.TypeOf(reply).Elem()
g, ctx := errgroup.WithContext(ctx)
for i := 0; i < int(r.server); i++ {
j := i
g.Go(func() error {
nrp := reflect.New(rtp).Interface()
return r.pool[j].Call(ctx, serviceMethod, args, nrp)
})
}
return g.Wait()
}
func (r *wrr) Call(ctx context.Context, serviceMethod string, args interface{}, reply interface{}) (err error) {
if r.weight == 0 {
log.Error("wrr get() error weight:%d server:%d idx:%d", len(r.pool), r.server, r.idx)
return ErrNoClient
}
v := atomic.AddInt64(&r.idx, 1)
for i := int64(0); i < r.server; i++ {
cli := r.pool[int((v+i)%r.weight)]
if err = cli.Call(ctx, serviceMethod, args, reply); err != ErrNoClient {
return
}
}
return ErrNoClient
}
type key interface {
Key() int64
}
type sharding struct {
pool []*Client
weight int64
server int64
idx int64
}
// Boardcast broad cast to all clients.
// NOTE: reply must be ptr.
func (r *sharding) Boardcast(ctx context.Context, serviceMethod string, args interface{}, reply interface{}) (err error) {
if r.weight == 0 {
log.Error("wrr get() error weight:%d server:%d idx:%d", len(r.pool), r.server, r.idx)
return ErrNoClient
}
rtp := reflect.TypeOf(reply).Elem()
g, ctx := errgroup.WithContext(ctx)
for i := 0; i < int(r.server); i++ {
j := i
g.Go(func() error {
nrp := reflect.New(rtp).Interface()
return r.pool[j].Call(ctx, serviceMethod, args, nrp)
})
}
return g.Wait()
}
func (r *sharding) Call(ctx context.Context, serviceMethod string, args interface{}, reply interface{}) (err error) {
if r.weight == 0 {
log.Error("wrr get() error weight:%d server:%d idx:%d", len(r.pool), r.server, r.idx)
return ErrNoClient
}
if k, ok := args.(key); ok {
if err = r.pool[int(k.Key()%r.server)].Call(ctx, serviceMethod, args, reply); err != ErrNoClient {
return
}
}
return ErrNoClient
}

View File

@@ -0,0 +1,95 @@
package rpc
import (
"context"
"fmt"
"math/rand"
"sync/atomic"
"testing"
"time"
"go-common/library/conf/env"
"go-common/library/ecode"
"go-common/library/log"
"go-common/library/naming"
"go-common/library/naming/discovery"
rcontext "go-common/library/net/rpc/context"
)
func TestBreaker(t *testing.T) {
env.DeployEnv = "dev"
env.Zone = "testzone"
log.Init(&log.Config{Stdout: false})
d := discovery.New(nil)
cancel, err := d.Register(context.TODO(), &naming.Instance{
Zone: "testzone",
Env: "dev",
AppID: "test.appid",
Hostname: "test.host",
Addrs: []string{"gorpc://127.0.0.1:9000"},
Version: "1",
LastTs: time.Now().UnixNano(),
Metadata: map[string]string{"weight": "100"},
})
if err != nil {
panic(err)
}
defer func() {
cancel()
time.Sleep(time.Second)
}()
svr := NewServer(&ServerConfig{Proto: "tcp", Addr: "127.0.0.1:9000"})
err = svr.Register(&BreakerRPC{})
if err != nil {
panic(err)
}
time.Sleep(time.Millisecond * 200)
RunCli()
}
type BreakerReq struct {
Name string
}
type BreakerReply struct {
Success bool
}
type BreakerRPC struct{}
// Ping check connection success.
func (r *BreakerRPC) Ping(c rcontext.Context, arg *BreakerReq, res *BreakerReply) (err error) {
if rand.Int31n(100) < 40 {
return ecode.ServerErr
}
res.Success = true
return
}
func RunCli() {
var success int64
var su int64
var se int64
var other int64
cli := NewDiscoveryCli("test.appid", nil)
for i := 0; i < 1000; i++ {
var res BreakerReply
err := cli.Call(
context.Background(),
"BreakerRPC.Ping",
&BreakerReq{Name: "test"},
&res,
)
if err == nil || ecode.OK.Equal(err) {
atomic.AddInt64(&success, 1)
} else if ecode.ServiceUnavailable.Equal(err) {
atomic.AddInt64(&su, 1)
} else if ecode.ServerErr.Equal(err) {
atomic.AddInt64(&se, 1)
} else {
atomic.AddInt64(&other, 1)
}
time.Sleep(time.Millisecond * 9)
}
fmt.Printf("success:%d su:%d se:%d other:%d \n", success, su, se, other)
}

507
library/net/rpc/client.go Normal file
View File

@@ -0,0 +1,507 @@
// Copyright 2009 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package rpc
import (
"bufio"
"context"
"encoding/gob"
"errors"
"io"
"log"
"net"
"sync"
"sync/atomic"
"time"
"go-common/library/conf/env"
"go-common/library/ecode"
xlog "go-common/library/log"
"go-common/library/net/metadata"
"go-common/library/net/netutil/breaker"
"go-common/library/net/trace"
"go-common/library/stat"
xtime "go-common/library/time"
perr "github.com/pkg/errors"
)
const (
_family = "gorpc"
_pingDuration = time.Second * 1
)
var (
stats = stat.RPCClient
// ErrShutdown shutdown error.
ErrShutdown = errors.New("connection is shut down")
// ErrNoClient current no rpc client.
ErrNoClient = errors.New("no rpc client")
errClient = new(client)
)
// ServerError represents an error that has been returned from
// the remote side of the RPC connection.
type ServerError string
func (e ServerError) Error() string {
return string(e)
}
// Call represents an active RPC.
type Call struct {
ServiceMethod string // The name of the service and method to call.
Args interface{} // The argument to the function (*struct).
Reply interface{} // The reply from the function (*struct).
Trace trace.Trace
Color string
RemoteIP string
Timeout time.Duration
Error error // After completion, the error status.
Done chan *Call // Strobes when call is complete.
}
// client represents an RPC Client.
// There may be multiple outstanding Calls associated
// with a single Client, and a Client may be used by
// multiple goroutines simultaneously.
type client struct {
codec *clientCodec
reqMutex sync.Mutex // protects following
request Request
mutex sync.Mutex // protects following
seq uint64
pending map[uint64]*Call
closing bool // user has called Close
shutdown bool // server has told us to stop
timeout time.Duration // call timeout
remoteAddr string // server address
}
func (client *client) send(call *Call) {
client.reqMutex.Lock()
defer client.reqMutex.Unlock()
// Register this call.
client.mutex.Lock()
if client.shutdown || client.closing {
call.Error = ErrShutdown
client.mutex.Unlock()
call.done()
return
}
seq := client.seq
client.seq++
client.mutex.Unlock()
// Encode and send the request.
client.request.Seq = seq
client.request.ServiceMethod = call.ServiceMethod
client.request.Color = call.Color
client.request.RemoteIP = call.RemoteIP
client.request.Timeout = call.Timeout
if call.Trace != nil {
trace.Inject(call.Trace, nil, &client.request.Trace)
} else {
client.request.Trace = TraceInfo{}
}
err := client.codec.WriteRequest(&client.request, call.Args)
if err != nil {
err = perr.WithStack(err)
if call != nil {
call.Error = err
call.done()
}
} else {
client.mutex.Lock()
client.pending[seq] = call
client.mutex.Unlock()
}
}
func (client *client) input() {
var err error
var response Response
for err == nil {
response = Response{}
err = client.codec.ReadResponseHeader(&response)
if err != nil {
break
}
seq := response.Seq
client.mutex.Lock()
call := client.pending[seq]
delete(client.pending, seq)
client.mutex.Unlock()
switch {
case call == nil:
// We've got no pending call. That usually means that
// WriteRequest partially failed, and call was already
// removed; response is a server telling us about an
// error reading request body. We should still attempt
// to read error body, but there's no one to give it to.
err = client.codec.ReadResponseBody(nil)
if err != nil {
err = errors.New("reading error body: " + err.Error())
}
case response.Error != "":
// We've got an error response. Give this to the request;
// any subsequent requests will get the ReadResponseBody
// error if there is one.
call.Error = ServerError(response.Error)
err = client.codec.ReadResponseBody(nil)
if err != nil {
err = errors.New("reading error body: " + err.Error())
}
call.done()
default:
err = client.codec.ReadResponseBody(call.Reply)
if err != nil {
call.Error = errors.New("reading body " + err.Error())
}
call.done()
}
}
// Terminate pending calls.
client.reqMutex.Lock()
client.mutex.Lock()
client.shutdown = true
closing := client.closing
if err == io.EOF {
if closing {
err = ErrShutdown
} else {
err = io.ErrUnexpectedEOF
}
}
for _, call := range client.pending {
call.Error = err
call.done()
}
client.mutex.Unlock()
client.reqMutex.Unlock()
if err != io.EOF && !closing {
log.Println("rpc: client protocol error:", err)
}
}
func (call *Call) done() {
select {
case call.Done <- call:
// ok
default:
// We don't want to block here. It is the caller's responsibility to make
// sure the channel has enough buffer space. See comment in Go().
log.Println("rpc: discarding Call reply due to insufficient Done chan capacity")
}
}
// Finish must called after Go.
func (call *Call) Finish() {
if call.Trace != nil {
call.Trace.Finish(&call.Error)
}
}
// newClient returns a new Client to handle requests to the
// set of services at the other end of the connection.
// It adds a buffer to the write side of the connection so
// the header and payload are sent as a unit.
func newClient(timeout time.Duration, conn net.Conn) (*client, error) {
encBuf := bufio.NewWriter(conn)
client := &clientCodec{conn, gob.NewDecoder(conn), gob.NewEncoder(encBuf), encBuf}
c := newClientWithCodec(client)
c.timeout = timeout
c.remoteAddr = conn.RemoteAddr().String()
// handshake
c.Call(_authServiceMethod, &Auth{User: env.AppID}, &struct{}{})
return c, nil
}
// newClientWithCodec is like newClient but uses the specified
// codec to encode requests and decode responses.
func newClientWithCodec(codec *clientCodec) *client {
client := &client{
codec: codec,
pending: make(map[uint64]*Call),
}
go client.input()
return client
}
type clientCodec struct {
rwc io.ReadWriteCloser
dec *gob.Decoder
enc *gob.Encoder
encBuf *bufio.Writer
}
func (c *clientCodec) WriteRequest(r *Request, body interface{}) (err error) {
if err = c.enc.Encode(r); err != nil {
return perr.WithStack(err)
}
if err = c.enc.Encode(body); err != nil {
return perr.WithStack(err)
}
return perr.WithStack(c.encBuf.Flush())
}
func (c *clientCodec) ReadResponseHeader(r *Response) error {
return perr.WithStack(c.dec.Decode(r))
}
func (c *clientCodec) ReadResponseBody(body interface{}) error {
return perr.WithStack(c.dec.Decode(body))
}
func (c *clientCodec) Close() error {
return perr.WithStack(c.rwc.Close())
}
// dial connects to an RPC server at the specified network address.
func dial(network, addr string, timeout time.Duration) (*client, error) {
// TODO dial timeout
conn, err := net.Dial(network, addr)
if err != nil {
err = perr.WithStack(err)
return nil, err
}
return newClient(timeout, conn)
}
// Close close the rpc client.
func (client *client) Close() error {
client.mutex.Lock()
if client.closing {
client.mutex.Unlock()
return ErrShutdown
}
client.closing = true
client.mutex.Unlock()
return client.codec.Close()
}
func (client *client) do(call *Call, done chan *Call) {
if done == nil {
done = make(chan *Call, 10) // buffered.
} else {
// If caller passes done != nil, it must arrange that
// done has enough buffer for the number of simultaneous
// RPCs that will be using that channel. If the channel
// is totally unbuffered, it's best not to run at all.
if cap(done) == 0 {
log.Panic("rpc: done channel is unbuffered")
}
}
call.Done = done
client.send(call)
}
// Go invokes the function asynchronously. It returns the Call structure representing
// the invocation. The done channel will signal when the call is complete by returning
// the same Call object. If done is nil, Go will allocate a new channel.
// If non-nil, done must be buffered or Go will deliberately crash.
// Must call Finish() after call Go.
func (client *client) Go(serviceMethod string, args interface{}, reply interface{}, done chan *Call) *Call {
call := new(Call)
call.ServiceMethod = serviceMethod
call.Args = args
call.Reply = reply
client.do(call, done)
return call
}
// Call invokes the named function, waits for it to complete, and returns its error status.
func (client *client) Call(serviceMethod string, args interface{}, reply interface{}) (err error) {
call := <-client.Go(serviceMethod, args, reply, make(chan *Call, 1)).Done
return call.Error
}
// Do do a rpc call.
func (client *client) Do(call *Call) {
client.do(call, make(chan *Call, 1))
}
type td struct {
path string
timeout time.Duration
}
// Client wrapper is a client holder with implements pinger.
type Client struct {
addr string
timeout xtime.Duration
client atomic.Value
quit chan struct{}
breaker *breaker.Group
}
// Dial connects to an RPC server at the specified network address.
func Dial(addr string, timeout xtime.Duration, bkc *breaker.Config) *Client {
client := &Client{
addr: addr,
timeout: timeout,
quit: make(chan struct{}),
}
// breaker
client.breaker = breaker.NewGroup(bkc)
// timeout
if timeout <= 0 {
client.timeout = xtime.Duration(300 * time.Millisecond)
}
client.timeout = timeout
// dial
rc, err := dial("tcp", addr, time.Duration(timeout))
if err != nil {
xlog.Error("dial(%s, %s) error(%v)", "tcp", addr, err)
} else {
client.client.Store(rc)
}
go client.ping()
return client
}
// Call invokes the named function, waits for it to complete, and returns its error status.
func (c *Client) Call(ctx context.Context, serviceMethod string, args interface{}, reply interface{}) (err error) {
var (
ok bool
code string
rc *client
call *Call
cancel func()
t trace.Trace
timeout = time.Duration(c.timeout)
)
if rc, ok = c.client.Load().(*client); !ok || rc == errClient {
xlog.Error("client is errClient (no rpc client) by ping addr(%s) error", c.addr)
return ErrNoClient
}
if t, ok = trace.FromContext(ctx); !ok {
t = trace.New(serviceMethod)
}
t = t.Fork(_family, serviceMethod)
t.SetTag(trace.String(trace.TagAddress, rc.remoteAddr))
defer t.Finish(&err)
// breaker
brk := c.breaker.Get(serviceMethod)
if err = brk.Allow(); err != nil {
code = "breaker"
stats.Incr(serviceMethod, code)
return
}
defer c.onBreaker(brk, &err)
// stat
now := time.Now()
defer func() {
stats.Timing(serviceMethod, int64(time.Since(now)/time.Millisecond))
if code != "" {
stats.Incr(serviceMethod, code)
}
}()
// timeout: get from conf
// if context > conf use conf else context
deliver := true
if deadline, ok := ctx.Deadline(); ok {
if ctimeout := time.Until(deadline); ctimeout < timeout {
timeout = ctimeout
deliver = false
}
}
if deliver {
ctx, cancel = context.WithTimeout(ctx, timeout)
defer cancel()
}
color := metadata.String(ctx, metadata.Color)
remoteIP := metadata.String(ctx, metadata.RemoteIP)
// call
call = &Call{
ServiceMethod: serviceMethod,
Args: args,
Reply: reply,
Trace: t,
Color: color,
RemoteIP: remoteIP,
Timeout: timeout,
}
rc.Do(call)
select {
case call = <-call.Done:
err = call.Error
code = ecode.Cause(err).Error()
case <-ctx.Done():
err = ecode.Deadline
code = "timeout"
}
return
}
func (c *Client) onBreaker(breaker breaker.Breaker, err *error) {
if err != nil && *err != nil && (*err == ErrShutdown || *err == io.ErrUnexpectedEOF || ecode.Deadline.Equal(*err) || ecode.ServiceUnavailable.Equal(*err) || ecode.ServerErr.Equal(*err)) {
breaker.MarkFailed()
} else {
breaker.MarkSuccess()
}
}
// ping ping the rpc connect and re connect when has an error.
func (c *Client) ping() {
var (
err error
cancel func()
call *Call
ctx context.Context
client, _ = c.client.Load().(*client)
)
for {
select {
case <-c.quit:
c.client.Store(errClient)
if client != nil {
client.Close()
}
return
default:
}
if client == nil || err != nil {
if client, err = dial("tcp", c.addr, time.Duration(c.timeout)); err != nil {
xlog.Error("dial(%s, %s) error(%v)", "tcp", c.addr, err)
time.Sleep(_pingDuration)
continue
}
c.client.Store(client)
}
ctx, cancel = context.WithTimeout(context.Background(), time.Duration(c.timeout))
select {
case call = <-client.Go(_pingServiceMethod, _pingArg, _pingArg, make(chan *Call, 1)).Done:
err = call.Error
case <-ctx.Done():
err = ecode.Deadline
}
cancel()
if err != nil {
if err == ErrShutdown || err == io.ErrUnexpectedEOF || ecode.Deadline.Equal(err) {
xlog.Error("rpc ping error beiTle addr(%s)", c.addr)
c.client.Store(errClient)
client.Close()
} else {
err = nil // never touch here
}
}
time.Sleep(_pingDuration)
}
}
// Close close client connection.
func (c *Client) Close() {
close(c.quit)
}

240
library/net/rpc/client2.go Normal file
View File

@@ -0,0 +1,240 @@
package rpc
import (
"context"
"fmt"
"net/url"
"strconv"
"sync/atomic"
"time"
"go-common/library/conf/env"
"go-common/library/log"
"go-common/library/naming"
"go-common/library/naming/discovery"
"go-common/library/net/netutil/breaker"
xtime "go-common/library/time"
)
const (
scheme = "gorpc"
_policySharding = "sharding"
)
// ClientConfig rpc client config.
type ClientConfig struct {
Policy string
Zone string
Cluster string
Color string
Timeout xtime.Duration
Breaker *breaker.Config
}
// Client2 support for load balancing and service discovery.
type Client2 struct {
c *ClientConfig
appID string
dis naming.Resolver
balancer atomic.Value
}
// NewDiscoveryCli new discovery client.
func NewDiscoveryCli(appID string, cf *ClientConfig) (c *Client2) {
if cf == nil {
cf = &ClientConfig{Timeout: xtime.Duration(300 * time.Millisecond)}
} else if cf.Timeout <= 0 {
cf.Timeout = xtime.Duration(300 * time.Millisecond)
}
c = &Client2{
c: cf,
appID: appID,
dis: discovery.Build(appID),
}
var pools = make(map[string]*Client)
fmt.Printf("开始创建:%s 的gorpc client等待从discovery拉取节点%s\n", c.appID, time.Now().Format("2006-01-02 15:04:05"))
event := c.dis.Watch()
select {
case _, ok := <-event:
if ok {
c.disc(pools)
fmt.Printf("结束创建:%s 的gorpc client从discovery拉取节点和创建成功%s\n", c.appID, time.Now().Format("2006-01-02 15:04:05"))
} else {
panic("刚启动就从discovery拉到了关闭的event")
}
case <-time.After(10 * time.Second):
fmt.Printf("失败创建:%s 的gorpc client竟然从discovery拉取节点超时了%s\n", c.appID, time.Now().Format("2006-01-02 15:04:05"))
if env.DeployEnv == env.DeployEnvProd {
panic("刚启动就从discovery拉节点超时请检查配置或联系Discovery维护者")
}
}
go c.discproc(event, pools)
return
}
// Boardcast boardcast all rpc client.
func (c *Client2) Boardcast(ctx context.Context, serviceMethod string, args interface{}, reply interface{}) (err error) {
var (
ok bool
b balancer
)
if b, ok = c.balancer.Load().(balancer); ok {
if err = b.Boardcast(ctx, serviceMethod, args, reply); err != ErrNoClient {
return
}
}
return nil
}
// Call invokes the named function, waits for it to complete, and returns its error status.
// this include rpc.Client.Call method, and takes a timeout.
func (c *Client2) Call(ctx context.Context, serviceMethod string, args interface{}, reply interface{}) (err error) {
var (
ok bool
b balancer
)
if b, ok = c.balancer.Load().(balancer); ok {
if err = b.Call(ctx, serviceMethod, args, reply); err != ErrNoClient {
return
}
}
stats.Incr(serviceMethod, "no_rpc_client")
return ErrNoClient
}
func (c *Client2) removeAndClose(pools, dcs map[string]*Client) {
if len(dcs) == 0 {
return
}
// after rpc timeout(double duration), close no used cliens
if c.c != nil {
to := c.c.Timeout
time.Sleep(2 * time.Duration(to))
}
for key, cli := range dcs {
delete(pools, key)
cli.Close()
}
}
func (c *Client2) discproc(event <-chan struct{}, pools map[string]*Client) {
for {
if _, ok := <-event; ok {
c.disc(pools)
continue
}
return
}
}
func (c *Client2) disc(pools map[string]*Client) (err error) {
var (
weights int64
key string
i, j, idx int
nodes map[string]struct{}
dcs map[string]*Client
blc balancer
cli *Client
cs, wcs []*Client
svr *naming.Instance
)
insMap, ok := c.dis.Fetch(context.Background())
if !ok {
log.Error("discovery fetch instance fail(%s)", c.appID)
return
}
zone := env.Zone
if c.c.Zone != "" {
zone = c.c.Zone
}
tinstance, ok := insMap[zone]
if !ok {
for _, value := range insMap {
tinstance = value
break
}
}
instance := make([]*naming.Instance, 0, len(tinstance))
for _, svr := range tinstance {
nsvr := new(naming.Instance)
*nsvr = *svr
cluster := svr.Metadata[naming.MetaCluster]
if c.c.Cluster != "" && c.c.Cluster != cluster {
continue
}
instance = append(instance, nsvr)
}
log.Info("discovery get %d instances ", len(instance))
if len(instance) > 0 {
nodes = make(map[string]struct{}, len(instance))
cs = make([]*Client, 0, len(instance))
dcs = make(map[string]*Client, len(pools))
svrWeights := make([]int, 0, len(instance))
weights = 0
for _, svr = range instance {
weight, err := strconv.ParseInt(svr.Metadata["weight"], 10, 64)
if err != nil {
weight = 10
}
key = svr.Hostname
nodes[key] = struct{}{}
var addr string
if cli, ok = pools[key]; !ok {
for _, saddr := range svr.Addrs {
u, err := url.Parse(saddr)
if err == nil && u.Scheme == scheme {
addr = u.Host
}
}
if addr == "" {
log.Warn("net/rpc: invalid rpc address(%s,%s,%v) found!", svr.AppID, svr.Hostname, svr.Addrs)
continue
}
cli = Dial(addr, c.c.Timeout, c.c.Breaker)
pools[key] = cli
}
svrWeights = append(svrWeights, int(weight))
weights += weight // calc all weight
log.Info("new cli %+v instance info %+v", addr, svr)
cs = append(cs, cli)
}
// delete old nodes
for key, cli = range pools {
if _, ok = nodes[key]; !ok {
log.Info("syncproc will delete node: %s", key)
dcs[key] = cli
}
}
// new client slice by weights
wcs = make([]*Client, 0, weights)
for i, j = 0, 0; i < int(weights); j++ { // j++ means next svr
idx = j % len(cs)
if svrWeights[idx] > 0 {
i++ // i++ means all weights must fill wrrClis
svrWeights[idx]--
wcs = append(wcs, cs[idx])
}
}
switch c.c.Policy {
case _policySharding:
blc = &sharding{
pool: wcs,
weight: int64(weights),
server: int64(len(instance)),
}
log.Info("discovery syncproc sharding weights:%d size:%d raw:%d", weights, weights, len(instance))
default:
blc = &wrr{
pool: wcs,
weight: int64(weights),
server: int64(len(instance)),
}
log.Info("discovery %s syncproc wrr weights:%d size:%d raw:%d", c.appID, weights, weights, len(instance))
}
c.balancer.Store(blc)
c.removeAndClose(pools, dcs)
}
return
}

View File

@@ -0,0 +1,154 @@
package rpc
import (
"context"
"net"
"sync"
"testing"
"time"
"go-common/library/conf/env"
"go-common/library/naming"
"go-common/library/naming/discovery"
xtime "go-common/library/time"
. "github.com/smartystreets/goconvey/convey"
)
var c = &discovery.Config{
Nodes: []string{"api.bilibili.co"},
Zone: "sh001",
Env: "test",
Key: "0c4b8fe3ff35a4b6",
Secret: "b370880d1aca7d3a289b9b9a7f4d6812",
Host: "host_1",
}
var in = &naming.Instance{
AppID: "test2",
Version: "1",
Metadata: map[string]string{
"test": "1",
"weight": "8",
"color": "",
"cluster": "red",
},
}
var in2 = &naming.Instance{
AppID: "test3",
Version: "1",
Metadata: map[string]string{
"test": "1",
"weight": "8",
"color": "",
"cluster": "red",
},
}
var (
svrAddr1, svrAddr2, svrAddr3 string
once1, once2, once3 sync.Once
)
type TestArgs struct {
A, B int
}
type TestReply struct {
C int
}
type TestTimeout struct {
T time.Duration
}
type TestRPC int
func startTestServer1() {
svr := newServer()
svr.RegisterName("RPC", new(TestRPC))
var l net.Listener
l, svrAddr1 = listenTCP()
go svr.Accept(l)
}
func TestDiscoveryCli(t *testing.T) {
env.Hostname = "host_1"
env.Zone = "sh001"
once1.Do(startTestServer1)
Convey("test discovery cli", t, func() {
once1.Do(startTestServer1)
in.Addrs = []string{scheme + "://" + svrAddr1}
dis := discovery.New(c)
_, err := dis.Register(context.TODO(), in)
So(err, ShouldBeNil)
cli := NewDiscoveryCli("test2", &ClientConfig{
Cluster: "",
Timeout: xtime.Duration(time.Second),
})
time.Sleep(time.Second * 2)
args := &TestArgs{7, 8}
reply := new(TestReply)
err = cli.Call(context.TODO(), "RPC.Add", args, reply)
So(err, ShouldBeNil)
})
Convey("test discovery no zone", t, func() {
env.Zone = "test2"
cli := NewDiscoveryCli("test2", &ClientConfig{
Cluster: "",
Timeout: xtime.Duration(time.Second),
})
time.Sleep(time.Second * 2)
args := &TestArgs{7, 8}
reply := new(TestReply)
err := cli.Call(context.TODO(), "RPC.Add", args, reply)
So(err, ShouldBeNil)
})
Convey("test discovery with color", t, func() {
env.Zone = "test2"
cli := NewDiscoveryCli("test2", &ClientConfig{
Color: "red",
Timeout: xtime.Duration(time.Second),
})
time.Sleep(time.Second * 2)
args := &TestArgs{7, 8}
reply := new(TestReply)
err := cli.Call(context.TODO(), "RPC.Add", args, reply)
So(err, ShouldBeNil)
})
Convey("test discovery with cluster", t, func() {
env.Zone = "test2"
cli := NewDiscoveryCli("test2", &ClientConfig{
Cluster: "red",
Timeout: xtime.Duration(time.Second),
})
time.Sleep(time.Second * 2)
args := &TestArgs{7, 8}
reply := new(TestReply)
err := cli.Call(context.TODO(), "RPC.Add", args, reply)
So(err, ShouldBeNil)
})
Convey("test conf Zone cli", t, func() {
env.Zone = "testsh"
once1.Do(startTestServer1)
in2.Addrs = []string{scheme + "://" + svrAddr1}
dis := discovery.New(c)
_, err := dis.Register(context.TODO(), in2)
So(err, ShouldBeNil)
env.Zone = "sh001"
cli := NewDiscoveryCli("test3", &ClientConfig{
Cluster: "",
Timeout: xtime.Duration(time.Second),
Zone: "testsh",
})
time.Sleep(time.Second * 2)
args := &TestArgs{7, 8}
reply := new(TestReply)
err = cli.Call(context.TODO(), "RPC.Add", args, reply)
So(err, ShouldBeNil)
})
}

View File

@@ -0,0 +1,26 @@
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = ["context.go"],
importpath = "go-common/library/net/rpc/context",
tags = ["automanaged"],
visibility = ["//visibility:public"],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,53 @@
package context
import (
ctx "context"
"time"
)
// Context web context interface
type Context interface {
ctx.Context
// Now get current time.
Now() time.Time
// Seq implement Context method Seq.
Seq() uint64
// ServiceMethod implement Context method ServiceMethod.
ServiceMethod() string
// User get caller user.
User() string
}
type rpcCtx struct {
ctx.Context
now time.Time
seq uint64
serviceMethod string
user string
}
// NewContext new a rpc context.
func NewContext(c ctx.Context, m, u string, s uint64) Context {
rc := &rpcCtx{Context: c, now: time.Now(), seq: s, serviceMethod: m, user: u}
return rc
}
func (c *rpcCtx) Seq() uint64 {
return c.seq
}
func (c *rpcCtx) ServiceMethod() string {
return c.serviceMethod
}
func (c *rpcCtx) Now() time.Time {
return c.now
}
func (c *rpcCtx) User() string {
return c.user
}

View File

@@ -0,0 +1,47 @@
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_test",
)
go_library(
name = "go_default_library",
srcs = ["interceptor.go"],
importpath = "go-common/library/net/rpc/interceptor",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//library/ecode:go_default_library",
"//library/log:go_default_library",
"//library/net/rpc/context:go_default_library",
"//library/stat:go_default_library",
"//vendor/golang.org/x/time/rate:go_default_library",
],
)
go_test(
name = "go_default_test",
srcs = ["interceptor_test.go"],
embed = [":go_default_library"],
rundir = ".",
tags = ["automanaged"],
deps = [
"//library/ecode:go_default_library",
"//library/net/rpc/context:go_default_library",
"//vendor/golang.org/x/time/rate:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,86 @@
package interceptor
import (
"fmt"
"net"
"strconv"
"strings"
"time"
"go-common/library/ecode"
"go-common/library/log"
"go-common/library/net/rpc/context"
"go-common/library/stat"
"golang.org/x/time/rate"
)
var stats = stat.RPCServer
const (
_innerService = "inner"
)
// Interceptor is rpc interceptor
type Interceptor struct {
// for limit rate
rateLimits map[string]*rate.Limiter
// for auth
token string
}
// NewInterceptor new a interceptor
func NewInterceptor(token string) *Interceptor {
in := &Interceptor{token: token}
in.rateLimits = make(map[string]*rate.Limiter)
return in
}
// Rate check the call is limit or not
func (i *Interceptor) Rate(c context.Context) error {
limit, ok := i.rateLimits[c.ServiceMethod()]
if ok && !limit.Allow() {
return ecode.Degrade
}
return nil
}
// Stat add stat info to ops
func (i *Interceptor) Stat(c context.Context, args interface{}, err error) {
const noUser = "no_user"
var (
user = c.User()
method = c.ServiceMethod()
tmsub = time.Since(c.Now())
)
if user == "" {
user = noUser
}
stats.Timing(user, int64(tmsub/time.Millisecond), method)
stats.Incr(user, method, strconv.Itoa((ecode.Cause(err).Code())))
if err != nil {
log.Errorv(c,
log.KV("args", fmt.Sprintf("%v", args)),
log.KV("method", method),
log.KV("duration", tmsub),
log.KV("error", fmt.Sprintf("%+v", err)),
)
} else {
if !strings.HasPrefix(method, _innerService) || bool(log.V(2)) {
log.Infov(c,
log.KV("args", fmt.Sprintf("%v", args)),
log.KV("method", method),
log.KV("duration", tmsub),
log.KV("error", fmt.Sprintf("%+v", err)),
)
}
}
}
// Auth check token has auth
func (i *Interceptor) Auth(c context.Context, addr net.Addr, token string) error {
if i.token != token {
return ecode.RPCNoAuth
}
return nil
}

View File

@@ -0,0 +1,54 @@
package interceptor
import (
ctx "context"
"errors"
"sync"
"testing"
"go-common/library/ecode"
"go-common/library/net/rpc/context"
"golang.org/x/time/rate"
)
var (
once sync.Once
i *Interceptor
c context.Context
)
func interceptor() {
i = NewInterceptor("test token")
c = context.NewContext(ctx.TODO(), "testMethod", "test user", 0)
}
func TestRate(t *testing.T) {
once.Do(interceptor)
if err := i.Rate(c); err != nil {
t.Errorf("TestRate error(%v)", err)
t.FailNow()
}
i.rateLimits["testMethod"] = rate.NewLimiter(1, 0)
if err := i.Rate(c); err != ecode.Degrade {
t.Errorf("TestRate error(%v)", err)
t.FailNow()
}
}
func TestStat(t *testing.T) {
once.Do(interceptor)
i.Stat(c, nil, errors.New("test error"))
}
func TestAuth(t *testing.T) {
once.Do(interceptor)
if err := i.Auth(c, nil, "test token"); err != nil {
t.Errorf("TestAuth error(%v)", err)
t.FailNow()
}
if err := i.Auth(c, nil, "token"); err != ecode.RPCNoAuth {
t.Errorf("TestAuth error(%v)", err)
t.FailNow()
}
}

View File

@@ -0,0 +1,86 @@
load(
"@io_bazel_rules_go//proto:def.bzl",
"go_proto_library",
)
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
"go_library",
)
proto_library(
name = "liverpc_proto",
srcs = ["liverpc.proto"],
tags = ["automanaged"],
)
go_proto_library(
name = "liverpc_go_proto",
compilers = ["@io_bazel_rules_go//proto:go_proto"],
importpath = "go-common/library/net/rpc/liverpc",
proto = ":liverpc_proto",
tags = ["automanaged"],
)
go_test(
name = "go_default_test",
srcs = ["client_conn_test.go"],
embed = [":go_default_library"],
rundir = ".",
tags = ["automanaged"],
deps = [
"//library/conf/env:go_default_library",
"//library/net/metadata:go_default_library",
"//vendor/github.com/smartystreets/goconvey/convey:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = [
"client.go",
"client_conn.go",
"option.go",
"protocol.go",
],
embed = [":liverpc_go_proto"],
importpath = "go-common/library/net/rpc/liverpc",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//library/conf/env:go_default_library",
"//library/log:go_default_library",
"//library/naming:go_default_library",
"//library/naming/discovery:go_default_library",
"//library/net/metadata:go_default_library",
"//library/net/trace:go_default_library",
"//library/stat:go_default_library",
"//library/time:go_default_library",
"//vendor/github.com/json-iterator/go:go_default_library",
"//vendor/github.com/pkg/errors:go_default_library",
"@com_github_gogo_protobuf//proto:go_default_library",
"@com_github_golang_protobuf//proto:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//library/net/rpc/liverpc/context:all-srcs",
"//library/net/rpc/liverpc/test:all-srcs",
"//library/net/rpc/liverpc/testdata:all-srcs",
],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,27 @@
### v1.0.8
- 加入CallOption
# v1.0.7
1. 调用liverpc使用color而不是group
# v1.0.6
1. 使用context cancel
2. 打日志
# v1.0.5
1. 默认分组改为default
# v1.0.4
1. 支持group分组转发
# v1.0.3
1. 监控上报code为空时不上报
# v1.0.2
2. 修改Header创建逻辑
# v1.0.1
1. 使用protobuf定义
# v1.0.0
1. 实现了liverpc协议

View File

@@ -0,0 +1,11 @@
# Owner
liugang
liuzhen
# Author
liugang
liuzhen
zhouzhichao
# Reviewer
liuzhen

View File

@@ -0,0 +1,10 @@
# See the OWNERS docs at https://go.k8s.io/owners
approvers:
- liugang
- liuzhen
- zhouzhichao
reviewers:
- liugang
- liuzhen
- zhouzhichao

View File

@@ -0,0 +1,5 @@
#### net/rpc/liverpc
##### 项目简介
打通go和直播原有rpc通信的道路

View File

@@ -0,0 +1,334 @@
package liverpc
import (
"context"
"fmt"
"net/url"
"sync/atomic"
"time"
"go-common/library/conf/env"
"go-common/library/log"
"go-common/library/naming"
"go-common/library/naming/discovery"
"go-common/library/net/metadata"
"go-common/library/net/trace"
"go-common/library/stat"
xtime "go-common/library/time"
"github.com/golang/protobuf/proto"
"github.com/pkg/errors"
)
// Key is ContextKey
type Key int
const (
_ Key = iota
// KeyHeader use this in context to pass rpc header field
// Depreated 请使用HeaderOption来传递Header
KeyHeader
// KeyTimeout deprecated
// Depreated 请使用HTTPOption来传递HTTP
KeyTimeout
)
const (
_scheme = "liverpc"
_dialRetries = 3
)
// Get Implement tracer carrier interface
func (m *Header) Get(key string) string {
if key == trace.KeyTraceID {
return m.TraceId
}
return ""
}
// Set Implement tracer carrier interface
func (m *Header) Set(key string, val string) {
if key == trace.KeyTraceID {
m.TraceId = val
}
}
var (
// ErrNoClient no RPC client.
errNoClient = errors.New("no rpc client")
errGroupInvalid = errors.New("invalid group")
stats = stat.RPCClient
)
// GroupAddrs a map struct storing addrs vary groups
type GroupAddrs map[string][]string
// ClientConfig client config.
type ClientConfig struct {
AppID string
Group string
Timeout xtime.Duration
ConnTimeout xtime.Duration
Addr string // if addr is provided, it will use add, else, use discovery
}
// Client is a RPC client.
type Client struct {
conf *ClientConfig
dis naming.Resolver
addrs atomic.Value // GroupAddrs
addrsIdx int64
}
// NewClient new a RPC client with discovery.
func NewClient(c *ClientConfig) *Client {
if c.Timeout <= 0 {
c.Timeout = xtime.Duration(time.Second)
}
if c.ConnTimeout <= 0 {
c.ConnTimeout = xtime.Duration(time.Second)
}
cli := &Client{
conf: c,
}
if c.Addr != "" {
groupAddrs := make(GroupAddrs)
groupAddrs[""] = []string{c.Addr}
cli.addrs.Store(groupAddrs)
return cli
}
cli.dis = discovery.Build(c.AppID)
// discovery watch & fetch nodes
event := cli.dis.Watch()
select {
case _, ok := <-event:
if !ok {
panic("刚启动就从discovery拉到了关闭的event")
}
cli.disFetch()
fmt.Printf("开始创建:%s 的liverpc client等待从discovery拉取节点%s\n", c.AppID, time.Now().Format("2006-01-02 15:04:05"))
case <-time.After(10 * time.Second):
fmt.Printf("失败创建:%s 的liverpc client竟然从discovery拉取节点超时了%s\n", c.AppID, time.Now().Format("2006-01-02 15:04:05"))
}
go cli.disproc(event)
return cli
}
func (c *Client) disproc(event <-chan struct{}) {
for {
_, ok := <-event
if !ok {
return
}
c.disFetch()
}
}
func (c *Client) disFetch() {
ins, ok := c.dis.Fetch(context.Background())
if !ok {
return
}
insZone, ok := ins[env.Zone]
if !ok {
return
}
addrs := make(GroupAddrs)
for _, svr := range insZone {
group, ok := svr.Metadata["color"]
if !ok {
group = ""
}
for _, addr := range svr.Addrs {
u, err := url.Parse(addr)
if err == nil && u.Scheme == _scheme {
addrs[group] = append(addrs[group], u.Host)
}
}
}
if len(addrs) > 0 {
c.addrs.Store(addrs)
}
}
// pickConn pick conn by addrs
func (c *Client) pickConn(ctx context.Context, addrs []string, dialTimeout time.Duration) (*ClientConn, error) {
var (
lastErr error
)
if len(addrs) == 0 {
lastErr = errors.New("addrs empty")
} else {
for i := 0; i < _dialRetries; i++ {
idx := atomic.AddInt64(&c.addrsIdx, 1)
addr := addrs[int(idx)%len(addrs)]
if dialTimeout == 0 {
dialTimeout = time.Duration(c.conf.ConnTimeout)
}
cc, err := Dial(ctx, "tcp", addr, time.Duration(c.conf.Timeout), dialTimeout)
if err != nil {
lastErr = errors.Wrapf(err, "Dial %s error", addr)
continue
}
return cc, nil
}
}
if lastErr != nil {
return nil, errors.WithMessage(errNoClient, lastErr.Error())
}
return nil, errors.WithStack(errNoClient)
}
// fetchAddrs fetch addrs by different strategies
// source_group first, come from request header if exists, currently only CallRaw supports source_group
// then env group, come from os.env
// since no invalid group found, return error
func (c *Client) fetchAddrs(ctx context.Context, request interface{}) (addrs []string, err error) {
var (
args *Args
groupAddrs GroupAddrs
ok bool
sourceGroup string
groups []string
)
defer func() {
if err != nil {
err = errors.WithMessage(errGroupInvalid, err.Error())
}
}()
// try parse request header and fetch source group
if args, ok = request.(*Args); ok && args.Header != nil {
sourceGroup = args.Header.SourceGroup
if sourceGroup != "" {
groups = append(groups, sourceGroup)
}
}
metaColor := metadata.String(ctx, metadata.Color)
if metaColor != "" && metaColor != sourceGroup {
groups = append(groups, metaColor)
}
if env.Color != "" && env.Color != metaColor {
groups = append(groups, env.Color)
}
groups = append(groups, "")
if groupAddrs, ok = c.addrs.Load().(GroupAddrs); !ok {
err = errors.New("addrs load error")
return
}
if len(groupAddrs) == 0 {
err = errors.New("group addrs empty")
return
}
for _, group := range groups {
if addrs, ok = groupAddrs[group]; ok {
break
}
}
if len(addrs) == 0 {
err = errors.Errorf("addrs empty source(%s), metadata(%s), env(%s), default empty, allAddrs(%+v)",
sourceGroup, metaColor, env.Color, groupAddrs)
return
}
return
}
// Call call the service method, waits for it to complete, and returns its error status.
// client: {service}
// serviceMethod: {version}|{controller.method}
// httpURL: /room/v1/Room/room_init
// httpURL: /{service}/{version}/{controller}/{method}
func (c *Client) Call(ctx context.Context, version int, serviceMethod string, in proto.Message, out proto.Message, opts ...CallOption) (err error) {
var (
cc *ClientConn
addrs []string
)
isPickErr := true
defer func() {
if cc != nil {
cc.Close()
}
if err != nil && isPickErr {
log.Error("liverpc Call pick connection error, version %d, method: %s, error: %+v", version, serviceMethod, err)
}
}() // for now it is non-persistent connection
var cInfo = &callInfo{}
for _, o := range opts {
o.before(cInfo)
}
addrs, err = c.fetchAddrs(ctx, in)
if err != nil {
return
}
cc, err = c.pickConn(ctx, addrs, cInfo.DialTimeout)
if err != nil {
return
}
isPickErr = false
cc.callInfo = cInfo
err = cc.Call(ctx, version, serviceMethod, in, out)
if err != nil {
return
}
for _, o := range opts {
o.after(cc.callInfo)
}
return
}
// CallRaw call the service method, waits for it to complete, and returns reply its error status.
// this is can be use without protobuf
// client: {service}
// serviceMethod: {version}|{controller.method}
// httpURL: /room/v1/Room/room_init
// httpURL: /{service}/{version}/{controller}/{method}
func (c *Client) CallRaw(ctx context.Context, version int, serviceMethod string, in *Args, opts ...CallOption) (out *Reply, err error) {
var (
cc *ClientConn
addrs []string
)
isPickErr := true
defer func() {
if cc != nil {
cc.Close()
}
if err != nil && isPickErr {
log.Error("liverpc CallRaw pick connection error, version %d, method: %s, error: %+v", version, serviceMethod, err)
}
}() // for now it is non-persistent connection
var cInfo = &callInfo{}
for _, o := range opts {
o.before(cInfo)
}
addrs, err = c.fetchAddrs(ctx, in)
if err != nil {
return
}
cc, err = c.pickConn(ctx, addrs, cInfo.DialTimeout)
if err != nil {
return
}
isPickErr = false
cc.callInfo = cInfo
out, err = cc.CallRaw(ctx, version, serviceMethod, in)
if err != nil {
return
}
for _, o := range opts {
o.after(cc.callInfo)
}
return
}
//Close handle client exit
func (c *Client) Close() {
if c.dis != nil {
c.dis.Close()
}
}

View File

@@ -0,0 +1,371 @@
package liverpc
import (
"context"
"encoding/binary"
"encoding/json"
"fmt"
"io"
"net"
"strconv"
"strings"
"time"
"go-common/library/conf/env"
"go-common/library/log"
"go-common/library/net/metadata"
"go-common/library/net/trace"
"github.com/gogo/protobuf/proto"
"github.com/json-iterator/go"
"github.com/pkg/errors"
)
// ClientConn connect represent a real client connection to a rpc server
type ClientConn struct {
addr string
network string
rwc io.ReadWriteCloser
Timeout time.Duration
DialTimeout time.Duration
callInfo *callInfo
}
type fullReqMsg struct {
Header *Header `json:"header"`
HTTP *HTTP `json:"http"`
Body interface{} `json:"body"`
}
// Dial dial a rpc server
func Dial(ctx context.Context, network, addr string, timeout time.Duration, connTimeout time.Duration) (*ClientConn, error) {
c := &ClientConn{
addr: addr,
network: network,
Timeout: timeout,
DialTimeout: connTimeout,
}
conn, err := net.DialTimeout(c.network, c.addr, c.DialTimeout)
if err != nil {
return nil, err
}
c.rwc = conn
return c, err
}
// Close close the caller connection.
func (c *ClientConn) Close() error {
if c.rwc != nil {
return c.rwc.Close()
}
return nil
}
func (c *ClientConn) writeRequest(ctx context.Context, req *protoReq) (err error) {
var (
headerBuf = make([]byte, _headerLen)
header = req.Header
body = req.Body
)
binary.BigEndian.PutUint32(headerBuf[0:4], header.magic)
binary.BigEndian.PutUint32(headerBuf[4:8], header.timestamp)
binary.BigEndian.PutUint32(headerBuf[8:12], header.checkSum)
binary.BigEndian.PutUint32(headerBuf[12:16], header.version)
binary.BigEndian.PutUint32(headerBuf[16:20], header.reserved)
binary.BigEndian.PutUint32(headerBuf[20:24], header.seq)
binary.BigEndian.PutUint32(headerBuf[24:28], uint32(len(body)))
copy(headerBuf[28:60], header.cmd)
if _, err = c.rwc.Write(headerBuf); err != nil {
err = errors.Wrap(err, "write req header error")
return
}
if log.V(2) {
log.Info("liverpc body: %s", string(body))
}
if _, err = c.rwc.Write(body); err != nil {
err = errors.Wrap(err, "write req body error")
return
}
return
}
func (c *ClientConn) readResponse(ctx context.Context, resp *protoResp) (err error) {
var (
headerBuf = make([]byte, _headerLen)
length int
)
if _, err = c.rwc.Read(headerBuf); err != nil {
err = errors.Wrap(err, "read resp header error")
return
}
resp.Header.magic = binary.BigEndian.Uint32(headerBuf[0:4])
resp.Header.timestamp = binary.BigEndian.Uint32(headerBuf[4:8])
resp.Header.checkSum = binary.BigEndian.Uint32(headerBuf[8:12])
resp.Header.version = binary.BigEndian.Uint32(headerBuf[12:16])
resp.Header.reserved = binary.BigEndian.Uint32(headerBuf[16:20])
resp.Header.seq = binary.BigEndian.Uint32(headerBuf[20:24])
resp.Header.length = binary.BigEndian.Uint32(headerBuf[24:28])
resp.Header.cmd = headerBuf[28:60]
resp.Body = make([]byte, resp.Header.length)
if length, err = io.ReadFull(c.rwc, resp.Body); err != nil {
err = errors.Wrap(err, "read resp body error")
return
}
if uint32(length) != resp.Header.length {
err = errors.New("bad resp body data")
return
}
return
}
func (c *ClientConn) composeReqPackHeader(reqPack *protoReq, version int, serviceMethod string) {
reqPack.Header.magic = _magic
reqPack.Header.checkSum = 0
reqPack.Header.seq = 1
reqPack.Header.timestamp = uint32(time.Now().Unix())
reqPack.Header.reserved = 0
reqPack.Header.version = uint32(version)
// command: {message_type}controller.method
reqPack.Header.cmd = make([]byte, 32)
reqPack.Header.cmd[0] = _cmdReqType
// serviceMethod: Room.room_init
copy(reqPack.Header.cmd[1:], []byte(serviceMethod))
}
func (c *ClientConn) setupDeadline(ctx context.Context) error {
var t time.Duration
if c.callInfo.Timeout != 0 {
t = c.callInfo.Timeout
} else {
t, _ = ctx.Value(KeyTimeout).(time.Duration)
}
if t == 0 {
t = c.Timeout
}
conn := c.rwc.(net.Conn)
if conn != nil {
err := conn.SetDeadline(time.Now().Add(t))
if err != nil {
conn.Close()
return err
}
}
return nil
}
// CallRaw call the service method, waits for it to complete, and returns reply its error status.
// this is can be use without protobuf
// client: {service}
// serviceMethod: {version}|{controller.method}
// httpURL: /room/v1/Room/room_init
// httpURL: /{service}/{version}/{controller}/{method}
func (c *ClientConn) CallRaw(ctx context.Context, version int, serviceMethod string, in *Args) (out *Reply, err error) {
var (
reqPack protoReq
respPack protoResp
code = "0"
now = time.Now()
uid int64
)
defer func() {
stats.Timing(serviceMethod, int64(time.Since(now)/time.Millisecond))
if code != "" {
stats.Incr(serviceMethod, code)
}
logging(ctx, version, serviceMethod, c.addr, err, time.Since(now), uid)
}()
if err = c.setupDeadline(ctx); err != nil {
return
}
// it is ok for request http field to be nil
if in.Header == nil {
if c.callInfo.Header != nil {
in.Header = c.callInfo.Header
} else if hdr, _ := ctx.Value(KeyHeader).(*Header); hdr != nil {
in.Header = hdr
} else {
in.Header = createHeader(ctx)
}
}
uid = in.Header.Uid
if in.HTTP == nil {
if c.callInfo.HTTP != nil {
in.HTTP = c.callInfo.HTTP
}
}
if in.Body == nil {
in.Body = map[string]interface{}{}
}
c.composeReqPackHeader(&reqPack, version, serviceMethod)
var reqBytes []byte
if reqBytes, err = json.Marshal(in); err != nil {
err = errors.Wrap(err, "CallRaw json marshal error")
code = "marshalErr"
return
}
reqPack.Body = reqBytes
ch := make(chan error, 1)
go func() {
ch <- c.sendAndRecv(ctx, &reqPack, &respPack)
}()
select {
case <-ctx.Done():
err = errors.WithStack(ctx.Err())
code = "canceled"
return
case err = <-ch:
if err != nil {
code = "ioErr"
return
}
}
out = &Reply{}
if err = json.Unmarshal(respPack.Body, out); err != nil {
err = errors.Wrap(err, "proto unmarshal error: "+string(respPack.Body))
code = "unmarshalErr"
return
}
return
}
func logging(ctx context.Context, version int, serviceMethod string, addr string, err error, ts time.Duration, uid int64) {
var (
path string
errMsg string
)
logFunc := log.Infov
if err != nil {
if errors.Cause(err) == context.Canceled {
logFunc = log.Warnv
} else {
logFunc = log.Errorv
}
errMsg = fmt.Sprintf("%+v", err)
}
path = "/v" + strconv.Itoa(version) + "/" + strings.Replace(serviceMethod, ".", "/", 1)
logFunc(ctx,
log.KV("path", path),
log.KV("error", errMsg),
log.KV("addr", addr),
log.KV("ts", float64(ts.Seconds())),
log.KV("uid", uid),
log.KV("log", "LIVERPC"),
)
}
func (c *ClientConn) sendAndRecv(ctx context.Context, reqPack *protoReq, respPack *protoResp) (err error) {
if err = c.writeRequest(ctx, reqPack); err != nil {
return
}
if err = c.readResponse(ctx, respPack); err != nil {
return
}
return
}
// Call call the service method, waits for it to complete, and returns its error status.
// this is used with protobuf generated msg
// client: {service}
// serviceMethod: {version}|{controller.method}
// httpURL: /room/v1/Room/room_init
// httpURL: /{service}/{version}/{controller}/{method}
func (c *ClientConn) Call(ctx context.Context, version int, serviceMethod string, in, out proto.Message) (err error) {
var (
reqPack protoReq
respPack protoResp
code = "0"
now = time.Now()
uid int64
)
defer func() {
stats.Timing(serviceMethod, int64(time.Since(now)/time.Millisecond))
if code != "" {
stats.Incr(serviceMethod, code)
}
logging(ctx, version, serviceMethod, c.addr, err, time.Since(now), uid)
}()
if err = c.setupDeadline(ctx); err != nil {
return
}
fullMsg := &fullReqMsg{}
if c.callInfo.Header != nil {
fullMsg.Header = c.callInfo.Header
} else if hdr, _ := ctx.Value(KeyHeader).(*Header); hdr != nil {
fullMsg.Header = hdr
} else {
fullMsg.Header = createHeader(ctx)
}
uid = fullMsg.Header.Uid
if c.callInfo.HTTP != nil {
fullMsg.HTTP = c.callInfo.HTTP
}
fullMsg.Body = in
// it is ok for request http field to be nil
c.composeReqPackHeader(&reqPack, version, serviceMethod)
var reqBody []byte
if reqBody, err = json.Marshal(fullMsg); err != nil {
err = errors.Wrap(err, "Call json marshal error")
code = "marshalErr"
return
}
reqPack.Body = reqBody
ch := make(chan error, 1)
go func() {
ch <- c.sendAndRecv(ctx, &reqPack, &respPack)
}()
select {
case <-ctx.Done():
err = errors.WithStack(ctx.Err())
code = "canceled"
return
case err = <-ch:
if err != nil {
code = "ioErr"
return
}
}
if err = jsoniter.Unmarshal(respPack.Body, out); err != nil {
err = errors.Wrap(err, "proto unmarshal error: "+string(respPack.Body))
code = "unmarshalErr"
return
}
return
}
func createHeader(ctx context.Context) *Header {
header := &Header{}
header.UserIp = metadata.String(ctx, metadata.RemoteIP)
header.Caller = strings.Replace(env.AppID, ".", "-", -1)
if header.Caller == "" {
header.Caller = "unknown"
}
tracer, ok := metadata.Value(ctx, metadata.Trace).(trace.Trace)
if ok {
trace.Inject(tracer, nil, header)
}
mid, _ := metadata.Value(ctx, "mid").(int64)
header.Uid = mid
if color := metadata.String(ctx, metadata.Color); color != "" {
header.SourceGroup = color
} else {
header.SourceGroup = env.Color
}
//header.Platform = ctx.Request.FormValue("platform")
return header
}

View File

@@ -0,0 +1,93 @@
package liverpc
import (
"context"
"math/rand"
"testing"
"time"
"go-common/library/conf/env"
"go-common/library/net/metadata"
. "github.com/smartystreets/goconvey/convey"
)
type testConn struct {
}
func (t *testConn) Read(p []byte) (n int, err error) {
return
}
func (t *testConn) Write(p []byte) (n int, err error) {
return
}
func (t *testConn) Close() (err error) {
return
}
func TestClientConn(t *testing.T) {
var (
req protoReq
resp protoResp
)
req.Header.magic = _magic
req.Header.checkSum = 0
req.Header.seq = rand.Uint32()
req.Header.timestamp = uint32(time.Now().Unix())
req.Header.reserved = 1
req.Header.version = 1
// command: {message_type}controller.method
req.Header.cmd = make([]byte, 32)
req.Header.cmd[0] = _cmdReqType
// serviceMethod: Room.room_init
copy(req.Header.cmd[1:], []byte("Room.room_init"))
codec := &ClientConn{rwc: &testConn{}}
if err := codec.writeRequest(context.TODO(), &req); err != nil {
t.Error(err)
t.FailNow()
}
if err := codec.readResponse(context.TODO(), &resp); err != nil {
t.Error(err)
t.FailNow()
}
t.Logf("request header:%+v body:%s", req.Header, req.Body)
t.Logf("response header:%+v body:%s", resp.Header, resp.Body)
}
func TestGroups(t *testing.T) {
Convey("groups", t, func() {
client := Client{}
// env before empty
env.Color = "red"
client.addrs.Store(GroupAddrs{
"red": []string{"r1", "r2"},
"": {"empty1", "empty2"},
"dark": {"d1", "d2"},
"blue": []string{"b1", "b2"},
})
addrs, _ := client.fetchAddrs(context.TODO(), nil)
So(addrs, ShouldResemble, []string{"r1", "r2"})
// can get env
env.Color = ""
addrs, _ = client.fetchAddrs(context.TODO(), nil)
So(addrs, ShouldResemble, []string{"empty1", "empty2"})
// from metadata.color
env.Color = "red"
addrs, _ = client.fetchAddrs(
metadata.NewContext(context.TODO(), metadata.MD{metadata.Color: "blue"}), nil)
So(addrs, ShouldResemble, []string{"b1", "b2"})
// header source_group always first
addrs, _ = client.fetchAddrs(
metadata.NewContext(context.TODO(), metadata.MD{metadata.Color: "blue"}),
&Args{Header: &Header{SourceGroup: "dark"}})
So(addrs, ShouldResemble, []string{"d1", "d2"})
})
}

View File

@@ -0,0 +1,29 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = ["context.go"],
importpath = "go-common/library/net/rpc/liverpc/context",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = ["//library/net/rpc/liverpc:go_default_library"],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,23 @@
package context
import (
"context"
"time"
"go-common/library/net/rpc/liverpc"
)
// WithHeader returns new context with header
// Deprecated: Use HeaderOption instead
func WithHeader(ctx context.Context, header *liverpc.Header) (ret context.Context) {
ret = context.WithValue(ctx, liverpc.KeyHeader, header)
return
}
// WithTimeout set timeout to rpc request
// Notice this is nothing related to to built-in context.WithTimeout
// Deprecated: Use TimeoutOption instead
func WithTimeout(ctx context.Context, time time.Duration) (ret context.Context) {
ret = context.WithValue(ctx, liverpc.KeyTimeout, time)
return
}

View File

@@ -0,0 +1,2 @@
#!/bin/bash
protoc --go_out=paths=source_relative:. liverpc.proto

View File

@@ -0,0 +1,252 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// source: liverpc.proto
package liverpc // import "go-common/library/net/rpc/liverpc"
import proto "github.com/golang/protobuf/proto"
import fmt "fmt"
import math "math"
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
type Header struct {
// APP_NAME.xxxxx , when separated by dot,
// the first part is always app_name, the rest is undefined
Caller string `protobuf:"bytes,1,opt,name=caller,proto3" json:"caller,omitempty"`
Uid int64 `protobuf:"varint,2,opt,name=uid,proto3" json:"uid,omitempty"`
Platform string `protobuf:"bytes,3,opt,name=platform,proto3" json:"platform,omitempty"`
Src string `protobuf:"bytes,4,opt,name=src,proto3" json:"src,omitempty"`
TraceId string `protobuf:"bytes,5,opt,name=trace_id,json=traceId,proto3" json:"trace_id,omitempty"`
UserIp string `protobuf:"bytes,7,opt,name=user_ip,json=userIp,proto3" json:"user_ip,omitempty"`
SourceGroup string `protobuf:"bytes,8,opt,name=source_group,json=sourceGroup,proto3" json:"source_group,omitempty"`
Buvid string `protobuf:"bytes,9,opt,name=buvid,proto3" json:"buvid,omitempty"`
// session data, format is http query
// such as access_token=abc&SESS_DATA=def
Sessdata2 string `protobuf:"bytes,10,opt,name=sessdata2,proto3" json:"sessdata2,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *Header) Reset() { *m = Header{} }
func (m *Header) String() string { return proto.CompactTextString(m) }
func (*Header) ProtoMessage() {}
func (*Header) Descriptor() ([]byte, []int) {
return fileDescriptor_liverpc_376c41dd15148bd6, []int{0}
}
func (m *Header) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_Header.Unmarshal(m, b)
}
func (m *Header) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_Header.Marshal(b, m, deterministic)
}
func (dst *Header) XXX_Merge(src proto.Message) {
xxx_messageInfo_Header.Merge(dst, src)
}
func (m *Header) XXX_Size() int {
return xxx_messageInfo_Header.Size(m)
}
func (m *Header) XXX_DiscardUnknown() {
xxx_messageInfo_Header.DiscardUnknown(m)
}
var xxx_messageInfo_Header proto.InternalMessageInfo
func (m *Header) GetCaller() string {
if m != nil {
return m.Caller
}
return ""
}
func (m *Header) GetUid() int64 {
if m != nil {
return m.Uid
}
return 0
}
func (m *Header) GetPlatform() string {
if m != nil {
return m.Platform
}
return ""
}
func (m *Header) GetSrc() string {
if m != nil {
return m.Src
}
return ""
}
func (m *Header) GetTraceId() string {
if m != nil {
return m.TraceId
}
return ""
}
func (m *Header) GetUserIp() string {
if m != nil {
return m.UserIp
}
return ""
}
func (m *Header) GetSourceGroup() string {
if m != nil {
return m.SourceGroup
}
return ""
}
func (m *Header) GetBuvid() string {
if m != nil {
return m.Buvid
}
return ""
}
func (m *Header) GetSessdata2() string {
if m != nil {
return m.Sessdata2
}
return ""
}
// http is inside the protocol body
// {"body":..., "header":..., "http":...}
// this is used when a proxy forward a http request to a rpc request
type HTTP struct {
IsHttps int32 `protobuf:"varint,1,opt,name=is_https,json=isHttps,proto3" json:"is_https,omitempty"`
Body string `protobuf:"bytes,2,opt,name=body,proto3" json:"body,omitempty"`
Cookie map[string]string `protobuf:"bytes,3,rep,name=cookie,proto3" json:"cookie,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
Header map[string]string `protobuf:"bytes,4,rep,name=header,proto3" json:"header,omitempty" protobuf_key:"bytes,1,opt,name=key,proto3" protobuf_val:"bytes,2,opt,name=value,proto3"`
Uri string `protobuf:"bytes,5,opt,name=uri,proto3" json:"uri,omitempty"`
Method string `protobuf:"bytes,6,opt,name=method,proto3" json:"method,omitempty"`
Protocol string `protobuf:"bytes,7,opt,name=protocol,proto3" json:"protocol,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *HTTP) Reset() { *m = HTTP{} }
func (m *HTTP) String() string { return proto.CompactTextString(m) }
func (*HTTP) ProtoMessage() {}
func (*HTTP) Descriptor() ([]byte, []int) {
return fileDescriptor_liverpc_376c41dd15148bd6, []int{1}
}
func (m *HTTP) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_HTTP.Unmarshal(m, b)
}
func (m *HTTP) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_HTTP.Marshal(b, m, deterministic)
}
func (dst *HTTP) XXX_Merge(src proto.Message) {
xxx_messageInfo_HTTP.Merge(dst, src)
}
func (m *HTTP) XXX_Size() int {
return xxx_messageInfo_HTTP.Size(m)
}
func (m *HTTP) XXX_DiscardUnknown() {
xxx_messageInfo_HTTP.DiscardUnknown(m)
}
var xxx_messageInfo_HTTP proto.InternalMessageInfo
func (m *HTTP) GetIsHttps() int32 {
if m != nil {
return m.IsHttps
}
return 0
}
func (m *HTTP) GetBody() string {
if m != nil {
return m.Body
}
return ""
}
func (m *HTTP) GetCookie() map[string]string {
if m != nil {
return m.Cookie
}
return nil
}
func (m *HTTP) GetHeader() map[string]string {
if m != nil {
return m.Header
}
return nil
}
func (m *HTTP) GetUri() string {
if m != nil {
return m.Uri
}
return ""
}
func (m *HTTP) GetMethod() string {
if m != nil {
return m.Method
}
return ""
}
func (m *HTTP) GetProtocol() string {
if m != nil {
return m.Protocol
}
return ""
}
func init() {
proto.RegisterType((*Header)(nil), "liverpc.Header")
proto.RegisterType((*HTTP)(nil), "liverpc.HTTP")
proto.RegisterMapType((map[string]string)(nil), "liverpc.HTTP.CookieEntry")
proto.RegisterMapType((map[string]string)(nil), "liverpc.HTTP.HeaderEntry")
}
func init() { proto.RegisterFile("liverpc.proto", fileDescriptor_liverpc_376c41dd15148bd6) }
var fileDescriptor_liverpc_376c41dd15148bd6 = []byte{
// 386 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x94, 0x91, 0xb1, 0xce, 0xd3, 0x30,
0x10, 0xc7, 0x95, 0x26, 0x4d, 0x1a, 0x17, 0x24, 0x64, 0x21, 0xf0, 0xf7, 0x89, 0xa1, 0x2d, 0x4b,
0x17, 0x1a, 0x51, 0x16, 0x60, 0x04, 0x21, 0xda, 0x0d, 0x45, 0x9d, 0x58, 0x22, 0xc7, 0x36, 0xad,
0xd5, 0xa4, 0xb6, 0x6c, 0xa7, 0x52, 0x9e, 0x94, 0x87, 0xe0, 0x25, 0xd0, 0xd9, 0xa6, 0x74, 0x60,
0x61, 0xbb, 0xdf, 0xdd, 0xfd, 0xed, 0xbb, 0xff, 0xa1, 0xa7, 0x9d, 0xbc, 0x0a, 0xa3, 0xd9, 0x46,
0x1b, 0xe5, 0x14, 0x2e, 0x22, 0xae, 0x7e, 0x25, 0x28, 0xdf, 0x09, 0xca, 0x85, 0xc1, 0x2f, 0x50,
0xce, 0x68, 0xd7, 0x09, 0x43, 0x92, 0x45, 0xb2, 0x2e, 0xeb, 0x48, 0xf8, 0x19, 0x4a, 0x07, 0xc9,
0xc9, 0x64, 0x91, 0xac, 0xd3, 0x1a, 0x42, 0xfc, 0x88, 0x66, 0xba, 0xa3, 0xee, 0x87, 0x32, 0x3d,
0x49, 0x7d, 0xef, 0x8d, 0xa1, 0xdb, 0x1a, 0x46, 0x32, 0x9f, 0x86, 0x10, 0x3f, 0xa0, 0x99, 0x33,
0x94, 0x89, 0x46, 0x72, 0x32, 0xf5, 0xe9, 0xc2, 0xf3, 0x9e, 0xe3, 0x97, 0xa8, 0x18, 0xac, 0x30,
0x8d, 0xd4, 0xa4, 0x08, 0x7f, 0x02, 0xee, 0x35, 0x5e, 0xa2, 0x27, 0x56, 0x0d, 0x86, 0x89, 0xe6,
0x68, 0xd4, 0xa0, 0xc9, 0xcc, 0x57, 0xe7, 0x21, 0xf7, 0x15, 0x52, 0xf8, 0x39, 0x9a, 0xb6, 0xc3,
0x55, 0x72, 0x52, 0xfa, 0x5a, 0x00, 0xfc, 0x0a, 0x95, 0x56, 0x58, 0xcb, 0xa9, 0xa3, 0x5b, 0x82,
0x7c, 0xe5, 0x6f, 0x62, 0xf5, 0x73, 0x82, 0xb2, 0xdd, 0xe1, 0xf0, 0x0d, 0x66, 0x92, 0xb6, 0x39,
0x39, 0xa7, 0xad, 0xdf, 0x76, 0x5a, 0x17, 0xd2, 0xee, 0x00, 0x31, 0x46, 0x59, 0xab, 0xf8, 0xe8,
0xf7, 0x2d, 0x6b, 0x1f, 0xe3, 0xb7, 0x28, 0x67, 0x4a, 0x9d, 0xa5, 0x20, 0xe9, 0x22, 0x5d, 0xcf,
0xb7, 0x0f, 0x9b, 0x3f, 0x76, 0xc2, 0x6b, 0x9b, 0xcf, 0xbe, 0xf6, 0xe5, 0xe2, 0xcc, 0x58, 0xc7,
0x46, 0x90, 0x9c, 0xbc, 0xaf, 0x24, 0xfb, 0x97, 0x24, 0x78, 0x1e, 0x25, 0xa1, 0xd1, 0x1b, 0x6d,
0x64, 0xf4, 0x08, 0x42, 0x38, 0x49, 0x2f, 0xdc, 0x49, 0x71, 0x92, 0x07, 0x7b, 0x02, 0xf9, 0x03,
0xc0, 0x1d, 0x99, 0xea, 0xa2, 0x71, 0x37, 0x7e, 0xfc, 0x80, 0xe6, 0x77, 0xf3, 0xc0, 0xa3, 0x67,
0x31, 0xc6, 0x93, 0x42, 0x08, 0xc6, 0x5d, 0x69, 0x37, 0x88, 0xb8, 0x61, 0x80, 0x8f, 0x93, 0xf7,
0x09, 0x48, 0xef, 0xe6, 0xfa, 0x1f, 0xe9, 0xa7, 0xd7, 0xdf, 0x97, 0x47, 0xf5, 0x86, 0xa9, 0xbe,
0x57, 0x97, 0xaa, 0x93, 0xad, 0xa1, 0x66, 0xac, 0x2e, 0xc2, 0x55, 0x46, 0xb3, 0x2a, 0x6e, 0xde,
0xe6, 0x7e, 0xc8, 0x77, 0xbf, 0x03, 0x00, 0x00, 0xff, 0xff, 0xfe, 0x68, 0x55, 0x93, 0x8d, 0x02,
0x00, 0x00,
}

View File

@@ -0,0 +1,35 @@
syntax = "proto3";
package liverpc;
option go_package = "go-common/library/net/rpc/liverpc";
message Header {
// APP_NAME.xxxxx , when separated by dot,
// the first part is always app_name, the rest is undefined
string caller = 1;
int64 uid = 2;
string platform = 3;
string src = 4;
string trace_id = 5;
string user_ip = 7;
string source_group = 8;
string buvid = 9;
// session data, format is http query
// such as access_token=abc&SESS_DATA=def
string sessdata2 = 10;
}
// http is inside the protocol body
// {"body":..., "header":..., "http":...}
// this is used when a proxy forward a http request to a rpc request
message HTTP {
int32 is_https = 1;
string body = 2; // the original body, only used when nessasary, usually null
map<string, string> cookie = 3;
map<string, string> header = 4;
string uri = 5; // user original uri
string method = 6; // http method
string protocol = 7; // not much use here
}

View File

@@ -0,0 +1,56 @@
package liverpc
import (
"time"
)
// CallOption ...
type CallOption interface {
before(*callInfo)
after(*callInfo)
}
type callInfo struct {
Header *Header
HTTP *HTTP
DialTimeout time.Duration
Timeout time.Duration
}
// TimeoutOption is timeout for a specific call
type TimeoutOption struct {
DialTimeout time.Duration
Timeout time.Duration
}
func (t TimeoutOption) before(info *callInfo) {
info.DialTimeout = t.DialTimeout
info.Timeout = t.Timeout
}
func (t TimeoutOption) after(*callInfo) {
}
// HeaderOption contains Header for liverpc
type HeaderOption struct {
Header *Header
}
func (h HeaderOption) before(info *callInfo) {
info.Header = h.Header
}
func (h HeaderOption) after(*callInfo) {
}
// HTTPOption contains HTTP for liverpc
type HTTPOption struct {
HTTP *HTTP
}
func (h HTTPOption) before(info *callInfo) {
info.HTTP = h.HTTP
}
func (h HTTPOption) after(*callInfo) {
}

View File

@@ -0,0 +1,45 @@
package liverpc
import "encoding/json"
const (
_magic = 2233
_headerLen = 60
_cmdReqType = byte('0')
)
type protoHeader struct {
magic uint32
timestamp uint32
checkSum uint32
version uint32
reserved uint32
seq uint32
length uint32
cmd []byte
}
type protoReq struct {
Header protoHeader
Body []byte
}
type protoResp struct {
Header protoHeader
Body []byte
}
// Args .
type Args struct {
Header *Header `json:"header"`
Body interface{} `json:"body"`
HTTP interface{} `json:"http"`
}
// Reply .
type Reply struct {
Code int `json:"code"`
Message string `json:"msg"`
Data json.RawMessage `json:"data"`
}

View File

@@ -0,0 +1,43 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
"go_library",
)
go_test(
name = "go_default_test",
srcs = ["client_test.go"],
rundir = ".",
tags = ["automanaged"],
deps = [
"//library/net/rpc/liverpc:go_default_library",
"//library/net/rpc/liverpc/testdata:go_default_library",
"//library/net/rpc/liverpc/testdata/v1:go_default_library",
"//library/net/rpc/liverpc/testdata/v2:go_default_library",
"//vendor/github.com/pkg/errors:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)
go_library(
name = "go_default_library",
srcs = ["client.go"],
importpath = "go-common/library/net/rpc/liverpc/test",
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,2 @@
// Package liverpc . used to suppress warning: no non-test Go files
package liverpc

View File

@@ -0,0 +1,181 @@
// to avoid recycle imports,
// the test must be in different package with liverpc...
// otherwise, test import => generated pb
// generated pb => import liverpc (which includes the test)
package liverpc
import (
"context"
"math/rand"
"testing"
"time"
"go-common/library/net/rpc/liverpc"
"go-common/library/net/rpc/liverpc/testdata"
v1 "go-common/library/net/rpc/liverpc/testdata/v1"
v2 "go-common/library/net/rpc/liverpc/testdata/v2"
"github.com/pkg/errors"
)
func TestDialClient(t *testing.T) {
cli := testdata.New(nil)
var req = &v1.RoomGetInfoReq{Id: 1002}
var hd = &liverpc.Header{
Platform: "ios",
Src: "test",
Buvid: "AUTO3315341311353015",
TraceId: "18abb1a2596c43ea:18abb1a2596c43ea:0:0",
Uid: 10,
Caller: "live-api.rpc",
UserIp: "127.0.0.1",
SourceGroup: "default",
}
var ctx = context.TODO()
reply, err := cli.V1Room.GetInfo(ctx, req, &liverpc.HeaderOption{Header: hd})
if err != nil {
t.Error(err)
t.FailNow()
}
t.Logf("reply:%v %v %v", reply.Code, reply.Msg, reply.Data)
_, err = cli.GetRawCli().CallRaw(context.TODO(), 2, "Room.get_by_ids", &liverpc.Args{})
if err != nil {
t.Error(err)
t.FailNow()
}
}
func TestCallRaw(t *testing.T) {
var cli = liverpc.NewClient(&liverpc.ClientConfig{AppID: "live.room"})
var hd = &liverpc.Header{
Platform: "ios",
Src: "test",
Buvid: "AUTO3315341311353015",
TraceId: "18abb1a2596c43ea:18abb1a2596c43ea:0:0",
Uid: 10,
Caller: "live-api.rpc",
UserIp: "127.0.0.1",
SourceGroup: "default",
}
var req = &liverpc.Args{Body: map[string]interface{}{"id": 1002}, Header: hd}
hd = nil
reply, err := cli.CallRaw(context.TODO(), 1, "Room.get_info", req, &liverpc.TimeoutOption{Timeout: 200 * time.Millisecond})
if err != nil {
t.Error(err)
t.FailNow()
}
t.Logf("reply:%v %v %v", reply.Code, reply.Message, string(reply.Data))
_, err = cli.CallRaw(context.TODO(), 1, "Room.get_info", req, &liverpc.TimeoutOption{Timeout: 2 * time.Millisecond})
if err == nil {
t.Error(errors.New("should fail"))
t.FailNow()
}
}
func TestMap(t *testing.T) {
client := liverpc.NewClient(&liverpc.ClientConfig{AppID: "live.room"})
var rpcClient = v2.NewRoomRPCClient(client)
var req = &v2.RoomGetByIdsReq{Ids: []int64{1002}}
var header = &liverpc.Header{
Platform: "ios",
Src: "test",
Buvid: "AUTO3315341311353015",
TraceId: "18abb1a2596c43ea:18abb1a2596c43ea:0:0",
Uid: 10,
Caller: "live-api.rpc",
UserIp: "127.0.0.1",
SourceGroup: "default",
}
var ctx = context.TODO()
reply, err := rpcClient.GetByIds(ctx, req, liverpc.HeaderOption{Header: header})
if err != nil {
t.Error(err)
t.FailNow()
}
t.Logf("reply:%v %v %v", reply.Code, reply.Msg, reply.Data)
}
func TestDiscoveryClient(t *testing.T) {
conf := &liverpc.ClientConfig{
AppID: "live.room",
}
cli := liverpc.NewClient(conf)
arg := &v1.RoomGetInfoReq{Id: 1001}
var rpcClient = v1.NewRoomRPCClient(cli)
reply, err := rpcClient.GetInfo(context.TODO(), arg)
if err != nil {
t.Error(err)
t.FailNow()
}
t.Logf("reply:%+v", reply)
}
func TestCancel(t *testing.T) {
conf := &liverpc.ClientConfig{
AppID: "live.room",
}
cli := liverpc.NewClient(conf)
arg := &v1.RoomGetInfoReq{Id: 1001}
var rpcClient = v1.NewRoomRPCClient(cli)
ctx, cancel := context.WithTimeout(context.TODO(), time.Millisecond)
defer cancel()
var err error
_, err = rpcClient.GetInfo(ctx, arg)
if err == nil || errors.Cause(err) != context.DeadlineExceeded {
t.Error(err)
t.FailNow()
}
}
func TestCallRawCancel(t *testing.T) {
var cli = liverpc.NewClient(&liverpc.ClientConfig{AppID: "live.room"})
var hd = &liverpc.Header{
Platform: "ios",
Src: "test",
Buvid: "AUTO3315341311353015",
TraceId: "18abb1a2596c43ea:18abb1a2596c43ea:0:0",
Uid: 10,
Caller: "live-api.rpc",
UserIp: "127.0.0.1",
SourceGroup: "default",
}
var req = &liverpc.Args{Body: map[string]interface{}{"id": 1002}, Header: hd}
hd = nil
ctx, cancel := context.WithTimeout(context.TODO(), time.Millisecond)
defer cancel()
_, err := cli.CallRaw(ctx, 1, "Room.get_info", req)
if err == nil || errors.Cause(err) != context.DeadlineExceeded {
t.Error(err)
t.FailNow()
}
t.Logf("err is +%v", err)
}
func BenchmarkDialClient(b *testing.B) {
rand.Seed(time.Now().UnixNano())
var header = &liverpc.Header{
Platform: "ios",
Src: "test",
Buvid: "AUTO3315341311353015",
TraceId: "18abb1a2596c43ea:18abb1a2596c43ea:0:0",
Uid: 10,
Caller: "live-api.rpc",
UserIp: "127.0.0.1",
SourceGroup: "default",
}
var ctx = context.TODO()
for i := 0; i < b.N; i++ { //use b.N for looping
id := rand.Intn(10000)
arg := &v1.RoomGetInfoReq{Id: int64(id)}
cli := liverpc.NewClient(&liverpc.ClientConfig{AppID: "live.room"})
var rpcClient = v1.NewRoomRPCClient(cli)
_, err := rpcClient.GetInfo(ctx, arg, liverpc.HeaderOption{Header: header})
if err != nil {
b.Errorf("%s %d", err, i)
b.FailNow()
}
}
}

37
library/net/rpc/liverpc/testdata/BUILD vendored Normal file
View File

@@ -0,0 +1,37 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = ["client.go"],
importpath = "go-common/library/net/rpc/liverpc/testdata",
tags = ["manual"],
visibility = ["//visibility:public"],
deps = [
"//library/net/rpc/liverpc:go_default_library",
"//library/net/rpc/liverpc/testdata/v1:go_default_library",
"//library/net/rpc/liverpc/testdata/v2:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//library/net/rpc/liverpc/testdata/v1:all-srcs",
"//library/net/rpc/liverpc/testdata/v2:all-srcs",
],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,46 @@
// Code generated by liverpcgen, DO NOT EDIT.
// source: *.proto files under this directory
// If you want to change this file, Please see README in go-common/app/tool/liverpc/protoc-gen-liverpc/
package testdata
import (
"go-common/library/net/rpc/liverpc"
"go-common/library/net/rpc/liverpc/testdata/v1"
"go-common/library/net/rpc/liverpc/testdata/v2"
)
// Client that represents a liverpc room service api
type Client struct {
cli *liverpc.Client
// V1Room presents the controller in liverpc
V1Room v1.RoomRPCClient
// V2Room presents the controller in liverpc
V2Room v2.RoomRPCClient
}
// DiscoveryAppId the discovery id is not the tree name
var DiscoveryAppId = "live.room"
// New a Client that represents a liverpc live.room service api
// conf can be empty, and it will use discovery to find service by default
// conf.AppID will be overwrite by a fixed value DiscoveryAppId
// therefore is no need to set
func New(conf *liverpc.ClientConfig) *Client {
if conf == nil {
conf = &liverpc.ClientConfig{}
}
conf.AppID = DiscoveryAppId
var realCli = liverpc.NewClient(conf)
cli := &Client{cli: realCli}
cli.clientInit(realCli)
return cli
}
func (cli *Client) GetRawCli() *liverpc.Client {
return cli.cli
}
func (cli *Client) clientInit(realCli *liverpc.Client) {
cli.V1Room = v1.NewRoomRPCClient(realCli)
cli.V2Room = v2.NewRoomRPCClient(realCli)
}

View File

@@ -0,0 +1,53 @@
load(
"@io_bazel_rules_go//proto:def.bzl",
"go_proto_library",
)
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
proto_library(
name = "v1_proto",
srcs = ["Room.proto"],
tags = ["automanaged"],
)
go_proto_library(
name = "v1_go_proto",
compilers = ["@io_bazel_rules_go//proto:go_grpc"],
importpath = "go-common/library/net/rpc/liverpc/testdata/v1",
proto = ":v1_proto",
tags = ["automanaged"],
)
go_library(
name = "go_default_library",
srcs = ["Room.liverpc.go"],
embed = [":v1_go_proto"],
importpath = "go-common/library/net/rpc/liverpc/testdata/v1",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//library/net/rpc/liverpc:go_default_library",
"@com_github_gogo_protobuf//proto:go_default_library",
"@com_github_golang_protobuf//proto:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,73 @@
// Code generated by protoc-gen-liverpc v0.1, DO NOT EDIT.
// source: v1/Room.proto
/*
Package v1 is a generated liverpc stub package.
This code was generated with go-common/app/tool/liverpc/protoc-gen-liverpc v0.1.
It is generated from these files:
v1/Room.proto
*/
package v1
import context "context"
import proto "github.com/golang/protobuf/proto"
import "go-common/library/net/rpc/liverpc"
var _ proto.Message // generate to suppress unused imports
// Imports only used by utility functions:
// ==============
// Room Interface
// ==============
type RoomRPCClient interface {
// * 根据房间id获取房间信息
GetInfoById(ctx context.Context, req *RoomGetInfoByIdReq, opts ...liverpc.CallOption) (resp *RoomGetInfoByIdResp, err error)
// * 获取房间基本信息接口,前端/移动端房间页使用
GetInfo(ctx context.Context, req *RoomGetInfoReq, opts ...liverpc.CallOption) (resp *RoomGetInfoResp, err error)
}
// ====================
// Room Live Rpc Client
// ====================
type roomRPCClient struct {
client *liverpc.Client
}
// NewRoomRPCClient creates a client that implements the RoomRPCClient interface.
func NewRoomRPCClient(client *liverpc.Client) RoomRPCClient {
return &roomRPCClient{
client: client,
}
}
func (c *roomRPCClient) GetInfoById(ctx context.Context, in *RoomGetInfoByIdReq, opts ...liverpc.CallOption) (*RoomGetInfoByIdResp, error) {
out := new(RoomGetInfoByIdResp)
err := doRPCRequest(ctx, c.client, 1, "Room.get_info_by_id", in, out, opts)
if err != nil {
return nil, err
}
return out, nil
}
func (c *roomRPCClient) GetInfo(ctx context.Context, in *RoomGetInfoReq, opts ...liverpc.CallOption) (*RoomGetInfoResp, error) {
out := new(RoomGetInfoResp)
err := doRPCRequest(ctx, c.client, 1, "Room.get_info", in, out, opts)
if err != nil {
return nil, err
}
return out, nil
}
// =====
// Utils
// =====
func doRPCRequest(ctx context.Context, client *liverpc.Client, version int, method string, in, out proto.Message, opts []liverpc.CallOption) (err error) {
err = client.Call(ctx, version, method, in, out, opts...)
return
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,119 @@
syntax = "proto3";
package room.v1;
option go_package = "v1";
service Room {
/** 根据房间id获取房间信息
*
*/
rpc get_info_by_id (RoomGetInfoByIdReq) returns (RoomGetInfoByIdResp);
/** 获取房间基本信息接口,前端/移动端房间页使用
*
*/
rpc get_info (RoomGetInfoReq) returns (RoomGetInfoResp);
}
message RoomGetInfoByIdReq {
repeated int64 ids = 1; // 房间id, 可以为短号
repeated string fields = 2; // 需要哪些字段, 不传默认所有
}
message RoomGetInfoByIdResp {
int64 code = 1; // code
string msg = 2; // msg
map<string, RoomInfo> data = 3; // 房间信息map
message RoomInfo {
int64 roomid = 1; // 房间id
string uname = 2; // 用户名, 不可靠.
string cover = 3; // 封面
int64 uid = 4; // 用户id
string live_time = 5; // 开播时间
int64 round_status = 6; // 轮播状态
int64 on_flag = 7; // 是否开播
string title = 8; // 直播间标题
string lock_status = 9; // 锁定到时间
string hidden_status = 10; // 隐藏到时间
string user_cover = 11; // 也是封面...
int64 short_id = 12; // 短号
int64 online = 13; // 在线人数
int64 area = 14; // 分区id
int64 area_v2_id = 15; // 分区v2 id
int64 area_v2_parent_id = 16; // 分区v2 父分区id
string area_v2_name = 17; // 分区v2名字 fields加了该字段才会给
string area_v2_parent_name = 18; // 分区v2父分区名字 fields加了该字段才会给
int64 attentions = 19; // 关注人数
}
}
message RoomGetInfoReq {
int64 id = 1; // 房间号或者短号
string from = 2; // 来源 房间页room link中心 link_center
}
message RoomGetInfoResp {
int64 code = 1; // code
string msg = 2; // msg
Data data = 3; //
message PendantWithDesc {
string name = 1; // 名字、标识
int64 position = 2; // 位置0无1左上2右上3右下4左下
string value = 3; // name对应的value可以是挂件的展示名字/对应的图片地址
string desc = 4; // 描述
}
message Pendant {
string name = 1; // 名字、标识
int64 position = 2; // 位置0无1左上2右上3右下4左下
string value = 3; // name对应的value可以是挂件的展示名字/对应的图片地址
}
message Pendants {
PendantWithDesc frame = 1; // web端房间页头像边框
PendantWithDesc badge = 2; // web端房间页头像角标
Pendant mobile_frame = 3; // 移动端房间页头像边框
Pendant mobile_badge = 4; // 移动端房间页头像角标
}
message Data {
int64 uid = 1; //
int64 room_id = 2; // 房间id
int64 short_id = 3; // 短号
string keyframe = 4; // 关键帧
int64 online = 5; // 在线人数
bool is_portrait = 6; // true为竖屏
string room_silent_type = 7; // 禁言类型 member medal level 或者空字符串
int64 room_silent_second = 8; // 剩余禁言时间,秒数
int64 room_silent_level = 9; // 禁言等级
int64 live_status = 10; // 闲置中0 直播中1 轮播中2
int64 area_id = 11; // 分区id
int64 parent_area_id = 12; // 父分区id
string area_name = 13; // 分区名字
string parent_area_name = 14; // 父分区名字
int64 old_area_id = 15; // 老分区id
string background = 16; // 背景url
string title = 17; // 房间标题
bool is_strict_room = 18; // 是否是限制房间 如果是 应该不连接弹幕 不展示礼物等等
string user_cover = 19; // 房间封面
string live_time = 20; // 开播时间
string pendants = 21; // 挂件列表
string area_pendants = 22; // 分区第一标志, 待定.
string description = 23; // 房间简介.
string tags = 24; // 房间标签, 逗号分隔字符串
string verify = 25; // 认证信息 没认证为空字符串
repeated string hot_words = 26; // 弹幕热词
int64 allow_change_area_time = 27; // 允许修改分区时间戳(主播可能被审核禁止修改分区),0表示没有限制 主播才有此字段
int64 allow_upload_cover_time = 28; // 允许上传封面的时间 0表示没有限制 主播才有此字段gs
Pendants new_pendants = 29; // 新挂件
string up_session = 30; // 一次开播标记
int64 pk_status = 31; // 0为该房间不处于pk中 1表示处于pk中(需调用pk基础信息接口获取pk信息)
}
}

View File

@@ -0,0 +1,53 @@
load(
"@io_bazel_rules_go//proto:def.bzl",
"go_proto_library",
)
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
proto_library(
name = "v2_proto",
srcs = ["Room.proto"],
tags = ["automanaged"],
)
go_proto_library(
name = "v2_go_proto",
compilers = ["@io_bazel_rules_go//proto:go_grpc"],
importpath = "go-common/library/net/rpc/liverpc/testdata/v2",
proto = ":v2_proto",
tags = ["automanaged"],
)
go_library(
name = "go_default_library",
srcs = ["Room.liverpc.go"],
embed = [":v2_go_proto"],
importpath = "go-common/library/net/rpc/liverpc/testdata/v2",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//library/net/rpc/liverpc:go_default_library",
"@com_github_gogo_protobuf//proto:go_default_library",
"@com_github_golang_protobuf//proto:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,62 @@
// Code generated by protoc-gen-liverpc v0.1, DO NOT EDIT.
// source: v2/Room.proto
/*
Package v2 is a generated liverpc stub package.
This code was generated with go-common/app/tool/liverpc/protoc-gen-liverpc v0.1.
It is generated from these files:
v2/Room.proto
*/
package v2
import context "context"
import proto "github.com/golang/protobuf/proto"
import "go-common/library/net/rpc/liverpc"
var _ proto.Message // generate to suppress unused imports
// Imports only used by utility functions:
// ==============
// Room Interface
// ==============
type RoomRPCClient interface {
// * 根据房间id获取房间信息v2
// 修正原来的get_info_by_id 在传了fields字段但是不包含roomid的情况下 依然会返回所有字段, 新版修正这个问题, 只会返回指定的字段.
GetByIds(ctx context.Context, req *RoomGetByIdsReq, opts ...liverpc.CallOption) (resp *RoomGetByIdsResp, err error)
}
// ====================
// Room Live Rpc Client
// ====================
type roomRPCClient struct {
client *liverpc.Client
}
// NewRoomRPCClient creates a client that implements the RoomRPCClient interface.
func NewRoomRPCClient(client *liverpc.Client) RoomRPCClient {
return &roomRPCClient{
client: client,
}
}
func (c *roomRPCClient) GetByIds(ctx context.Context, in *RoomGetByIdsReq, opts ...liverpc.CallOption) (*RoomGetByIdsResp, error) {
out := new(RoomGetByIdsResp)
err := doRPCRequest(ctx, c.client, 2, "Room.get_by_ids", in, out, opts)
if err != nil {
return nil, err
}
return out, nil
}
// =====
// Utils
// =====
func doRPCRequest(ctx context.Context, client *liverpc.Client, version int, method string, in, out proto.Message, opts []liverpc.CallOption) (err error) {
err = client.Call(ctx, version, method, in, out, opts...)
return
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,61 @@
syntax = "proto3";
package room.v2;
option go_package = "v2";
service Room {
/** 根据房间id获取房间信息v2
* 修正原来的get_info_by_id 在传了fields字段但是不包含roomid的情况下 依然会返回所有字段, 新版修正这个问题, 只会返回指定的字段.
*/
rpc get_by_ids (RoomGetByIdsReq) returns (RoomGetByIdsResp);
}
message RoomGetByIdsReq {
repeated int64 ids = 1; // 房间id, 尽可能传长号支持短号eg.短号1->长号40000则返回的房间信息map key是40000
int64 need_uinfo = 2; // 是否需要附加uname、face字段need_uinfo=1 尽量别传,传了请和@小卫报备!!
int64 need_broadcast_type = 3; // 是否需要broadcast_type字段need_broadcast_type=1
repeated string fields = 4; // 需要哪些字段, 不传默认给大多数字段
string from = 5; // 调用方来源英文格式约定部门_服务_业务 eg.live_activity_spring
}
message RoomGetByIdsResp {
int64 code = 1; // code
string msg = 2; // msg
map<string, RoomInfo> data = 3; // 房间信息map
message RoomInfo {
int64 roomid = 1; // 房间id
string uname = 2; // 用户名
string face = 3; // 用户头像
string verify = 4; // 加v认证信息
string cover = 5; // 关键帧 注need_uinfo=1时该字段优先表示封面图
int64 uid = 6; // 用户id
string live_time = 7; // 开播时间
int64 round_status = 8; // 轮播投递状态 1开启 0关闭
int64 on_flag = 9; // 轮播开启状态 1开启 0关闭
string title = 10; // 直播间标题
string tags = 11; // 直播间标签
string lock_status = 12; // 锁定到时间
string hidden_status = 13; // 隐藏到时间
string user_cover = 14; // 封面
int64 short_id = 15; // 短号
int64 online = 16; // 在线人数
int64 area = 17; // 分区id
int64 area_v2_id = 18; // 分区v2 id
int64 area_v2_parent_id = 19; // 分区v2 父分区id
int64 area_pk_status = 20; // 分区是否开放pk 0关闭 1开放
string area_v2_name = 21; // 分区v2名字
string area_v2_parent_name = 22; // 分区v2父分区名字
int64 attentions = 23; // 关注人数
string background = 24; // 房间背景图
int64 room_silent = 25; // 是否静默 0否,1注册会员,2全部
int64 room_shield = 26; // 是否使用房主的屏蔽用户作为房间全局屏蔽用户0不使用1使用
string try_time = 27; // 试用直播间到期时间
int64 live_status = 28; // 直播间状态 0关播 1直播中 2轮播中
int64 broadcast_type = 29; // 横竖屏只有传了need_broadcast_type才会返回 0横屏 1竖屏 -1异常情况
}
}

669
library/net/rpc/server.go Normal file
View File

@@ -0,0 +1,669 @@
package rpc
import (
"bufio"
ctx "context"
"encoding/gob"
"errors"
"flag"
"fmt"
"io"
"log"
"net"
"os"
"reflect"
"runtime"
"strings"
"sync"
"time"
"unicode"
"unicode/utf8"
"go-common/library/conf/dsn"
"go-common/library/conf/env"
xlog "go-common/library/log"
"go-common/library/net/metadata"
"go-common/library/net/rpc/context"
"go-common/library/net/rpc/interceptor"
"go-common/library/net/trace"
pkgerr "github.com/pkg/errors"
)
var (
_gorpcDSN string
)
func init() {
addFlag(flag.CommandLine)
}
func addFlag(fs *flag.FlagSet) {
v := os.Getenv("GORPC")
if v == "" {
if env.GORPCPort != "" {
v = "tcp://0.0.0.0:" + env.GORPCPort
} else {
v = "tcp://0.0.0.0:8099"
}
}
fs.StringVar(&_gorpcDSN, "gorpc", v, "listen go rpc dsn, or use GORPC env variable.")
}
func parseDSN(rawdsn string) *ServerConfig {
conf := new(ServerConfig)
d, err := dsn.Parse(rawdsn)
if err != nil {
panic(pkgerr.WithMessage(err, "net/rpc: invalid dsn"))
}
if _, err = d.Bind(conf); err != nil {
panic(pkgerr.WithMessage(err, "net/rpc: invalid dsn"))
}
return conf
}
// ServerConfig rpc server settings.
type ServerConfig struct {
Proto string `dsn:"network"`
Addr string `dsn:"address"`
}
// NewServer new a rpc server.
func NewServer(c *ServerConfig) *Server {
if c == nil {
if !flag.Parsed() {
fmt.Fprint(os.Stderr, "[net/rpc] please call flag.Parse() before Init go rpc server, some configure may not effect.\n")
}
c = parseDSN(_gorpcDSN)
} else {
fmt.Fprintf(os.Stderr, "[net/rpc] config will be deprecated, argument will be ignored. please use -gorpc flag or GORPC env to configure go rpc server.\n")
}
s := newServer()
s.Interceptor = interceptor.NewInterceptor("")
go rpcListen(c, s)
return s
}
// rpcListen start rpc listen.
func rpcListen(c *ServerConfig, s *Server) {
l, err := net.Listen(c.Proto, c.Addr)
if err != nil {
xlog.Error("net.Listen(rpcAddr:(%v)) error(%v)", c.Addr, err)
panic(err)
}
// if process exit, then close the rpc bind
defer func() {
xlog.Info("rpc addr:(%s) close", c.Addr)
if err := l.Close(); err != nil {
xlog.Error("listener.Close() error(%v)", err)
}
}()
xlog.Info("start rpc listen addr: %s", c.Addr)
s.Accept(l)
}
var (
// Precompute the reflect type for error. Can't use error directly
// because Typeof takes an empty interface value. This is annoying.
typeOfError = reflect.TypeOf((*error)(nil)).Elem()
ctxType = reflect.TypeOf((*context.Context)(nil)).Elem()
)
// methodType 方法类型
type methodType struct {
method reflect.Method //方法
ArgType reflect.Type //参数类型
ReplyType reflect.Type //回复类型
}
// service 服务
type service struct {
name string // name of service
rcvr reflect.Value // receiver of methods for the service
typ reflect.Type // type of the receiver
method map[string]*methodType // registered methods 注册的方法
}
// Request is a header written before every RPC call. It is used internally
// but documented here as an aid to debugging, such as when analyzing
// network traffic.
type Request struct {
Color string // color
RemoteIP string // remoteIP
Timeout time.Duration // timeout
ServiceMethod string // format: "Service.Method"
Seq uint64 // sequence number chosen by client
Trace TraceInfo // trace info
ctx context.Context
}
// Auth handshake struct.
type Auth struct {
User string
}
// Response is a header written before every RPC return. It is used internally
// but documented here as an aid to debugging, such as when analyzing
// network traffic.
type Response struct {
ServiceMethod string // echoes that of the Request
Seq uint64 // echoes that of the request
Error string // error, if any.
}
// Interceptor interface.
type Interceptor interface {
Rate(context.Context) error
Stat(context.Context, interface{}, error)
Auth(context.Context, net.Addr, string) error // ip, token
}
// Server represents an RPC Server.
type Server struct {
lis net.Listener
serviceMap map[string]*service
Interceptor Interceptor //拦截器
}
// newServer returns a new Server.
func newServer() *Server {
return &Server{serviceMap: make(map[string]*service)}
}
// DefaultServer is the default instance of *Server.
var DefaultServer = newServer()
// Is this an exported - upper case - name?
func isExported(name string) bool {
rune, _ := utf8.DecodeRuneInString(name)
return unicode.IsUpper(rune)
}
// Is this type exported or a builtin?
func isExportedOrBuiltinType(t reflect.Type) bool {
for t.Kind() == reflect.Ptr {
t = t.Elem()
}
// PkgPath will be non-empty even for an exported type,
// so we need to check the type name as well.
return isExported(t.Name()) || t.PkgPath() == ""
}
// Register publishes in the server the set of methods of the
// receiver value that satisfy the following conditions:
// - exported method of exported type
// - two arguments, both of exported type
// - the second argument is a pointer
// - one return value, of type error
// It returns an error if the receiver is not an exported type or has
// no suitable methods. It also logs the error using package log.
// The client accesses each method using a string of the form "Type.Method",
// where Type is the receiver's concrete type.
func (server *Server) Register(rcvr interface{}) (err error) {
if err = server.register(rcvr, "", false); err != nil {
return
}
return server.register(new(pinger), _service, true)
}
// RegisterName is like Register but uses the provided name for the type
// instead of the receiver's concrete type.
func (server *Server) RegisterName(name string, rcvr interface{}) (err error) {
if err = server.register(rcvr, name, true); err != nil {
return
}
return server.register(new(pinger), _service, true)
}
func (server *Server) register(rcvr interface{}, name string, useName bool) error {
if server.serviceMap == nil {
server.serviceMap = make(map[string]*service)
}
s := new(service)
s.typ = reflect.TypeOf(rcvr)
s.rcvr = reflect.ValueOf(rcvr)
sname := reflect.Indirect(s.rcvr).Type().Name()
if useName {
sname = name
}
if sname == "" {
s := "rpc.Register: no service name for type " + s.typ.String()
log.Print(s)
return errors.New(s)
}
if !isExported(sname) && !useName {
s := "rpc.Register: type " + sname + " is not exported"
log.Print(s)
return errors.New(s)
}
if _, present := server.serviceMap[sname]; present {
return errors.New("rpc: service already defined: " + sname)
}
s.name = sname
// Install the methods
s.method = suitableMethods(s.typ, true)
if len(s.method) == 0 {
str := ""
// To help the user, see if a pointer receiver would work.
method := suitableMethods(reflect.PtrTo(s.typ), false)
if len(method) != 0 {
str = "rpc.Register: type " + sname + " has no exported methods of suitable type (hint: pass a pointer to value of that type)"
} else {
str = "rpc.Register: type " + sname + " has no exported methods of suitable type"
}
log.Print(str)
return errors.New(str)
}
server.serviceMap[s.name] = s
return nil
}
// suitableMethods returns suitable Rpc methods of typ, it will report
// error using log if reportErr is true.
func suitableMethods(typ reflect.Type, reportErr bool) map[string]*methodType {
methods := make(map[string]*methodType)
for m := 0; m < typ.NumMethod(); m++ {
method := typ.Method(m)
mtype := method.Type
mname := method.Name
// Method must be exported.
if method.PkgPath != "" {
continue
}
// Method needs ins: receiver, context, *arg, *reply.
if mtype.NumIn() != 4 {
if reportErr {
log.Println("method", mname, "has wrong number of ins:", mtype.NumIn())
}
continue
}
// First arg need not be a pointer.
argType := mtype.In(1)
if !argType.Implements(ctxType) {
if reportErr {
log.Println(mname, "argument type must implements:", ctxType)
}
continue
}
// Second arg need not be a pointer.
argType = mtype.In(2)
if !isExportedOrBuiltinType(argType) {
if reportErr {
log.Println(mname, "argument type not exported:", argType)
}
continue
}
// Thrid arg must be a pointer.
replyType := mtype.In(3)
if replyType.Kind() != reflect.Ptr {
if reportErr {
log.Println("method", mname, "reply type not a pointer:", replyType)
}
continue
}
// Reply type must be exported.
if !isExportedOrBuiltinType(replyType) {
if reportErr {
log.Println("method", mname, "reply type not exported:", replyType)
}
continue
}
// Method needs one out.
if mtype.NumOut() != 1 {
if reportErr {
log.Println("method", mname, "has wrong number of outs:", mtype.NumOut())
}
continue
}
// The return type of the method must be error.
if returnType := mtype.Out(0); returnType != typeOfError {
if reportErr {
log.Println("method", mname, "returns", returnType.String(), "not error")
}
continue
}
methods[mname] = &methodType{method: method, ArgType: argType, ReplyType: replyType}
}
return methods
}
// A value sent as a placeholder for the server's response value when the server
// receives an invalid request. It is never decoded by the client since the Response
// contains an error when it is used.
var invalidRequest = struct{}{}
func (server *Server) sendResponse(c context.Context, codec *serverCodec, reply interface{}, errmsg string) {
var (
err error
ts Response
resp = &codec.resp
)
if errmsg != "" {
reply = invalidRequest
}
ts.ServiceMethod = c.ServiceMethod()
ts.Seq = c.Seq()
ts.Error = errmsg
codec.sending.Lock()
// NOTE must keep resp goroutine safe
*resp = ts
// Encode the response header
if err = codec.writeResponse(reply); err != nil {
log.Println("rpc: writing response:", err)
}
codec.sending.Unlock()
}
func (s *service) call(c context.Context, server *Server, mtype *methodType, argv, replyv reflect.Value, codec *serverCodec) {
var (
err error
errmsg string
errInter interface{}
cv reflect.Value
returnValues []reflect.Value
)
t, _ := trace.FromContext(c)
defer func() {
if err1 := recover(); err1 != nil {
err = err1.(error)
errmsg = err.Error()
const size = 64 << 10
buf := make([]byte, size)
buf = buf[:runtime.Stack(buf, false)]
xlog.Error("rpc call panic: %v \n%s", err1, buf)
server.sendResponse(c, codec, replyv.Interface(), errmsg)
if server.Interceptor != nil {
server.Interceptor.Stat(c, argv.Interface(), err)
}
if t != nil {
t.Finish(&err)
}
}
}()
// rate limit
if server.Interceptor != nil {
if err = server.Interceptor.Rate(c); err != nil {
errmsg = err.Error()
}
}
if err == nil {
// Invoke the method, providing a new value for the reply.
cv = reflect.New(ctxType)
*cv.Interface().(*context.Context) = c
returnValues = mtype.method.Func.Call([]reflect.Value{s.rcvr, cv.Elem(), argv, replyv})
// The return value for the method is an error.
if errInter = returnValues[0].Interface(); errInter != nil {
err = errInter.(error)
errmsg = pkgerr.Cause(err).Error()
}
}
server.sendResponse(c, codec, replyv.Interface(), errmsg)
// stat
if server.Interceptor != nil {
server.Interceptor.Stat(c, argv.Interface(), err)
}
if t != nil {
t.Finish(&err)
}
}
type serverCodec struct {
sending sync.Mutex
resp Response
req Request
auth Auth
rwc io.ReadWriteCloser
dec *gob.Decoder
enc *gob.Encoder
encBuf *bufio.Writer
addr net.Addr
closed bool
}
func (c *serverCodec) readRequestHeader() error {
return pkgerr.WithStack(c.dec.Decode(&c.req))
}
func (c *serverCodec) readRequestBody(body interface{}) error {
return pkgerr.WithStack(c.dec.Decode(body))
}
func (c *serverCodec) writeResponse(body interface{}) (err error) {
if err = c.enc.Encode(&c.resp); err != nil {
err = pkgerr.WithStack(err)
if c.encBuf.Flush() == nil {
// Gob couldn't encode the header. Should not happen, so if it does,
// shut down the connection to signal that the connection is broken.
log.Println("rpc: gob error encoding response:", err)
c.close()
}
return
}
if err = c.enc.Encode(body); err != nil {
err = pkgerr.WithStack(err)
if c.encBuf.Flush() == nil {
// Was a gob problem encoding the body but the header has been written.
// Shut down the connection to signal that the connection is broken.
log.Println("rpc: gob error encoding body:", err)
c.close()
}
return
}
return pkgerr.WithStack(c.encBuf.Flush())
}
func (c *serverCodec) close() error {
if c.closed {
// Only call c.rwc.Close once; otherwise the semantics are undefined.
return nil
}
c.closed = true
return c.rwc.Close()
}
// ServeConn runs the server on a single connection.
// ServeConn blocks, serving the connection until the client hangs up.
// The caller typically invokes ServeConn in a go statement.
// ServeConn uses the gob wire format (see package gob) on the
// connection. To use an alternate codec, use ServeCodec.
func (server *Server) ServeConn(conn net.Conn) {
buf := bufio.NewWriter(conn)
srv := &serverCodec{
rwc: conn,
dec: gob.NewDecoder(conn),
enc: gob.NewEncoder(buf),
encBuf: buf,
addr: conn.RemoteAddr(),
}
server.serveCodec(srv)
}
// serveCodec is like ServeConn but uses the specified codec to
// decode requests and encode responses.
func (server *Server) serveCodec(codec *serverCodec) {
req := &codec.req
for {
// serve request
service, mtype, argv, replyv, err := server.readRequest(codec)
if err != nil {
if err != io.EOF {
log.Println("rpc:", err)
}
if req.ctx == nil {
break
}
errmsg := err.Error()
if req.ServiceMethod == _authServiceMethod {
errmsg = ""
}
server.sendResponse(req.ctx, codec, invalidRequest, errmsg)
continue
}
if req.ServiceMethod == _authServiceMethod {
codec.auth = *(argv.Interface().(*Auth))
req.ctx = context.NewContext(ctx.Background(), req.ServiceMethod, codec.auth.User, req.Seq)
server.sendResponse(req.ctx, codec, invalidRequest, "")
continue
}
go service.call(req.ctx, server, mtype, argv, replyv, codec)
}
codec.close()
}
func (server *Server) readRequest(codec *serverCodec) (service *service, mtype *methodType, argv, replyv reflect.Value, err error) {
var req = &codec.req
*req = Request{}
if service, mtype, err = server.readRequestHeader(codec); err != nil {
// keepreading
if req.ctx == nil {
return
}
// discard body
codec.readRequestBody(nil)
return
}
// Decode the argument value.
argIsValue := false // if true, need to indirect before calling.
if mtype.ArgType.Kind() == reflect.Ptr {
argv = reflect.New(mtype.ArgType.Elem())
} else {
argv = reflect.New(mtype.ArgType)
argIsValue = true
}
// argv guaranteed to be a pointer now.
if err = codec.readRequestBody(argv.Interface()); err != nil {
return
}
if argIsValue {
argv = argv.Elem()
}
replyv = reflect.New(mtype.ReplyType.Elem())
return
}
func (server *Server) readRequestHeader(codec *serverCodec) (service *service, mtype *methodType, err error) {
var t trace.Trace
req := &codec.req
if err = codec.readRequestHeader(); err != nil {
return
}
if t, _ = trace.Extract(nil, &req.Trace); t == nil {
t = trace.New(req.ServiceMethod)
}
t.SetTitle(req.ServiceMethod)
t.SetTag(trace.String(trace.TagAddress, codec.addr.String()))
md := metadata.MD{
metadata.Trace: t,
metadata.Color: req.Color,
metadata.RemoteIP: req.RemoteIP,
metadata.Caller: req.Trace.Caller,
}
// FIXME(maojian) Timeout?
c1 := metadata.NewContext(ctx.Background(), md)
caller := codec.auth.User
if caller == "" {
caller = req.Trace.Caller
}
// NOTE ctx not nil then keepreading
req.ctx = context.NewContext(c1, req.ServiceMethod, caller, req.Seq)
// We read the header successfully. If we see an error now,
// we can still recover and move on to the next request.
dot := strings.LastIndex(req.ServiceMethod, ".")
if dot < 0 {
err = errors.New("rpc: service/method request ill-formed: " + req.ServiceMethod)
return
}
serviceName := req.ServiceMethod[:dot]
methodName := req.ServiceMethod[dot+1:]
// Look up the request.
service = server.serviceMap[serviceName]
if service == nil {
err = errors.New("rpc: can't find service " + req.ServiceMethod)
return
}
mtype = service.method[methodName]
if mtype == nil {
err = errors.New("rpc: can't find method " + req.ServiceMethod)
}
return
}
// Accept accepts connections on the listener and serves requests
// for each incoming connection. Accept blocks until the listener
// returns a non-nil error. The caller typically invokes Accept in a
// go statement.
func (server *Server) Accept(lis net.Listener) {
for {
conn, err := lis.Accept()
if err != nil {
log.Print("rpc.Serve: accept:", err.Error())
return
}
go server.ServeConn(conn)
}
}
// Close stop the rpc server.
func (server *Server) Close() error {
if server.lis != nil {
return server.lis.Close()
}
return nil
}
// Register publishes the receiver's methods in the DefaultServer.
func Register(rcvr interface{}) error { return DefaultServer.Register(rcvr) }
// RegisterName is like Register but uses the provided name for the type
// instead of the receiver's concrete type.
func RegisterName(name string, rcvr interface{}) error {
return DefaultServer.RegisterName(name, rcvr)
}
// ServeConn runs the DefaultServer on a single connection.
// ServeConn blocks, serving the connection until the client hangs up.
// The caller typically invokes ServeConn in a go statement.
// ServeConn uses the gob wire format (see package gob) on the
// connection. To use an alternate codec, use ServeCodec.
func ServeConn(conn net.Conn) {
DefaultServer.ServeConn(conn)
}
// Accept accepts connections on the listener and serves requests
// to DefaultServer for each incoming connection.
// Accept blocks; the caller typically invokes it in a go statement.
func Accept(lis net.Listener) { DefaultServer.Accept(lis) }
const (
_authServiceMethod = "inner.Auth"
_pingServiceMethod = "inner.Ping"
_service = "inner"
)
var (
_pingArg = &struct{}{}
)
// pinger rpc ping service
type pinger struct {
}
// Ping rpc ping.
func (p *pinger) Ping(c context.Context, arg *struct{}, reply *struct{}) error {
return nil
}
// Auth
func (p *pinger) Auth(c context.Context, arg *Auth, reply *struct{}) error {
return nil
}

View File

@@ -0,0 +1,613 @@
package rpc
import (
"errors"
"fmt"
"log"
"net"
"runtime"
"strings"
"sync"
"sync/atomic"
"testing"
"time"
"go-common/library/net/rpc/context"
)
var (
newSvr, newInterceptorServer *Server
serverAddr, newServerAddr, newServerInterceptorAddr string
once, newOnce, newInterceptorOnce sync.Once
testInterceptor *TestInterceptor
statCount uint64
)
const (
_testToken = "test_token"
)
type TestInterceptor struct {
Token, RateMethod string
}
type Args struct {
A, B int
}
type Reply struct {
C int
}
type Arith int
// Some of Arith's methods have value args, some have pointer args. That's deliberate.
func (t *TestInterceptor) Rate(c context.Context) error {
log.Printf("Interceptor rate method: %s, current: %s", t.RateMethod, c.ServiceMethod())
if t.RateMethod == c.ServiceMethod() {
return fmt.Errorf("Interceptor rate method: %s, time: %s", c.ServiceMethod(), c.Now())
}
return nil
}
func (t *TestInterceptor) Auth(c context.Context, addr net.Addr, token string) error {
if t.Token != token {
return fmt.Errorf("Interceptor auth token: %s, ip: %s seq: %d failed", token, addr, c.Seq())
}
log.Printf("Interceptor auth token: %s, ip: %s, seq: %d ok", token, addr, c.Seq())
return nil
}
func (t *TestInterceptor) Stat(c context.Context, args interface{}, err error) {
atomic.AddUint64(&statCount, 1)
}
func (t *Arith) Auth(c context.Context, args Auth, reply *Reply) error {
return nil
}
func (t *Arith) Add(c context.Context, args Args, reply *Reply) error {
reply.C = args.A + args.B
return nil
}
func (t *Arith) Mul(c context.Context, args *Args, reply *Reply) error {
reply.C = args.A * args.B
return nil
}
func (t *Arith) Div(c context.Context, args Args, reply *Reply) error {
if args.B == 0 {
return errors.New("divide by zero")
}
reply.C = args.A / args.B
return nil
}
func (t *Arith) String(c context.Context, args *Args, reply *string) error {
*reply = fmt.Sprintf("%d+%d=%d", args.A, args.B, args.A+args.B)
return nil
}
func (t *Arith) Scan(c context.Context, args string, reply *Reply) (err error) {
_, err = fmt.Sscan(args, &reply.C)
return
}
func (t *Arith) Error(c context.Context, args *Args, reply *Reply) error {
panic("ERROR")
}
type hidden int
func (t *hidden) Exported(c context.Context, args Args, reply *Reply) error {
reply.C = args.A + args.B
return nil
}
type Embed struct {
hidden
}
// NOTE listen and start the server
func listenTCP() (net.Listener, string) {
l, e := net.Listen("tcp", "127.0.0.1:0") // any available address
if e != nil {
log.Fatalf("net.Listen tcp :0: %v", e)
}
return l, l.Addr().String()
}
func startServer() {
Register(new(Arith))
Register(new(Embed))
RegisterName("net.rpc.Arith", new(Arith))
var l net.Listener
l, serverAddr = listenTCP()
log.Println("Test RPC server listening on", serverAddr)
go Accept(l)
}
func startNewServer() {
newSvr = newServer()
newSvr.Register(new(Arith))
newSvr.Register(new(Embed))
newSvr.RegisterName("net.rpc.Arith", new(Arith))
newSvr.RegisterName("newServer.Arith", new(Arith))
var l net.Listener
l, newServerAddr = listenTCP()
log.Println("NewServer test RPC server listening on", newServerAddr)
go newSvr.Accept(l)
}
func startNewInterceptorServer() {
testInterceptor = &TestInterceptor{Token: _testToken}
newInterceptorServer = newServer()
newInterceptorServer.Register(new(Arith))
newInterceptorServer.Register(new(Embed))
newInterceptorServer.RegisterName("net.rpc.Arith", new(Arith))
newInterceptorServer.RegisterName("newServer.Arith", new(Arith))
newInterceptorServer.Interceptor = testInterceptor
var l net.Listener
l, newServerInterceptorAddr = listenTCP()
log.Println("NewInterceptorServer test RPC server listening on", newServerAddr)
go newInterceptorServer.Accept(l)
}
// NOTE test rpc call with check expected
func TestServerRPC(t *testing.T) {
once.Do(startServer)
newOnce.Do(startNewServer)
newInterceptorOnce.Do(startNewInterceptorServer)
testRPC(t, serverAddr)
testRPC(t, newServerAddr)
testNewServerRPC(t, newServerAddr)
testNewServerAuthRPC(t, newServerInterceptorAddr)
testNewInterceptorServerRPC(t, newServerInterceptorAddr)
testNewInterceptorServerRateRPC(t, newServerInterceptorAddr)
}
func testRPC(t *testing.T, addr string) {
client, err := dial("tcp", addr, time.Second)
if err != nil {
t.Fatal("dialing", err)
}
defer client.Close()
// Synchronous calls
args := &Args{7, 8}
reply := new(Reply)
err = client.Call("Arith.Add", args, reply)
if err != nil {
t.Errorf("Add: expected no error but got string %q", err.Error())
}
if reply.C != args.A+args.B {
t.Errorf("Add: expected %d got %d", reply.C, args.A+args.B)
}
// Methods exported from unexported embedded structs
args = &Args{7, 0}
reply = new(Reply)
err = client.Call("Embed.Exported", args, reply)
if err != nil {
t.Errorf("Add: expected no error but got string %q", err.Error())
}
if reply.C != args.A+args.B {
t.Errorf("Add: expected %d got %d", reply.C, args.A+args.B)
}
// Nonexistent method
args = &Args{7, 0}
reply = new(Reply)
err = client.Call("Arith.BadOperation", args, reply)
// expect an error
if err == nil {
t.Error("BadOperation: expected error")
} else if !strings.HasPrefix(err.Error(), "rpc: can't find method ") {
t.Errorf("BadOperation: expected can't find method error; got %q", err)
}
// Unknown service
args = &Args{7, 8}
reply = new(Reply)
err = client.Call("Arith.Unknown", args, reply)
if err == nil {
t.Error("expected error calling unknown service")
} else if !strings.Contains(err.Error(), "method") {
t.Error("expected error about method; got", err)
}
// Out of order.
args = &Args{7, 8}
mulReply := new(Reply)
mulCall := client.Go("Arith.Mul", args, mulReply, nil)
addReply := new(Reply)
addCall := client.Go("Arith.Add", args, addReply, nil)
addCall = <-addCall.Done
if addCall.Error != nil {
t.Errorf("Add: expected no error but got string %q", addCall.Error.Error())
}
if addReply.C != args.A+args.B {
t.Errorf("Add: expected %d got %d", addReply.C, args.A+args.B)
}
mulCall = <-mulCall.Done
if mulCall.Error != nil {
t.Errorf("Mul: expected no error but got string %q", mulCall.Error.Error())
}
if mulReply.C != args.A*args.B {
t.Errorf("Mul: expected %d got %d", mulReply.C, args.A*args.B)
}
// Error test
args = &Args{7, 0}
reply = new(Reply)
err = client.Call("Arith.Div", args, reply)
// expect an error: zero divide
if err == nil {
t.Error("Div: expected error")
} else if err.Error() != "divide by zero" {
t.Error("Div: expected divide by zero error; got", err)
}
// Bad type.
reply = new(Reply)
err = client.Call("Arith.Add", reply, reply) // args, reply would be the correct thing to use
if err == nil {
t.Error("expected error calling Arith.Add with wrong arg type")
} else if !strings.Contains(err.Error(), "type") {
t.Error("expected error about type; got", err)
}
// Non-struct argument
const Val = 12345
str := fmt.Sprint(Val)
reply = new(Reply)
err = client.Call("Arith.Scan", &str, reply)
if err != nil {
t.Errorf("Scan: expected no error but got string %q", err.Error())
} else if reply.C != Val {
t.Errorf("Scan: expected %d got %d", Val, reply.C)
}
// Non-struct reply
args = &Args{27, 35}
str = ""
err = client.Call("Arith.String", args, &str)
if err != nil {
t.Errorf("String: expected no error but got string %q", err.Error())
}
expect := fmt.Sprintf("%d+%d=%d", args.A, args.B, args.A+args.B)
if str != expect {
t.Errorf("String: expected %s got %s", expect, str)
}
args = &Args{7, 8}
reply = new(Reply)
err = client.Call("Arith.Mul", args, reply)
if err != nil {
t.Errorf("Mul: expected no error but got string %q", err.Error())
}
if reply.C != args.A*args.B {
t.Errorf("Mul: expected %d got %d", reply.C, args.A*args.B)
}
// ServiceName contain "." character
args = &Args{7, 8}
reply = new(Reply)
err = client.Call("net.rpc.Arith.Add", args, reply)
if err != nil {
t.Errorf("Add: expected no error but got string %q", err.Error())
}
if reply.C != args.A+args.B {
t.Errorf("Add: expected %d got %d", reply.C, args.A+args.B)
}
}
func testNewServerRPC(t *testing.T, addr string) {
client, err := dial("tcp", addr, time.Second)
if err != nil {
t.Fatal("dialing", err)
}
defer client.Close()
// Synchronous calls
args := &Args{7, 8}
reply := new(Reply)
err = client.Call("newServer.Arith.Add", args, reply)
if err != nil {
t.Errorf("Add: expected no error but got string %q", err.Error())
}
if reply.C != args.A+args.B {
t.Errorf("Add: expected %d got %d", reply.C, args.A+args.B)
}
}
func testNewInterceptorServerRPC(t *testing.T, addr string) {
client, err := dial("tcp", addr, time.Second)
if err != nil {
t.Fatal("authing", err)
}
defer client.Close()
// Synchronous calls
args := &Args{7, 8}
reply := new(Reply)
err = client.Call("newServer.Arith.Add", args, reply)
if err != nil {
t.Errorf("Add: expected no error but got string %q", err.Error())
}
if reply.C != args.A+args.B {
t.Errorf("Add: expected %d got %d", reply.C, args.A+args.B)
}
}
func testNewInterceptorServerRateRPC(t *testing.T, addr string) {
client, err := dial("tcp", addr, time.Second)
if err != nil {
t.Fatal("authing", err)
}
defer client.Close()
// Synchronous calls
args := &Args{7, 8}
reply := new(Reply)
err = client.Call("Arith.Add", args, reply)
if err != nil {
t.Errorf("Add: expected no error but got string %q", err.Error())
}
if reply.C != args.A+args.B {
t.Errorf("Add: expected %d got %d", reply.C, args.A+args.B)
}
// check rate the method
testInterceptor.RateMethod = "Arith.Add"
args = &Args{7, 8}
reply = new(Reply)
err = client.Call("Arith.Add", args, reply)
if err == nil {
t.Errorf("Add: expected error this rate method")
}
}
func testNewServerAuthRPC(t *testing.T, addr string) {
_, err := dial("tcp", addr, time.Second)
if err != nil {
t.Errorf("Auth: expected error %q", err.Error())
}
}
// NOTE test no point structs for registration
type ReplyNotPointer int
type ArgNotPublic int
type ReplyNotPublic int
type NeedsPtrType int
type local struct{}
func (t *ReplyNotPointer) ReplyNotPointer(c context.Context, args *Args, reply Reply) error {
return nil
}
func (t *ArgNotPublic) ArgNotPublic(c context.Context, args *local, reply *Reply) error {
return nil
}
func (t *ReplyNotPublic) ReplyNotPublic(c context.Context, args *Args, reply *local) error {
return nil
}
func (t *NeedsPtrType) NeedsPtrType(c context.Context, args *Args, reply *Reply) error {
return nil
}
// Check that registration handles lots of bad methods and a type with no suitable methods.
func TestRegistrationError(t *testing.T) {
err := Register(new(ReplyNotPointer))
if err == nil {
t.Error("expected error registering ReplyNotPointer")
}
err = Register(new(ArgNotPublic))
if err == nil {
t.Error("expected error registering ArgNotPublic")
}
err = Register(new(ReplyNotPublic))
if err == nil {
t.Error("expected error registering ReplyNotPublic")
}
err = Register(NeedsPtrType(0))
if err == nil {
t.Error("expected error registering NeedsPtrType")
} else if !strings.Contains(err.Error(), "pointer") {
t.Error("expected hint when registering NeedsPtrType")
}
}
// NOTE test multiple call methods
func dialDirect() (*client, error) {
return dial("tcp", serverAddr, time.Second)
}
func countMallocs(dial func() (*client, error), t *testing.T) float64 {
once.Do(startServer)
client, err := dial()
if err != nil {
t.Fatal("error dialing", err)
}
defer client.Close()
args := &Args{7, 8}
reply := new(Reply)
return testing.AllocsPerRun(100, func() {
err := client.Call("Arith.Add", args, reply)
if err != nil {
t.Errorf("Add: expected no error but got string %q", err.Error())
}
if reply.C != args.A+args.B {
t.Errorf("Add: expected %d got %d", reply.C, args.A+args.B)
}
})
}
func TestCountMallocs(t *testing.T) {
if testing.Short() {
t.Skip("skipping malloc count in short mode")
}
if runtime.GOMAXPROCS(0) > 1 {
t.Skip("skipping; GOMAXPROCS>1")
}
fmt.Printf("mallocs per rpc round trip: %v\n", countMallocs(dialDirect, t))
}
func TestTCPClose(t *testing.T) {
once.Do(startServer)
client, err := dialDirect()
if err != nil {
t.Fatalf("dialing: %v", err)
}
defer client.Close()
args := Args{17, 8}
var reply Reply
err = client.Call("Arith.Mul", args, &reply)
if err != nil {
t.Fatal("arith error:", err)
}
t.Logf("Arith: %d*%d=%d\n", args.A, args.B, reply)
if reply.C != args.A*args.B {
t.Errorf("Add: expected %d got %d", reply.C, args.A*args.B)
}
}
func TestErrorAfterClientClose(t *testing.T) {
once.Do(startServer)
client, err := dialDirect()
if err != nil {
t.Fatalf("dialing: %v", err)
}
err = client.Close()
if err != nil {
t.Fatal("close error:", err)
}
err = client.Call("Arith.Add", &Args{7, 9}, new(Reply))
if err != ErrShutdown {
t.Errorf("Forever: expected ErrShutdown got %v", err)
}
}
// Tests the fix to issue 11221. Without the fix, this loops forever or crashes.
func TestAcceptExitAfterListenerClose(t *testing.T) {
newSvr = newServer()
newSvr.Register(new(Arith))
newSvr.RegisterName("net.rpc.Arith", new(Arith))
newSvr.RegisterName("newServer.Arith", new(Arith))
var l net.Listener
l, newServerAddr = listenTCP()
l.Close()
newSvr.Accept(l)
}
func TestParseDSN(t *testing.T) {
c := parseDSN("tcp://127.0.0.1:8099")
if c.Proto != "tcp" {
t.Error("parse dsn proto not equal tcp")
}
if c.Addr != "127.0.0.1:8099" {
t.Error("parse dsn addr not equal")
}
}
func benchmarkEndToEnd(dial func() (*client, error), b *testing.B) {
once.Do(startServer)
client, err := dial()
if err != nil {
b.Fatal("error dialing:", err)
}
defer client.Close()
// Synchronous calls
args := &Args{7, 8}
b.ResetTimer()
b.RunParallel(func(pb *testing.PB) {
reply := new(Reply)
for pb.Next() {
err := client.Call("Arith.Add", args, reply)
if err != nil {
b.Fatalf("rpc error: Add: expected no error but got string %q", err.Error())
}
if reply.C != args.A+args.B {
b.Fatalf("rpc error: Add: expected %d got %d", reply.C, args.A+args.B)
}
}
})
}
func benchmarkEndToEndAsync(dial func() (*client, error), b *testing.B) {
if b.N == 0 {
return
}
const MaxConcurrentCalls = 100
once.Do(startServer)
client, err := dial()
if err != nil {
b.Fatal("error dialing:", err)
}
defer client.Close()
// Asynchronous calls
args := &Args{7, 8}
procs := 4 * runtime.GOMAXPROCS(-1)
send := int32(b.N)
recv := int32(b.N)
var wg sync.WaitGroup
wg.Add(procs)
gate := make(chan bool, MaxConcurrentCalls)
res := make(chan *Call, MaxConcurrentCalls)
b.ResetTimer()
for p := 0; p < procs; p++ {
go func() {
for atomic.AddInt32(&send, -1) >= 0 {
gate <- true
reply := new(Reply)
client.Go("Arith.Add", args, reply, res)
}
}()
go func() {
for call := range res {
A := call.Args.(*Args).A
B := call.Args.(*Args).B
C := call.Reply.(*Reply).C
if A+B != C {
return
}
<-gate
if atomic.AddInt32(&recv, -1) == 0 {
close(res)
}
}
wg.Done()
}()
}
wg.Wait()
}
func BenchmarkEndToEnd(b *testing.B) {
benchmarkEndToEnd(dialDirect, b)
}
func BenchmarkEndToEndAsync(b *testing.B) {
benchmarkEndToEndAsync(dialDirect, b)
}

57
library/net/rpc/trace.go Normal file
View File

@@ -0,0 +1,57 @@
package rpc
import (
"strconv"
"go-common/library/net/trace"
)
// TraceInfo propagate trace propagate gorpc call
type TraceInfo struct {
ID uint64
SpanID uint64
ParentID uint64
Level int32
Sampled bool
Caller string
Title string
Time int64
}
// Set implement trace.Carrier
func (i *TraceInfo) Set(key string, val string) {
switch key {
case trace.KeyTraceID:
i.ID, _ = strconv.ParseUint(val, 10, 64)
case trace.KeyTraceSpanID:
i.SpanID, _ = strconv.ParseUint(val, 10, 64)
case trace.KeyTraceParentID:
i.ParentID, _ = strconv.ParseUint(val, 10, 64)
case trace.KeyTraceSampled:
i.Sampled, _ = strconv.ParseBool(val)
case trace.KeyTraceLevel:
lv, _ := strconv.Atoi(val)
i.Level = int32(lv)
case trace.KeyTraceCaller:
i.Caller = val
}
}
// Get implement trace.Carrier
func (i *TraceInfo) Get(key string) string {
switch key {
case trace.KeyTraceID:
return strconv.FormatUint(i.ID, 10)
case trace.KeyTraceSpanID:
return strconv.FormatUint(i.SpanID, 10)
case trace.KeyTraceParentID:
return strconv.FormatUint(i.ParentID, 10)
case trace.KeyTraceSampled:
return strconv.FormatBool(i.Sampled)
case trace.KeyTraceLevel:
return strconv.Itoa(int(i.Level))
case trace.KeyTraceCaller:
return i.Caller
}
return ""
}

View File

@@ -0,0 +1,124 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
"go_library",
)
go_test(
name = "go_default_test",
srcs = [
"logging_test.go",
"server_test.go",
],
embed = [":go_default_library"],
rundir = ".",
tags = ["automanaged"],
deps = [
"//library/ecode:go_default_library",
"//library/ecode/pb:go_default_library",
"//library/ecode/tip:go_default_library",
"//library/log:go_default_library",
"//library/net/metadata:go_default_library",
"//library/net/netutil/breaker:go_default_library",
"//library/net/rpc/warden/proto/testproto:go_default_library",
"//library/net/trace:go_default_library",
"//library/time:go_default_library",
"//vendor/github.com/pkg/errors:go_default_library",
"//vendor/github.com/stretchr/testify/assert:go_default_library",
"@com_github_golang_protobuf//ptypes:go_default_library_gen",
"@org_golang_google_grpc//:go_default_library",
"@org_golang_google_grpc//codes:go_default_library",
"@org_golang_google_grpc//status:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = [
"client.go",
"logging.go",
"recovery.go",
"server.go",
"stats.go",
"validate.go",
],
importpath = "go-common/library/net/rpc/warden",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//library/conf/dsn:go_default_library",
"//library/conf/env:go_default_library",
"//library/conf/flagvar:go_default_library",
"//library/ecode:go_default_library",
"//library/log:go_default_library",
"//library/naming:go_default_library",
"//library/naming/discovery:go_default_library",
"//library/net/metadata:go_default_library",
"//library/net/netutil/breaker:go_default_library",
"//library/net/rpc/warden/balancer/wrr:go_default_library",
"//library/net/rpc/warden/encoding/json:go_default_library",
"//library/net/rpc/warden/resolver:go_default_library",
"//library/net/rpc/warden/status:go_default_library",
"//library/net/trace:go_default_library",
"//library/stat:go_default_library",
"//library/stat/summary:go_default_library",
"//library/stat/sys/cpu:go_default_library",
"//library/time:go_default_library",
"//vendor/github.com/pkg/errors:go_default_library",
"//vendor/gopkg.in/go-playground/validator.v9:go_default_library",
"@org_golang_google_grpc//:go_default_library",
"@org_golang_google_grpc//codes:go_default_library",
"@org_golang_google_grpc//credentials:go_default_library",
"@org_golang_google_grpc//keepalive:go_default_library",
"@org_golang_google_grpc//metadata:go_default_library",
"@org_golang_google_grpc//peer:go_default_library",
"@org_golang_google_grpc//reflection:go_default_library",
"@org_golang_google_grpc//status:go_default_library",
],
)
go_test(
name = "go_default_xtest",
srcs = ["exapmle_test.go"],
tags = ["automanaged"],
deps = [
"//library/log:go_default_library",
"//library/net/netutil/breaker:go_default_library",
"//library/net/rpc/warden:go_default_library",
"//library/net/rpc/warden/proto/testproto:go_default_library",
"//library/time:go_default_library",
"@org_golang_google_grpc//:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//library/net/rpc/warden/balancer/wrr:all-srcs",
"//library/net/rpc/warden/benchmark/bench/client:all-srcs",
"//library/net/rpc/warden/benchmark/bench/proto:all-srcs",
"//library/net/rpc/warden/benchmark/bench/server:all-srcs",
"//library/net/rpc/warden/benchmark/helloworld/client:all-srcs",
"//library/net/rpc/warden/benchmark/helloworld/server:all-srcs",
"//library/net/rpc/warden/encoding/json:all-srcs",
"//library/net/rpc/warden/examples/client:all-srcs",
"//library/net/rpc/warden/examples/grpcDebug:all-srcs",
"//library/net/rpc/warden/examples/server:all-srcs",
"//library/net/rpc/warden/metadata:all-srcs",
"//library/net/rpc/warden/proto/testproto:all-srcs",
"//library/net/rpc/warden/resolver:all-srcs",
"//library/net/rpc/warden/status:all-srcs",
],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,51 @@
### net/rpc/warden
##### Version 1.1.9
1. 增加NonBlock模式
##### Version 1.1.8
1. 新增appid mock
##### Version 1.1.7
1. 兼容cpu为0和wrr dt为0的情况
##### Version 1.1.6
1. 修改caller传递和获取方式
2. 添加error detail example
##### Version 1.1.5
1. 增加server端json格式支持
##### Version 1.1.4
1. 判断reosvler.builder为nil之后再注册
##### Version 1.1.3
1. 支持zone和clusters
##### Version 1.1.2
1. 业务错误日志记为 WARN
##### Version 1.1.1
1. server实现了返回cpu信息
##### Version 1.1.0
1. 增加ErrorDetail
2. 修复日志打印error信息丢失问题
##### Version 1.0.3
1. 给server增加keepalive参数
##### Version 1.0.2
1. 替代默认的timoue使用durtaion.Shrink()来传递context
2. 修复peer.Addr为nil时会panic的问题
##### Version 1.0.1
1. 去除timeout的手动传递改为使用grpc默认自带的grpc-timeout
2. 获取server address改为使用call option的方式去除对balancer的依赖
##### Version 1.0.0
1. 使用NewClient来新建一个RPC客户端并默认集成trace、log、recovery、moniter拦截器
2. 使用NewServer来新建一个RPC服务端并默认集成trace、log、recovery、moniter拦截器

View File

@@ -0,0 +1,10 @@
# Owner
maojian
caoguoliang
# Author
maojian
caoguoliang
# Reviewer
maojian

View File

@@ -0,0 +1,8 @@
# See the OWNERS docs at https://go.k8s.io/owners
approvers:
- caoguoliang
- maojian
reviewers:
- caoguoliang
- maojian

View File

@@ -0,0 +1,13 @@
#### net/rcp/warden
##### 项目简介
来自 bilibili 主站技术部的 RPC 框架,融合主站技术部的核心科技,带来如飞一般的体验。
##### 编译环境
- **请只用 Golang v1.9.x 以上版本编译执行**
##### 依赖包
- [grpc](google.golang.org/grpc)

View File

@@ -0,0 +1,59 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
"go_library",
)
go_test(
name = "go_default_test",
srcs = ["wrr_test.go"],
embed = [":go_default_library"],
rundir = ".",
tags = ["automanaged"],
deps = [
"//library/net/metadata:go_default_library",
"//library/net/rpc/warden/metadata:go_default_library",
"@org_golang_google_grpc//balancer:go_default_library",
"@org_golang_google_grpc//resolver:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = ["wrr.go"],
importpath = "go-common/library/net/rpc/warden/balancer/wrr",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//library/log:go_default_library",
"//library/net/metadata:go_default_library",
"//library/net/rpc/warden/metadata:go_default_library",
"//library/stat/summary:go_default_library",
"@org_golang_google_grpc//:go_default_library",
"@org_golang_google_grpc//balancer:go_default_library",
"@org_golang_google_grpc//balancer/base:go_default_library",
"@org_golang_google_grpc//codes:go_default_library",
"@org_golang_google_grpc//metadata:go_default_library",
"@org_golang_google_grpc//resolver:go_default_library",
"@org_golang_google_grpc//status:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//library/net/rpc/warden/balancer/wrr/test:all-srcs",
],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,14 @@
### business/warden/balancer/wrr
##### Version 1.2.1
1. 删除了netflix ribbon的权重算法改成了平方根算法
##### Version 1.2.0
1. 实现了动态计算的调度轮询算法(使用了服务端的成功率数据,替换基于本地计算的成功率数据)
##### Version 1.1.0
1. 实现了动态计算的调度轮询算法
##### Version 1.0.0
1. 实现了带权重可以识别Color的轮询算法

View File

@@ -0,0 +1,8 @@
# Owner
caoguoliang
# Author
caoguoliang
# Reviewer
maojian

View File

@@ -0,0 +1,7 @@
# See the OWNERS docs at https://go.k8s.io/owners
approvers:
- caoguoliang
reviewers:
- caoguoliang
- maojian

View File

@@ -0,0 +1,13 @@
#### business/warden/balancer/wrr
##### 项目简介
warden 的 weighted round robin负载均衡模块主要用于为每个RPC请求返回一个Server节点以供调用
##### 编译环境
- **请只用 Golang v1.9.x 以上版本编译执行**
##### 依赖包
- [grpc](google.golang.org/grpc)

View File

@@ -0,0 +1,51 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
"go_library",
)
go_test(
name = "go_default_test",
srcs = ["base_test.go"],
rundir = ".",
tags = ["automanaged"],
deps = [
"//library/conf/env:go_default_library",
"//library/naming:go_default_library",
"//library/net/netutil/breaker:go_default_library",
"//library/net/rpc/warden:go_default_library",
"//library/net/rpc/warden/balancer/wrr:go_default_library",
"//library/net/rpc/warden/proto/testproto:go_default_library",
"//library/net/rpc/warden/resolver:go_default_library",
"//library/time:go_default_library",
"@org_golang_google_grpc//:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//library/net/rpc/warden/balancer/wrr/test/client:all-srcs",
"//library/net/rpc/warden/balancer/wrr/test/server:all-srcs",
],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)
go_library(
name = "go_default_library",
srcs = ["base.go"],
importpath = "go-common/library/net/rpc/warden/balancer/wrr/test",
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1 @@
package test

View File

@@ -0,0 +1,298 @@
package test
import (
"context"
"io"
"log"
"os"
"sync"
"testing"
"time"
"go-common/library/conf/env"
"go-common/library/naming"
"go-common/library/net/netutil/breaker"
"go-common/library/net/rpc/warden"
"go-common/library/net/rpc/warden/balancer/wrr"
pb "go-common/library/net/rpc/warden/proto/testproto"
"go-common/library/net/rpc/warden/resolver"
xtime "go-common/library/time"
"google.golang.org/grpc"
)
type testBuilder struct {
addrs []*naming.Instance
}
type testDiscovery struct {
mu sync.Mutex
b *testBuilder
id string
ch chan struct{}
}
func (b *testBuilder) Build(id string) naming.Resolver {
return &testDiscovery{id: id, b: b}
}
func (b *testBuilder) Scheme() string {
return "testbuilder"
}
func (d *testDiscovery) Fetch(ctx context.Context) (map[string][]*naming.Instance, bool) {
d.mu.Lock()
addrs := d.b.addrs
d.mu.Unlock()
if len(addrs) == 0 {
return nil, false
}
return map[string][]*naming.Instance{env.Zone: addrs}, true
}
func (d *testDiscovery) Watch() <-chan struct{} {
d.mu.Lock()
defer d.mu.Unlock()
if d.ch == nil {
d.ch = make(chan struct{}, 1)
}
return d.ch
}
func (d *testDiscovery) Close() error {
return nil
}
func (d *testDiscovery) Scheme() string {
return "discovery"
}
func (d *testDiscovery) set(addrs []*naming.Instance) {
d.mu.Lock()
defer d.mu.Unlock()
d.b.addrs = addrs
select {
case d.ch <- struct{}{}:
default:
return
}
}
func TestMain(m *testing.M) {
s1 := runServer(":18080")
s2 := runServer(":18081")
s3 := runServer(":18082")
b = &testBuilder{}
resolver.Register(b)
dis = b.Build("test_app").(*testDiscovery)
go func() {
time.Sleep(time.Millisecond * 10)
dis.set([]*naming.Instance{{
Addrs: []string{"grpc://127.0.0.1:18080"},
AppID: "test_app",
Metadata: map[string]string{"weight": "100"},
}, {
Addrs: []string{"grpc://127.0.0.1:18081"},
AppID: "test_app",
Metadata: map[string]string{"color": "red"},
}, {
Addrs: []string{"grpc://127.0.0.1:18082"},
AppID: "test_app",
}})
}()
c = newClient()
time.Sleep(time.Millisecond * 30)
ret := m.Run()
ctx, cancel := context.WithTimeout(context.Background(), time.Second*1)
defer cancel()
s1.Shutdown(ctx)
s2.Shutdown(ctx)
s3.Shutdown(ctx)
os.Exit(ret)
}
type helloServer struct {
addr string
}
func (s *helloServer) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
return &pb.HelloReply{Message: s.addr}, nil
}
func (s *helloServer) StreamHello(ss pb.Greeter_StreamHelloServer) error {
for i := 0; i < 3; i++ {
in, err := ss.Recv()
if err == io.EOF {
return nil
}
if err != nil {
return err
}
ret := &pb.HelloReply{Message: "Hello " + in.Name, Success: true}
err = ss.Send(ret)
if err != nil {
return err
}
}
return nil
}
func runServer(addr string) *warden.Server {
server := warden.NewServer(&warden.ServerConfig{Timeout: xtime.Duration(time.Second)})
pb.RegisterGreeterServer(server.Server(), &helloServer{addr: addr})
go func() {
err := server.Run(addr)
if err != nil {
panic("run server failed!" + err.Error())
}
}()
return server
}
// NewClient returns a new blank Client instance with a default client interceptor.
// opt can be used to add grpc dial options.
func newClient() (client pb.GreeterClient) {
c := warden.NewClient(&warden.ClientConfig{
Dial: xtime.Duration(time.Second * 10),
Timeout: xtime.Duration(time.Second * 10),
Breaker: &breaker.Config{
Window: xtime.Duration(3 * time.Second),
Sleep: xtime.Duration(3 * time.Second),
Bucket: 10,
Ratio: 0.3,
Request: 20,
},
},
grpc.WithBalancerName(wrr.Name),
)
conn, err := c.Dial(context.Background(), "discovery://authority/111")
if err != nil {
log.Fatalf("can't not connect: %v", err)
}
client = pb.NewGreeterClient(conn)
return
}
var b *testBuilder
var dis *testDiscovery
var c pb.GreeterClient
func TestBalancer(t *testing.T) {
testBalancerBasic(t)
testBalancerFailover(t)
testBalancerUpdateColor(t)
testBalancerUpdateScore(t)
}
func testBalancerBasic(t *testing.T) {
time.Sleep(time.Millisecond * 10)
var idx8080 int
var idx8082 int
for i := 0; i < 6; i++ {
resp, err := c.SayHello(context.Background(), &pb.HelloRequest{Age: 123, Name: "asdasd"})
if err != nil {
t.Fatalf("testBalancerBasic: say hello failed!err:=%v", err)
}
if resp.Message == ":18082" {
idx8082++
} else if resp.Message == ":18080" {
idx8080++
}
}
if idx8080 != 3 {
t.Fatalf("testBalancerBasic: server 18080 response times should be 3")
}
if idx8082 != 3 {
t.Fatalf("testBalancerBasic: server 18082 response times should be 3")
}
}
func testBalancerFailover(t *testing.T) {
dis.set([]*naming.Instance{{
Addrs: []string{"grpc://127.0.0.1:18080"},
AppID: "test_app",
Metadata: map[string]string{"weight": "100"},
}, {
Addrs: []string{"grpc://127.0.0.1:18081"},
AppID: "test_app",
Metadata: map[string]string{"color": "red"},
}})
time.Sleep(time.Millisecond * 20)
var idx8080 int
var idx8082 int
for i := 0; i < 4; i++ {
resp, err := c.SayHello(context.Background(), &pb.HelloRequest{Age: 123, Name: "asdasd"})
if err != nil {
t.Fatalf("testBalancerFailover: say hello failed!err:=%v", err)
}
if resp.Message == ":18082" {
idx8082++
} else if resp.Message == ":18080" {
idx8080++
}
}
if idx8080 != 4 {
t.Fatalf("testBalancerFailover: server 8080 response should be 4")
}
}
func testBalancerUpdateColor(t *testing.T) {
dis.set([]*naming.Instance{{
Addrs: []string{"grpc://127.0.0.1:18080"},
AppID: "test_app",
Metadata: map[string]string{"weight": "100"},
}, {
Addrs: []string{"grpc://127.0.0.1:18081"},
AppID: "test_app",
}})
time.Sleep(time.Millisecond * 30)
var idx8080 int
var idx8081 int
for i := 0; i < 4; i++ {
resp, err := c.SayHello(context.Background(), &pb.HelloRequest{Age: 123, Name: "asdasd"})
if err != nil {
t.Fatalf("testBalancerUpdateColor: say hello failed!err:=%v", err)
}
if resp.Message == ":18081" {
idx8081++
} else if resp.Message == ":18080" {
idx8080++
}
}
if idx8080 != 2 {
t.Fatalf("testBalancerUpdateColor: server 8080 response should be 2")
}
if idx8081 != 2 {
t.Fatalf("testBalancerUpdateColor: server 8081 response should be 2")
}
}
func testBalancerUpdateScore(t *testing.T) {
dis.set([]*naming.Instance{{
Addrs: []string{"grpc://127.0.0.1:18080"},
AppID: "test_app",
Metadata: map[string]string{"weight": "100"},
}, {
Addrs: []string{"grpc://127.0.0.1:18081"},
AppID: "test_app",
Metadata: map[string]string{"weight": "300"},
}})
time.Sleep(time.Millisecond * 10)
var idx8080 int
var idx8081 int
for i := 0; i < 4; i++ {
resp, err := c.SayHello(context.Background(), &pb.HelloRequest{Age: 123, Name: "asdasd"})
if err != nil {
t.Fatalf("testBalancerUpdateScore: say hello failed!err:=%v", err)
}
if resp.Message == ":18081" {
idx8081++
} else if resp.Message == ":18080" {
idx8080++
}
}
if idx8080 != 1 {
t.Fatalf("testBalancerUpdateScore: server 8080 response should be 2")
}
if idx8081 != 3 {
t.Fatalf("testBalancerUpdateScore: server 8081 response should be 2")
}
}

View File

@@ -0,0 +1,43 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_binary",
"go_library",
)
go_binary(
name = "client",
embed = [":go_default_library"],
tags = ["automanaged"],
)
go_library(
name = "go_default_library",
srcs = ["client.go"],
importpath = "go-common/library/net/rpc/warden/balancer/wrr/test/client",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//library/exp/feature:go_default_library",
"//library/log:go_default_library",
"//library/net/rpc/warden:go_default_library",
"//library/net/rpc/warden/proto/testproto:go_default_library",
"//library/net/rpc/warden/resolver:go_default_library",
"//library/net/rpc/warden/resolver/direct:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,73 @@
package main
import (
"context"
"flag"
"fmt"
"sync/atomic"
"time"
"go-common/library/exp/feature"
"go-common/library/log"
"go-common/library/net/rpc/warden"
pb "go-common/library/net/rpc/warden/proto/testproto"
"go-common/library/net/rpc/warden/resolver"
"go-common/library/net/rpc/warden/resolver/direct"
)
var addrs string
var cli pb.GreeterClient
var concurrency int
var name string
var req int64
var qps int64
func init() {
log.Init(&log.Config{Stdout: false})
flag.StringVar(&addrs, "addr", "127.0.0.1:8000,127.0.0.1:8001", "-addr 127.0.0.1:8080,127.0.0.1:8081")
flag.IntVar(&concurrency, "c", 3, "-c 5")
flag.StringVar(&name, "name", "test", "-name test")
}
func main() {
go calcuQPS()
feature.DefaultGate.AddFlag(flag.CommandLine)
flag.Parse()
feature.DefaultGate.SetFromMap(map[string]bool{"dwrr": true})
resolver.Register(direct.New())
c := warden.NewClient(nil)
conn, err := c.Dial(context.Background(), fmt.Sprintf("direct://d/%s", addrs))
if err != nil {
panic(err)
}
cli = pb.NewGreeterClient(conn)
for i := 0; i < concurrency; i++ {
go func() {
for {
say()
time.Sleep(time.Millisecond * 5)
}
}()
}
time.Sleep(time.Hour)
}
func calcuQPS() {
var creq, breq int64
for {
time.Sleep(time.Second * 5)
creq = atomic.LoadInt64(&req)
delta := creq - breq
atomic.StoreInt64(&qps, delta/5)
breq = creq
fmt.Println("HTTP QPS: ", atomic.LoadInt64(&qps))
}
}
func say() {
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
reply, err := cli.SayHello(ctx, &pb.HelloRequest{Name: name, Age: 10})
if err == nil && reply.Success {
atomic.AddInt64(&req, 1)
}
}

View File

@@ -0,0 +1,41 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_binary",
"go_library",
)
go_binary(
name = "server",
embed = [":go_default_library"],
tags = ["automanaged"],
)
go_library(
name = "go_default_library",
srcs = ["server.go"],
importpath = "go-common/library/net/rpc/warden/balancer/wrr/test/server",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//library/ecode:go_default_library",
"//library/log:go_default_library",
"//library/net/rpc/warden:go_default_library",
"//library/net/rpc/warden/proto/testproto:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,91 @@
package main
import (
"context"
"flag"
"fmt"
"hash/crc32"
"io"
"math/rand"
"sync/atomic"
"time"
"go-common/library/ecode"
"go-common/library/log"
"go-common/library/net/rpc/warden"
pb "go-common/library/net/rpc/warden/proto/testproto"
)
var (
req int64
qps int64
cpu int
errRate int
sleep time.Duration
)
func init() {
log.Init(&log.Config{Stdout: false})
flag.IntVar(&cpu, "cpu", 3000, "cpu time")
flag.IntVar(&errRate, "err", 0, "error rate")
flag.DurationVar(&sleep, "sleep", 0, "sleep time")
}
func calcuQPS() {
var creq, breq int64
for {
time.Sleep(time.Second * 5)
creq = atomic.LoadInt64(&req)
delta := creq - breq
atomic.StoreInt64(&qps, delta/5)
breq = creq
fmt.Println("HTTP QPS: ", atomic.LoadInt64(&qps))
}
}
func main() {
flag.Parse()
server := warden.NewServer(nil)
pb.RegisterGreeterServer(server.Server(), &helloServer{})
_, err := server.Start()
if err != nil {
panic(err)
}
calcuQPS()
}
type helloServer struct {
}
func (s *helloServer) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
atomic.AddInt64(&req, 1)
if in.Name == "err" {
if rand.Intn(100) < errRate {
return nil, ecode.ServiceUnavailable
}
}
time.Sleep(time.Millisecond * time.Duration(in.Age))
time.Sleep(sleep)
for i := 0; i < cpu+rand.Intn(cpu); i++ {
crc32.Checksum([]byte(`testasdwfwfsddsfgwddcscschttp://git.bilibili.co/platform/go-common/merge_requests/new?merge_request%5Bsource_branch%5D=stress%2Fcodel`), crc32.IEEETable)
}
return &pb.HelloReply{Message: "Hello " + in.Name, Success: true}, nil
}
func (s *helloServer) StreamHello(ss pb.Greeter_StreamHelloServer) error {
for i := 0; i < 3; i++ {
in, err := ss.Recv()
if err == io.EOF {
return nil
}
if err != nil {
return err
}
ret := &pb.HelloReply{Message: "Hello " + in.Name, Success: true}
err = ss.Send(ret)
if err != nil {
return err
}
}
return nil
}

View File

@@ -0,0 +1,276 @@
package wrr
import (
"context"
"math"
"strconv"
"sync"
"sync/atomic"
"time"
"go-common/library/log"
nmd "go-common/library/net/metadata"
wmeta "go-common/library/net/rpc/warden/metadata"
"go-common/library/stat/summary"
"google.golang.org/grpc"
"google.golang.org/grpc/balancer"
"google.golang.org/grpc/balancer/base"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/resolver"
"google.golang.org/grpc/status"
)
var _ base.PickerBuilder = &wrrPickerBuilder{}
var _ balancer.Picker = &wrrPicker{}
// var dwrrFeature feature.Feature = "dwrr"
// Name is the name of round_robin balancer.
const Name = "wrr"
// newBuilder creates a new weighted-roundrobin balancer builder.
func newBuilder() balancer.Builder {
return base.NewBalancerBuilder(Name, &wrrPickerBuilder{})
}
func init() {
//feature.DefaultGate.Add(map[feature.Feature]feature.Spec{
// dwrrFeature: {Default: false},
//})
balancer.Register(newBuilder())
}
type serverInfo struct {
cpu int64
success uint64 // float64 bits
}
type subConn struct {
conn balancer.SubConn
addr resolver.Address
meta wmeta.MD
err summary.Summary
lantency summary.Summary
si serverInfo
// effective weight
ewt int64
// current weight
cwt int64
// last score
score float64
}
// statistics is info for log
type statistics struct {
addr string
ewt int64
cs float64
ss float64
lantency float64
cpu float64
req int64
}
// Stats is grpc Interceptor for client to collect server stats
func Stats() grpc.UnaryClientInterceptor {
return func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) (err error) {
var (
trailer metadata.MD
md nmd.MD
ok bool
)
if md, ok = nmd.FromContext(ctx); !ok {
md = nmd.MD{}
} else {
md = md.Copy()
}
ctx = nmd.NewContext(ctx, md)
opts = append(opts, grpc.Trailer(&trailer))
err = invoker(ctx, method, req, reply, cc, opts...)
conn, ok := md["conn"].(*subConn)
if !ok {
return
}
if strs, ok := trailer[nmd.CPUUsage]; ok {
if cpu, err2 := strconv.ParseInt(strs[0], 10, 64); err2 == nil && cpu > 0 {
atomic.StoreInt64(&conn.si.cpu, cpu)
}
}
var reqs, errs int64
if strs, ok := trailer[nmd.Requests]; ok {
reqs, _ = strconv.ParseInt(strs[0], 10, 64)
}
if strs, ok := trailer[nmd.Errors]; ok {
errs, _ = strconv.ParseInt(strs[0], 10, 64)
}
if reqs > 0 && reqs >= errs {
success := float64(reqs-errs) / float64(reqs)
if success == 0 {
success = 0.1
}
atomic.StoreUint64(&conn.si.success, math.Float64bits(success))
}
return
}
}
type wrrPickerBuilder struct{}
func (*wrrPickerBuilder) Build(readySCs map[resolver.Address]balancer.SubConn) balancer.Picker {
p := &wrrPicker{
colors: make(map[string]*wrrPicker),
}
for addr, sc := range readySCs {
meta, ok := addr.Metadata.(wmeta.MD)
if !ok {
meta = wmeta.MD{
Weight: 10,
}
}
subc := &subConn{
conn: sc,
addr: addr,
meta: meta,
ewt: meta.Weight,
score: -1,
err: summary.New(time.Second, 10),
lantency: summary.New(time.Second, 10),
si: serverInfo{cpu: 500, success: math.Float64bits(1)},
}
if meta.Color == "" {
p.subConns = append(p.subConns, subc)
continue
}
// if color not empty, use color picker
cp, ok := p.colors[meta.Color]
if !ok {
cp = &wrrPicker{}
p.colors[meta.Color] = cp
}
cp.subConns = append(cp.subConns, subc)
}
return p
}
type wrrPicker struct {
// subConns is the snapshot of the weighted-roundrobin balancer when this picker was
// created. The slice is immutable. Each Get() will do a round robin
// selection from it and return the selected SubConn.
subConns []*subConn
colors map[string]*wrrPicker
updateAt int64
mu sync.Mutex
}
func (p *wrrPicker) Pick(ctx context.Context, opts balancer.PickOptions) (balancer.SubConn, func(balancer.DoneInfo), error) {
if color := nmd.String(ctx, nmd.Color); color != "" {
if cp, ok := p.colors[color]; ok {
return cp.pick(ctx, opts)
}
}
return p.pick(ctx, opts)
}
func (p *wrrPicker) pick(ctx context.Context, opts balancer.PickOptions) (balancer.SubConn, func(balancer.DoneInfo), error) {
var (
conn *subConn
totalWeight int64
)
if len(p.subConns) <= 0 {
return nil, nil, balancer.ErrNoSubConnAvailable
}
p.mu.Lock()
// nginx wrr load balancing algorithm: http://blog.csdn.net/zhangskd/article/details/50194069
for _, sc := range p.subConns {
totalWeight += sc.ewt
sc.cwt += sc.ewt
if conn == nil || conn.cwt < sc.cwt {
conn = sc
}
}
conn.cwt -= totalWeight
p.mu.Unlock()
start := time.Now()
if cmd, ok := nmd.FromContext(ctx); ok {
cmd["conn"] = conn
}
//if !feature.DefaultGate.Enabled(dwrrFeature) {
// return conn.conn, nil, nil
//}
return conn.conn, func(di balancer.DoneInfo) {
ev := int64(0) // error value ,if error set 1
if di.Err != nil {
if st, ok := status.FromError(di.Err); ok {
// only counter the local grpc error, ignore any business error
if st.Code() != codes.Unknown && st.Code() != codes.OK {
ev = 1
}
}
}
conn.err.Add(ev)
now := time.Now()
conn.lantency.Add(now.Sub(start).Nanoseconds() / 1e5)
u := atomic.LoadInt64(&p.updateAt)
if now.UnixNano()-u < int64(time.Second) {
return
}
if !atomic.CompareAndSwapInt64(&p.updateAt, u, now.UnixNano()) {
return
}
var (
stats = make([]statistics, len(p.subConns))
count int
total float64
)
for i, conn := range p.subConns {
cpu := float64(atomic.LoadInt64(&conn.si.cpu))
ss := math.Float64frombits(atomic.LoadUint64(&conn.si.success))
errc, req := conn.err.Value()
lagv, lagc := conn.lantency.Value()
if req > 0 && lagc > 0 && lagv > 0 {
// client-side success ratio
cs := 1 - (float64(errc) / float64(req))
if cs <= 0 {
cs = 0.1
} else if cs <= 0.2 && req <= 5 {
cs = 0.2
}
lag := float64(lagv) / float64(lagc)
conn.score = math.Sqrt((cs * ss * ss * 1e9) / (lag * cpu))
stats[i] = statistics{cs: cs, ss: ss, lantency: lag, cpu: cpu, req: req}
}
stats[i].addr = conn.addr.Addr
if conn.score > 0 {
total += conn.score
count++
}
}
// count must be greater than 1,otherwise will lead ewt to 0
if count < 2 {
return
}
avgscore := total / float64(count)
p.mu.Lock()
for i, conn := range p.subConns {
if conn.score <= 0 {
conn.score = avgscore
}
conn.ewt = int64(conn.score * float64(conn.meta.Weight))
stats[i].ewt = conn.ewt
}
p.mu.Unlock()
log.Info("warden wrr(%s): %+v", conn.addr.ServerName, stats)
}, nil
}

View File

@@ -0,0 +1,95 @@
package wrr
import (
"context"
"fmt"
"testing"
nmd "go-common/library/net/metadata"
wmeta "go-common/library/net/rpc/warden/metadata"
"google.golang.org/grpc/balancer"
"google.golang.org/grpc/resolver"
)
type testSubConn struct {
addr resolver.Address
}
func (s *testSubConn) UpdateAddresses([]resolver.Address) {
}
// Connect starts the connecting for this SubConn.
func (s *testSubConn) Connect() {
fmt.Println(s.addr.Addr)
}
func TestBalancerPick(t *testing.T) {
scs := map[resolver.Address]balancer.SubConn{}
sc1 := &testSubConn{
addr: resolver.Address{
Addr: "test1",
Metadata: wmeta.MD{
Weight: 8,
},
},
}
sc2 := &testSubConn{
addr: resolver.Address{
Addr: "test2",
Metadata: wmeta.MD{
Weight: 4,
Color: "red",
},
},
}
sc3 := &testSubConn{
addr: resolver.Address{
Addr: "test3",
Metadata: wmeta.MD{
Weight: 2,
Color: "red",
},
},
}
scs[sc1.addr] = sc1
scs[sc2.addr] = sc2
scs[sc3.addr] = sc3
b := &wrrPickerBuilder{}
picker := b.Build(scs)
res := []string{"test1", "test1", "test1", "test1"}
for i := 0; i < 3; i++ {
conn, _, err := picker.Pick(context.Background(), balancer.PickOptions{})
if err != nil {
t.Fatalf("picker.Pick failed!idx:=%d", i)
}
sc := conn.(*testSubConn)
if sc.addr.Addr != res[i] {
t.Fatalf("the subconn picked(%s),but expected(%s)", sc.addr.Addr, res[i])
}
}
res2 := []string{"test2", "test3", "test2", "test2", "test3", "test2"}
ctx := nmd.NewContext(context.Background(), nmd.New(map[string]interface{}{"color": "red"}))
for i := 0; i < 6; i++ {
conn, _, err := picker.Pick(ctx, balancer.PickOptions{})
if err != nil {
t.Fatalf("picker.Pick failed!idx:=%d", i)
}
sc := conn.(*testSubConn)
if sc.addr.Addr != res2[i] {
t.Fatalf("the (%d) subconn picked(%s),but expected(%s)", i, sc.addr.Addr, res2[i])
}
}
ctx = nmd.NewContext(context.Background(), nmd.New(map[string]interface{}{"color": "black"}))
for i := 0; i < 4; i++ {
conn, _, err := picker.Pick(ctx, balancer.PickOptions{})
if err != nil {
t.Fatalf("picker.Pick failed!idx:=%d", i)
}
sc := conn.(*testSubConn)
if sc.addr.Addr != res[i] {
t.Fatalf("the (%d) subconn picked(%s),but expected(%s)", i, sc.addr.Addr, res[i])
}
}
}

View File

@@ -0,0 +1,45 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_binary",
"go_library",
)
go_binary(
name = "client",
embed = [":go_default_library"],
tags = ["automanaged"],
)
go_library(
name = "go_default_library",
srcs = ["client.go"],
importpath = "go-common/library/net/rpc/warden/benchmark/bench/client",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//library/net/netutil/breaker:go_default_library",
"//library/net/rpc/warden:go_default_library",
"//library/net/rpc/warden/benchmark/bench/proto:go_default_library",
"//library/time:go_default_library",
"//vendor/github.com/montanaflynn/stats:go_default_library",
"@com_github_gogo_protobuf//proto:go_default_library",
"@org_golang_google_grpc//:go_default_library",
"@org_golang_x_net//context:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,189 @@
package main
import (
"flag"
"log"
"reflect"
"sync"
"sync/atomic"
"time"
"go-common/library/net/netutil/breaker"
"go-common/library/net/rpc/warden"
"go-common/library/net/rpc/warden/benchmark/bench/proto"
xtime "go-common/library/time"
goproto "github.com/gogo/protobuf/proto"
"github.com/montanaflynn/stats"
"golang.org/x/net/context"
"google.golang.org/grpc"
)
const (
iws = 65535 * 1000
iwsc = 65535 * 10000
readBuffer = 32 * 1024
writeBuffer = 32 * 1024
)
var concurrency = flag.Int("c", 50, "concurrency")
var total = flag.Int("t", 500000, "total requests for all clients")
var host = flag.String("s", "127.0.0.1:8972", "server ip and port")
var isWarden = flag.Bool("w", true, "is warden or grpc client")
var strLen = flag.Int("l", 600, "the length of the str")
func wardenCli() proto.HelloClient {
log.Println("start warden cli")
client := warden.NewClient(&warden.ClientConfig{
Dial: xtime.Duration(time.Second * 10),
Timeout: xtime.Duration(time.Second * 10),
Breaker: &breaker.Config{
Window: xtime.Duration(3 * time.Second),
Sleep: xtime.Duration(3 * time.Second),
Bucket: 10,
Ratio: 0.3,
Request: 20,
},
},
grpc.WithInitialWindowSize(iws),
grpc.WithInitialConnWindowSize(iwsc),
grpc.WithReadBufferSize(readBuffer),
grpc.WithWriteBufferSize(writeBuffer))
conn, err := client.Dial(context.Background(), *host)
if err != nil {
log.Fatalf("did not connect: %v", err)
}
cli := proto.NewHelloClient(conn)
return cli
}
func grpcCli() proto.HelloClient {
log.Println("start grpc cli")
conn, err := grpc.Dial(*host, grpc.WithInsecure(),
grpc.WithInitialWindowSize(iws),
grpc.WithInitialConnWindowSize(iwsc),
grpc.WithReadBufferSize(readBuffer),
grpc.WithWriteBufferSize(writeBuffer))
if err != nil {
log.Fatalf("did not connect: %v", err)
}
cli := proto.NewHelloClient(conn)
return cli
}
func main() {
flag.Parse()
c := *concurrency
m := *total / c
var wg sync.WaitGroup
wg.Add(c)
log.Printf("concurrency: %d\nrequests per client: %d\n\n", c, m)
args := prepareArgs()
b, _ := goproto.Marshal(args)
log.Printf("message size: %d bytes\n\n", len(b))
var trans uint64
var transOK uint64
d := make([][]int64, c)
for i := 0; i < c; i++ {
dt := make([]int64, 0, m)
d = append(d, dt)
}
var cli proto.HelloClient
if *isWarden {
cli = wardenCli()
} else {
cli = grpcCli()
}
//warmup
cli.Say(context.Background(), args)
totalT := time.Now().UnixNano()
for i := 0; i < c; i++ {
go func(i int) {
for j := 0; j < m; j++ {
t := time.Now().UnixNano()
reply, err := cli.Say(context.Background(), args)
t = time.Now().UnixNano() - t
d[i] = append(d[i], t)
if err == nil && reply.Field1 == "OK" {
atomic.AddUint64(&transOK, 1)
}
atomic.AddUint64(&trans, 1)
}
wg.Done()
}(i)
}
wg.Wait()
totalT = time.Now().UnixNano() - totalT
totalT = totalT / 1e6
log.Printf("took %d ms for %d requests\n", totalT, *total)
totalD := make([]int64, 0, *total)
for _, k := range d {
totalD = append(totalD, k...)
}
totalD2 := make([]float64, 0, *total)
for _, k := range totalD {
totalD2 = append(totalD2, float64(k))
}
mean, _ := stats.Mean(totalD2)
median, _ := stats.Median(totalD2)
max, _ := stats.Max(totalD2)
min, _ := stats.Min(totalD2)
tp99, _ := stats.Percentile(totalD2, 99)
tp999, _ := stats.Percentile(totalD2, 99.9)
log.Printf("sent requests : %d\n", *total)
log.Printf("received requests_OK : %d\n", atomic.LoadUint64(&transOK))
log.Printf("throughput (TPS) : %d\n", int64(c*m)*1000/totalT)
log.Printf("mean: %v ms, median: %v ms, max: %v ms, min: %v ms, p99: %v ms, p999:%v ms\n", mean/1e6, median/1e6, max/1e6, min/1e6, tp99/1e6, tp999/1e6)
}
func prepareArgs() *proto.BenchmarkMessage {
b := true
var i int32 = 120000
var i64 int64 = 98765432101234
var s = "许多往事在眼前一幕一幕,变的那麼模糊"
repeat := *strLen / (8 * 54)
if repeat == 0 {
repeat = 1
}
var str string
for i := 0; i < repeat; i++ {
str += s
}
var args proto.BenchmarkMessage
v := reflect.ValueOf(&args).Elem()
num := v.NumField()
for k := 0; k < num; k++ {
field := v.Field(k)
if field.Type().Kind() == reflect.Ptr {
switch v.Field(k).Type().Elem().Kind() {
case reflect.Int, reflect.Int32:
field.Set(reflect.ValueOf(&i))
case reflect.Int64:
field.Set(reflect.ValueOf(&i64))
case reflect.Bool:
field.Set(reflect.ValueOf(&b))
case reflect.String:
field.Set(reflect.ValueOf(&str))
}
} else {
switch field.Kind() {
case reflect.Int, reflect.Int32, reflect.Int64:
field.SetInt(9876543)
case reflect.Bool:
field.SetBool(true)
case reflect.String:
field.SetString(str)
}
}
}
return &args
}

View File

@@ -0,0 +1,56 @@
load(
"@io_bazel_rules_go//proto:def.bzl",
"go_proto_library",
)
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
proto_library(
name = "grpc_proto",
srcs = ["hello.proto"],
tags = ["automanaged"],
deps = ["@gogo_special_proto//github.com/gogo/protobuf/gogoproto"],
)
go_proto_library(
name = "grpc_go_proto",
compilers = ["@io_bazel_rules_go//proto:gogofast_grpc"],
importpath = "go-common/library/net/rpc/warden/benchmark/bench/proto",
proto = ":grpc_proto",
tags = ["automanaged"],
deps = ["@com_github_gogo_protobuf//gogoproto:go_default_library"],
)
go_library(
name = "go_default_library",
srcs = [],
embed = [":grpc_go_proto"],
importpath = "go-common/library/net/rpc/warden/benchmark/bench/proto",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"@com_github_gogo_protobuf//gogoproto:go_default_library",
"@com_github_golang_protobuf//proto:go_default_library",
"@org_golang_google_grpc//:go_default_library",
"@org_golang_x_net//context:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,60 @@
syntax = "proto3";
package grpc;
import "github.com/gogo/protobuf/gogoproto/gogo.proto";
option optimize_for = SPEED;
option (gogoproto.goproto_enum_prefix_all) = false;
option (gogoproto.goproto_getters_all) = false;
option (gogoproto.unmarshaler_all) = true;
option (gogoproto.marshaler_all) = true;
option (gogoproto.sizer_all) = true;
service Hello {
// Sends a greeting
rpc Say (BenchmarkMessage) returns (BenchmarkMessage) {}
}
message BenchmarkMessage {
string field1 = 1;
string field9 = 9;
string field18 = 18;
bool field80 = 80;
bool field81 = 81;
int32 field2 = 2;
int32 field3 = 3;
int32 field280 = 280;
int32 field6 = 6;
int64 field22 = 22;
string field4 = 4;
fixed64 field5 = 5;
bool field59 = 59;
string field7 = 7;
int32 field16 = 16;
int32 field130 = 130;
bool field12 = 12;
bool field17 = 17;
bool field13 = 13;
bool field14 = 14;
int32 field104 = 104;
int32 field100 = 100;
int32 field101 = 101;
string field102 = 102;
string field103 = 103;
int32 field29 = 29;
bool field30 = 30;
int32 field60 = 60;
int32 field271 = 271;
int32 field272 = 272;
int32 field150 = 150;
int32 field23 = 23;
bool field24 = 24 ;
int32 field25 = 25 ;
bool field78 = 78;
int32 field67 = 67;
int32 field68 = 68;
int32 field128 = 128;
string field129 = 129;
int32 field131 = 131;
}

View File

@@ -0,0 +1,42 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_binary",
"go_library",
)
go_binary(
name = "server",
embed = [":go_default_library"],
tags = ["automanaged"],
)
go_library(
name = "go_default_library",
srcs = ["server.go"],
importpath = "go-common/library/net/rpc/warden/benchmark/bench/server",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//library/net/rpc/warden:go_default_library",
"//library/net/rpc/warden/benchmark/bench/proto:go_default_library",
"//library/time:go_default_library",
"//vendor/github.com/prometheus/client_golang/prometheus/promhttp:go_default_library",
"@org_golang_google_grpc//:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,103 @@
package main
import (
"context"
"flag"
"log"
"net"
"net/http"
_ "net/http/pprof"
"sync/atomic"
"time"
"go-common/library/net/rpc/warden"
"go-common/library/net/rpc/warden/benchmark/bench/proto"
xtime "go-common/library/time"
"github.com/prometheus/client_golang/prometheus/promhttp"
"google.golang.org/grpc"
)
const (
iws = 65535 * 1000
iwsc = 65535 * 10000
readBuffer = 32 * 1024
writeBuffer = 32 * 1024
)
var reqNum uint64
type Hello struct{}
func (t *Hello) Say(ctx context.Context, args *proto.BenchmarkMessage) (reply *proto.BenchmarkMessage, err error) {
s := "OK"
var i int32 = 100
args.Field1 = s
args.Field2 = i
atomic.AddUint64(&reqNum, 1)
return args, nil
}
var host = flag.String("s", "0.0.0.0:8972", "listened ip and port")
var isWarden = flag.Bool("w", true, "is warden or grpc client")
func main() {
go func() {
log.Println("run http at :6060")
http.HandleFunc("/metrics", func(w http.ResponseWriter, r *http.Request) {
h := promhttp.Handler()
h.ServeHTTP(w, r)
})
log.Println(http.ListenAndServe("0.0.0.0:6060", nil))
}()
flag.Parse()
go stat()
if *isWarden {
runWarden()
} else {
runGrpc()
}
}
func runGrpc() {
log.Println("run grpc")
lis, err := net.Listen("tcp", *host)
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
s := grpc.NewServer(grpc.InitialWindowSize(iws),
grpc.InitialConnWindowSize(iwsc),
grpc.ReadBufferSize(readBuffer),
grpc.WriteBufferSize(writeBuffer))
proto.RegisterHelloServer(s, &Hello{})
s.Serve(lis)
}
func runWarden() {
log.Println("run warden")
s := warden.NewServer(&warden.ServerConfig{Timeout: xtime.Duration(time.Second * 3)},
grpc.InitialWindowSize(iws),
grpc.InitialConnWindowSize(iwsc),
grpc.ReadBufferSize(readBuffer),
grpc.WriteBufferSize(writeBuffer))
proto.RegisterHelloServer(s.Server(), &Hello{})
s.Run(*host)
}
func stat() {
ticker := time.NewTicker(time.Second * 5)
defer ticker.Stop()
var last uint64
lastTs := uint64(time.Now().UnixNano())
for {
<-ticker.C
now := atomic.LoadUint64(&reqNum)
nowTs := uint64(time.Now().UnixNano())
qps := (now - last) * 1e6 / ((nowTs - lastTs) / 1e3)
last = now
lastTs = nowTs
log.Println("qps:", qps)
}
}

View File

@@ -0,0 +1,15 @@
#!/bin/bash
go build -o client greeter_client.go
echo size 100 concurrent 30
./client -s 100 -c 30
echo size 1000 concurrent 30
./client -s 1000 -c 30
echo size 10000 concurrent 30
./client -s 10000 -c 30
echo size 100 concurrent 300
./client -s 100 -c 300
echo size 1000 concurrent 300
./client -s 1000 -c 300
echo size 10000 concurrent 300
./client -s 10000 -c 300
rm client

View File

@@ -0,0 +1,41 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_binary",
"go_library",
)
go_binary(
name = "client",
embed = [":go_default_library"],
tags = ["automanaged"],
)
go_library(
name = "go_default_library",
srcs = ["greeter_client.go"],
importpath = "go-common/library/net/rpc/warden/benchmark/helloworld/client",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//library/net/netutil/breaker:go_default_library",
"//library/net/rpc/warden:go_default_library",
"//library/net/rpc/warden/proto/testproto:go_default_library",
"//library/time:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,85 @@
package main
import (
"context"
"flag"
"fmt"
"math/rand"
"sync"
"sync/atomic"
"time"
"go-common/library/net/netutil/breaker"
"go-common/library/net/rpc/warden"
pb "go-common/library/net/rpc/warden/proto/testproto"
xtime "go-common/library/time"
)
var (
ccf = &warden.ClientConfig{
Dial: xtime.Duration(time.Second * 10),
Timeout: xtime.Duration(time.Second * 10),
Breaker: &breaker.Config{
Window: xtime.Duration(3 * time.Second),
Sleep: xtime.Duration(3 * time.Second),
Bucket: 10,
Ratio: 0.3,
Request: 20,
},
}
cli pb.GreeterClient
wg sync.WaitGroup
reqSize int
concurrency int
request int
all int64
)
func init() {
flag.IntVar(&reqSize, "s", 10, "request size")
flag.IntVar(&concurrency, "c", 10, "concurrency")
flag.IntVar(&request, "r", 1000, "request per routine")
}
func main() {
flag.Parse()
name := randSeq(reqSize)
cli = newClient()
for i := 0; i < concurrency; i++ {
wg.Add(1)
go sayHello(&pb.HelloRequest{Name: name})
}
wg.Wait()
fmt.Printf("per request cost %v\n", all/int64(request*concurrency))
}
func sayHello(in *pb.HelloRequest) {
defer wg.Done()
now := time.Now()
for i := 0; i < request; i++ {
cli.SayHello(context.TODO(), in)
}
delta := time.Since(now)
atomic.AddInt64(&all, int64(delta/time.Millisecond))
}
var letters = []rune("abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ")
func randSeq(n int) string {
b := make([]rune, n)
for i := range b {
b[i] = letters[rand.Intn(len(letters))]
}
return string(b)
}
func newClient() (cli pb.GreeterClient) {
client := warden.NewClient(ccf)
conn, err := client.Dial(context.TODO(), "127.0.0.1:9999")
if err != nil {
return
}
cli = pb.NewGreeterClient(conn)
return
}

View File

@@ -0,0 +1,41 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_binary",
"go_library",
)
go_binary(
name = "server",
embed = [":go_default_library"],
tags = ["automanaged"],
)
go_library(
name = "go_default_library",
srcs = ["greeter_server.go"],
importpath = "go-common/library/net/rpc/warden/benchmark/helloworld/server",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//library/net/rpc/warden:go_default_library",
"//library/net/rpc/warden/proto/testproto:go_default_library",
"//library/time:go_default_library",
"//vendor/github.com/prometheus/client_golang/prometheus/promhttp:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,50 @@
package main
import (
"context"
"net/http"
"time"
"go-common/library/net/rpc/warden"
pb "go-common/library/net/rpc/warden/proto/testproto"
xtime "go-common/library/time"
"github.com/prometheus/client_golang/prometheus/promhttp"
)
var (
config = &warden.ServerConfig{Timeout: xtime.Duration(time.Second)}
)
func main() {
newServer()
}
type hello struct {
}
func (s *hello) SayHello(c context.Context, in *pb.HelloRequest) (out *pb.HelloReply, err error) {
out = new(pb.HelloReply)
out.Message = in.Name
return
}
func (s *hello) StreamHello(ss pb.Greeter_StreamHelloServer) error {
return nil
}
func newServer() {
server := warden.NewServer(config)
pb.RegisterGreeterServer(server.Server(), &hello{})
go func() {
http.HandleFunc("/metrics", func(w http.ResponseWriter, r *http.Request) {
h := promhttp.Handler()
h.ServeHTTP(w, r)
})
http.ListenAndServe("0.0.0.0:9998", nil)
}()
err := server.Run(":9999")
if err != nil {
return
}
}

View File

@@ -0,0 +1,333 @@
package warden
import (
"context"
"fmt"
"net/url"
"os"
"strconv"
"strings"
"sync"
"time"
"go-common/library/conf/env"
"go-common/library/conf/flagvar"
"go-common/library/ecode"
"go-common/library/naming"
"go-common/library/naming/discovery"
nmd "go-common/library/net/metadata"
"go-common/library/net/netutil/breaker"
"go-common/library/net/rpc/warden/balancer/wrr"
"go-common/library/net/rpc/warden/resolver"
"go-common/library/net/rpc/warden/status"
"go-common/library/net/trace"
xtime "go-common/library/time"
"github.com/pkg/errors"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/metadata"
"google.golang.org/grpc/peer"
gstatus "google.golang.org/grpc/status"
)
var _grpcTarget flagvar.StringVars
var (
_once sync.Once
_defaultCliConf = &ClientConfig{
Dial: xtime.Duration(time.Second * 10),
Timeout: xtime.Duration(time.Millisecond * 250),
Subset: 50,
}
_defaultClient *Client
)
// ClientConfig is rpc client conf.
type ClientConfig struct {
Dial xtime.Duration
Timeout xtime.Duration
Breaker *breaker.Config
Method map[string]*ClientConfig
Clusters []string
Zone string
Subset int
NonBlock bool
}
// Client is the framework's client side instance, it contains the ctx, opt and interceptors.
// Create an instance of Client, by using NewClient().
type Client struct {
conf *ClientConfig
breaker *breaker.Group
mutex sync.RWMutex
opt []grpc.DialOption
handlers []grpc.UnaryClientInterceptor
}
// handle returns a new unary client interceptor for OpenTracing\Logging\LinkTimeout.
func (c *Client) handle() grpc.UnaryClientInterceptor {
return func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) (err error) {
var (
ok bool
cmd nmd.MD
t trace.Trace
gmd metadata.MD
conf *ClientConfig
cancel context.CancelFunc
addr string
p peer.Peer
)
var ec ecode.Codes = ecode.OK
// apm tracing
if t, ok = trace.FromContext(ctx); ok {
t = t.Fork(_family, method)
defer t.Finish(&err)
}
// setup metadata
gmd = metadata.MD{}
trace.Inject(t, trace.GRPCFormat, gmd)
c.mutex.RLock()
if conf, ok = c.conf.Method[method]; !ok {
conf = c.conf
}
c.mutex.RUnlock()
brk := c.breaker.Get(method)
if err = brk.Allow(); err != nil {
statsClient.Incr(method, "breaker")
return
}
defer onBreaker(brk, &err)
_, ctx, cancel = conf.Timeout.Shrink(ctx)
defer cancel()
// meta color
if cmd, ok = nmd.FromContext(ctx); ok {
var color, ip, port string
if color, ok = cmd[nmd.Color].(string); ok {
gmd[nmd.Color] = []string{color}
}
if ip, ok = cmd[nmd.RemoteIP].(string); ok {
gmd[nmd.RemoteIP] = []string{ip}
}
if port, ok = cmd[nmd.RemotePort].(string); ok {
gmd[nmd.RemotePort] = []string{port}
}
} else {
// NOTE: dead code delete it after test in futrue
cmd = nmd.MD{}
ctx = nmd.NewContext(ctx, cmd)
}
gmd[nmd.Caller] = []string{env.AppID}
// merge with old matadata if exists
if oldmd, ok := metadata.FromOutgoingContext(ctx); ok {
gmd = metadata.Join(gmd, oldmd)
}
ctx = metadata.NewOutgoingContext(ctx, gmd)
opts = append(opts, grpc.Peer(&p))
if err = invoker(ctx, method, req, reply, cc, opts...); err != nil {
gst, _ := gstatus.FromError(err)
ec = status.ToEcode(gst)
err = errors.WithMessage(ec, gst.Message())
}
if p.Addr != nil {
addr = p.Addr.String()
}
if t != nil {
t.SetTag(trace.String(trace.TagAddress, addr), trace.String(trace.TagComment, ""))
}
return
}
}
func onBreaker(breaker breaker.Breaker, err *error) {
if err != nil && *err != nil {
if ecode.ServerErr.Equal(*err) || ecode.ServiceUnavailable.Equal(*err) || ecode.Deadline.Equal(*err) || ecode.LimitExceed.Equal(*err) {
breaker.MarkFailed()
return
}
}
breaker.MarkSuccess()
}
// NewConn will create a grpc conn by default config.
func NewConn(target string, opt ...grpc.DialOption) (*grpc.ClientConn, error) {
return DefaultClient().Dial(context.Background(), target, opt...)
}
// NewClient returns a new blank Client instance with a default client interceptor.
// opt can be used to add grpc dial options.
func NewClient(conf *ClientConfig, opt ...grpc.DialOption) *Client {
resolver.Register(discovery.Builder())
c := new(Client)
if err := c.SetConfig(conf); err != nil {
panic(err)
}
c.UseOpt(grpc.WithBalancerName(wrr.Name))
c.UseOpt(opt...)
c.Use(c.recovery(), clientLogging(), c.handle(), wrr.Stats())
return c
}
// DefaultClient returns a new default Client instance with a default client interceptor and default dialoption.
// opt can be used to add grpc dial options.
func DefaultClient() *Client {
resolver.Register(discovery.Builder())
_once.Do(func() {
_defaultClient = NewClient(nil)
})
return _defaultClient
}
// SetConfig hot reloads client config
func (c *Client) SetConfig(conf *ClientConfig) (err error) {
if conf == nil {
conf = _defaultCliConf
}
if conf.Dial <= 0 {
conf.Dial = xtime.Duration(time.Second * 10)
}
if conf.Timeout <= 0 {
conf.Timeout = xtime.Duration(time.Millisecond * 250)
}
if conf.Subset <= 0 {
conf.Subset = 50
}
// FIXME(maojian) check Method dial/timeout
c.mutex.Lock()
c.conf = conf
if c.breaker == nil {
c.breaker = breaker.NewGroup(conf.Breaker)
} else {
c.breaker.Reload(conf.Breaker)
}
c.mutex.Unlock()
return nil
}
// Use attachs a global inteceptor to the Client.
// For example, this is the right place for a circuit breaker or error management inteceptor.
func (c *Client) Use(handlers ...grpc.UnaryClientInterceptor) *Client {
finalSize := len(c.handlers) + len(handlers)
if finalSize >= int(_abortIndex) {
panic("warden: client use too many handlers")
}
mergedHandlers := make([]grpc.UnaryClientInterceptor, finalSize)
copy(mergedHandlers, c.handlers)
copy(mergedHandlers[len(c.handlers):], handlers)
c.handlers = mergedHandlers
return c
}
// UseOpt attachs a global grpc DialOption to the Client.
func (c *Client) UseOpt(opt ...grpc.DialOption) *Client {
c.opt = append(c.opt, opt...)
return c
}
// Dial creates a client connection to the given target.
// Target format is scheme://authority/endpoint?query_arg=value
// example: discovery://default/account.account.service?cluster=shfy01&cluster=shfy02
func (c *Client) Dial(ctx context.Context, target string, opt ...grpc.DialOption) (conn *grpc.ClientConn, err error) {
if !c.conf.NonBlock {
c.opt = append(c.opt, grpc.WithBlock())
}
c.opt = append(c.opt, grpc.WithInsecure())
c.opt = append(c.opt, grpc.WithUnaryInterceptor(c.chainUnaryClient()))
c.opt = append(c.opt, opt...)
c.mutex.RLock()
conf := c.conf
c.mutex.RUnlock()
if conf.Dial > 0 {
var cancel context.CancelFunc
ctx, cancel = context.WithTimeout(ctx, time.Duration(conf.Dial))
defer cancel()
}
if u, e := url.Parse(target); e == nil {
v := u.Query()
for _, c := range c.conf.Clusters {
v.Add(naming.MetaCluster, c)
}
if c.conf.Zone != "" {
v.Add(naming.MetaZone, c.conf.Zone)
}
if v.Get("subset") == "" && c.conf.Subset > 0 {
v.Add("subset", strconv.FormatInt(int64(c.conf.Subset), 10))
}
u.RawQuery = v.Encode()
// 比较_grpcTarget中的appid是否等于u.path中的appid并替换成mock的地址
for _, t := range _grpcTarget {
strs := strings.SplitN(t, "=", 2)
if len(strs) == 2 && ("/"+strs[0]) == u.Path {
u.Path = "/" + strs[1]
u.Scheme = "passthrough"
u.RawQuery = ""
break
}
}
target = u.String()
}
if conn, err = grpc.DialContext(ctx, target, c.opt...); err != nil {
fmt.Fprintf(os.Stderr, "warden client: dial %s error %v!", target, err)
}
err = errors.WithStack(err)
return
}
// DialTLS creates a client connection over tls transport to the given target.
func (c *Client) DialTLS(ctx context.Context, target string, file string, name string) (conn *grpc.ClientConn, err error) {
var creds credentials.TransportCredentials
creds, err = credentials.NewClientTLSFromFile(file, name)
if err != nil {
err = errors.WithStack(err)
return
}
c.opt = append(c.opt, grpc.WithBlock())
c.opt = append(c.opt, grpc.WithTransportCredentials(creds))
c.opt = append(c.opt, grpc.WithUnaryInterceptor(c.chainUnaryClient()))
c.mutex.RLock()
conf := c.conf
c.mutex.RUnlock()
if conf.Dial > 0 {
var cancel context.CancelFunc
ctx, cancel = context.WithTimeout(ctx, time.Duration(conf.Dial))
defer cancel()
}
conn, err = grpc.DialContext(ctx, target, c.opt...)
err = errors.WithStack(err)
return
}
// chainUnaryClient creates a single interceptor out of a chain of many interceptors.
//
// Execution is done in left-to-right order, including passing of context.
// For example ChainUnaryClient(one, two, three) will execute one before two before three.
func (c *Client) chainUnaryClient() grpc.UnaryClientInterceptor {
n := len(c.handlers)
if n == 0 {
return func(ctx context.Context, method string, req, reply interface{},
cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
return invoker(ctx, method, req, reply, cc, opts...)
}
}
return func(ctx context.Context, method string, req, reply interface{},
cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
var (
i int
chainHandler grpc.UnaryInvoker
)
chainHandler = func(ictx context.Context, imethod string, ireq, ireply interface{}, ic *grpc.ClientConn, iopts ...grpc.CallOption) error {
if i == n-1 {
return invoker(ictx, imethod, ireq, ireply, ic, iopts...)
}
i++
return c.handlers[i](ictx, imethod, ireq, ireply, ic, chainHandler, iopts...)
}
return c.handlers[0](ctx, method, req, reply, cc, chainHandler, opts...)
}
}

View File

@@ -0,0 +1,33 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = ["json.go"],
importpath = "go-common/library/net/rpc/warden/encoding/json",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"@com_github_gogo_protobuf//jsonpb:go_default_library",
"@com_github_gogo_protobuf//proto:go_default_library",
"@org_golang_google_grpc//encoding:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,53 @@
package codec
import (
"bytes"
"encoding/json"
"github.com/gogo/protobuf/jsonpb"
"github.com/gogo/protobuf/proto"
"google.golang.org/grpc/encoding"
)
//Reference https://jbrandhorst.com/post/grpc-json/
func init() {
encoding.RegisterCodec(JSON{
Marshaler: jsonpb.Marshaler{
EmitDefaults: true,
OrigName: true,
},
})
}
// JSON is impl of encoding.Codec
type JSON struct {
jsonpb.Marshaler
jsonpb.Unmarshaler
}
// Name is name of JSON
func (j JSON) Name() string {
return "json"
}
// Marshal is json marshal
func (j JSON) Marshal(v interface{}) (out []byte, err error) {
if pm, ok := v.(proto.Message); ok {
b := new(bytes.Buffer)
err := j.Marshaler.Marshal(b, pm)
if err != nil {
return nil, err
}
return b.Bytes(), nil
}
return json.Marshal(v)
}
// Unmarshal is json unmarshal
func (j JSON) Unmarshal(data []byte, v interface{}) (err error) {
if pm, ok := v.(proto.Message); ok {
b := bytes.NewBuffer(data)
return j.Unmarshaler.Unmarshal(b, pm)
}
return json.Unmarshal(data, v)
}

View File

@@ -0,0 +1,43 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_binary",
"go_library",
)
go_binary(
name = "client",
embed = [":go_default_library"],
tags = ["automanaged"],
)
go_library(
name = "go_default_library",
srcs = ["client.go"],
importpath = "go-common/library/net/rpc/warden/examples/client",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//library/ecode:go_default_library",
"//library/net/rpc/warden:go_default_library",
"//library/net/rpc/warden/proto/testproto:go_default_library",
"//vendor/github.com/pkg/errors:go_default_library",
"@com_github_golang_protobuf//ptypes:go_default_library_gen",
"@io_bazel_rules_go//proto/wkt:any_go_proto",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,48 @@
package main
import (
"context"
"flag"
"fmt"
"go-common/library/ecode"
"go-common/library/net/rpc/warden"
pb "go-common/library/net/rpc/warden/proto/testproto"
"github.com/golang/protobuf/ptypes"
"github.com/golang/protobuf/ptypes/any"
"github.com/pkg/errors"
)
// usage: ./client -grpc.target=test.service=127.0.0.1:8080
func main() {
flag.Parse()
conn, err := warden.NewConn("discovery://d/test.service?t=t&cluster=asdasd")
if err != nil {
panic(err)
}
cli := pb.NewGreeterClient(conn)
normalCall(cli)
errDetailCall(cli)
}
func normalCall(cli pb.GreeterClient) {
reply, err := cli.SayHello(context.Background(), &pb.HelloRequest{Name: "tom", Age: 23})
if err != nil {
panic(err)
}
fmt.Println("get reply:", *reply)
}
func errDetailCall(cli pb.GreeterClient) {
_, err := cli.SayHello(context.Background(), &pb.HelloRequest{Name: "err_detail_test", Age: 12})
if err != nil {
any := errors.Cause(err).(ecode.Codes).Details()[0].(*any.Any)
var re pb.HelloReply
err := ptypes.UnmarshalAny(any, &re)
if err == nil {
fmt.Printf("cli.SayHello get error detail!detail:=%v", re)
}
return
}
}

View File

@@ -0,0 +1,41 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
"go_binary",
)
go_library(
name = "go_default_library",
srcs = ["client.go"],
importpath = "go-common/library/net/rpc/warden/examples/grpcDebug",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"@com_github_gogo_protobuf//jsonpb:go_default_library",
"@org_golang_google_grpc//:go_default_library",
"@org_golang_google_grpc//credentials:go_default_library",
"@org_golang_google_grpc//encoding:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)
go_binary(
name = "grpcDebug",
embed = [":go_default_library"],
tags = ["automanaged"],
)

View File

@@ -0,0 +1,103 @@
package main
import (
"context"
"flag"
"fmt"
"io/ioutil"
"os"
"github.com/gogo/protobuf/jsonpb"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials"
"google.golang.org/grpc/encoding"
)
// Reply for test
type Reply struct {
res []byte
}
var data string
var file string
var method string
var addr string
var tlsCert string
var tlsServerName string
//Reference https://jbrandhorst.com/post/grpc-json/
func init() {
encoding.RegisterCodec(JSON{
Marshaler: jsonpb.Marshaler{
EmitDefaults: true,
OrigName: true,
},
})
flag.StringVar(&data, "data", `{"name":"longxia","age":19}`, `{"name":"longxia","age":19}`)
flag.StringVar(&file, "file", ``, `./data.json`)
flag.StringVar(&method, "method", "/testproto.Greeter/SayHello", `/testproto.Greeter/SayHello`)
flag.StringVar(&addr, "addr", "127.0.0.1:8080", `127.0.0.1:8080`)
flag.StringVar(&tlsCert, "cert", "", `./cert.pem`)
flag.StringVar(&tlsServerName, "server_name", "", `hello_server`)
}
// 该example因为使用的是json传输格式所以只能用于调试或测试用于线上会导致性能下降
// 使用方法:
// ./grpcDebug -data='{"name":"xia","age":19}' -addr=127.0.0.1:8080 -method=/testproto.Greeter/SayHello
// ./grpcDebug -file=data.json -addr=127.0.0.1:8080 -method=/testproto.Greeter/SayHello
func main() {
flag.Parse()
opts := []grpc.DialOption{
grpc.WithInsecure(),
grpc.WithDefaultCallOptions(grpc.CallContentSubtype(JSON{}.Name())),
}
if tlsCert != "" {
creds, err := credentials.NewClientTLSFromFile(tlsCert, tlsServerName)
if err != nil {
panic(err)
}
opts = append(opts, grpc.WithTransportCredentials(creds))
}
if file != "" {
content, err := ioutil.ReadFile(file)
if err != nil {
fmt.Println("ioutil.ReadFile %s failed!err:=%v", file, err)
os.Exit(1)
}
if len(content) > 0 {
data = string(content)
}
}
conn, err := grpc.Dial(addr, opts...)
if err != nil {
panic(err)
}
var reply Reply
err = grpc.Invoke(context.Background(), method, []byte(data), &reply, conn)
if err != nil {
panic(err)
}
fmt.Println(string(reply.res))
}
// JSON is impl of encoding.Codec
type JSON struct {
jsonpb.Marshaler
jsonpb.Unmarshaler
}
// Name is name of JSON
func (j JSON) Name() string {
return "json"
}
// Marshal is json marshal
func (j JSON) Marshal(v interface{}) (out []byte, err error) {
return v.([]byte), nil
}
// Unmarshal is json unmarshal
func (j JSON) Unmarshal(data []byte, v interface{}) (err error) {
v.(*Reply).res = data
return nil
}

View File

@@ -0,0 +1 @@
{"name":"xia","age":19}

View File

@@ -0,0 +1,45 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_binary",
"go_library",
)
go_binary(
name = "server",
embed = [":go_default_library"],
tags = ["automanaged"],
)
go_library(
name = "go_default_library",
srcs = ["main.go"],
importpath = "go-common/library/net/rpc/warden/examples/server",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//library/ecode:go_default_library",
"//library/ecode/pb:go_default_library",
"//library/log:go_default_library",
"//library/net/rpc/warden:go_default_library",
"//library/net/rpc/warden/proto/testproto:go_default_library",
"//library/time:go_default_library",
"@com_github_golang_protobuf//ptypes:go_default_library_gen",
"@org_golang_google_grpc//:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,108 @@
package main
import (
"context"
"fmt"
"io"
"os"
"os/signal"
"syscall"
"time"
"go-common/library/ecode"
epb "go-common/library/ecode/pb"
"go-common/library/log"
"go-common/library/net/rpc/warden"
pb "go-common/library/net/rpc/warden/proto/testproto"
xtime "go-common/library/time"
"github.com/golang/protobuf/ptypes"
"google.golang.org/grpc"
)
type helloServer struct {
addr string
}
func (s *helloServer) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
if in.Name == "err_detail_test" {
any, _ := ptypes.MarshalAny(&pb.HelloReply{Success: true, Message: "this is test detail"})
err := epb.From(ecode.AccessDenied)
err.ErrDetail = any
return nil, err
}
return &pb.HelloReply{Message: fmt.Sprintf("hello %s from %s", in.Name, s.addr)}, nil
}
func (s *helloServer) StreamHello(ss pb.Greeter_StreamHelloServer) error {
for i := 0; i < 3; i++ {
in, err := ss.Recv()
if err == io.EOF {
return nil
}
if err != nil {
return err
}
ret := &pb.HelloReply{Message: "Hello " + in.Name, Success: true}
err = ss.Send(ret)
if err != nil {
return err
}
}
return nil
}
func runServer(addr string) *warden.Server {
server := warden.NewServer(&warden.ServerConfig{
//服务端每个请求的默认超时时间
Timeout: xtime.Duration(time.Second),
})
server.Use(middleware())
pb.RegisterGreeterServer(server.Server(), &helloServer{addr: addr})
go func() {
err := server.Run(addr)
if err != nil {
panic("run server failed!" + err.Error())
}
}()
return server
}
func main() {
log.Init(&log.Config{Stdout: true})
server := runServer("0.0.0.0:8080")
signalHandler(server)
}
//类似于中间件
func middleware() grpc.UnaryServerInterceptor {
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
//记录调用方法
log.Info("method:%s", info.FullMethod)
//call chain
resp, err = handler(ctx, req)
return
}
}
func signalHandler(s *warden.Server) {
var (
ch = make(chan os.Signal, 1)
)
signal.Notify(ch, syscall.SIGHUP, syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT)
for {
si := <-ch
switch si {
case syscall.SIGQUIT, syscall.SIGTERM, syscall.SIGINT:
log.Info("get a signal %s, stop the consume process", si.String())
ctx, cancel := context.WithTimeout(context.Background(), time.Second*3)
defer cancel()
//gracefully shutdown with timeout
s.Shutdown(ctx)
return
case syscall.SIGHUP:
default:
return
}
}
}

View File

@@ -0,0 +1,91 @@
package warden_test
import (
"context"
"fmt"
"io"
"time"
"go-common/library/log"
"go-common/library/net/netutil/breaker"
"go-common/library/net/rpc/warden"
pb "go-common/library/net/rpc/warden/proto/testproto"
xtime "go-common/library/time"
"google.golang.org/grpc"
)
type helloServer struct {
}
func (s *helloServer) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
return &pb.HelloReply{Message: "Hello " + in.Name, Success: true}, nil
}
func (s *helloServer) StreamHello(ss pb.Greeter_StreamHelloServer) error {
for i := 0; i < 3; i++ {
in, err := ss.Recv()
if err == io.EOF {
return nil
}
if err != nil {
return err
}
ret := &pb.HelloReply{Message: "Hello " + in.Name, Success: true}
err = ss.Send(ret)
if err != nil {
return err
}
}
return nil
}
func ExampleServer() {
s := warden.NewServer(&warden.ServerConfig{Timeout: xtime.Duration(time.Second), Addr: ":8080"})
// apply server interceptor middleware
s.Use(func(ctx context.Context, req interface{}, args *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
newctx, cancel := context.WithTimeout(ctx, time.Second*10)
defer cancel()
resp, err := handler(newctx, req)
return resp, err
})
pb.RegisterGreeterServer(s.Server(), &helloServer{})
s.Start()
}
func ExampleClient() {
client := warden.NewClient(&warden.ClientConfig{
Dial: xtime.Duration(time.Second * 10),
Timeout: xtime.Duration(time.Second * 10),
Breaker: &breaker.Config{
Window: xtime.Duration(3 * time.Second),
Sleep: xtime.Duration(3 * time.Second),
Bucket: 10,
Ratio: 0.3,
Request: 20,
},
})
// apply client interceptor middleware
client.Use(func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) (ret error) {
newctx, cancel := context.WithTimeout(ctx, time.Second*5)
defer cancel()
ret = invoker(newctx, method, req, reply, cc, opts...)
return
})
conn, err := client.Dial(context.Background(), "127.0.0.1:8080")
if err != nil {
log.Error("did not connect: %v", err)
return
}
defer conn.Close()
c := pb.NewGreeterClient(conn)
name := "2233"
rp, err := c.SayHello(context.Background(), &pb.HelloRequest{Name: name, Age: 18})
if err != nil {
log.Error("could not greet: %v", err)
return
}
fmt.Println("rp", *rp)
}

View File

@@ -0,0 +1,115 @@
package warden
import (
"context"
"fmt"
"strconv"
"time"
"google.golang.org/grpc"
"google.golang.org/grpc/peer"
"go-common/library/ecode"
"go-common/library/log"
"go-common/library/net/metadata"
"go-common/library/stat"
)
var (
statsClient = stat.RPCClient
statsServer = stat.RPCServer
)
func logFn(code int, dt time.Duration) func(context.Context, ...log.D) {
switch {
case code < 0:
return log.Errorv
case dt >= time.Millisecond*500:
// TODO: slowlog make it configurable.
return log.Warnv
case code > 0:
return log.Warnv
}
return log.Infov
}
// clientLogging warden grpc logging
func clientLogging() grpc.UnaryClientInterceptor {
return func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
startTime := time.Now()
var peerInfo peer.Peer
opts = append(opts, grpc.Peer(&peerInfo))
// invoker requests
err := invoker(ctx, method, req, reply, cc, opts...)
// after request
code := ecode.Cause(err).Code()
duration := time.Since(startTime)
// monitor
statsClient.Timing(method, int64(duration/time.Millisecond))
statsClient.Incr(method, strconv.Itoa(code))
var ip string
if peerInfo.Addr != nil {
ip = peerInfo.Addr.String()
}
logFields := []log.D{
log.KV("ip", ip),
log.KV("path", method),
log.KV("ret", code),
// TODO: it will panic if someone remove String method from protobuf message struct that auto generate from protoc.
log.KV("args", req.(fmt.Stringer).String()),
log.KV("ts", duration.Seconds()),
log.KV("source", "grpc-access-log"),
}
if err != nil {
logFields = append(logFields, log.KV("error", err.Error()), log.KV("stack", fmt.Sprintf("%+v", err)))
}
logFn(code, duration)(ctx, logFields...)
return err
}
}
// serverLogging warden grpc logging
func serverLogging() grpc.UnaryServerInterceptor {
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
startTime := time.Now()
caller := metadata.String(ctx, metadata.Caller)
var remoteIP string
if peerInfo, ok := peer.FromContext(ctx); ok {
remoteIP = peerInfo.Addr.String()
}
var quota float64
if deadline, ok := ctx.Deadline(); ok {
quota = time.Until(deadline).Seconds()
}
// call server handler
resp, err := handler(ctx, req)
// after server response
code := ecode.Cause(err).Code()
duration := time.Since(startTime)
// monitor
statsServer.Timing(caller, int64(duration/time.Millisecond), info.FullMethod)
statsServer.Incr(caller, info.FullMethod, strconv.Itoa(code))
logFields := []log.D{
log.KV("user", caller),
log.KV("ip", remoteIP),
log.KV("path", info.FullMethod),
log.KV("ret", code),
// TODO: it will panic if someone remove String method from protobuf message struct that auto generate from protoc.
log.KV("args", req.(fmt.Stringer).String()),
log.KV("ts", duration.Seconds()),
log.KV("timeout_quota", quota),
log.KV("source", "grpc-access-log"),
}
if err != nil {
logFields = append(logFields, log.KV("error", err.Error()), log.KV("stack", fmt.Sprintf("%+v", err)))
}
logFn(code, duration)(ctx, logFields...)
return resp, err
}
}

View File

@@ -0,0 +1,55 @@
package warden
import (
"context"
"reflect"
"testing"
"time"
"go-common/library/log"
)
func Test_logFn(t *testing.T) {
type args struct {
code int
dt time.Duration
}
tests := []struct {
name string
args args
want func(context.Context, ...log.D)
}{
{
name: "ok",
args: args{code: 0, dt: time.Millisecond},
want: log.Infov,
},
{
name: "slowlog",
args: args{code: 0, dt: time.Second},
want: log.Warnv,
},
{
name: "business error",
args: args{code: 2233, dt: time.Millisecond},
want: log.Warnv,
},
{
name: "system error",
args: args{code: -1, dt: 0},
want: log.Errorv,
},
{
name: "system error and slowlog",
args: args{code: -1, dt: time.Second},
want: log.Errorv,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := logFn(tt.args.code, tt.args.dt); reflect.ValueOf(got).Pointer() != reflect.ValueOf(tt.want).Pointer() {
t.Errorf("unexpect log function!")
}
})
}
}

View File

@@ -0,0 +1,28 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = ["meta.go"],
importpath = "go-common/library/net/rpc/warden/metadata",
tags = ["automanaged"],
visibility = ["//visibility:public"],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,7 @@
package metadata
// MD is context metadata for balancer and resolver
type MD struct {
Weight int64
Color string
}

View File

@@ -0,0 +1,56 @@
load(
"@io_bazel_rules_go//proto:def.bzl",
"go_proto_library",
)
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
proto_library(
name = "testproto_proto",
srcs = ["hello.proto"],
tags = ["automanaged"],
deps = ["@gogo_special_proto//github.com/gogo/protobuf/gogoproto"],
)
go_proto_library(
name = "testproto_go_proto",
compilers = ["@io_bazel_rules_go//proto:gogofast_grpc"],
importpath = "go-common/library/net/rpc/warden/proto/testproto",
proto = ":testproto_proto",
tags = ["automanaged"],
deps = ["@com_github_gogo_protobuf//gogoproto:go_default_library"],
)
go_library(
name = "go_default_library",
srcs = [],
embed = [":testproto_go_proto"],
importpath = "go-common/library/net/rpc/warden/proto/testproto",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"@com_github_gogo_protobuf//gogoproto:go_default_library",
"@com_github_golang_protobuf//proto:go_default_library",
"@org_golang_google_grpc//:go_default_library",
"@org_golang_x_net//context:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,642 @@
// Code generated by protoc-gen-gogo. DO NOT EDIT.
// source: hello.proto
/*
Package testproto is a generated protocol buffer package.
It is generated from these files:
hello.proto
It has these top-level messages:
HelloRequest
HelloReply
*/
package testproto
import proto "github.com/golang/protobuf/proto"
import fmt "fmt"
import math "math"
import _ "github.com/gogo/protobuf/gogoproto"
import context "golang.org/x/net/context"
import grpc "google.golang.org/grpc"
import io "io"
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion2 // please upgrade the proto package
// The request message containing the user's name.
type HelloRequest struct {
Name string `protobuf:"bytes,1,opt,name=name,proto3" json:"name" validate:"required"`
Age int32 `protobuf:"varint,2,opt,name=age,proto3" json:"age" validate:"min=0"`
}
func (m *HelloRequest) Reset() { *m = HelloRequest{} }
func (m *HelloRequest) String() string { return proto.CompactTextString(m) }
func (*HelloRequest) ProtoMessage() {}
func (*HelloRequest) Descriptor() ([]byte, []int) { return fileDescriptorHello, []int{0} }
// The response message containing the greetings
type HelloReply struct {
Message string `protobuf:"bytes,1,opt,name=message,proto3" json:"message,omitempty"`
Success bool `protobuf:"varint,2,opt,name=success,proto3" json:"success,omitempty"`
}
func (m *HelloReply) Reset() { *m = HelloReply{} }
func (m *HelloReply) String() string { return proto.CompactTextString(m) }
func (*HelloReply) ProtoMessage() {}
func (*HelloReply) Descriptor() ([]byte, []int) { return fileDescriptorHello, []int{1} }
func init() {
proto.RegisterType((*HelloRequest)(nil), "testproto.HelloRequest")
proto.RegisterType((*HelloReply)(nil), "testproto.HelloReply")
}
// Reference imports to suppress errors if they are not otherwise used.
var _ context.Context
var _ grpc.ClientConn
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
const _ = grpc.SupportPackageIsVersion4
// Client API for Greeter service
type GreeterClient interface {
// Sends a greeting
SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error)
// A bidirectional streaming RPC call recvice HelloRequest return HelloReply
StreamHello(ctx context.Context, opts ...grpc.CallOption) (Greeter_StreamHelloClient, error)
}
type greeterClient struct {
cc *grpc.ClientConn
}
func NewGreeterClient(cc *grpc.ClientConn) GreeterClient {
return &greeterClient{cc}
}
func (c *greeterClient) SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error) {
out := new(HelloReply)
err := grpc.Invoke(ctx, "/testproto.Greeter/SayHello", in, out, c.cc, opts...)
if err != nil {
return nil, err
}
return out, nil
}
func (c *greeterClient) StreamHello(ctx context.Context, opts ...grpc.CallOption) (Greeter_StreamHelloClient, error) {
stream, err := grpc.NewClientStream(ctx, &_Greeter_serviceDesc.Streams[0], c.cc, "/testproto.Greeter/StreamHello", opts...)
if err != nil {
return nil, err
}
x := &greeterStreamHelloClient{stream}
return x, nil
}
type Greeter_StreamHelloClient interface {
Send(*HelloRequest) error
Recv() (*HelloReply, error)
grpc.ClientStream
}
type greeterStreamHelloClient struct {
grpc.ClientStream
}
func (x *greeterStreamHelloClient) Send(m *HelloRequest) error {
return x.ClientStream.SendMsg(m)
}
func (x *greeterStreamHelloClient) Recv() (*HelloReply, error) {
m := new(HelloReply)
if err := x.ClientStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
// Server API for Greeter service
type GreeterServer interface {
// Sends a greeting
SayHello(context.Context, *HelloRequest) (*HelloReply, error)
// A bidirectional streaming RPC call recvice HelloRequest return HelloReply
StreamHello(Greeter_StreamHelloServer) error
}
func RegisterGreeterServer(s *grpc.Server, srv GreeterServer) {
s.RegisterService(&_Greeter_serviceDesc, srv)
}
func _Greeter_SayHello_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(HelloRequest)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(GreeterServer).SayHello(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/testproto.Greeter/SayHello",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(GreeterServer).SayHello(ctx, req.(*HelloRequest))
}
return interceptor(ctx, in, info, handler)
}
func _Greeter_StreamHello_Handler(srv interface{}, stream grpc.ServerStream) error {
return srv.(GreeterServer).StreamHello(&greeterStreamHelloServer{stream})
}
type Greeter_StreamHelloServer interface {
Send(*HelloReply) error
Recv() (*HelloRequest, error)
grpc.ServerStream
}
type greeterStreamHelloServer struct {
grpc.ServerStream
}
func (x *greeterStreamHelloServer) Send(m *HelloReply) error {
return x.ServerStream.SendMsg(m)
}
func (x *greeterStreamHelloServer) Recv() (*HelloRequest, error) {
m := new(HelloRequest)
if err := x.ServerStream.RecvMsg(m); err != nil {
return nil, err
}
return m, nil
}
var _Greeter_serviceDesc = grpc.ServiceDesc{
ServiceName: "testproto.Greeter",
HandlerType: (*GreeterServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "SayHello",
Handler: _Greeter_SayHello_Handler,
},
},
Streams: []grpc.StreamDesc{
{
StreamName: "StreamHello",
Handler: _Greeter_StreamHello_Handler,
ServerStreams: true,
ClientStreams: true,
},
},
Metadata: "hello.proto",
}
func (m *HelloRequest) Marshal() (dAtA []byte, err error) {
size := m.Size()
dAtA = make([]byte, size)
n, err := m.MarshalTo(dAtA)
if err != nil {
return nil, err
}
return dAtA[:n], nil
}
func (m *HelloRequest) MarshalTo(dAtA []byte) (int, error) {
var i int
_ = i
var l int
_ = l
if len(m.Name) > 0 {
dAtA[i] = 0xa
i++
i = encodeVarintHello(dAtA, i, uint64(len(m.Name)))
i += copy(dAtA[i:], m.Name)
}
if m.Age != 0 {
dAtA[i] = 0x10
i++
i = encodeVarintHello(dAtA, i, uint64(m.Age))
}
return i, nil
}
func (m *HelloReply) Marshal() (dAtA []byte, err error) {
size := m.Size()
dAtA = make([]byte, size)
n, err := m.MarshalTo(dAtA)
if err != nil {
return nil, err
}
return dAtA[:n], nil
}
func (m *HelloReply) MarshalTo(dAtA []byte) (int, error) {
var i int
_ = i
var l int
_ = l
if len(m.Message) > 0 {
dAtA[i] = 0xa
i++
i = encodeVarintHello(dAtA, i, uint64(len(m.Message)))
i += copy(dAtA[i:], m.Message)
}
if m.Success {
dAtA[i] = 0x10
i++
if m.Success {
dAtA[i] = 1
} else {
dAtA[i] = 0
}
i++
}
return i, nil
}
func encodeVarintHello(dAtA []byte, offset int, v uint64) int {
for v >= 1<<7 {
dAtA[offset] = uint8(v&0x7f | 0x80)
v >>= 7
offset++
}
dAtA[offset] = uint8(v)
return offset + 1
}
func (m *HelloRequest) Size() (n int) {
var l int
_ = l
l = len(m.Name)
if l > 0 {
n += 1 + l + sovHello(uint64(l))
}
if m.Age != 0 {
n += 1 + sovHello(uint64(m.Age))
}
return n
}
func (m *HelloReply) Size() (n int) {
var l int
_ = l
l = len(m.Message)
if l > 0 {
n += 1 + l + sovHello(uint64(l))
}
if m.Success {
n += 2
}
return n
}
func sovHello(x uint64) (n int) {
for {
n++
x >>= 7
if x == 0 {
break
}
}
return n
}
func sozHello(x uint64) (n int) {
return sovHello(uint64((x << 1) ^ uint64((int64(x) >> 63))))
}
func (m *HelloRequest) Unmarshal(dAtA []byte) error {
l := len(dAtA)
iNdEx := 0
for iNdEx < l {
preIndex := iNdEx
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowHello
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
fieldNum := int32(wire >> 3)
wireType := int(wire & 0x7)
if wireType == 4 {
return fmt.Errorf("proto: HelloRequest: wiretype end group for non-group")
}
if fieldNum <= 0 {
return fmt.Errorf("proto: HelloRequest: illegal tag %d (wire type %d)", fieldNum, wire)
}
switch fieldNum {
case 1:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowHello
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return ErrInvalidLengthHello
}
postIndex := iNdEx + intStringLen
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Name = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
case 2:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field Age", wireType)
}
m.Age = 0
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowHello
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
m.Age |= (int32(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
default:
iNdEx = preIndex
skippy, err := skipHello(dAtA[iNdEx:])
if err != nil {
return err
}
if skippy < 0 {
return ErrInvalidLengthHello
}
if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF
}
iNdEx += skippy
}
}
if iNdEx > l {
return io.ErrUnexpectedEOF
}
return nil
}
func (m *HelloReply) Unmarshal(dAtA []byte) error {
l := len(dAtA)
iNdEx := 0
for iNdEx < l {
preIndex := iNdEx
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowHello
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
fieldNum := int32(wire >> 3)
wireType := int(wire & 0x7)
if wireType == 4 {
return fmt.Errorf("proto: HelloReply: wiretype end group for non-group")
}
if fieldNum <= 0 {
return fmt.Errorf("proto: HelloReply: illegal tag %d (wire type %d)", fieldNum, wire)
}
switch fieldNum {
case 1:
if wireType != 2 {
return fmt.Errorf("proto: wrong wireType = %d for field Message", wireType)
}
var stringLen uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowHello
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
stringLen |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
intStringLen := int(stringLen)
if intStringLen < 0 {
return ErrInvalidLengthHello
}
postIndex := iNdEx + intStringLen
if postIndex > l {
return io.ErrUnexpectedEOF
}
m.Message = string(dAtA[iNdEx:postIndex])
iNdEx = postIndex
case 2:
if wireType != 0 {
return fmt.Errorf("proto: wrong wireType = %d for field Success", wireType)
}
var v int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return ErrIntOverflowHello
}
if iNdEx >= l {
return io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
v |= (int(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
m.Success = bool(v != 0)
default:
iNdEx = preIndex
skippy, err := skipHello(dAtA[iNdEx:])
if err != nil {
return err
}
if skippy < 0 {
return ErrInvalidLengthHello
}
if (iNdEx + skippy) > l {
return io.ErrUnexpectedEOF
}
iNdEx += skippy
}
}
if iNdEx > l {
return io.ErrUnexpectedEOF
}
return nil
}
func skipHello(dAtA []byte) (n int, err error) {
l := len(dAtA)
iNdEx := 0
for iNdEx < l {
var wire uint64
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflowHello
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
wire |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
wireType := int(wire & 0x7)
switch wireType {
case 0:
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflowHello
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
iNdEx++
if dAtA[iNdEx-1] < 0x80 {
break
}
}
return iNdEx, nil
case 1:
iNdEx += 8
return iNdEx, nil
case 2:
var length int
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflowHello
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
length |= (int(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
iNdEx += length
if length < 0 {
return 0, ErrInvalidLengthHello
}
return iNdEx, nil
case 3:
for {
var innerWire uint64
var start int = iNdEx
for shift := uint(0); ; shift += 7 {
if shift >= 64 {
return 0, ErrIntOverflowHello
}
if iNdEx >= l {
return 0, io.ErrUnexpectedEOF
}
b := dAtA[iNdEx]
iNdEx++
innerWire |= (uint64(b) & 0x7F) << shift
if b < 0x80 {
break
}
}
innerWireType := int(innerWire & 0x7)
if innerWireType == 4 {
break
}
next, err := skipHello(dAtA[start:])
if err != nil {
return 0, err
}
iNdEx = start + next
}
return iNdEx, nil
case 4:
return iNdEx, nil
case 5:
iNdEx += 4
return iNdEx, nil
default:
return 0, fmt.Errorf("proto: illegal wireType %d", wireType)
}
}
panic("unreachable")
}
var (
ErrInvalidLengthHello = fmt.Errorf("proto: negative length found during unmarshaling")
ErrIntOverflowHello = fmt.Errorf("proto: integer overflow")
)
func init() { proto.RegisterFile("hello.proto", fileDescriptorHello) }
var fileDescriptorHello = []byte{
// 296 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x9c, 0x90, 0x3f, 0x4e, 0xc3, 0x30,
0x14, 0xc6, 0x63, 0xfe, 0xb5, 0x75, 0x19, 0x90, 0x11, 0x22, 0x2a, 0x92, 0x53, 0x79, 0xca, 0xd2,
0xb4, 0xa2, 0x1b, 0x02, 0x09, 0x85, 0x01, 0xe6, 0xf4, 0x04, 0x4e, 0xfa, 0x48, 0x23, 0x25, 0x75,
0x6a, 0x3b, 0x48, 0xb9, 0x03, 0x07, 0xe0, 0x48, 0x1d, 0x7b, 0x82, 0x88, 0x86, 0xad, 0x63, 0x4f,
0x80, 0x62, 0x28, 0x20, 0xb1, 0x75, 0x7b, 0x3f, 0x7f, 0xfa, 0x7e, 0x4f, 0x7e, 0xb8, 0x3b, 0x83,
0x34, 0x15, 0x5e, 0x2e, 0x85, 0x16, 0xa4, 0xa3, 0x41, 0x69, 0x33, 0xf6, 0x06, 0x71, 0xa2, 0x67,
0x45, 0xe8, 0x45, 0x22, 0x1b, 0xc6, 0x22, 0x16, 0x43, 0xf3, 0x1c, 0x16, 0xcf, 0x86, 0x0c, 0x98,
0xe9, 0xab, 0xc9, 0x24, 0x3e, 0x7d, 0x6a, 0x44, 0x01, 0x2c, 0x0a, 0x50, 0x9a, 0x8c, 0xf1, 0xd1,
0x9c, 0x67, 0x60, 0xa3, 0x3e, 0x72, 0x3b, 0xbe, 0xb3, 0xa9, 0x1c, 0xc3, 0xdb, 0xca, 0x39, 0x7f,
0xe1, 0x69, 0x32, 0xe5, 0x1a, 0x6e, 0x98, 0x84, 0x45, 0x91, 0x48, 0x98, 0xb2, 0xc0, 0x84, 0x64,
0x80, 0x0f, 0x79, 0x0c, 0xf6, 0x41, 0x1f, 0xb9, 0xc7, 0xfe, 0xd5, 0xa6, 0x72, 0x1a, 0xdc, 0x56,
0xce, 0xd9, 0x6f, 0x25, 0x4b, 0xe6, 0x77, 0x23, 0x16, 0x34, 0x01, 0xbb, 0xc7, 0xf8, 0x7b, 0x67,
0x9e, 0x96, 0xc4, 0xc6, 0xad, 0x0c, 0x94, 0x6a, 0x04, 0x66, 0x69, 0xb0, 0xc3, 0x26, 0x51, 0x45,
0x14, 0x81, 0x52, 0x46, 0xdd, 0x0e, 0x76, 0x78, 0xfd, 0x8a, 0x70, 0xeb, 0x51, 0x02, 0x68, 0x90,
0xe4, 0x16, 0xb7, 0x27, 0xbc, 0x34, 0x42, 0x72, 0xe9, 0xfd, 0x1c, 0xc2, 0xfb, 0xfb, 0xad, 0xde,
0xc5, 0xff, 0x20, 0x4f, 0x4b, 0x66, 0x91, 0x07, 0xdc, 0x9d, 0x68, 0x09, 0x3c, 0xdb, 0x53, 0xe0,
0xa2, 0x11, 0xf2, 0xed, 0xe5, 0x9a, 0x5a, 0xab, 0x35, 0xb5, 0x96, 0x35, 0x45, 0xab, 0x9a, 0xa2,
0xf7, 0x9a, 0xa2, 0xb7, 0x0f, 0x6a, 0x85, 0x27, 0xa6, 0x31, 0xfe, 0x0c, 0x00, 0x00, 0xff, 0xff,
0x13, 0x57, 0x88, 0x03, 0xae, 0x01, 0x00, 0x00,
}

View File

@@ -0,0 +1,32 @@
syntax = "proto3";
package testproto;
import "github.com/gogo/protobuf/gogoproto/gogo.proto";
option (gogoproto.goproto_enum_prefix_all) = false;
option (gogoproto.goproto_getters_all) = false;
option (gogoproto.unmarshaler_all) = true;
option (gogoproto.marshaler_all) = true;
option (gogoproto.sizer_all) = true;
// The greeting service definition.
service Greeter {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply) {}
// A bidirectional streaming RPC call recvice HelloRequest return HelloReply
rpc StreamHello(stream HelloRequest) returns (stream HelloReply) {}
}
// The request message containing the user's name.
message HelloRequest {
string name = 1 [(gogoproto.jsontag) = "name", (gogoproto.moretags) = "validate:\"required\""];
int32 age = 2 [(gogoproto.jsontag) = "age", (gogoproto.moretags) = "validate:\"min=0\""];
}
// The response message containing the greetings
message HelloReply {
string message = 1;
bool success = 2;
}

View File

@@ -0,0 +1,61 @@
package warden
import (
"context"
"fmt"
"os"
"runtime"
"go-common/library/ecode"
"go-common/library/log"
"google.golang.org/grpc"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
)
// recovery is a server interceptor that recovers from any panics.
func (s *Server) recovery() grpc.UnaryServerInterceptor {
return func(ctx context.Context, req interface{}, args *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (resp interface{}, err error) {
defer func() {
if rerr := recover(); rerr != nil {
const size = 64 << 10
buf := make([]byte, size)
rs := runtime.Stack(buf, false)
if rs > size {
rs = size
}
buf = buf[:rs]
pl := fmt.Sprintf("grpc server panic: %v\n%v\n%s\n", req, rerr, buf)
fmt.Fprintf(os.Stderr, pl)
log.Error(pl)
err = status.Errorf(codes.Unknown, ecode.ServerErr.Error())
}
}()
resp, err = handler(ctx, req)
return
}
}
// recovery return a client interceptor that recovers from any panics.
func (c *Client) recovery() grpc.UnaryClientInterceptor {
return func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) (err error) {
defer func() {
if rerr := recover(); rerr != nil {
const size = 64 << 10
buf := make([]byte, size)
rs := runtime.Stack(buf, false)
if rs > size {
rs = size
}
buf = buf[:rs]
pl := fmt.Sprintf("grpc client panic: %v\n%v\n%v\n%s\n", req, reply, rerr, buf)
fmt.Fprintf(os.Stderr, pl)
log.Error(pl)
err = ecode.ServerErr
}
}()
err = invoker(ctx, method, req, reply, cc, opts...)
return
}
}

View File

@@ -0,0 +1,45 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = [
"resolver.go",
"util.go",
],
importpath = "go-common/library/net/rpc/warden/resolver",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//library/conf/env:go_default_library",
"//library/log:go_default_library",
"//library/naming:go_default_library",
"//library/net/rpc/warden/metadata:go_default_library",
"//vendor/github.com/dgryski/go-farm:go_default_library",
"//vendor/github.com/pkg/errors:go_default_library",
"@org_golang_google_grpc//resolver:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//library/net/rpc/warden/resolver/direct:all-srcs",
"//library/net/rpc/warden/resolver/livezk:all-srcs",
"//library/net/rpc/warden/resolver/test:all-srcs",
],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,14 @@
### business/warden/resolver
##### Version 1.1.0
1. 增加了子集选择算法
##### Version 1.0.2
1. 增加GET接口
##### Version 1.0.1
1. 支持zone和clusters
##### Version 1.0.0
1. 实现了基本的服务发现功能

View File

@@ -0,0 +1,8 @@
# Owner
caoguoliang
# Author
caoguoliang
# Reviewer
maojian

View File

@@ -0,0 +1,7 @@
# See the OWNERS docs at https://go.k8s.io/owners
approvers:
- caoguoliang
reviewers:
- caoguoliang
- maojian

View File

@@ -0,0 +1,13 @@
#### business/warden/resolver
##### 项目简介
warden 的 服务发现模块用于从底层的注册中心中获取Server节点列表并返回给GRPC
##### 编译环境
- **请只用 Golang v1.9.x 以上版本编译执行**
##### 依赖包
- [grpc](google.golang.org/grpc)

View File

@@ -0,0 +1,49 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_test",
"go_library",
)
go_test(
name = "go_default_test",
srcs = ["direct_test.go"],
embed = [":go_default_library"],
rundir = ".",
tags = ["automanaged"],
deps = [
"//library/net/netutil/breaker:go_default_library",
"//library/net/rpc/warden:go_default_library",
"//library/net/rpc/warden/proto/testproto:go_default_library",
"//library/net/rpc/warden/resolver:go_default_library",
"//library/time:go_default_library",
],
)
go_library(
name = "go_default_library",
srcs = ["direct.go"],
importpath = "go-common/library/net/rpc/warden/resolver/direct",
tags = ["automanaged"],
visibility = ["//visibility:public"],
deps = [
"//library/conf/env:go_default_library",
"//library/naming:go_default_library",
"//library/net/rpc/warden/resolver:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
visibility = ["//visibility:public"],
)

View File

@@ -0,0 +1,6 @@
### business/warden/resolver/direct
##### Version 1.0.0
1. 实现了基本的服务发现直连功能

View File

@@ -0,0 +1,14 @@
#### business/warden/resolver/direct
##### 项目简介
warden 的直连服务模块用于通过IP地址列表直接连接后端服务
连接字符串格式 direct://default/192.168.1.1:8080,192.168.1.2:8081
##### 编译环境
- **请只用 Golang v1.9.x 以上版本编译执行**
##### 依赖包
- [grpc](google.golang.org/grpc)

Some files were not shown because too many files have changed in this diff Show More