From 28f68495ec55cbffabb1a8a638e13152e96e1830 Mon Sep 17 00:00:00 2001 From: liuxiaobo <1224730913@qq.com> Date: Tue, 17 Jun 2025 00:00:19 +0800 Subject: [PATCH] =?UTF-8?q?=E5=B7=A5=E4=BD=9C=E8=AE=A1=E5=88=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 9 ++- common/gameService/gameService.go | 102 ++++++++++++++++++++++++++ common/pb/service.proto | 22 ++++-- common/proto/pb/service.pb.go | 72 +++++++++--------- common/rpc/rpcGameUser.go | 10 +-- common/userBindService/userService.go | 8 +- server/colorgame/server/processor.go | 6 +- server/colorgame/server/service.go | 39 ++-------- 8 files changed, 179 insertions(+), 89 deletions(-) create mode 100644 common/gameService/gameService.go diff --git a/README.md b/README.md index 2750e70..83174d8 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,8 @@ 3. 玩法逻辑 9. 优化计划 - 1. etcd重试机制。 - 2. 节点注册时加上自己是有状态还是无状态还是有序。每个服务注册时加上自己能接收的消息范围。客户端发消息不需要注明发给哪个服务。 - 3. gate根据消息id决定路由给哪类节点,根据节点是否状态服选择节点。 - 4. 通过广播机制,将玩家在哪个状态服里同步给所有服,保存到内存里。(后续优化,暂不考虑) \ No newline at end of file + 1. etcd重试机制。(已优化) + 2. 节点注册时加上自己是有状态还是无状态还是有序。每个服务注册时加上自己能接收的消息范围。(节点是否有状态参考service.proto来处理,无需注册到etcd。服务消息范围也没必要加上,类似于EnterRoom消息所有玩法都需要处理。还是交给客户端发消息时自行决定发到哪类节点) + 3. gate根据消息id决定路由给哪类节点,根据节点是否状态服选择节点。(同上) + 4. 通过广播机制,将玩家在哪个状态服里同步给所有服,保存到内存里。(后续优化,暂不考虑) + 5. 优化rpc及send通过serviceTypeId来处理向哪一个节点发送消息。(已优化,避免每次都要查找node) \ No newline at end of file diff --git a/common/gameService/gameService.go b/common/gameService/gameService.go new file mode 100644 index 0000000..302b5c2 --- /dev/null +++ b/common/gameService/gameService.go @@ -0,0 +1,102 @@ +package gameService + +import ( + "fmt" + "game/common/proto/pb" + "game/common/userBindService" + "game/server/colorgame/model" + "github.com/fox/fox/etcd" + "github.com/fox/fox/ipb" + "github.com/fox/fox/log" + "github.com/fox/fox/processor" + "github.com/fox/fox/service" + "github.com/golang/protobuf/proto" + "time" +) + +type GameService struct { + *service.NatsService + processor *processor.Processor + bindService *userBindService.UserBindService +} + +func NewGameService(param *service.InitNatsServiceParams) *GameService { + var err error + s := new(GameService) + s.NatsService, err = service.NewNatsService(param) + if err != nil { + log.Fatal(err.Error()) + return nil + } + s.bindService = userBindService.NewUserBindService(model.UserBindServiceRedis, s.ServiceEtcd()) + s.processor = processor.NewProcessor() + return s +} + +func (s *GameService) BindService() *userBindService.UserBindService { + return s.bindService +} + +func (s *GameService) Processor() *processor.Processor { + return s.processor +} + +// 依赖子类实现,通过节点id获取topic名再调用SendByTopic +func (s *GameService) SendByServiceId(serviceId int, msg *ipb.InternalMsg) error { + node, err := s.findNode(pb.ServiceTypeId(serviceId), msg.UserId) + if err != nil { + return err + } + topic := service.TopicEx(node.Name) + return s.SendByTopic(topic, msg) +} + +// 向内部服务发送消息 +func (s *GameService) SendServiceData(topic string, connId uint32, userId int64, msgId int32, data []byte) { + iMsg := ipb.MakeMsg(s.Name(), connId, userId, msgId, data) + _ = s.SendByTopic(topic, iMsg) +} + +// 向内部服务发送消息 +func (s *GameService) SendServiceMsg(sid pb.ServiceTypeId, userId int64, msgId int32, msg proto.Message) { + node, err := s.findNode(sid, userId) + if err != nil { + log.Error(err.Error()) + return + } + topic := service.TopicEx(node.Name) + log.DebugF("send to:%v msg id:%v, msg:%v", topic, pb.MsgId(msgId), msg.String()) + data, _ := proto.Marshal(msg) + s.SendServiceData(topic, 0, userId, msgId, data) +} + +// 向内部服务发送消息 +func (s *GameService) findNode(serviceId pb.ServiceTypeId, uid int64) (*etcd.ServiceNode, error) { + var err error + var node *etcd.ServiceNode + switch s.bindService.CheckServiceNodeStateType(serviceId) { + case etcd.SNST_Stateful: + node, err = s.bindService.FindServiceNode(serviceId, uid) + case etcd.SNST_Stateless: + node, err = s.bindService.HashServiceNode(serviceId, uid) + case etcd.SNST_Ordered: + node, err = s.bindService.HashServiceNode(serviceId, uid) + default: + log.ErrorF(s.Log("查找节点类型失败。sid:%v", serviceId)) + return nil, fmt.Errorf(s.Log("查找节点类型失败。sid:%v", serviceId)) + } + if err != nil { + return nil, fmt.Errorf(s.Log("查找节点类型失败。sid:%v err:%v", serviceId, err)) + } + return node, nil +} + +// call实现 +func (s *GameService) CallByServiceId(serviceId int, timeout time.Duration, msg *ipb.InternalMsg) (*ipb.InternalMsg, error) { + node, err := s.findNode(pb.ServiceTypeId(serviceId), msg.UserId) + if err != nil { + return nil, err + } + topic := service.RpcTopicEx(node.Name) + return s.CallByTopic(topic, timeout, msg) +} diff --git a/common/pb/service.proto b/common/pb/service.proto index ed14155..df1d035 100644 --- a/common/pb/service.proto +++ b/common/pb/service.proto @@ -2,17 +2,25 @@ syntax = "proto3"; package pb; option go_package = "common/proto/pb"; +// 节点范围为10000-99999 +// 无状态节点:10000-19999 +// 有状态节点:20000-29999 +// 有序节点:30000-39999 enum ServiceTypeId { STI_Unknown = 0; - STI_Gate = 100; // 网关id - STI_Login = 101; // 登陆服 - STI_Chat = 102; // 聊天服 - STI_DB = 103; // db服 - STI_Match = 104; // 匹配服 - STI_Lobby = 105; // 大厅服 + // 无状态节点 + STI_Login = 10000; // 登陆服 + STI_Chat = 10005; // 聊天服 + STI_Match = 10010; // 匹配服 + STI_Lobby = 10015; // 大厅服 - STI_ColorGame = 120; // color game + // 有状态节点 + STI_Gate = 20000; // 网关id + STI_ColorGame = 20005; // color game + + // 有序节点 + STI_DB = 30000; // db服 } diff --git a/common/proto/pb/service.pb.go b/common/proto/pb/service.pb.go index 5194bc6..4a94807 100644 --- a/common/proto/pb/service.pb.go +++ b/common/proto/pb/service.pb.go @@ -21,40 +21,47 @@ const ( _ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20) ) +// 节点范围为10000-99999 +// 无状态节点:10000-19999 +// 有状态节点:20000-29999 +// 有序节点:30000-39999 type ServiceTypeId int32 const ( - ServiceTypeId_STI_Unknown ServiceTypeId = 0 - ServiceTypeId_STI_Gate ServiceTypeId = 100 // 网关id - ServiceTypeId_STI_Login ServiceTypeId = 101 // 登陆服 - ServiceTypeId_STI_Chat ServiceTypeId = 102 // 聊天服 - ServiceTypeId_STI_DB ServiceTypeId = 103 // db服 - ServiceTypeId_STI_Match ServiceTypeId = 104 // 匹配服 - ServiceTypeId_STI_Lobby ServiceTypeId = 105 // 大厅服 - ServiceTypeId_STI_ColorGame ServiceTypeId = 120 // color game + ServiceTypeId_STI_Unknown ServiceTypeId = 0 + // 无状态节点 + ServiceTypeId_STI_Login ServiceTypeId = 10000 // 登陆服 + ServiceTypeId_STI_Chat ServiceTypeId = 10005 // 聊天服 + ServiceTypeId_STI_Match ServiceTypeId = 10010 // 匹配服 + ServiceTypeId_STI_Lobby ServiceTypeId = 10015 // 大厅服 + // 有状态节点 + ServiceTypeId_STI_Gate ServiceTypeId = 20000 // 网关id + ServiceTypeId_STI_ColorGame ServiceTypeId = 20005 // color game + // 有序节点 + ServiceTypeId_STI_DB ServiceTypeId = 30000 // db服 ) // Enum value maps for ServiceTypeId. var ( ServiceTypeId_name = map[int32]string{ - 0: "STI_Unknown", - 100: "STI_Gate", - 101: "STI_Login", - 102: "STI_Chat", - 103: "STI_DB", - 104: "STI_Match", - 105: "STI_Lobby", - 120: "STI_ColorGame", + 0: "STI_Unknown", + 10000: "STI_Login", + 10005: "STI_Chat", + 10010: "STI_Match", + 10015: "STI_Lobby", + 20000: "STI_Gate", + 20005: "STI_ColorGame", + 30000: "STI_DB", } ServiceTypeId_value = map[string]int32{ "STI_Unknown": 0, - "STI_Gate": 100, - "STI_Login": 101, - "STI_Chat": 102, - "STI_DB": 103, - "STI_Match": 104, - "STI_Lobby": 105, - "STI_ColorGame": 120, + "STI_Login": 10000, + "STI_Chat": 10005, + "STI_Match": 10010, + "STI_Lobby": 10015, + "STI_Gate": 20000, + "STI_ColorGame": 20005, + "STI_DB": 30000, } ) @@ -89,17 +96,16 @@ var File_service_proto protoreflect.FileDescriptor const file_service_proto_rawDesc = "" + "\n" + - "\rservice.proto\x12\x02pb*\x88\x01\n" + + "\rservice.proto\x12\x02pb*\x92\x01\n" + "\rServiceTypeId\x12\x0f\n" + - "\vSTI_Unknown\x10\x00\x12\f\n" + - "\bSTI_Gate\x10d\x12\r\n" + - "\tSTI_Login\x10e\x12\f\n" + - "\bSTI_Chat\x10f\x12\n" + - "\n" + - "\x06STI_DB\x10g\x12\r\n" + - "\tSTI_Match\x10h\x12\r\n" + - "\tSTI_Lobby\x10i\x12\x11\n" + - "\rSTI_ColorGame\x10xB\x11Z\x0fcommon/proto/pbb\x06proto3" + "\vSTI_Unknown\x10\x00\x12\x0e\n" + + "\tSTI_Login\x10\x90N\x12\r\n" + + "\bSTI_Chat\x10\x95N\x12\x0e\n" + + "\tSTI_Match\x10\x9aN\x12\x0e\n" + + "\tSTI_Lobby\x10\x9fN\x12\x0e\n" + + "\bSTI_Gate\x10\xa0\x9c\x01\x12\x13\n" + + "\rSTI_ColorGame\x10\xa5\x9c\x01\x12\f\n" + + "\x06STI_DB\x10\xb0\xea\x01B\x11Z\x0fcommon/proto/pbb\x06proto3" var ( file_service_proto_rawDescOnce sync.Once diff --git a/common/rpc/rpcGameUser.go b/common/rpc/rpcGameUser.go index 627735d..359a86e 100644 --- a/common/rpc/rpcGameUser.go +++ b/common/rpc/rpcGameUser.go @@ -4,7 +4,6 @@ import ( "encoding/json" "game/common/model/user" "game/common/proto/pb" - "game/common/userBindService" "github.com/fox/fox/ipb" "github.com/fox/fox/log" "github.com/fox/fox/service" @@ -16,12 +15,7 @@ const ( ) // 获取玩家数据 -func RpcGetGameUser(bindService *userBindService.UserBindService, s service.IService, uid int64) (*user.GameUser, pb.ErrCode) { - node, err := bindService.HashServiceNode(pb.ServiceTypeId_STI_DB, uid) - if err != nil { - log.ErrorF("db service node error:%v", err) - return nil, pb.ErrCode_SystemErr - } +func RpcGetGameUser(s service.IService, uid int64) (*user.GameUser, pb.ErrCode) { us := &user.GameUser{ User: user.User{ ID: uid, @@ -31,7 +25,7 @@ func RpcGetGameUser(bindService *userBindService.UserBindService, s service.ISer }, } rpcMsg := ipb.MakeRpcMsg(GetGameUser, uid, us) - rspMsg, err := s.Call(service.RpcTopicEx(node.Name), timeout, rpcMsg) + rspMsg, err := s.CallByServiceId(int(pb.ServiceTypeId_STI_DB), timeout, rpcMsg) if err != nil { log.ErrorF("call rpc:%v err:%s ", rpcMsg.RpcMsgId, err.Error()) return nil, pb.ErrCode_SystemErr diff --git a/common/userBindService/userService.go b/common/userBindService/userService.go index 92b7637..3e6afdf 100644 --- a/common/userBindService/userService.go +++ b/common/userBindService/userService.go @@ -164,7 +164,8 @@ func (m *UserBindService) HashServiceNode(typeId pb.ServiceTypeId, uid int64) (* } // 要查找的服务必须是状态服,无状态服不需要查找指定服务。 -// 如果玩家是首次使用该服务,则随机一个服务并保存玩家该服务节点。下次查找时返回该节点。 +// 如果玩家是首次使用该服务,则随机一个最新服务并保存玩家该服务节点。下次查找时返回该节点。 +// 如果是老玩家,则从redis中查找他呆的节点返回 func (m *UserBindService) FindServiceNode(typeId pb.ServiceTypeId, userId int64) (*etcd.ServiceNode, error) { if userId > 0 { // 向redis中查询。redis中保留的服务节点不一定是可用的,还需要向etcd中验证 @@ -208,3 +209,8 @@ func (m *UserBindService) RangeUserAllServiceNode(userId int64, proc func(node * } return } + +// 获取节点是有状态或无状态或有序节点 +func (m *UserBindService) CheckServiceNodeStateType(serviceId pb.ServiceTypeId) etcd.ServiceNodeStateType { + return etcd.ServiceNodeStateType(serviceId / 10000) +} diff --git a/server/colorgame/server/processor.go b/server/colorgame/server/processor.go index 8b1ec59..a1aee0f 100644 --- a/server/colorgame/server/processor.go +++ b/server/colorgame/server/processor.go @@ -15,7 +15,7 @@ const ( ) func (s *ColorService) initProcessor() { - s.processor.RegisterMessages(processor.RegisterMetas{ + s.Processor().RegisterMessages(processor.RegisterMetas{ pb.MsgId_ReqEnterRoomId: {pb.ReqEnterRoom{}, s.onEnterRoom}, }) } @@ -23,9 +23,9 @@ func (s *ColorService) initProcessor() { // 进房间 func (s *ColorService) onEnterRoom(iMsg *ipb.InternalMsg, req *pb.ReqEnterRoom) { ksync.GoSafe(func() { - us, code := rpc.RpcGetGameUser(s.bindService, s, iMsg.UserId) + us, code := rpc.RpcGetGameUser(s, iMsg.UserId) if code != pb.ErrCode_OK { - s.SendServiceMsg(service.TopicEx(iMsg.ServiceName), iMsg.ConnId, iMsg.UserId, + s.SendServiceMsg(pb.ServiceTypeId_STI_Gate, iMsg.UserId, int32(pb.MsgId_RspEnterRoomId), &pb.RspEnterRoom{Code: pb.ErrCode_SystemErr}) return } diff --git a/server/colorgame/server/service.go b/server/colorgame/server/service.go index 4ab502a..ad4ab90 100644 --- a/server/colorgame/server/service.go +++ b/server/colorgame/server/service.go @@ -3,15 +3,13 @@ package server import ( "fmt" "game/common/baseroom" + "game/common/gameService" "game/common/proto/pb" "game/common/serviceName" - "game/common/userBindService" "game/server/colorgame/config" - "game/server/colorgame/model" "game/server/colorgame/room" "github.com/fox/fox/ipb" "github.com/fox/fox/log" - "github.com/fox/fox/processor" "github.com/fox/fox/service" "github.com/golang/protobuf/proto" ) @@ -19,9 +17,7 @@ import ( var Color []service.IService type ColorService struct { - *service.NatsService - processor *processor.Processor - bindService *userBindService.UserBindService + *gameService.GameService //roomMgr *baseroom.RoomMgr playerMgr *baseroom.PlayerMgr @@ -50,10 +46,9 @@ func Stop() { } func newColorService(serviceId int) service.IService { - var err error s := new(ColorService) sName := fmt.Sprintf("%v-%d", serviceName.ColorGame, serviceId) - s.NatsService, err = service.NewNatsService(&service.InitNatsServiceParams{ + s.GameService = gameService.NewGameService(&service.InitNatsServiceParams{ EtcdAddress: config.Cfg.Etcd.Address, EtcdUsername: "", EtcdPassword: "", @@ -64,17 +59,11 @@ func newColorService(serviceId int) service.IService { TypeId: int(pb.ServiceTypeId_STI_ColorGame), Version: config.Cfg.BuildDate, }) - if err != nil { - log.Fatal(err.Error()) - return nil - } - s.bindService = userBindService.NewUserBindService(model.UserBindServiceRedis, s.ServiceEtcd()) - s.processor = processor.NewProcessor() s.initProcessor() s.playerMgr = baseroom.NewPlayerMgr(nil) factory := &room.RoomFactory{} - s.room, _ = factory.CreateRoom(int(pb.ServiceTypeId_STI_ColorGame), 0, s, s.playerMgr, s.bindService) + s.room, _ = factory.CreateRoom(int(pb.ServiceTypeId_STI_ColorGame), 0, s, s.playerMgr, s.BindService()) s.status = baseroom.SsWorking //s.roomMgr = baseroom.NewRoomMgr(&room.RoomFactory{}) @@ -137,8 +126,8 @@ func (s *ColorService) OnMessage(data []byte) error { log.Error(err.Error()) return err } - if req, err := s.processor.Unmarshal(iMsg.MsgId, iMsg.Msg); err == nil { - err = s.processor.Dispatch(iMsg.MsgId, iMsg, req) + if req, err := s.Processor().Unmarshal(iMsg.MsgId, iMsg.Msg); err == nil { + err = s.Processor().Dispatch(iMsg.MsgId, iMsg, req) } else { var user baseroom.IPlayer var rm baseroom.IRoom @@ -154,19 +143,3 @@ func (s *ColorService) OnMessage(data []byte) error { log.Debug(s.Log("received message:%v", pb.MsgId(iMsg.MsgId))) return nil } - -// 向内部服务发送消息 -func (s *ColorService) SendServiceData(topic string, connId uint32, userId int64, msgId int32, data []byte) { - iMsg := ipb.MakeMsg(s.Name(), connId, userId, msgId, data) - _ = s.Send(topic, iMsg) -} - -// 向内部服务发送消息 -func (s *ColorService) SendServiceMsg(topic string, connId uint32, userId int64, msgId int32, msg proto.Message) { - if msgId == int32(pb.MsgId_RspMatchRoomId) { - _ = connId - } - log.DebugF("send to:%v msg id:%v, msg:%v", topic, pb.MsgId(msgId), msg.String()) - data, _ := proto.Marshal(msg) - s.SendServiceData(topic, connId, userId, msgId, data) -}