add p2c peak ewma load balancer
This commit is contained in:
@@ -18,20 +18,13 @@ import (
|
|||||||
func gracefulHandler(ctx *svc.ServiceContext) http.HandlerFunc {
|
func gracefulHandler(ctx *svc.ServiceContext) http.HandlerFunc {
|
||||||
logger := executors.NewLessExecutor(time.Second)
|
logger := executors.NewLessExecutor(time.Second)
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
var resp types.Response
|
|
||||||
|
|
||||||
conn, ok := ctx.Client.Next()
|
|
||||||
if !ok {
|
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
host, err := os.Hostname()
|
host, err := os.Hostname()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, http.StatusText(http.StatusNotImplemented), http.StatusNotImplemented)
|
http.Error(w, http.StatusText(http.StatusNotImplemented), http.StatusNotImplemented)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
conn := ctx.Client.Conn()
|
||||||
client := graceful.NewGraceServiceClient(conn)
|
client := graceful.NewGraceServiceClient(conn)
|
||||||
rp, err := client.Grace(context.Background(), &graceful.Request{From: host})
|
rp, err := client.Grace(context.Background(), &graceful.Request{From: host})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -40,6 +33,7 @@ func gracefulHandler(ctx *svc.ServiceContext) http.HandlerFunc {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var resp types.Response
|
||||||
resp.Host = rp.Host
|
resp.Host = rp.Host
|
||||||
logger.DoOrDiscard(func() {
|
logger.DoOrDiscard(func() {
|
||||||
fmt.Printf("%s from host: %s\n", time.Now().Format("15:04:05"), rp.Host)
|
fmt.Printf("%s from host: %s\n", time.Now().Format("15:04:05"), rp.Host)
|
||||||
|
|||||||
@@ -18,20 +18,13 @@ import (
|
|||||||
func gracefulHandler(ctx *svc.ServiceContext) http.HandlerFunc {
|
func gracefulHandler(ctx *svc.ServiceContext) http.HandlerFunc {
|
||||||
logger := executors.NewLessExecutor(time.Second)
|
logger := executors.NewLessExecutor(time.Second)
|
||||||
return func(w http.ResponseWriter, r *http.Request) {
|
return func(w http.ResponseWriter, r *http.Request) {
|
||||||
var resp types.Response
|
|
||||||
|
|
||||||
conn, ok := ctx.Client.Next()
|
|
||||||
if !ok {
|
|
||||||
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
host, err := os.Hostname()
|
host, err := os.Hostname()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, http.StatusText(http.StatusNotImplemented), http.StatusNotImplemented)
|
http.Error(w, http.StatusText(http.StatusNotImplemented), http.StatusNotImplemented)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
conn := ctx.Client.Conn()
|
||||||
client := graceful.NewGraceServiceClient(conn)
|
client := graceful.NewGraceServiceClient(conn)
|
||||||
rp, err := client.Grace(context.Background(), &graceful.Request{From: host})
|
rp, err := client.Grace(context.Background(), &graceful.Request{From: host})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -40,6 +33,7 @@ func gracefulHandler(ctx *svc.ServiceContext) http.HandlerFunc {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var resp types.Response
|
||||||
resp.Host = rp.Host
|
resp.Host = rp.Host
|
||||||
logger.DoOrDiscard(func() {
|
logger.DoOrDiscard(func() {
|
||||||
fmt.Printf("%s from host: %s\n", time.Now().Format("15:04:05"), rp.Host)
|
fmt.Printf("%s from host: %s\n", time.Now().Format("15:04:05"), rp.Host)
|
||||||
|
|||||||
@@ -28,12 +28,7 @@ func main() {
|
|||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-ticker.C:
|
case <-ticker.C:
|
||||||
conn, ok := client.Next()
|
conn := client.Conn()
|
||||||
if !ok {
|
|
||||||
time.Sleep(time.Second)
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
greet := unary.NewGreeterClient(conn)
|
greet := unary.NewGreeterClient(conn)
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
|
||||||
resp, err := greet.Greet(ctx, &unary.Request{
|
resp, err := greet.Greet(ctx, &unary.Request{
|
||||||
|
|||||||
37
example/rpc/client/lb/main.go
Normal file
37
example/rpc/client/lb/main.go
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"zero/core/discov"
|
||||||
|
"zero/example/rpc/remote/unary"
|
||||||
|
"zero/rpcx"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
cli := rpcx.MustNewClient(rpcx.RpcClientConf{
|
||||||
|
Etcd: discov.EtcdConf{
|
||||||
|
Hosts: []string{"localhost:2379"},
|
||||||
|
Key: "rpcx",
|
||||||
|
},
|
||||||
|
})
|
||||||
|
greet := unary.NewGreeterClient(cli.Conn())
|
||||||
|
ticker := time.NewTicker(time.Second)
|
||||||
|
defer ticker.Stop()
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-ticker.C:
|
||||||
|
resp, err := greet.Greet(context.Background(), &unary.Request{
|
||||||
|
Name: "kevin",
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("X", err.Error())
|
||||||
|
} else {
|
||||||
|
fmt.Println("=>", resp.Greet)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -26,11 +26,7 @@ func main() {
|
|||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
conn, ok := client.Next()
|
conn := client.Conn()
|
||||||
if !ok {
|
|
||||||
log.Fatal("no server")
|
|
||||||
}
|
|
||||||
|
|
||||||
greet := stream.NewStreamGreeterClient(conn)
|
greet := stream.NewStreamGreeterClient(conn)
|
||||||
stm, err := greet.Greet(context.Background())
|
stm, err := greet.Greet(context.Background())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|||||||
@@ -4,7 +4,6 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"zero/core/conf"
|
"zero/core/conf"
|
||||||
@@ -25,11 +24,7 @@ func main() {
|
|||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case <-ticker.C:
|
case <-ticker.C:
|
||||||
conn, ok := client.Next()
|
conn := client.Conn()
|
||||||
if !ok {
|
|
||||||
log.Fatal("no server")
|
|
||||||
}
|
|
||||||
|
|
||||||
greet := unary.NewGreeterClient(conn)
|
greet := unary.NewGreeterClient(conn)
|
||||||
resp, err := greet.Greet(context.Background(), &unary.Request{
|
resp, err := greet.Greet(context.Background(), &unary.Request{
|
||||||
Name: "kevin",
|
Name: "kevin",
|
||||||
|
|||||||
@@ -1,17 +1,13 @@
|
|||||||
{
|
{
|
||||||
"Name": "rpc.unary",
|
"Name": "rpc.unary",
|
||||||
"MetricsUrl": "http://localhost:2222/add",
|
"Log": {
|
||||||
|
"Mode": "volume"
|
||||||
|
},
|
||||||
"ListenOn": "localhost:3457",
|
"ListenOn": "localhost:3457",
|
||||||
"Auth": false,
|
|
||||||
"Etcd": {
|
"Etcd": {
|
||||||
"Hosts": [
|
"Hosts": [
|
||||||
"localhost:2379"
|
"localhost:2379"
|
||||||
],
|
],
|
||||||
"Key": "rpcx"
|
"Key": "rpcx"
|
||||||
},
|
|
||||||
"Redis": {
|
|
||||||
"Host": "localhost:6379",
|
|
||||||
"Type": "node",
|
|
||||||
"Key": "apps"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"flag"
|
"flag"
|
||||||
"log"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"zero/core/conf"
|
"zero/core/conf"
|
||||||
@@ -20,11 +19,7 @@ var (
|
|||||||
)
|
)
|
||||||
|
|
||||||
func handle(w http.ResponseWriter, r *http.Request) {
|
func handle(w http.ResponseWriter, r *http.Request) {
|
||||||
conn, ok := client.Next()
|
conn := client.Conn()
|
||||||
if !ok {
|
|
||||||
log.Fatal("no server")
|
|
||||||
}
|
|
||||||
|
|
||||||
greet := portal.NewPortalClient(conn)
|
greet := portal.NewPortalClient(conn)
|
||||||
resp, err := greet.Portal(r.Context(), &portal.PortalRequest{
|
resp, err := greet.Portal(r.Context(), &portal.PortalRequest{
|
||||||
Name: "kevin",
|
Name: "kevin",
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
"flag"
|
"flag"
|
||||||
|
|
||||||
"zero/core/conf"
|
"zero/core/conf"
|
||||||
@@ -33,11 +32,7 @@ func NewPortalServer(client *rpcx.RpcClient) *PortalServer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (gs *PortalServer) Portal(ctx context.Context, req *portal.PortalRequest) (*portal.PortalResponse, error) {
|
func (gs *PortalServer) Portal(ctx context.Context, req *portal.PortalRequest) (*portal.PortalResponse, error) {
|
||||||
conn, ok := gs.userRpc.Next()
|
conn := gs.userRpc.Conn()
|
||||||
if !ok {
|
|
||||||
return nil, errors.New("internal error")
|
|
||||||
}
|
|
||||||
|
|
||||||
greet := user.NewUserClient(conn)
|
greet := user.NewUserClient(conn)
|
||||||
resp, err := greet.GetGrade(ctx, &user.UserRequest{
|
resp, err := greet.GetGrade(ctx, &user.UserRequest{
|
||||||
Name: req.Name,
|
Name: req.Name,
|
||||||
|
|||||||
@@ -42,7 +42,7 @@ func NewClient(c RpcClientConf, options ...internal.ClientOption) (*RpcClient, e
|
|||||||
if len(c.Server) > 0 {
|
if len(c.Server) > 0 {
|
||||||
client, err = internal.NewDirectClient(c.Server, opts...)
|
client, err = internal.NewDirectClient(c.Server, opts...)
|
||||||
} else if err = c.Etcd.Validate(); err == nil {
|
} else if err = c.Etcd.Validate(); err == nil {
|
||||||
client, err = internal.NewRoundRobinRpcClient(c.Etcd.Hosts, c.Etcd.Key, opts...)
|
client, err = internal.NewDiscovClient(c.Etcd.Hosts, c.Etcd.Key, opts...)
|
||||||
}
|
}
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
@@ -54,7 +54,7 @@ func NewClient(c RpcClientConf, options ...internal.ClientOption) (*RpcClient, e
|
|||||||
}
|
}
|
||||||
|
|
||||||
func NewClientNoAuth(c discov.EtcdConf) (*RpcClient, error) {
|
func NewClientNoAuth(c discov.EtcdConf) (*RpcClient, error) {
|
||||||
client, err := internal.NewRoundRobinRpcClient(c.Hosts, c.Key)
|
client, err := internal.NewDiscovClient(c.Hosts, c.Key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -64,6 +64,6 @@ func NewClientNoAuth(c discov.EtcdConf) (*RpcClient, error) {
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (rc *RpcClient) Next() (*grpc.ClientConn, bool) {
|
func (rc *RpcClient) Conn() *grpc.ClientConn {
|
||||||
return rc.client.Next()
|
return rc.client.Conn()
|
||||||
}
|
}
|
||||||
|
|||||||
172
rpcx/internal/balancer/p2c/p2c.go
Normal file
172
rpcx/internal/balancer/p2c/p2c.go
Normal file
@@ -0,0 +1,172 @@
|
|||||||
|
package p2c
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"math"
|
||||||
|
"math/rand"
|
||||||
|
"sync"
|
||||||
|
"sync/atomic"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"zero/core/timex"
|
||||||
|
"zero/rpcx/internal/codes"
|
||||||
|
|
||||||
|
"google.golang.org/grpc/balancer"
|
||||||
|
"google.golang.org/grpc/balancer/base"
|
||||||
|
"google.golang.org/grpc/resolver"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
Name = "p2c_ewma"
|
||||||
|
decayTime = int64(time.Millisecond * 600)
|
||||||
|
forcePick = int64(time.Second)
|
||||||
|
initSuccess = 1000
|
||||||
|
throttleSuccess = initSuccess / 2
|
||||||
|
penalty = int64(math.MaxInt32)
|
||||||
|
pickTimes = 3
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
balancer.Register(newBuilder())
|
||||||
|
}
|
||||||
|
|
||||||
|
type p2cPickerBuilder struct {
|
||||||
|
}
|
||||||
|
|
||||||
|
func newBuilder() balancer.Builder {
|
||||||
|
return base.NewBalancerBuilder(Name, new(p2cPickerBuilder))
|
||||||
|
}
|
||||||
|
|
||||||
|
func (b *p2cPickerBuilder) Build(readySCs map[resolver.Address]balancer.SubConn) balancer.Picker {
|
||||||
|
if len(readySCs) == 0 {
|
||||||
|
return base.NewErrPicker(balancer.ErrNoSubConnAvailable)
|
||||||
|
}
|
||||||
|
|
||||||
|
var conns []*subConn
|
||||||
|
for addr, conn := range readySCs {
|
||||||
|
conns = append(conns, &subConn{
|
||||||
|
addr: addr,
|
||||||
|
conn: conn,
|
||||||
|
success: initSuccess,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return &p2cPicker{
|
||||||
|
conns: conns,
|
||||||
|
r: rand.New(rand.NewSource(time.Now().UnixNano())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type p2cPicker struct {
|
||||||
|
conns []*subConn
|
||||||
|
r *rand.Rand
|
||||||
|
lock sync.Mutex
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *p2cPicker) Pick(ctx context.Context, info balancer.PickInfo) (
|
||||||
|
conn balancer.SubConn, done func(balancer.DoneInfo), err error) {
|
||||||
|
p.lock.Lock()
|
||||||
|
defer p.lock.Unlock()
|
||||||
|
|
||||||
|
var chosen *subConn
|
||||||
|
switch len(p.conns) {
|
||||||
|
case 0:
|
||||||
|
return nil, nil, balancer.ErrNoSubConnAvailable
|
||||||
|
case 1:
|
||||||
|
chosen = p.choose(p.conns[0], nil)
|
||||||
|
case 2:
|
||||||
|
chosen = p.choose(p.conns[0], p.conns[1])
|
||||||
|
default:
|
||||||
|
var node1, node2 *subConn
|
||||||
|
for i := 0; i < pickTimes; i++ {
|
||||||
|
a := p.r.Intn(len(p.conns))
|
||||||
|
b := p.r.Intn(len(p.conns) - 1)
|
||||||
|
if b >= a {
|
||||||
|
b++
|
||||||
|
}
|
||||||
|
node1 = p.conns[a]
|
||||||
|
node2 = p.conns[b]
|
||||||
|
if node1.healthy() && node2.healthy() {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
chosen = p.choose(node1, node2)
|
||||||
|
}
|
||||||
|
|
||||||
|
atomic.AddInt64(&chosen.inflight, 1)
|
||||||
|
return chosen.conn, p.buildDoneFunc(chosen), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *p2cPicker) buildDoneFunc(c *subConn) func(info balancer.DoneInfo) {
|
||||||
|
start := int64(timex.Now())
|
||||||
|
return func(info balancer.DoneInfo) {
|
||||||
|
atomic.AddInt64(&c.inflight, -1)
|
||||||
|
now := int64(timex.Now())
|
||||||
|
last := atomic.SwapInt64(&c.last, int64(now))
|
||||||
|
td := now - last
|
||||||
|
if td < 0 {
|
||||||
|
td = 0
|
||||||
|
}
|
||||||
|
w := math.Exp(float64(-td) / float64(decayTime))
|
||||||
|
lag := now - start
|
||||||
|
if lag < 0 {
|
||||||
|
lag = 0
|
||||||
|
}
|
||||||
|
olag := atomic.LoadUint64(&c.lag)
|
||||||
|
if olag == 0 {
|
||||||
|
w = 0
|
||||||
|
}
|
||||||
|
atomic.StoreUint64(&c.lag, uint64(float64(olag)*w+float64(lag)*(1-w)))
|
||||||
|
success := initSuccess
|
||||||
|
if info.Err != nil && !codes.Acceptable(info.Err) {
|
||||||
|
success = 0
|
||||||
|
}
|
||||||
|
osucc := atomic.LoadUint64(&c.success)
|
||||||
|
atomic.StoreUint64(&c.success, uint64(float64(osucc)*w+float64(success)*(1-w)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *p2cPicker) choose(c1, c2 *subConn) *subConn {
|
||||||
|
start := int64(timex.Now())
|
||||||
|
if c2 == nil {
|
||||||
|
atomic.StoreInt64(&c1.pick, start)
|
||||||
|
return c1
|
||||||
|
}
|
||||||
|
|
||||||
|
if c1.load() > c2.load() {
|
||||||
|
c1, c2 = c2, c1
|
||||||
|
}
|
||||||
|
|
||||||
|
pick := atomic.LoadInt64(&c2.pick)
|
||||||
|
if start-pick > forcePick && atomic.CompareAndSwapInt64(&c2.pick, pick, start) {
|
||||||
|
return c2
|
||||||
|
} else {
|
||||||
|
atomic.StoreInt64(&c1.pick, start)
|
||||||
|
return c1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type subConn struct {
|
||||||
|
addr resolver.Address
|
||||||
|
conn balancer.SubConn
|
||||||
|
lag uint64
|
||||||
|
inflight int64
|
||||||
|
success uint64
|
||||||
|
last int64
|
||||||
|
pick int64
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *subConn) healthy() bool {
|
||||||
|
return atomic.LoadUint64(&c.success) > throttleSuccess
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *subConn) load() int64 {
|
||||||
|
lag := int64(math.Sqrt(float64(atomic.LoadUint64(&c.lag) + 1)))
|
||||||
|
load := lag * atomic.LoadInt64(&c.inflight)
|
||||||
|
if load == 0 {
|
||||||
|
return penalty
|
||||||
|
} else {
|
||||||
|
return load
|
||||||
|
}
|
||||||
|
}
|
||||||
7
rpcx/internal/balancer/p2c/p2c_test.go
Normal file
7
rpcx/internal/balancer/p2c/p2c_test.go
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
package p2c
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestP2cPicker_Pick(t *testing.T) {
|
||||||
|
|
||||||
|
}
|
||||||
@@ -2,7 +2,6 @@ package roundrobin
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
|
||||||
"math/rand"
|
"math/rand"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
@@ -12,20 +11,23 @@ import (
|
|||||||
"google.golang.org/grpc/resolver"
|
"google.golang.org/grpc/resolver"
|
||||||
)
|
)
|
||||||
|
|
||||||
const Name = "zero_rr"
|
const Name = "rr"
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
balancer.Register(newRoundRobinBuilder())
|
balancer.Register(newBuilder())
|
||||||
}
|
}
|
||||||
|
|
||||||
type roundRobinPickerBuilder struct {
|
type roundRobinPickerBuilder struct{}
|
||||||
}
|
|
||||||
|
|
||||||
func newRoundRobinBuilder() balancer.Builder {
|
func newBuilder() balancer.Builder {
|
||||||
return base.NewBalancerBuilder(Name, new(roundRobinPickerBuilder))
|
return base.NewBalancerBuilder(Name, new(roundRobinPickerBuilder))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *roundRobinPickerBuilder) Build(readySCs map[resolver.Address]balancer.SubConn) balancer.Picker {
|
func (b *roundRobinPickerBuilder) Build(readySCs map[resolver.Address]balancer.SubConn) balancer.Picker {
|
||||||
|
if len(readySCs) == 0 {
|
||||||
|
return base.NewErrPicker(balancer.ErrNoSubConnAvailable)
|
||||||
|
}
|
||||||
|
|
||||||
rand.Seed(time.Now().UnixNano())
|
rand.Seed(time.Now().UnixNano())
|
||||||
picker := &roundRobinPicker{
|
picker := &roundRobinPicker{
|
||||||
index: rand.Int(),
|
index: rand.Int(),
|
||||||
@@ -49,7 +51,6 @@ type roundRobinPicker struct {
|
|||||||
|
|
||||||
func (p *roundRobinPicker) Pick(ctx context.Context, info balancer.PickInfo) (
|
func (p *roundRobinPicker) Pick(ctx context.Context, info balancer.PickInfo) (
|
||||||
conn balancer.SubConn, done func(balancer.DoneInfo), err error) {
|
conn balancer.SubConn, done func(balancer.DoneInfo), err error) {
|
||||||
fmt.Println(p.conns)
|
|
||||||
p.lock.Lock()
|
p.lock.Lock()
|
||||||
defer p.lock.Unlock()
|
defer p.lock.Unlock()
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ type (
|
|||||||
ClientOption func(options *ClientOptions)
|
ClientOption func(options *ClientOptions)
|
||||||
|
|
||||||
Client interface {
|
Client interface {
|
||||||
Next() (*grpc.ClientConn, bool)
|
Conn() *grpc.ClientConn
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -5,25 +5,15 @@ import (
|
|||||||
"path"
|
"path"
|
||||||
|
|
||||||
"zero/core/breaker"
|
"zero/core/breaker"
|
||||||
|
"zero/rpcx/internal/codes"
|
||||||
|
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
"google.golang.org/grpc/codes"
|
|
||||||
"google.golang.org/grpc/status"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func acceptable(err error) bool {
|
|
||||||
switch status.Code(err) {
|
|
||||||
case codes.DeadlineExceeded, codes.Internal, codes.Unavailable, codes.DataLoss:
|
|
||||||
return false
|
|
||||||
default:
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func BreakerInterceptor(ctx context.Context, method string, req, reply interface{},
|
func BreakerInterceptor(ctx context.Context, method string, req, reply interface{},
|
||||||
cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
|
cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
|
||||||
breakerName := path.Join(cc.Target(), method)
|
breakerName := path.Join(cc.Target(), method)
|
||||||
return breaker.DoWithAcceptable(breakerName, func() error {
|
return breaker.DoWithAcceptable(breakerName, func() error {
|
||||||
return invoker(ctx, method, req, reply, cc, opts...)
|
return invoker(ctx, method, req, reply, cc, opts...)
|
||||||
}, acceptable)
|
}, codes.Acceptable)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import (
|
|||||||
|
|
||||||
"zero/core/breaker"
|
"zero/core/breaker"
|
||||||
"zero/core/stat"
|
"zero/core/stat"
|
||||||
|
rcodes "zero/rpcx/internal/codes"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
"google.golang.org/grpc/codes"
|
"google.golang.org/grpc/codes"
|
||||||
@@ -32,7 +33,7 @@ func TestBreakerInterceptorNotFound(t *testing.T) {
|
|||||||
for i := 0; i < 1000; i++ {
|
for i := 0; i < 1000; i++ {
|
||||||
assert.Equal(t, err, breaker.DoWithAcceptable("call", func() error {
|
assert.Equal(t, err, breaker.DoWithAcceptable("call", func() error {
|
||||||
return err
|
return err
|
||||||
}, acceptable))
|
}, rcodes.Acceptable))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -42,7 +43,7 @@ func TestBreakerInterceptorDeadlineExceeded(t *testing.T) {
|
|||||||
for i := 0; i < 1000; i++ {
|
for i := 0; i < 1000; i++ {
|
||||||
e := breaker.DoWithAcceptable("call", func() error {
|
e := breaker.DoWithAcceptable("call", func() error {
|
||||||
return err
|
return err
|
||||||
}, acceptable)
|
}, rcodes.Acceptable)
|
||||||
errs[e]++
|
errs[e]++
|
||||||
}
|
}
|
||||||
assert.Equal(t, 2, len(errs))
|
assert.Equal(t, 2, len(errs))
|
||||||
|
|||||||
15
rpcx/internal/codes/error.go
Normal file
15
rpcx/internal/codes/error.go
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
package codes
|
||||||
|
|
||||||
|
import (
|
||||||
|
"google.golang.org/grpc/codes"
|
||||||
|
"google.golang.org/grpc/status"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Acceptable(err error) bool {
|
||||||
|
switch status.Code(err) {
|
||||||
|
case codes.DeadlineExceeded, codes.Internal, codes.Unavailable, codes.DataLoss:
|
||||||
|
return false
|
||||||
|
default:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,7 +3,6 @@ package internal
|
|||||||
import (
|
import (
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
"google.golang.org/grpc/balancer/roundrobin"
|
"google.golang.org/grpc/balancer/roundrobin"
|
||||||
"google.golang.org/grpc/connectivity"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type DirectClient struct {
|
type DirectClient struct {
|
||||||
@@ -22,11 +21,6 @@ func NewDirectClient(server string, opts ...ClientOption) (*DirectClient, error)
|
|||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *DirectClient) Next() (*grpc.ClientConn, bool) {
|
func (c *DirectClient) Conn() *grpc.ClientConn {
|
||||||
state := c.conn.GetState()
|
return c.conn
|
||||||
if state == connectivity.Ready {
|
|
||||||
return c.conn, true
|
|
||||||
} else {
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,36 +1,35 @@
|
|||||||
package internal
|
package internal
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"zero/core/discov"
|
"fmt"
|
||||||
"zero/rpcx/internal/balancer/roundrobin"
|
"strings"
|
||||||
|
|
||||||
|
"zero/rpcx/internal/balancer/p2c"
|
||||||
"zero/rpcx/internal/resolver"
|
"zero/rpcx/internal/resolver"
|
||||||
|
|
||||||
"google.golang.org/grpc"
|
"google.golang.org/grpc"
|
||||||
"google.golang.org/grpc/connectivity"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
resolver.RegisterResolver()
|
||||||
|
}
|
||||||
|
|
||||||
type DiscovClient struct {
|
type DiscovClient struct {
|
||||||
conn *grpc.ClientConn
|
conn *grpc.ClientConn
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewDiscovClient(etcd discov.EtcdConf, opts ...ClientOption) (*DiscovClient, error) {
|
func NewDiscovClient(endpoints []string, key string, opts ...ClientOption) (*DiscovClient, error) {
|
||||||
resolver.RegisterResolver(etcd)
|
opts = append(opts, WithDialOption(grpc.WithBalancerName(p2c.Name)))
|
||||||
opts = append(opts, WithDialOption(grpc.WithBalancerName(roundrobin.Name)))
|
target := fmt.Sprintf("%s://%s/%s", resolver.DiscovScheme,
|
||||||
conn, err := dial("discov:///", opts...)
|
strings.Join(endpoints, resolver.EndpointSep), key)
|
||||||
|
conn, err := dial(target, opts...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &DiscovClient{
|
return &DiscovClient{conn: conn}, nil
|
||||||
conn: conn,
|
|
||||||
}, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *DiscovClient) Next() (*grpc.ClientConn, bool) {
|
func (c *DiscovClient) Conn() *grpc.ClientConn {
|
||||||
state := c.conn.GetState()
|
return c.conn
|
||||||
if state == connectivity.Ready {
|
|
||||||
return c.conn, true
|
|
||||||
} else {
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,32 +1,42 @@
|
|||||||
package resolver
|
package resolver
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"zero/core/discov"
|
"zero/core/discov"
|
||||||
|
|
||||||
"google.golang.org/grpc/resolver"
|
"google.golang.org/grpc/resolver"
|
||||||
)
|
)
|
||||||
|
|
||||||
const discovScheme = "discov"
|
const (
|
||||||
|
DiscovScheme = "discov"
|
||||||
|
EndpointSep = ","
|
||||||
|
)
|
||||||
|
|
||||||
type discovBuilder struct {
|
var builder discovBuilder
|
||||||
etcd discov.EtcdConf
|
|
||||||
}
|
type discovBuilder struct{}
|
||||||
|
|
||||||
func (b *discovBuilder) Scheme() string {
|
func (b *discovBuilder) Scheme() string {
|
||||||
return discovScheme
|
return DiscovScheme
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *discovBuilder) Build(target resolver.Target, cc resolver.ClientConn, opts resolver.BuildOptions) (
|
func (b *discovBuilder) Build(target resolver.Target, cc resolver.ClientConn, opts resolver.BuildOptions) (
|
||||||
resolver.Resolver, error) {
|
resolver.Resolver, error) {
|
||||||
sub, err := discov.NewSubscriber(b.etcd.Hosts, b.etcd.Key)
|
if target.Scheme != DiscovScheme {
|
||||||
|
return nil, fmt.Errorf("bad scheme: %s", target.Scheme)
|
||||||
|
}
|
||||||
|
|
||||||
|
hosts := strings.Split(target.Authority, EndpointSep)
|
||||||
|
sub, err := discov.NewSubscriber(hosts, target.Endpoint)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
sub.AddListener(func() {
|
update := func() {
|
||||||
vals := sub.Values()
|
|
||||||
var addrs []resolver.Address
|
var addrs []resolver.Address
|
||||||
for _, val := range vals {
|
for _, val := range sub.Values() {
|
||||||
addrs = append(addrs, resolver.Address{
|
addrs = append(addrs, resolver.Address{
|
||||||
Addr: val,
|
Addr: val,
|
||||||
})
|
})
|
||||||
@@ -34,7 +44,9 @@ func (b *discovBuilder) Build(target resolver.Target, cc resolver.ClientConn, op
|
|||||||
cc.UpdateState(resolver.State{
|
cc.UpdateState(resolver.State{
|
||||||
Addresses: addrs,
|
Addresses: addrs,
|
||||||
})
|
})
|
||||||
})
|
}
|
||||||
|
sub.AddListener(update)
|
||||||
|
update()
|
||||||
|
|
||||||
return &discovResolver{
|
return &discovResolver{
|
||||||
cc: cc,
|
cc: cc,
|
||||||
@@ -51,8 +63,6 @@ func (r *discovResolver) Close() {
|
|||||||
func (r *discovResolver) ResolveNow(options resolver.ResolveNowOptions) {
|
func (r *discovResolver) ResolveNow(options resolver.ResolveNowOptions) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func RegisterResolver(etcd discov.EtcdConf) {
|
func RegisterResolver() {
|
||||||
resolver.Register(&discovBuilder{
|
resolver.Register(&builder)
|
||||||
etcd: etcd,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,102 +0,0 @@
|
|||||||
package internal
|
|
||||||
|
|
||||||
import (
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"zero/core/discov"
|
|
||||||
"zero/core/logx"
|
|
||||||
"zero/core/threading"
|
|
||||||
|
|
||||||
"google.golang.org/grpc"
|
|
||||||
"google.golang.org/grpc/connectivity"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
coolOffTime = time.Second * 5
|
|
||||||
retryTimes = 3
|
|
||||||
)
|
|
||||||
|
|
||||||
type (
|
|
||||||
RoundRobinSubClient struct {
|
|
||||||
*discov.RoundRobinSubClient
|
|
||||||
}
|
|
||||||
|
|
||||||
ConsistentSubClient struct {
|
|
||||||
*discov.ConsistentSubClient
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
func NewRoundRobinRpcClient(endpoints []string, key string, opts ...ClientOption) (*RoundRobinSubClient, error) {
|
|
||||||
subClient, err := discov.NewRoundRobinSubClient(endpoints, key, func(server string) (interface{}, error) {
|
|
||||||
return dial(server, opts...)
|
|
||||||
}, func(server string, conn interface{}) error {
|
|
||||||
return closeConn(conn.(*grpc.ClientConn))
|
|
||||||
}, discov.Exclusive())
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else {
|
|
||||||
return &RoundRobinSubClient{subClient}, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewConsistentRpcClient(endpoints []string, key string, opts ...ClientOption) (*ConsistentSubClient, error) {
|
|
||||||
subClient, err := discov.NewConsistentSubClient(endpoints, key, func(server string) (interface{}, error) {
|
|
||||||
return dial(server, opts...)
|
|
||||||
}, func(server string, conn interface{}) error {
|
|
||||||
return closeConn(conn.(*grpc.ClientConn))
|
|
||||||
})
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
} else {
|
|
||||||
return &ConsistentSubClient{subClient}, nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cli *RoundRobinSubClient) Next() (*grpc.ClientConn, bool) {
|
|
||||||
return next(func() (interface{}, bool) {
|
|
||||||
return cli.RoundRobinSubClient.Next()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func (cli *ConsistentSubClient) Next(key string) (*grpc.ClientConn, bool) {
|
|
||||||
return next(func() (interface{}, bool) {
|
|
||||||
return cli.ConsistentSubClient.Next(key)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
func closeConn(conn *grpc.ClientConn) error {
|
|
||||||
// why to close the conn asynchronously is because maybe another goroutine
|
|
||||||
// is using the same conn, we can wait the coolOffTime to let the other
|
|
||||||
// goroutine to finish using the conn.
|
|
||||||
// after the conn unregistered, the balancer will not assign the conn,
|
|
||||||
// but maybe the already assigned tasks are still using it.
|
|
||||||
threading.GoSafe(func() {
|
|
||||||
time.Sleep(coolOffTime)
|
|
||||||
if err := conn.Close(); err != nil {
|
|
||||||
logx.Error(err)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func next(nextFn func() (interface{}, bool)) (*grpc.ClientConn, bool) {
|
|
||||||
for i := 0; i < retryTimes; i++ {
|
|
||||||
v, ok := nextFn()
|
|
||||||
if !ok {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
conn, yes := v.(*grpc.ClientConn)
|
|
||||||
if !yes {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
|
|
||||||
switch conn.GetState() {
|
|
||||||
case connectivity.Ready:
|
|
||||||
return conn, true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return nil, false
|
|
||||||
}
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
package internal
|
|
||||||
|
|
||||||
import (
|
|
||||||
"math/rand"
|
|
||||||
"sync"
|
|
||||||
"time"
|
|
||||||
|
|
||||||
"google.golang.org/grpc"
|
|
||||||
)
|
|
||||||
|
|
||||||
type RRClient struct {
|
|
||||||
conns []*grpc.ClientConn
|
|
||||||
index int
|
|
||||||
lock sync.Mutex
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewRRClient(endpoints []string) (*RRClient, error) {
|
|
||||||
var conns []*grpc.ClientConn
|
|
||||||
for _, endpoint := range endpoints {
|
|
||||||
conn, err := dial(endpoint)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
conns = append(conns, conn)
|
|
||||||
}
|
|
||||||
|
|
||||||
rand.Seed(time.Now().UnixNano())
|
|
||||||
return &RRClient{
|
|
||||||
conns: conns,
|
|
||||||
index: rand.Intn(len(conns)),
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (c *RRClient) Next() *grpc.ClientConn {
|
|
||||||
c.lock.Lock()
|
|
||||||
defer c.lock.Unlock()
|
|
||||||
c.index = (c.index + 1) % len(c.conns)
|
|
||||||
return c.conns[c.index]
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
package main
|
|
||||||
|
|
||||||
import (
|
|
||||||
"zero/core/discov"
|
|
||||||
"zero/core/lang"
|
|
||||||
"zero/rpcx/internal"
|
|
||||||
)
|
|
||||||
|
|
||||||
func main() {
|
|
||||||
cli, err := internal.NewDiscovClient(discov.EtcdConf{
|
|
||||||
Hosts: []string{"localhost:2379"},
|
|
||||||
Key: "rpcx",
|
|
||||||
})
|
|
||||||
lang.Must(err)
|
|
||||||
|
|
||||||
cli.Next()
|
|
||||||
}
|
|
||||||
@@ -57,10 +57,5 @@ func (p *RpcProxy) TakeConn(ctx context.Context) (*grpc.ClientConn, error) {
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
conn, ok := val.(*RpcClient).Next()
|
return val.(*RpcClient).Conn(), nil
|
||||||
if !ok {
|
|
||||||
return nil, grpc.ErrServerStopped
|
|
||||||
}
|
|
||||||
|
|
||||||
return conn, nil
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user