26 changed files with 983 additions and 433 deletions
@ -0,0 +1,16 @@
|
||||
package logic |
||||
|
||||
import "sonet/pkg/utils/logger" |
||||
|
||||
// RoomLoader todo 从持久存储加载用户群关系
|
||||
type RoomLoader struct { |
||||
} |
||||
|
||||
func NewRoomLoader() *RoomLoader { |
||||
return &RoomLoader{} |
||||
} |
||||
|
||||
func (l *RoomLoader) Load(uid string) (groupIds []string, err error) { |
||||
logger.Infof("load %s join rooms", uid) |
||||
return |
||||
} |
||||
@ -0,0 +1,110 @@
|
||||
package deliver |
||||
|
||||
import ( |
||||
"hash/fnv" |
||||
"sort" |
||||
"strconv" |
||||
) |
||||
|
||||
type PickNode struct { |
||||
Key string |
||||
Weight int |
||||
} |
||||
|
||||
type ConsistentHashPicker struct { |
||||
nodes []*PickNode |
||||
replicas int |
||||
salt string |
||||
hashKeys []uint32 // sorted hashKeys
|
||||
hashNodes map[uint32]*PickNode |
||||
length int |
||||
} |
||||
|
||||
func NewConsistentHashPicker(nodes []*PickNode, replicas int, salt string) *ConsistentHashPicker { |
||||
return &ConsistentHashPicker{ |
||||
nodes: nodes, |
||||
replicas: replicas, |
||||
salt: salt, |
||||
hashKeys: make([]uint32, 0, len(nodes)*replicas), |
||||
hashNodes: make(map[uint32]*PickNode, len(nodes)*replicas), |
||||
} |
||||
} |
||||
|
||||
var ( |
||||
DefaultReplicas = 10 |
||||
DefaultSalt = "this_is_salt" |
||||
) |
||||
|
||||
func (c *ConsistentHashPicker) hashFnv32(data []byte) uint32 { |
||||
f := fnv.New32() |
||||
_, err := f.Write(data) |
||||
if err != nil { |
||||
panic(err) |
||||
} |
||||
return f.Sum32() |
||||
} |
||||
|
||||
// Init 构建hash环
|
||||
func (c *ConsistentHashPicker) Init() { |
||||
for _, node := range c.nodes { |
||||
weight := node.Weight |
||||
if weight < 1 { |
||||
weight = 1 |
||||
} |
||||
for i := 0; i < weight; i++ { |
||||
for j := 0; j < c.replicas; j++ { |
||||
key := c.hashFnv32([]byte(strconv.Itoa(i) + node.Key + strconv.Itoa(j) + c.salt)) |
||||
|
||||
if _, ok := c.hashNodes[key]; !ok { |
||||
c.hashKeys = append(c.hashKeys, key) |
||||
} |
||||
c.hashNodes[key] = node |
||||
} |
||||
} |
||||
} |
||||
sort.Slice(c.hashKeys, func(i, j int) bool { |
||||
return c.hashKeys[i] < c.hashKeys[j] |
||||
}) |
||||
c.length = len(c.hashKeys) |
||||
} |
||||
|
||||
func (c *ConsistentHashPicker) Pick(key string) (node *PickNode, ok bool) { |
||||
if c.length == 0 { |
||||
return |
||||
} |
||||
hash := c.hashFnv32([]byte(key + c.salt)) |
||||
|
||||
idx := sort.Search(c.length, func(i int) bool { // 二分查找最小为true的index
|
||||
return c.hashKeys[i] >= hash |
||||
}) |
||||
if idx == c.length { |
||||
idx = 0 |
||||
} |
||||
node, ok = c.hashNodes[c.hashKeys[idx]] |
||||
return |
||||
} |
||||
|
||||
// PickOffset
|
||||
// key: pick key
|
||||
// offset: 顺时针第n个节点
|
||||
// return node: pick node
|
||||
// return same: node是否是offset=0时的相同节点
|
||||
func (c *ConsistentHashPicker) PickOffset(key string, offset int) (node *PickNode, same bool, ok bool) { |
||||
if c.length == 0 { |
||||
return |
||||
} |
||||
hash := c.hashFnv32([]byte(key + c.salt)) |
||||
idx := sort.Search(c.length, func(i int) bool { |
||||
return c.hashKeys[i] >= hash |
||||
}) |
||||
if idx == c.length { |
||||
idx = 0 |
||||
} |
||||
hit := (idx + offset) % c.length |
||||
if hit < 0 { |
||||
hit = c.length + hit |
||||
} |
||||
node, ok = c.hashNodes[c.hashKeys[hit]] |
||||
same = c.hashNodes[c.hashKeys[idx]] == node |
||||
return |
||||
} |
||||
@ -0,0 +1,51 @@
|
||||
package deliver |
||||
|
||||
import ( |
||||
"strconv" |
||||
"testing" |
||||
) |
||||
|
||||
func TestConsistentHashPicker(t *testing.T) { |
||||
var nodes []*PickNode |
||||
var hits = make(map[string]int, 10) |
||||
for i := 0; i < 10; i++ { |
||||
node := &PickNode{ |
||||
Key: "node-" + strconv.Itoa(i), |
||||
Weight: 10, |
||||
} |
||||
if i < 5 { |
||||
node.Weight = 5 // 遵循 weight 进行负载均衡
|
||||
} |
||||
nodes = append(nodes, node) |
||||
hits[node.Key] = 0 |
||||
} |
||||
picker := NewConsistentHashPicker(nodes, DefaultReplicas, DefaultSalt) |
||||
picker.Init() |
||||
|
||||
for i := 0; i < 100000; i++ { |
||||
node1, ok1 := picker.Pick(strconv.Itoa(i)) |
||||
node2, ok2 := picker.Pick(strconv.Itoa(i)) |
||||
if !ok1 || !ok2 { |
||||
t.Error("pick non node") |
||||
} |
||||
if node1 != node2 { |
||||
t.Error("pick same key not same node") |
||||
} |
||||
hits[node1.Key]++ |
||||
} |
||||
|
||||
for nodeKey, hit := range hits { |
||||
if hit == 0 { |
||||
t.Errorf("node %s not picked", nodeKey) |
||||
} |
||||
} |
||||
|
||||
node1, same1, ok1 := picker.PickOffset("100", 1) |
||||
node2, same2, ok2 := picker.PickOffset("100", 1) |
||||
if !ok1 || !ok2 { |
||||
t.Error("pick non node") |
||||
} |
||||
if node1 != node2 || same1 != same2 { |
||||
t.Error("pick same key not same node") |
||||
} |
||||
} |
||||
@ -1,218 +0,0 @@
|
||||
package deliver |
||||
|
||||
import ( |
||||
"context" |
||||
"fmt" |
||||
"google.golang.org/grpc" |
||||
"google.golang.org/grpc/credentials/insecure" |
||||
"google.golang.org/protobuf/proto" |
||||
"sonet/api/gen/postal" |
||||
"sonet/pkg/grpc/balancer" |
||||
"sonet/pkg/grpc/discovery" |
||||
"sonet/pkg/plugins/mq" |
||||
"sonet/pkg/protocol/event" |
||||
"sonet/pkg/utils/logger" |
||||
"sync" |
||||
"sync/atomic" |
||||
) |
||||
|
||||
type GroupLoader interface { |
||||
Load(uid string) (groupIds []string, err error) |
||||
} |
||||
|
||||
type GroupDeliver struct { |
||||
svcName string |
||||
groupLoader GroupLoader |
||||
consumer mq.Consumer |
||||
resolver discovery.Resolver |
||||
postal postal.PostalClient // postal consistent hash client
|
||||
directPostalDialOptions []grpc.DialOption |
||||
directPostals *atomic.Value //map[string]*Postal , postal server 直连客户端
|
||||
lock *sync.RWMutex |
||||
} |
||||
|
||||
func NewGroupDeliver( |
||||
msgInServiceName string, |
||||
groupLoader GroupLoader, |
||||
consumer mq.Consumer, |
||||
resolver discovery.Resolver, |
||||
directPostalDialOptions []grpc.DialOption, |
||||
) *GroupDeliver { |
||||
directPostals := &atomic.Value{} |
||||
directPostals.Store(make(map[string]*Postal, 3)) |
||||
|
||||
return &GroupDeliver{ |
||||
svcName: msgInServiceName, |
||||
groupLoader: groupLoader, |
||||
consumer: consumer, |
||||
resolver: resolver, |
||||
directPostalDialOptions: directPostalDialOptions, |
||||
directPostals: directPostals, |
||||
lock: &sync.RWMutex{}, |
||||
} |
||||
} |
||||
|
||||
func (d *GroupDeliver) Init(ctx context.Context, grpcResolver discovery.GrpcResolver, opts ...grpc.DialOption) (err error) { |
||||
err = d.initPostal(ctx, grpcResolver, opts...) |
||||
if err != nil { |
||||
return |
||||
} |
||||
|
||||
// initial all postal direct clients
|
||||
servers, err := d.resolver.ResolveAll(ctx, postal.Postal_ServiceDesc.ServiceName) |
||||
if err != nil { |
||||
return |
||||
} |
||||
d.buildPostals(servers) |
||||
|
||||
// watch postal server instance
|
||||
err = d.watchPostal(ctx) |
||||
if err != nil { |
||||
return |
||||
} |
||||
|
||||
err = d.subscribe() |
||||
return |
||||
} |
||||
|
||||
// toPostalGid 加上 svc name 前缀避免和其他服务群组冲突
|
||||
func (d *GroupDeliver) toPostalGid(gid string) string { |
||||
return d.svcName + "." + gid |
||||
} |
||||
|
||||
// subscribe postal online events
|
||||
func (d *GroupDeliver) subscribe() error { |
||||
consumerChannel := fmt.Sprintf("%s:%s", "deliver", d.svcName) |
||||
return d.consumer.Subscribe(event.TopicOnline, consumerChannel, func(message *mq.Message) (err error) { |
||||
online := &event.Online{} |
||||
if e := online.UnmarshalBinary(message.Body); e != nil { |
||||
logger.Error("deliver unmarshal online event payload error: ", e) |
||||
return |
||||
} |
||||
// load uid groups join to postal
|
||||
groupIds, err := d.groupLoader.Load(online.Uid) |
||||
if err != nil { |
||||
logger.Error("deliver load groups error:", err) |
||||
return |
||||
} |
||||
if err = d.GroupJoin(context.Background(), online.Uid, groupIds); err != nil { |
||||
logger.Errorf("deliver uid %s group join error: %v", online.Uid, err) |
||||
return |
||||
} |
||||
return |
||||
}) |
||||
} |
||||
|
||||
func (d *GroupDeliver) initPostal(ctx context.Context, resolver discovery.GrpcResolver, opts ...grpc.DialOption) (err error) { |
||||
balancer.InitConsistentHashBuilder() |
||||
rb, err := resolver.Resolver() |
||||
if err != nil { |
||||
return |
||||
} |
||||
|
||||
postalUrl := resolver.DialUrl(postal.Postal_ServiceDesc.ServiceName) |
||||
var options []grpc.DialOption |
||||
// consistent hash lb
|
||||
options = append(options, grpc.WithDefaultServiceConfig(fmt.Sprintf(`{"loadBalancingPolicy":"%s"}`, balancer.ConsistentHash))) |
||||
options = append(options, grpc.WithResolvers(rb)) |
||||
options = append(options, grpc.WithTransportCredentials(insecure.NewCredentials())) |
||||
options = append(options, opts...) |
||||
conn, err := grpc.DialContext(ctx, postalUrl, options...) |
||||
if err != nil { |
||||
return |
||||
} |
||||
d.postal = postal.NewPostalClient(conn) |
||||
return |
||||
} |
||||
|
||||
// watchPostal watch postal service list
|
||||
func (d *GroupDeliver) watchPostal(ctx context.Context) (err error) { |
||||
ch, err := d.resolver.Watch(ctx, postal.Postal_ServiceDesc.ServiceName) |
||||
if err != nil { |
||||
return |
||||
} |
||||
go func() { |
||||
for { |
||||
select { |
||||
case <-ctx.Done(): |
||||
return |
||||
case servers := <-ch: |
||||
d.buildPostals(servers) |
||||
} |
||||
} |
||||
}() |
||||
return |
||||
} |
||||
|
||||
func (d *GroupDeliver) buildPostals(servers []discovery.Server) { |
||||
postals := make(map[string]*Postal) |
||||
directPostals := d.directPostals.Load().(map[string]*Postal) |
||||
for _, server := range servers { |
||||
if p, ok := directPostals[server.Addr]; ok { |
||||
postals[server.Addr] = p |
||||
continue |
||||
} |
||||
// new connection
|
||||
conn, err := grpc.DialContext(context.Background(), server.Addr, d.directPostalDialOptions...) |
||||
if err != nil { |
||||
logger.Errorf("dial postal server %+v error: %v", server.Addr, err) |
||||
continue |
||||
} |
||||
postals[server.Addr] = NewPostal(conn) |
||||
} |
||||
|
||||
// close old connection
|
||||
oldPostals := directPostals |
||||
d.directPostals.Store(postals) |
||||
for addr, p := range oldPostals { |
||||
if _, ok := postals[addr]; !ok { |
||||
p.Close() |
||||
} |
||||
} |
||||
} |
||||
|
||||
func (d *GroupDeliver) DeliverGroup(ctx context.Context, gid string, msg proto.Message, options ...Option) (err error) { |
||||
gid = d.toPostalGid(gid) |
||||
|
||||
// deliver to all postal
|
||||
message, err := protoMessage2Deliver(d.svcName, msg) |
||||
if err != nil { |
||||
return |
||||
} |
||||
req := postal.ReqDeliverGroup{Gid: gid, Msg: message} |
||||
for _, p := range d.directPostals.Load().(map[string]*Postal) { |
||||
p.DeliverGroup(&req) |
||||
} |
||||
return |
||||
} |
||||
|
||||
func (d *GroupDeliver) GroupDissolve(gid string) { |
||||
gid = d.toPostalGid(gid) |
||||
|
||||
req := postal.ReqGroupDissolve{Gid: gid} |
||||
for _, p := range d.directPostals.Load().(map[string]*Postal) { |
||||
p.GroupDissolve(&req) |
||||
} |
||||
} |
||||
|
||||
func (d *GroupDeliver) GroupJoin(ctx context.Context, uid string, gids []string) (err error) { |
||||
if len(gids) == 0 { |
||||
return |
||||
} |
||||
for i := 0; i < len(gids); i++ { |
||||
gids[i] = d.toPostalGid(gids[i]) |
||||
} |
||||
|
||||
ctx = context.WithValue(ctx, balancer.ConsistentHashKey, uid) |
||||
req := &postal.ReqGroupJoin{Uid: uid, Gids: gids} |
||||
_, err = d.postal.GroupJoin(ctx, req) |
||||
return |
||||
} |
||||
|
||||
func (d *GroupDeliver) GroupLeave(ctx context.Context, uid string, gids []string) (err error) { |
||||
req := &postal.ReqGroupLeave{Uid: uid, Gids: gids} |
||||
|
||||
ctx = context.WithValue(ctx, balancer.ConsistentHashKey, uid) |
||||
_, err = d.postal.GroupLeave(ctx, req) |
||||
return |
||||
} |
||||
@ -0,0 +1,136 @@
|
||||
package deprecated |
||||
|
||||
import ( |
||||
"context" |
||||
"fmt" |
||||
"google.golang.org/grpc" |
||||
"google.golang.org/grpc/credentials/insecure" |
||||
"google.golang.org/protobuf/proto" |
||||
"sonet/api/gen/postal" |
||||
"sonet/pkg/grpc/balancer" |
||||
"sonet/pkg/grpc/discovery" |
||||
"sonet/pkg/protocol/deliver" |
||||
"sonet/pkg/utils/logger" |
||||
) |
||||
|
||||
type Status int16 |
||||
|
||||
const ( |
||||
StatusSuccess Status = 1 |
||||
StatusError Status = 2 |
||||
StatusReceiverOffline Status = 10 |
||||
) |
||||
|
||||
// DDeliver n包,通知消息投递
|
||||
// Deprecated
|
||||
type DDeliver struct { |
||||
svcName string |
||||
postal postal.PostalClient |
||||
} |
||||
|
||||
func NewDDeliver(msgInServiceName string) *DDeliver { |
||||
return &DDeliver{ |
||||
svcName: msgInServiceName, |
||||
} |
||||
} |
||||
|
||||
func (d *DDeliver) InitWithResolver(ctx context.Context, resolver discovery.GrpcResolver, opts ...grpc.DialOption) (err error) { |
||||
balancer.InitConsistentHashBuilder() |
||||
rb, err := resolver.Resolver() |
||||
if err != nil { |
||||
return |
||||
} |
||||
|
||||
postalUrl := resolver.DialUrl(postal.Postal_ServiceDesc.ServiceName) |
||||
var options []grpc.DialOption |
||||
// consistent hash lb
|
||||
options = append(options, grpc.WithDefaultServiceConfig(fmt.Sprintf(`{"loadBalancingPolicy":"%s"}`, balancer.ConsistentHash))) |
||||
options = append(options, grpc.WithResolvers(rb)) |
||||
options = append(options, grpc.WithTransportCredentials(insecure.NewCredentials())) |
||||
options = append(options, opts...) |
||||
conn, err := grpc.DialContext(ctx, postalUrl, options...) |
||||
if err != nil { |
||||
return |
||||
} |
||||
d.postal = postal.NewPostalClient(conn) |
||||
return |
||||
} |
||||
|
||||
func (d *DDeliver) InitWithAddr(postalAddr string, opts ...grpc.DialOption) (err error) { |
||||
// Conn *grpc.ClientConn
|
||||
var options []grpc.DialOption |
||||
options = append(options, grpc.WithTransportCredentials(insecure.NewCredentials())) |
||||
options = append(options, opts...) |
||||
conn, err := grpc.Dial(postalAddr, options...) |
||||
if err != nil { |
||||
return |
||||
} |
||||
d.postal = postal.NewPostalClient(conn) |
||||
return |
||||
} |
||||
|
||||
func (d *DDeliver) Deliver(ctx context.Context, msg proto.Message, receiver string, options ...deliver.Option) (Status, error) { |
||||
return d.deliver0(ctx, msg, []string{receiver}, options...) |
||||
} |
||||
|
||||
func (d *DDeliver) DeliverBatch(ctx context.Context, msg proto.Message, receivers []string, options ...deliver.Option) (Status, error) { |
||||
return d.deliver0(ctx, msg, receivers, options...) |
||||
} |
||||
|
||||
func (d *DDeliver) deliver0(ctx context.Context, msg proto.Message, receivers []string, options ...deliver.Option) (status Status, err error) { |
||||
//opts := deliver.defaultOptions
|
||||
//if options != nil {
|
||||
// for _, opt := range options {
|
||||
// opt.f(&opts)
|
||||
// }
|
||||
//}
|
||||
if receivers == nil || len(receivers) == 0 { |
||||
// return StatusError, errors.New("receivers is empty")
|
||||
return StatusSuccess, nil |
||||
} |
||||
|
||||
// encode msg
|
||||
message, err := deliver.Proto2DeliverMessage(d.svcName, msg) |
||||
if err != nil { |
||||
return StatusError, err |
||||
} |
||||
|
||||
// deliver to gateway
|
||||
if len(receivers) == 1 { |
||||
// deliver one receiver
|
||||
reqDeliver := &postal.ReqDeliver{ |
||||
Receiver: receivers[0], |
||||
Msg: message, |
||||
} |
||||
|
||||
ctx = context.WithValue(ctx, balancer.ConsistentHashKey, reqDeliver.Receiver) |
||||
res, err := d.postal.Deliver(ctx, reqDeliver) |
||||
if err != nil { |
||||
return StatusError, err |
||||
} |
||||
// TODO res code
|
||||
if res.Ok { |
||||
status = StatusSuccess |
||||
} else { |
||||
status = StatusError |
||||
} |
||||
logger.Info("deliver result: ", err, res) |
||||
} else { |
||||
|
||||
// deliver batch receiver
|
||||
ctx = context.WithValue(ctx, balancer.ConsistentHashKey, receivers[0]) |
||||
req := &postal.ReqDeliverBatch{Receivers: receivers, Msg: message} |
||||
res, err := d.postal.DeliverBatch(ctx, req) |
||||
if err != nil { |
||||
logger.Error("deliver error: ", err) |
||||
return StatusError, err |
||||
} |
||||
if res.Ok { |
||||
status = StatusSuccess |
||||
} else { |
||||
status = StatusError |
||||
} |
||||
logger.Info("deliver batch result: ", res) |
||||
} |
||||
return |
||||
} |
||||
@ -0,0 +1,140 @@
|
||||
package deliver |
||||
|
||||
import ( |
||||
"context" |
||||
"errors" |
||||
"fmt" |
||||
"google.golang.org/protobuf/proto" |
||||
"sonet/api/gen/postal" |
||||
"sonet/pkg/plugins/mq" |
||||
"sonet/pkg/protocol/event" |
||||
"sonet/pkg/utils/collect" |
||||
"sonet/pkg/utils/logger" |
||||
) |
||||
|
||||
type GroupLoader interface { |
||||
Load(uid string) (groupIds []string, err error) |
||||
} |
||||
|
||||
type GroupDeliver struct { |
||||
svcName string |
||||
groupLoader GroupLoader |
||||
consumer mq.Consumer |
||||
postalPicker *PostalPicker |
||||
} |
||||
|
||||
func NewGroupDeliver( |
||||
msgInServiceName string, |
||||
groupLoader GroupLoader, |
||||
consumer mq.Consumer, |
||||
) *GroupDeliver { |
||||
return &GroupDeliver{ |
||||
svcName: msgInServiceName, |
||||
groupLoader: groupLoader, |
||||
consumer: consumer, |
||||
} |
||||
} |
||||
|
||||
func (d *GroupDeliver) Init(ctx context.Context) (err error) { |
||||
err = d.subscribe() |
||||
return |
||||
} |
||||
|
||||
// toPostalGid 加上 svc name 前缀避免和其他服务群组冲突
|
||||
func (d *GroupDeliver) toPostalGid(gid string) string { |
||||
return d.svcName + "." + gid |
||||
} |
||||
|
||||
// subscribe postal online events
|
||||
func (d *GroupDeliver) subscribe() error { |
||||
consumerChannel := fmt.Sprintf("%s:%s", "deliver", d.svcName) |
||||
return d.consumer.Subscribe(event.TopicOnline, consumerChannel, func(message *mq.Message) (err error) { |
||||
online := &event.Online{} |
||||
if e := online.UnmarshalBinary(message.Body); e != nil { |
||||
logger.Error("deliver unmarshal online event payload error: ", e) |
||||
return |
||||
} |
||||
// load uid groups join to postal
|
||||
groupIds, err := d.groupLoader.Load(online.Uid) |
||||
if err != nil { |
||||
logger.Error("deliver load groups error:", err) |
||||
return |
||||
} |
||||
if err = d.GroupJoin(context.Background(), online.Uid, groupIds); err != nil { |
||||
logger.Errorf("deliver uid %s group join error: %v", online.Uid, err) |
||||
return |
||||
} |
||||
return |
||||
}) |
||||
} |
||||
|
||||
// DeliverGroup 群组消息广播投递到所有 postal 节点
|
||||
func (d *GroupDeliver) DeliverGroup(ctx context.Context, gid string, msg proto.Message, options ...Option) (err error) { |
||||
message, err := Proto2DeliverMessage(d.svcName, msg) |
||||
if err != nil { |
||||
return |
||||
} |
||||
clients := d.postalPicker.PickAll() |
||||
|
||||
var errs []error |
||||
req := postal.ReqDeliverGroup{Gid: d.toPostalGid(gid), Msg: message} |
||||
for _, client := range clients { |
||||
_, err := client.DeliverGroup(ctx, &req) |
||||
if err != nil { |
||||
errs = append(errs, err) |
||||
} |
||||
} |
||||
if len(errs) > 0 { |
||||
err = errors.Join(errs...) |
||||
return |
||||
} |
||||
return |
||||
} |
||||
|
||||
// GroupDissolve 群组消息广播投递到所有 postal 节点
|
||||
func (d *GroupDeliver) GroupDissolve(ctx context.Context, gid string) (err error) { |
||||
clients := d.postalPicker.PickAll() |
||||
|
||||
var errs []error |
||||
req := postal.ReqGroupDissolve{Gid: d.toPostalGid(gid)} |
||||
for _, client := range clients { |
||||
_, err := client.GroupDissolve(ctx, &req) |
||||
if err != nil { |
||||
errs = append(errs, err) |
||||
} |
||||
} |
||||
if len(errs) > 0 { |
||||
err = errors.Join(errs...) |
||||
return |
||||
} |
||||
return |
||||
} |
||||
|
||||
func (d *GroupDeliver) GroupJoin(ctx context.Context, uid string, gids []string) (err error) { |
||||
if len(gids) == 0 { |
||||
return |
||||
} |
||||
gids = collect.Mapping(gids, d.toPostalGid) |
||||
|
||||
client, err := d.postalPicker.Pick(uid) |
||||
if err != nil { |
||||
return |
||||
} |
||||
_, err = client.GroupJoin(ctx, &postal.ReqGroupJoin{Uid: uid, Gids: gids}) |
||||
return |
||||
} |
||||
|
||||
func (d *GroupDeliver) GroupLeave(ctx context.Context, uid string, gids []string) (err error) { |
||||
if len(gids) == 0 { |
||||
return |
||||
} |
||||
gids = collect.Mapping(gids, d.toPostalGid) |
||||
|
||||
client, err := d.postalPicker.Pick(uid) |
||||
if err != nil { |
||||
return |
||||
} |
||||
req := &postal.ReqGroupLeave{Uid: uid, Gids: gids} |
||||
_, err = client.GroupLeave(ctx, req) |
||||
return |
||||
} |
||||
@ -1,45 +0,0 @@
|
||||
package deliver |
||||
|
||||
import ( |
||||
"context" |
||||
"google.golang.org/grpc" |
||||
"sonet/api/gen/postal" |
||||
"sonet/pkg/utils/logger" |
||||
) |
||||
|
||||
type Postal struct { |
||||
conn *grpc.ClientConn |
||||
client postal.PostalClient |
||||
} |
||||
|
||||
func NewPostal(conn *grpc.ClientConn) *Postal { |
||||
return &Postal{ |
||||
conn: conn, |
||||
client: postal.NewPostalClient(conn), |
||||
} |
||||
} |
||||
|
||||
func (p *Postal) Close() { |
||||
err := p.conn.Close() |
||||
if err != nil { |
||||
logger.Error("postal close error: ", err) |
||||
} |
||||
} |
||||
|
||||
// DeliverGroup 群消息发送
|
||||
func (p *Postal) DeliverGroup(req *postal.ReqDeliverGroup) { |
||||
// todo put in chan
|
||||
res, err := p.client.DeliverGroup(context.Background(), req) |
||||
if err != nil || !res.Ok { |
||||
logger.Errorf("deliver to postal failed: %+v, %v", res, err) |
||||
} |
||||
} |
||||
|
||||
// GroupDissolve 解散群组
|
||||
func (p *Postal) GroupDissolve(req *postal.ReqGroupDissolve) { |
||||
// todo put in chan
|
||||
_, err := p.client.GroupDissolve(context.Background(), req) |
||||
if err != nil { |
||||
logger.Errorf("group dissolve failed: %v", err) |
||||
} |
||||
} |
||||
@ -0,0 +1,40 @@
|
||||
package deliver |
||||
|
||||
import ( |
||||
"google.golang.org/grpc" |
||||
"sonet/api/gen/postal" |
||||
"sonet/pkg/utils/logger" |
||||
) |
||||
|
||||
type PostalNode struct { |
||||
addr string |
||||
conn *grpc.ClientConn |
||||
client postal.PostalClient |
||||
} |
||||
|
||||
func NewPostalNode(addr string, conn *grpc.ClientConn) *PostalNode { |
||||
return &PostalNode{ |
||||
addr: addr, |
||||
conn: conn, |
||||
client: postal.NewPostalClient(conn), |
||||
} |
||||
} |
||||
|
||||
func (p *PostalNode) Close() { |
||||
err := p.conn.Close() |
||||
if err != nil { |
||||
logger.Error("postal close error: ", err) |
||||
} |
||||
} |
||||
|
||||
func (p *PostalNode) GetAddr() string { |
||||
return p.addr |
||||
} |
||||
|
||||
func (p *PostalNode) GetConn() *grpc.ClientConn { |
||||
return p.conn |
||||
} |
||||
|
||||
func (p *PostalNode) GetClient() postal.PostalClient { |
||||
return p.client |
||||
} |
||||
@ -0,0 +1,172 @@
|
||||
package deliver |
||||
|
||||
import ( |
||||
"context" |
||||
"errors" |
||||
"fmt" |
||||
"google.golang.org/grpc" |
||||
"sonet/api/gen/postal" |
||||
"sonet/pkg/grpc/discovery" |
||||
"sonet/pkg/utils/collect" |
||||
"sonet/pkg/utils/logger" |
||||
"sync/atomic" |
||||
) |
||||
|
||||
type PostalPicker struct { |
||||
resolver discovery.Resolver |
||||
directPostalDialOptions []grpc.DialOption |
||||
directPostalNodes atomic.Value // map[string]*PostalNode , postal server 直连客户端
|
||||
picker atomic.Value // *ConsistentHashPicker
|
||||
} |
||||
|
||||
func NewPostalPicker(resolver discovery.Resolver, directPostalDialOptions ...grpc.DialOption) *PostalPicker { |
||||
return &PostalPicker{ |
||||
resolver: resolver, |
||||
directPostalDialOptions: directPostalDialOptions, |
||||
} |
||||
} |
||||
|
||||
func (p *PostalPicker) Init(ctx context.Context) (err error) { |
||||
// initial all postal direct clients
|
||||
servers, err := p.resolver.ResolveAll(ctx, postal.Postal_ServiceDesc.ServiceName) |
||||
if err != nil { |
||||
return |
||||
} |
||||
p.newPostalServers(servers) |
||||
|
||||
// watch postal server instance
|
||||
err = p.watchPostal(ctx) |
||||
if err != nil { |
||||
return |
||||
} |
||||
return |
||||
} |
||||
|
||||
// watchPostal watch postal service list
|
||||
func (p *PostalPicker) watchPostal(ctx context.Context) (err error) { |
||||
ch, err := p.resolver.Watch(ctx, postal.Postal_ServiceDesc.ServiceName) |
||||
if err != nil { |
||||
return |
||||
} |
||||
go func() { |
||||
for { |
||||
select { |
||||
case <-ctx.Done(): |
||||
return |
||||
case servers := <-ch: |
||||
p.newPostalServers(servers) |
||||
} |
||||
} |
||||
}() |
||||
return |
||||
} |
||||
|
||||
// newPostalServers postal集群节点增减
|
||||
func (p *PostalPicker) newPostalServers(servers []discovery.Server) { |
||||
newPostal := make(map[string]*PostalNode) |
||||
var pickServers []discovery.Server |
||||
oldPostal, ok := p.directPostalNodes.Load().(map[string]*PostalNode) |
||||
if !ok { |
||||
oldPostal = make(map[string]*PostalNode) |
||||
} |
||||
|
||||
for _, server := range servers { |
||||
if p, ok := oldPostal[server.Addr]; ok { |
||||
newPostal[server.Addr] = p |
||||
continue |
||||
} |
||||
// new connection
|
||||
conn, err := grpc.DialContext(context.Background(), server.Addr, p.directPostalDialOptions...) |
||||
if err != nil { |
||||
logger.Errorf("dial postal server %+v error: %v", server, err) |
||||
continue |
||||
} |
||||
newPostal[server.Addr] = NewPostalNode(server.Addr, conn) |
||||
pickServers = append(pickServers, server) |
||||
} |
||||
// 重新构建 consistent hash picker
|
||||
p.newConsistentHash(pickServers) |
||||
|
||||
// close old connection
|
||||
p.directPostalNodes.Store(newPostal) |
||||
for addr, p := range oldPostal { |
||||
if _, ok := newPostal[addr]; !ok { |
||||
p.Close() |
||||
} |
||||
} |
||||
} |
||||
|
||||
func (p *PostalPicker) newConsistentHash(servers []discovery.Server) { |
||||
nodes := collect.Mapping(servers, func(server discovery.Server) *PickNode { |
||||
return &PickNode{ |
||||
Key: server.Addr, |
||||
Weight: server.GetWeight(), |
||||
} |
||||
}) |
||||
|
||||
picker := NewConsistentHashPicker(nodes, DefaultReplicas, DefaultSalt) |
||||
picker.Init() |
||||
p.picker.Store(picker) |
||||
} |
||||
|
||||
func (p *PostalPicker) Pick(key string) (client postal.PostalClient, err error) { |
||||
postalNode, err := p.PickNode(key) |
||||
if err != nil { |
||||
return |
||||
} |
||||
client = postalNode.client |
||||
return |
||||
} |
||||
|
||||
func (p *PostalPicker) PickNode(key string) (postalNode *PostalNode, err error) { |
||||
picker := p.picker.Load().(*ConsistentHashPicker) |
||||
node, ok := picker.Pick(key) |
||||
if !ok { |
||||
err = errors.New("no available postal server") |
||||
return |
||||
} |
||||
postalNode, ok = p.directPostalNodes.Load().(map[string]*PostalNode)[node.Key] |
||||
if !ok { |
||||
err = fmt.Errorf("no conn for postal server %s", node.Key) |
||||
return |
||||
} |
||||
return |
||||
} |
||||
|
||||
// PickOffset
|
||||
// return same: client 是否与 offset=0 时相同
|
||||
func (p *PostalPicker) PickOffset(key string, offset int) (client postal.PostalClient, same bool, err error) { |
||||
postalNode, same, err := p.PickOffsetNode(key, offset) |
||||
if err != nil { |
||||
return |
||||
} |
||||
client = postalNode.client |
||||
return |
||||
} |
||||
|
||||
func (p *PostalPicker) PickOffsetNode(key string, offset int) (postalNode *PostalNode, same bool, err error) { |
||||
picker := p.picker.Load().(*ConsistentHashPicker) |
||||
node, same, ok := picker.PickOffset(key, offset) |
||||
if !ok { |
||||
err = errors.New("no available postal server") |
||||
return |
||||
} |
||||
postalNode, ok = p.directPostalNodes.Load().(map[string]*PostalNode)[node.Key] |
||||
if !ok { |
||||
err = fmt.Errorf("no conn for postal server %s", node.Key) |
||||
return |
||||
} |
||||
return |
||||
} |
||||
|
||||
func (p *PostalPicker) PickAll() (clients []postal.PostalClient) { |
||||
nodes, ok := p.directPostalNodes.Load().(map[string]*PostalNode) |
||||
if !ok { |
||||
return |
||||
} |
||||
clients = make([]postal.PostalClient, 0, len(nodes)) |
||||
for _, node := range nodes { |
||||
clients = append(clients, node.client) |
||||
} |
||||
return |
||||
} |
||||
@ -0,0 +1,28 @@
|
||||
package nets |
||||
|
||||
import "testing" |
||||
|
||||
func TestIpConvert(t *testing.T) { |
||||
ip := "192.168.1.110" |
||||
// ip := "255.255.255.255"
|
||||
i64, err := Ip2i64(ip) |
||||
if err != nil { |
||||
t.Error(err) |
||||
} |
||||
ip2 := I642Ip(int64(int32(i64))) |
||||
if ip2 != ip { |
||||
t.Error("ip parse error") |
||||
} |
||||
} |
||||
|
||||
func TestAddressConvert(t *testing.T) { |
||||
addr := "192.168.1.110:8080" |
||||
i64, err := Address2i64(addr) |
||||
if err != nil { |
||||
t.Error(err) |
||||
} |
||||
addr2 := I642Address(i64) |
||||
if addr2 != addr { |
||||
t.Error("address parse error") |
||||
} |
||||
} |
||||
Loading…
Reference in new issue