24 changed files with 544 additions and 97 deletions
@ -1,30 +1,5 @@ |
|||||||
package main |
package main |
||||||
|
|
||||||
import ( |
|
||||||
"fmt" |
|
||||||
"net" |
|
||||||
) |
|
||||||
|
|
||||||
func main() { |
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 |
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