项目地址

mango:https://github.com/GoldBaby5511/mango.git
mango-client:https://github.com/GoldBaby5511/mango-client.git
mango-user-center:https://github.com/GoldBaby5511/mango-user-center.git
mango-admin:https://github.com/GoldBaby5511/mango-admin.git
mango-admin-ui:https://github.com/GoldBaby5511/mango-admin-ui.git

开发环境

  • Windows10专业版
  • Goland2021.2
  • go1.15.2

使用

  • 执行script\statup.bat脚本,会依次编译并启动logger.exe、center.exe、config.exe、gateway.exe、login.exe
  • 解压client,双击执行client.exe,单击“微信登录”,选择Test001

设计

​ 采用网状结构,除center服务于logger服务之外,其他所有服务两两互联。服务启动时先向center发送注册,注册成功后center将服务信息广播至所有已注册服务,同时将已注册服务基础信息下发至新注册服务,所有服务收到注册信息后进行两两互联,具体结构及流程如下。

服务连接关系

服务分类

  • Gateway:网关,与Client交互,转发Client消息
  • Center:中心服,负责服务注册与管理
  • Config:配置中心,所有服务配置,支持本地JSON文件以及携程Apollo配置中心(推荐该方式),配置修改实时下发至相关订阅服务
  • lobby:登陆服务,登陆验证,维护在线用户信息
  • property:财富服务,用户财富操作,如分数加减等
  • List:房间列表服务
  • Room:房间服务
  • Table:桌子服务,具体游戏实现
  • daemon:守护服务,执行管理端命令,开启/守护相关服务

基础属性

属性名 属性值
AppName 服务名称
AppType 服务类型
AppID 服务ID(全局唯一)
CenterAddr 中心服务地址
ListenOnAddr 本服务监听地址

目录结构

├─api
│ ├─center
│ ├─client
│ ├─config
│ ├─gameddz
│ ├─gate
│ ├─logger
│ ├─proto
│ ├─room
│ ├─table
│ └─types
├─build
│ └─package
├─cmd
│ ├─center
│ │ └─business
│ ├─config
│ │ ├─business
│ │ └─conf
│ ├─gateway
│ │ └─business
│ ├─logger
│ │ └─business
│ ├─login
│ │ └─business
│ ├─room
│ │ └─business
│ │ └─player
│ └─table
│ └─business
│ ├─player
│ └─table
│ └─ddz
├─configs
│ ├─center
│ ├─config
│ ├─gateway
│ ├─logger
│ └─login
├─examples
│ └─client
├─pkg
│ ├─chanrpc
│ ├─conf
│ │ └─apollo
│ ├─gate
│ ├─go
│ ├─log
│ ├─module
│ ├─network
│ │ ├─json
│ │ └─protobuf
│ ├─timer
│ └─util
├─scripts
└─third_party
└─agollo

/api : 所使用的proto目录

/build/package : Docker镜像脚本

/cmd :程序主干服务入口、业务实现、如center、gateway等等

/configs : 所有服务json配置文件

/examples : 一个示例客户端,unity编写

/pkg : 封装了所有服务共用基础框架

/scripts : 启动脚本,目前只有windows下

/third_party/agollo : 协程apollo配置中心,第三方包


启动流程

程序入口(以center为例)为gate包内start方法,需传入服务名称;导入主业务包并初始化,如下

package mainimport (_ "xlddz/cmd/center/business""xlddz/pkg/gate"
)func main() {gate.Start("center")
}

business包init(),主要注册该服务需要处理的消息以及事件映射

import _ "xlddz/cmd/center/business"func init() {//注册消息g.MsgRegister(&center.RegisterAppReq{}, n.CMDCenter, uint16(center.CMDID_Center_IDAppRegReq), handleRegisterAppReq)g.MsgRegister(&center.AppStateNotify{}, n.CMDCenter, uint16(center.CMDID_Center_IDAppState), handleAppStateNotify)g.MsgRegister(&center.AppPulseNotify{}, n.CMDCenter, uint16(center.CMDID_Center_IDPulseNotify), handleAppPulseNotify)g.MsgRegister(&center.AppOfflineReq{}, n.CMDCenter, uint16(center.CMDID_Center_IDAppOfflineReq), handleAppOfflineReq)g.MsgRegister(&center.AppUpdateReq{}, n.CMDCenter, uint16(center.CMDID_Center_IDAppUpdateReq), handleAppUpdateReq)//注册事件g.EventRegister(g.ConnectSuccess, connectSuccess)g.EventRegister(g.Disconnect, disconnect)
}

