24 changed files with 544 additions and 97 deletions
@ -1,30 +1,5 @@
|
||||
package main |
||||
|
||||
import ( |
||||
"fmt" |
||||
"net" |
||||
) |
||||
|
||||
func main() { |
||||
|
||||
//listen, err := net.Listen("tcp", ":7878")
|
||||
//addr := listen.(*net.TCPListener).Addr()
|
||||
//port := addr.(*net.TCPAddr).Port
|
||||
//fmt.Println(err, listen, addr, port)
|
||||
// listen.(*net.TCPListener).Addr().(*net.TCPAddr).Port
|
||||
|
||||
addr, err := net.ResolveTCPAddr("tcp", "192.168.1.110:7788") |
||||
fmt.Println(err) |
||||
fmt.Println(addr.Port) |
||||
if addr.IP == nil { |
||||
fmt.Println("ip is nil") |
||||
} |
||||
fmt.Println(addr.IP.String()) |
||||
|
||||
//addrPort, err := netip.ParseAddrPort(":7788")
|
||||
//fmt.Println(err)
|
||||
//fmt.Println(addrPort.Port())
|
||||
//fmt.Println(addrPort.Addr())
|
||||
//fmt.Println(addrPort.String())
|
||||
|
||||
} |
||||
|
||||
@ -1 +1,62 @@
|
||||
package logic |
||||
|
||||
import ( |
||||
"context" |
||||
"github.com/gin-gonic/gin" |
||||
"net/http" |
||||
"sonet/internal/gateway_http/config" |
||||
"sonet/pkg/grpc/generic" |
||||
"sonet/pkg/protocol/session" |
||||
"sonet/pkg/utils/resp" |
||||
"sonet/pkg/utils/strs" |
||||
) |
||||
|
||||
type GrpcGenericHandler struct { |
||||
grpcFactory *generic.GrpcGenericClientFactory |
||||
} |
||||
|
||||
func NewGrpcGenericHandler(grpcFactory *generic.GrpcGenericClientFactory) *GrpcGenericHandler { |
||||
return &GrpcGenericHandler{ |
||||
grpcFactory: grpcFactory, |
||||
} |
||||
} |
||||
|
||||
func (h *GrpcGenericHandler) Route(route gin.IRoutes) { |
||||
route.POST("/:svc/:method", h.handler) |
||||
} |
||||
|
||||
func (h *GrpcGenericHandler) handler(c *gin.Context) { |
||||
ctx := context.Background() |
||||
subject, err := config.GetSubject(c) |
||||
if err == nil { |
||||
ctx = session.PutSubject(ctx, session.NewRpcSubject(subject.Uid)) |
||||
} |
||||
|
||||
svc := strs.UpperInitialLetter(c.Param("svc")) |
||||
method := strs.UpperInitialLetter(c.Param("method")) |
||||
if svc == "" || method == "" { |
||||
c.JSON(http.StatusBadRequest, resp.Error("svc not found")) |
||||
return |
||||
} |
||||
|
||||
grpcClient, err := h.grpcFactory.GetClient(ctx, svc) |
||||
if err != nil { |
||||
c.JSON(http.StatusForbidden, resp.Error(err.Error())) |
||||
return |
||||
} |
||||
body := make(map[string]interface{}) |
||||
err = c.BindJSON(&body) |
||||
if err != nil { |
||||
c.JSON(http.StatusBadRequest, resp.Error("parse request body error: "+err.Error())) |
||||
return |
||||
} |
||||
|
||||
res, err := grpcClient.InvokeUnaryJson(ctx, method, body) |
||||
if err != nil { |
||||
c.JSON(http.StatusInternalServerError, resp.Error(err.Error())) |
||||
return |
||||
} |
||||
// j, err := res.MarshalJSON()
|
||||
// c.Render(http.StatusOK, RenderMarshaledJson{j})
|
||||
c.JSON(http.StatusOK, resp.Success(res)) |
||||
} |
||||
|
||||
@ -0,0 +1,54 @@
|
||||
package logic |
||||
|
||||
import ( |
||||
"context" |
||||
"fmt" |
||||
"github.com/gin-gonic/gin" |
||||
"google.golang.org/grpc" |
||||
"google.golang.org/grpc/credentials/insecure" |
||||
"google.golang.org/protobuf/types/known/emptypb" |
||||
"net/http" |
||||
"sonet/api/gen/postal" |
||||
"sonet/internal/gateway_http/config" |
||||
"sonet/pkg/grpc/balancer" |
||||
"sonet/pkg/grpc/discovery" |
||||
"sonet/pkg/utils/resp" |
||||
) |
||||
|
||||
type PostalBalancer struct { |
||||
postalClient postal.PostalClient |
||||
} |
||||
|
||||
func NewPostalBalancer() *PostalBalancer { |
||||
return &PostalBalancer{} |
||||
} |
||||
|
||||
func (h *PostalBalancer) Init(ctx context.Context) { |
||||
balancer.InitConsistentHashBuilder() |
||||
|
||||
postalUrl := discovery.BuildResolverUrl(postal.Postal_ServiceDesc.ServiceName) |
||||
conn, err := grpc.DialContext(ctx, postalUrl, |
||||
grpc.WithTransportCredentials(insecure.NewCredentials()), |
||||
// consistent hash lb
|
||||
grpc.WithDefaultServiceConfig(fmt.Sprintf(`{"loadBalancingPolicy":"%s"}`, balancer.ConsistentHash)), |
||||
) |
||||
if err != nil { |
||||
return |
||||
} |
||||
h.postalClient = postal.NewPostalClient(conn) |
||||
} |
||||
|
||||
func (h *PostalBalancer) Endpoint(c *gin.Context) { |
||||
subject, err := config.GetSubject(c) |
||||
if err != nil { |
||||
c.JSON(http.StatusBadRequest, resp.Fail(err.Error())) |
||||
return |
||||
} |
||||
ctx := context.WithValue(context.Background(), balancer.ConsistentHashKey, subject.Uid) |
||||
res, err := h.postalClient.Endpoint(ctx, &emptypb.Empty{}) |
||||
if err != nil { |
||||
c.JSON(http.StatusInternalServerError, resp.Error(err.Error())) |
||||
return |
||||
} |
||||
c.JSON(http.StatusOK, resp.Success(res.Endpoint)) |
||||
} |
||||
@ -0,0 +1,80 @@
|
||||
package logic |
||||
|
||||
import ( |
||||
"context" |
||||
"go.etcd.io/etcd/api/v3/mvccpb" |
||||
clientv3 "go.etcd.io/etcd/client/v3" |
||||
"sonet/api/gen/postal" |
||||
"sonet/pkg/grpc/discovery" |
||||
"sonet/pkg/utils/logger" |
||||
) |
||||
|
||||
type PostalMonitor struct { |
||||
client *clientv3.Client |
||||
keyPrefix string |
||||
} |
||||
|
||||
func NewPostalMonitor(client *clientv3.Client) *PostalMonitor { |
||||
return &PostalMonitor{ |
||||
client: client, |
||||
} |
||||
} |
||||
|
||||
func (m *PostalMonitor) Init(ctx context.Context) (err error) { |
||||
m.keyPrefix = discovery.BuildPrefix(discovery.Server{Name: postal.Postal_ServiceDesc.ServiceName}) |
||||
go m.watch(ctx) |
||||
m.build(ctx) |
||||
return |
||||
} |
||||
|
||||
func (m *PostalMonitor) Next() { |
||||
|
||||
} |
||||
|
||||
func (m *PostalMonitor) build(ctx context.Context) { |
||||
res, err := m.client.Get(ctx, m.keyPrefix, clientv3.WithPrefix()) |
||||
if err != nil { |
||||
return |
||||
} |
||||
//for _, kv := range res.Kvs {
|
||||
//
|
||||
//}
|
||||
m.update(res.Kvs) |
||||
|
||||
} |
||||
|
||||
func (m *PostalMonitor) watch(ctx context.Context) { |
||||
w := m.client.Watch(ctx, m.keyPrefix, clientv3.WithPrefix()) |
||||
cancelCh := ctx.Done() |
||||
|
||||
for { |
||||
select { |
||||
case <-cancelCh: |
||||
return |
||||
case res := <-w: |
||||
if err := res.Err(); err != nil { |
||||
logger.Errorf("watch etcd instance error: %v\n", err) |
||||
continue |
||||
} |
||||
rebuild := false |
||||
eLoop: |
||||
for _, event := range res.Events { |
||||
switch event.Type { |
||||
case clientv3.EventTypePut: |
||||
fallthrough |
||||
case clientv3.EventTypeDelete: |
||||
rebuild = true |
||||
break eLoop |
||||
} |
||||
} |
||||
if rebuild { |
||||
go m.build(ctx) |
||||
} |
||||
} |
||||
} |
||||
|
||||
} |
||||
|
||||
func (m *PostalMonitor) update(value []*mvccpb.KeyValue) { |
||||
|
||||
} |
||||
@ -0,0 +1,16 @@
|
||||
package config |
||||
|
||||
import ( |
||||
"github.com/sirupsen/logrus" |
||||
"google.golang.org/grpc/grpclog" |
||||
"sonet/pkg/utils/logger" |
||||
) |
||||
|
||||
func initLogger() { |
||||
logrus.SetFormatter(&logrus.TextFormatter{ |
||||
ForceColors: true, |
||||
TimestampFormat: "2006-01-02 15:04:05", //时间格式
|
||||
FullTimestamp: true, |
||||
}) |
||||
grpclog.SetLoggerV2(logger.Logger) |
||||
} |
||||
@ -0,0 +1,98 @@
|
||||
package balancer |
||||
|
||||
import ( |
||||
"errors" |
||||
"fmt" |
||||
"google.golang.org/grpc/balancer" |
||||
"google.golang.org/grpc/balancer/base" |
||||
"google.golang.org/grpc/grpclog" |
||||
"google.golang.org/grpc/resolver" |
||||
"strconv" |
||||
) |
||||
|
||||
const ConsistentHash = "consistent_hash_x" |
||||
|
||||
var ConsistentHashKey = "consistent-hash" |
||||
|
||||
func InitConsistentHashBuilder() { |
||||
balancer.Register(newConsistentHashBuilder()) |
||||
} |
||||
|
||||
// newConsistentHashBuilder creates a new ConsistentHash balancer builder.
|
||||
func newConsistentHashBuilder() balancer.Builder { |
||||
return base.NewBalancerBuilder( |
||||
ConsistentHash, |
||||
&consistentHashPickerBuilder{}, |
||||
base.Config{HealthCheck: true}, |
||||
) |
||||
} |
||||
|
||||
type consistentHashPickerBuilder struct{} |
||||
|
||||
func (b *consistentHashPickerBuilder) Build(buildInfo base.PickerBuildInfo) balancer.Picker { |
||||
grpclog.Infof("consistentHashPicker: newPicker called with buildInfo: %v", buildInfo) |
||||
if len(buildInfo.ReadySCs) == 0 { |
||||
return base.NewErrPicker(balancer.ErrNoSubConnAvailable) |
||||
} |
||||
|
||||
picker := &consistentHashPicker{ |
||||
subConns: make(map[string]balancer.SubConn), |
||||
hash: NewKetama(DefaultReplicas, nil), |
||||
} |
||||
|
||||
for sc, conInfo := range buildInfo.ReadySCs { |
||||
weight := GetWeight(conInfo.Address) |
||||
for i := 0; i < weight; i++ { |
||||
node := wrapAddr(conInfo.Address.Addr, i) |
||||
picker.hash.Add(node) |
||||
picker.subConns[node] = sc |
||||
} |
||||
} |
||||
return picker |
||||
} |
||||
|
||||
type consistentHashPicker struct { |
||||
subConns map[string]balancer.SubConn |
||||
hash *Ketama |
||||
} |
||||
|
||||
func (p *consistentHashPicker) Pick(info balancer.PickInfo) (ret balancer.PickResult, err error) { |
||||
key, ok := info.Ctx.Value(ConsistentHashKey).(string) |
||||
if !ok || key == "" { |
||||
//key = strconv.Itoa(rand.Intn(65536))
|
||||
//grpclog.Warning("empty consistent hash key")
|
||||
panic(errors.New("empty consistent hash key")) |
||||
} |
||||
targetAddr, ok := p.hash.Get(key) |
||||
if ok { |
||||
ret.SubConn = p.subConns[targetAddr] |
||||
} |
||||
return |
||||
} |
||||
|
||||
func wrapAddr(addr string, idx int) string { |
||||
return fmt.Sprintf("%s-%d", addr, idx) |
||||
} |
||||
|
||||
func GetWeight(addr resolver.Address) (weight int) { |
||||
weight = DefaultWeight |
||||
if addr.Attributes == nil { |
||||
return |
||||
} |
||||
|
||||
val := addr.Attributes.Value(WeightKey) |
||||
switch val.(type) { |
||||
case int: |
||||
weight = val.(int) |
||||
case string: |
||||
w, err := strconv.Atoi(val.(string)) |
||||
if err != nil { |
||||
grpclog.Errorf("instance weight format error: %v\n", val) |
||||
return |
||||
} |
||||
weight = w |
||||
default: |
||||
grpclog.Errorf("instance weight value type not string: %v\n", val) |
||||
} |
||||
return |
||||
} |
||||
@ -0,0 +1,136 @@
|
||||
package balancer |
||||
|
||||
import ( |
||||
"hash/fnv" |
||||
"sort" |
||||
"strconv" |
||||
"sync" |
||||
) |
||||
|
||||
type HashFunc func(data []byte) uint32 |
||||
|
||||
var ( |
||||
DefaultReplicas = 10 |
||||
Salt = "this_is_salt" |
||||
) |
||||
|
||||
func DefaultHash(data []byte) uint32 { |
||||
f := fnv.New32() |
||||
_, err := f.Write(data) |
||||
if err != nil { |
||||
panic(err) |
||||
} |
||||
return f.Sum32() |
||||
} |
||||
|
||||
type Ketama struct { |
||||
sync.Mutex |
||||
hash HashFunc |
||||
replicas int |
||||
keys []int // Sorted keys
|
||||
hashMap map[int]string |
||||
} |
||||
|
||||
func NewKetama(replicas int, fn HashFunc) *Ketama { |
||||
h := &Ketama{ |
||||
replicas: replicas, |
||||
hash: fn, |
||||
hashMap: make(map[int]string), |
||||
} |
||||
if h.replicas <= 0 { |
||||
h.replicas = DefaultReplicas |
||||
} |
||||
if h.hash == nil { |
||||
h.hash = DefaultHash |
||||
} |
||||
return h |
||||
} |
||||
|
||||
func (h *Ketama) IsEmpty() bool { |
||||
h.Lock() |
||||
defer h.Unlock() |
||||
|
||||
return len(h.keys) == 0 |
||||
} |
||||
|
||||
func (h *Ketama) Add(nodes ...string) { |
||||
h.Lock() |
||||
defer h.Unlock() |
||||
|
||||
for _, node := range nodes { |
||||
for i := 0; i < h.replicas; i++ { |
||||
key := int(h.hash([]byte(strconv.Itoa(i) + node + Salt))) |
||||
|
||||
if _, ok := h.hashMap[key]; !ok { |
||||
h.keys = append(h.keys, key) |
||||
} |
||||
h.hashMap[key] = node |
||||
} |
||||
} |
||||
sort.Ints(h.keys) |
||||
} |
||||
|
||||
func (h *Ketama) Remove(nodes ...string) { |
||||
h.Lock() |
||||
defer h.Unlock() |
||||
|
||||
deletedKey := make([]int, 0) |
||||
for _, node := range nodes { |
||||
for i := 0; i < h.replicas; i++ { |
||||
key := int(h.hash([]byte(strconv.Itoa(i) + node + Salt))) |
||||
|
||||
if _, ok := h.hashMap[key]; ok { |
||||
deletedKey = append(deletedKey, key) |
||||
delete(h.hashMap, key) |
||||
} |
||||
} |
||||
} |
||||
if len(deletedKey) > 0 { |
||||
h.deleteKeys(deletedKey) |
||||
} |
||||
} |
||||
|
||||
func (h *Ketama) deleteKeys(deletedKeys []int) { |
||||
sort.Ints(deletedKeys) |
||||
|
||||
index := 0 |
||||
count := 0 |
||||
for _, key := range deletedKeys { |
||||
for ; index < len(h.keys); index++ { |
||||
h.keys[index-count] = h.keys[index] |
||||
|
||||
if key == h.keys[index] { |
||||
count++ |
||||
index++ |
||||
break |
||||
} |
||||
} |
||||
} |
||||
|
||||
for ; index < len(h.keys); index++ { |
||||
h.keys[index-count] = h.keys[index] |
||||
} |
||||
|
||||
h.keys = h.keys[:len(h.keys)-count] |
||||
} |
||||
|
||||
func (h *Ketama) Get(key string) (string, bool) { |
||||
if h.IsEmpty() { |
||||
return "", false |
||||
} |
||||
|
||||
hash := int(h.hash([]byte(key + Salt))) |
||||
|
||||
h.Lock() |
||||
defer h.Unlock() |
||||
|
||||
idx := sort.Search(len(h.keys), func(i int) bool { |
||||
return h.keys[i] >= hash |
||||
}) |
||||
|
||||
if idx == len(h.keys) { |
||||
idx = 0 |
||||
} |
||||
str, ok := h.hashMap[h.keys[idx]] |
||||
return str, ok |
||||
} |
||||
@ -0,0 +1,6 @@
|
||||
package balancer |
||||
|
||||
const ( |
||||
WeightKey = "weight" |
||||
DefaultWeight = 10 |
||||
) |
||||
@ -0,0 +1,18 @@
|
||||
package meta |
||||
|
||||
import ( |
||||
"context" |
||||
"google.golang.org/grpc/metadata" |
||||
) |
||||
|
||||
func GetUid(ctx context.Context, key string) (string, bool) { |
||||
md, ok := metadata.FromIncomingContext(ctx) |
||||
if !ok { |
||||
return "", false |
||||
} |
||||
vals := md.Get(key) |
||||
if len(vals) == 0 { |
||||
return "", false |
||||
} |
||||
return vals[len(vals)-1], true |
||||
} |
||||
Loading…
Reference in new issue