gate.Start方法内依次完成下列操作

  • 初始化logger
  • 初始化基础配置
  • 启动业务gogroutine
  • 启动网络IO协程
gate.Start("center")func Start(appName string) {conf.AppInfo.AppName = appName// loggerl, err := log.New(conf.AppInfo.AppName)if err != nil {panic(err)}log.Export(l)defer l.Close()//baseConfigconf.LoadBaseConfig()if conf.AppInfo.AppType == n.AppCenter {apollo.RegisterConfig("", conf.AppInfo.AppType, conf.AppInfo.AppID, nil)}wg.Add(2)go func() {Skeleton.Run()wg.Done()}()go func() {Run()wg.Done()}()// closec := make(chan os.Signal, 1)signal.Notify(c, os.Interrupt, os.Kill)sig := <-clog.Info("主流程", "服务器关闭 (signal: %v)", sig)Stop()
}

gate.Run()会根据配置启动监听,并连接Center

func Run() {...//本服务监听地址var tcpServer *n.TCPServerif conf.AppInfo.ListenOnAddress != "" {tcpServer = new(n.TCPServer)tcpServer.Addr = conf.AppInfo.ListenOnAddresstcpServer.MaxConnNum = MaxConnNumtcpServer.PendingWriteNum = PendingWriteNumtcpServer.LenMsgLen = LenMsgLentcpServer.MaxMsgLen = MaxMsgLentcpServer.GetConfig = apollo.GetConfigAsInt64tcpServer.NewAgent = func(conn *n.TCPConn, agentId uint64) n.AgentClient {a := &agentClient{id: agentId, conn: conn, info: n.BaseAgentInfo{AgentType: n.NormalUser}}if agentChanRPC != nil {agentChanRPC.Go(ConnectSuccess, a, agentId)}return a}}//连接Centerif conf.AppInfo.CenterAddr != "" && conf.AppInfo.AppType != n.AppCenter {newServerItem(n.BaseAgentInfo{AgentType: n.CommonServer, AppName: "center", AppType: n.AppCenter, ListenOnAddress: conf.AppInfo.CenterAddr}, true, PendingWriteNum)}...
}

与Center连接成功后发送注册,注册成功后center将服务信息广播至所有已注册服务,同时将已注册服务基础信息下发至新注册服务,所有服务收到注册信息后进行两两互联

func newServerItem(info n.BaseAgentInfo, autoReconnect bool, pendingWriteNum int) {...tcpClient.NewAgent = func(conn *n.TCPConn) n.AgentServer {...sendRegAppReq(a)...return a}...
}func (a *agentServer) Run() {for {...switch bm.Cmd.SubCmdID {case uint16(center.CMDID_Center_IDAppRegRsp):var m center.RegisterAppRsp_ = proto.Unmarshal(msgData, &m)if m.GetRegResult() == 0 {...if !(conf.AppInfo.AppType == m.GetAppType() && conf.AppInfo.AppID == m.GetAppId()) && !ok {if m.GetAppAddress() != "" {...//与其他服务进行两两互联newServerItem(info, false, 0)} else {log.Warning("agentServer", "没有地址?,%v,%v,%v,%v",m.GetAppName(), m.GetAppType(), m.GetAppId(), m.GetAppAddress())}}...} else {log.Warning("agentServer", "注册失败,RouterId=%v,原因=%v", m.GetCenterId(), m.GetReregToken())}...}}
}

当与配置中心互联成功后向配置中心注册并获取本服务所有配置

func newServerItem(info n.BaseAgentInfo, autoReconnect bool, pendingWriteNum int) {...tcpClient.NewAgent = func(conn *n.TCPConn) n.AgentServer {...//在这里注册本服务的配置请求if n.AppConfig == info.AppType {apollo.SetNetAgent(a)apollo.RegisterConfig("", conf.AppInfo.AppType, conf.AppInfo.AppID, nil)}...return a}...
}
func RegisterConfig(key string, reqAppType, reqAppId uint32, cb cbNotify) {...SendSubscribeReq(nsKey, false)
}func SendSubscribeReq(k ConfKey, cancel bool) {...cmd := network.TCPCommand{MainCmdID: uint16(network.AppConfig), SubCmdID: uint16(config.CMDID_Config_IDApolloCfgReq)}bm := network.BaseMessage{MyMessage: &req, Cmd: cmd}netAgent.SendMessage(bm)
}

config服务受到配置请求后会查找已加载的配置,相应请求,相关逻辑处理在 /cmd/config/business/business.go内

func handleApolloCfgReq(args []interface{}) {b := args[n.DataIndex].(n.BaseMessage)m := (b.MyMessage).(*config.ApolloCfgReq)log.Debug("配置", "收到配置请求,AppType=%v,AppId=%v,KeyName=%v,SubAppType=%v,SubAppId=%v,Subscribe=%v",m.GetAppType(), m.GetAppId(), m.GetKeyName(), m.GetSubAppType(), m.GetSubAppId(), m.GetSubscribe())listerIndex := getListenerIndex(m.GetSubAppType(), m.GetSubAppId())if listerIndex < 0 {log.Warning("配置", "配置不存在,NameSpace=%v,KeyName=%v", m.GetNameSpace(), m.GetKeyName())return}listenerList[listerIndex].addSubscriptionItem(m.GetAppType(), m.GetAppId(), m.GetSubAppType(), m.GetSubAppId(), m.GetKeyName())if m.GetSubscribe()&uint32(config.ApolloCfgReq_NEED_RSP) != 0 {listenerList[listerIndex].notifySubscriptionList(m.GetKeyName())}
}

gate包在init的时候会注册一个配置中心相应,处理方法在handleApolloCfgRsp内

func init() {...MsgRegister(&config.ApolloCfgRsp{}, n.CMDConfig, uint16(config.CMDID_Config_IDApolloCfgRsp), handleApolloCfgRsp)
}

当请求配置消息得到响应后,会通过gate在init内的注册将相应路由到handleApolloCfgRsp内

func (a *agentClient) Run() {for {...//解析并路由消息cmd, msg, err = processor.Unmarshal(unmarshalCmd.MainCmdID, unmarshalCmd.SubCmdID, msgData)if err != nil {log.Error("agentClient", "unmarshal message,headCmd=%v,error: %v", bm.Cmd, err)continue}err = processor.Route(n.BaseMessage{MyMessage: msg, TraceId: bm.TraceId}, a, cmd, dataReq)if err != nil {log.Error("agentClient", "client agentClient route message error: %v,cmd=%v", err, cmd)continue}}
}func handleApolloCfgRsp(args []interface{}) {//将消息丢到apollo包内,存储相关配置apollo.ProcessConfigRsp(args[n.DataIndex].(n.BaseMessage).MyMessage.(*config.ApolloCfgRsp))//获取日志服地址进行连接logAddr := apollo.GetConfig("日志服务器地址", "")if logAddr != "" && tcpLog != nil && !tcpLog.IsRunning() {ConnectLogServer(logAddr)}//若业务层需要则将配置抛到业务层go func() {if agentChanRPC != nil {agentChanRPC.Call0(ConfigChangeNotify)}}()
}

一般服务当与日志服连接成功后,则认为服务启动流程就结束了,但是这个没有统一标准,比如一些服务是需要根据配置响应做一些业务上的初始化后才算完成,比如room、table之类就需要获取桌子或初始化桌子等

func ConnectLogServer(logAddr string) {...if conf.AppInfo.AppType != n.AppLogger && logAddr != "" && tcpLog != nil && !tcpLog.IsRunning() {...tcpLog.NewAgent = func(conn *n.TCPConn) n.AgentServer {...log.Info("gate", "日志服务器连接成功,服务启动完成,阔以开始了... ...")...return a}tcpLog.Start()}
}

项目地址:https://github.com/GoldBaby5511/mango.git,由于本人能力有限,如有好建议请不吝赐教,若觉得有参考价值还望不吝点亮小星星。

相关博客

mango(一):杂谈项目由来:https://blog.csdn.net/weixin_42780662/article/details/122006434


参考引用

  • leaf:https://github.com/name5566/leaf.git
  • agollo:https://github.com/apolloconfig/agollo.git
  • fsnotify:https://github.com/fsnotify/fsnotify.git
  • proto:https://github.com/protocolbuffers/protobuf.git
  • project-layout:https://github.com/golang-standards/project-layout.git

交流群

  • QQ群:781335145
  • 点击链接加入群聊【mango交流群】:https://jq.qq.com/?_wv=1027&k=z40wrFuD

mango(二):架构相关推荐

  1. 系统架构师设计培训心得之二——架构设计

    培训过程中,老师用例子说明了一个项目的架构设计的流程.按步骤可以分为: 框架技术的选择应用: 架构平台重构与设计过程: 领域建模: 行为建模: 这四个步骤中,第三步与第四步是最重要的核心. 一. 框架 ...

  2. 【工业串口和网络软件通讯平台(SuperIO)教程】二.架构和组成部分

    1.1    架构结构图 1.1.1    层次示意图 1.1.2    模型对象示意图 1.2    IO管理器 IO管理器是对串口和网络通讯链路的管理.调度.针对串口和网络通讯链路的特点,在IO管 ...

  3. 架构师之路二-架构设计方法论

    概念解析 在文章开始之前需要先理解几个概念: 什么是方法论? 我们拿到一个输入,然后根据这个输入预期一个输出,把中间这个过程描述出来就是方法论.所以我们本篇讲的架构师方法论就是架构师先拿到经过需求分析 ...

  4. 分析了一下360安全卫士的 HOOK(二)——架构与实现(zt)

    连接:http://blog.csdn.net/lionzl/article/details/7738182 上一篇的分析中漏掉了三个函数,现补上: NtSetSystemInformation    ...

  5. ssh整合步骤之二(架构设计)

    1.新建一个com.njupt.action的包,再在里面李建一个UserAction,然后ctrl + c ,ctrl + v(6次).这时候便完成的一个三层的 体系架构,如下图所示:

  6. Git 之二 架构、工作流程、.git 目录文件

    写在前面   Git 的官网上有很详细的使用教程(当然有翻译版本),具体地址是 https://git-scm.com/book/zh/v2.唯一不足就是,很多讲解并没有实机演示.但是,毫无疑问,官网 ...

  7. 拉勾网《32个Java面试必考点》学习笔记之十二------架构演进与容器技术

    本文为拉勾网<32个Java面试必考点>学习笔记.只是对视频内容进行简单整理,详细内容还请自行观看视频<32个Java面试必考点>.若本文侵犯了相关所有者的权益,请联系:txz ...

  8. 转:乐视秒杀:每秒十万笔交易的数据架构解读

    随着乐视硬件抢购的不断升级,乐视集团支付面临的请求压力百倍乃至千倍的暴增.作为商品购买的最后一环,保证用户快速稳定地完成支付尤为重要.所以在2015年11月,我们对整个支付系统进行了全面的架构升级,使 ...

  9. 每秒处理10万订单乐视集团支付架构--转

    随着乐视硬件抢购的不断升级,乐视集团支付面临的请求压力百倍乃至千倍的暴增.作为商品购买的最后一环,保证用户快速稳定的完成支付尤为重要.所以在15年11月,我们对整个支付系统进行了全面的架构升级,使之具 ...

最新文章

  1. python画图代码turtle-Python使用Turtle图形函数画图 颜色填充!
  2. Python3 系列之 可变参数和关键字参数
  3. 华硕主板X99-E WS/USB 3.1 Intel Realsense D435摄像头掉线是否与Intel推行的xhci有关?
  4. 浅谈 Python 中的多线程。
  5. 【转】基于jquery,bootstrap数据验证插件bootstrapValidator 教程
  6. DeferredResult – Spring MVC中的异步处理
  7. sql 排序 分组 层级 筛选 - God聚会啊
  8. 吴恩达机器学习作业5.偏差和方差
  9. Java必知必会:异常机制详解
  10. 配置多个git账号_Git ssh配置(Mac)
  11. hdu 5003 模拟水题 (2014鞍山网赛G题)
  12. db_cxx.h: No such file or directory
  13. 动手DIY一个智能镜子
  14. 【TS】GARCH模型(1)
  15. stm32增加固件库工程
  16. 最简单的联想笔记本重装系统方法,一键重装系统图解
  17. 【无标题】scp的使用
  18. python中函数的使用
  19. 分享 10 个最常见的 JavaScript 问题
  20. RoboCup仿真3D底层通信模块介绍(二)

热门文章

  1. 数据结构(3)--线性表实现一元多项式加法
  2. iOS APP启动图片适配iPhoneX
  3. golang goroutine实现_Go goroutine理解
  4. 使用element-ui的upload组件上传代码包时遇到的问题及总结
  5. 非识不可C3D模型详解
  6. 一、webpack是什么?
  7. oracle怎么增量备份,Oracle 增量备份
  8. 发改委:三家电商正自查整改 厂商退出京东
  9. 基于IMU的航姿算法
  10. 《卡尔曼滤波原理及应用-MATLAB仿真》程序-2.2