探索 Nano 内置分布式游戏服务器方案测试用例

简介: 探索 Nano 内置分布式游戏服务器方案测试用例

Nano 分布式游戏服务器方案


Nano包含内置的分布式系统解决方案,可让您轻松创建分布式游戏服务器。

How to build distributed system with Nano


示例仓库

  • 笔者改过后的 Test Case:node_test.go


开始探索


笔者本地环境


go version
# go version go1.14.2 darwin/amd64


PingCap 测试套件


pingcap/check

Suite:Suite 将给定的值注册为要运行的测试套件。在给定值中以Test前缀开头的任何方法都将被视为测试方法。

TestingT:TestingT 运行所有用 Suite 函数注册的测试套件,将结果打印到 stdout,并将任何失败报告给 "testing" 包。

新建一个 demo_test.go,我们感受一下:


package cluster_test
import (
  "testing"
  . "github.com/pingcap/check"
)
type demoSuite struct{}
var _ = Suite(&demoSuite{})
func (s *demoSuite) TestNodeStartup(c *C) {
  c.Assert(nil, IsNil)
}
// 运行测试
func TestDemo(t *testing.T) {
  TestingT(t)
}


如果你是 VSCode + VSCode-GO,可以直接 run test

image.gif

默认使用 protobuf 进行数据交换

掘金 tag:protobuf,很多掘友会手把手教。

我们可以查看下benchmark/testdata/test.proto


syntax = "proto3";
package t;
message Ping {
    string Content = 1;
}
message Pong {
    string Content = 2;
}


就是按照 protobuf 的规范定义消息,生成相关语言的代码,用来交换数据。


测试用例源码分析


package cluster_test
import (
  "strings"
  "testing"
  "github.com/lonng/nano/benchmark/io"
  "github.com/lonng/nano/benchmark/testdata"
  "github.com/lonng/nano/cluster"
  "github.com/lonng/nano/component"
  "github.com/lonng/nano/scheduler"
  "github.com/lonng/nano/session"
  . "github.com/pingcap/check"
)
type nodeSuite struct{}
// 注册测试套件
var _ = Suite(&nodeSuite{})
type (
  // 定义运行在 Master(主) 服务器上的 Nano 组件
  MasterComponent struct{ component.Base }
  // 定义运行在 Gate(网关) 服务器上的 Nano 组件
  GateComponent struct{ component.Base }
  // 定义运行在 Game(游戏业务逻辑) 服务器上的 Nano 组件
  GameComponent struct{ component.Base }
)
// Master 服务器上名为 `Test` Handler 方法,对外提供服务
func (c *MasterComponent) Test(session *session.Session, _ []byte) error {
  // 推送消息
  return session.Push("test", &testdata.Pong{Content: "master server pong"})
}
// Gate 服务器上名为 `Test` Handler 方法,对外提供服务
func (c *GateComponent) Test(session *session.Session, ping *testdata.Ping) error {
  // 推送消息
  return session.Push("test", &testdata.Pong{Content: "gate server pong"})
}
// Gate 服务器上名为 `Test2` Handler 方法,对外提供服务
func (c *GateComponent) Test2(session *session.Session, ping *testdata.Ping) error {
  // 响应消息
  return session.Response(&testdata.Pong{Content: "gate server pong2"})
}
// Game 服务器上名为 `Test` Handler 方法,对外提供服务
func (c *GameComponent) Test(session *session.Session, _ []byte) error {
  // 推送消息
  return session.Push("test", &testdata.Pong{Content: "game server pong"})
}
// Game 服务器上名为 `Test2` Handler 方法,对外提供服务
func (c *GameComponent) Test2(session *session.Session, ping *testdata.Ping) error {
  return session.Response(&testdata.Pong{Content: "game server pong2"})
}
func TestNode(t *testing.T) {
  TestingT(t)
}
func (s *nodeSuite) TestNodeStartup(c *C) {
  // 开启一个全局的任务调度
  go scheduler.Sched()
  // 结束前清理
  defer scheduler.Close()
  // 注册 Master 服务器的 Components
  masterComps := &component.Components{}
  masterComps.Register(&MasterComponent{})
  // 集群 Master 服务器配置
  masterNode := &cluster.Node{
    // 服务地址
    ServiceAddr: "127.0.0.1:4450",
  }
  masterNode.Options = cluster.Options{
    IsMaster:   true,        // 设定为 Master Node
    Components: masterComps, // 设置 Master Components
  }
  // 启动 Master 节点服务器
  err := masterNode.Startup()
  // 断言启动是否有错误
  c.Assert(err, IsNil)
  // 断言 LocalService 是否是我们定义的 MasterComponent
  masterHandler := masterNode.Handler()
  c.Assert(masterHandler.LocalService(), DeepEquals, []string{"MasterComponent"})
  // 注册 Gate 服务器节点的 Components
  gateComps := &component.Components{}
  gateComps.Register(&GateComponent{})
  // 网关服务器配置
  gateNode := &cluster.Node{
    // 提供给其它服务远程调用的地址
    ServiceAddr: "127.0.0.1:14451",
  }
  gateNode.Options = cluster.Options{
    // Master 服务器地址
    AdvertiseAddr: "127.0.0.1:4450",
    // Client 连接地址
    ClientAddr: "127.0.0.1:14452",
    // 网关组件
    Components: gateComps,
  }
  // 启动网关服务器
  err = gateNode.Startup()
  // 断言是否有启动错误
  c.Assert(err, IsNil)
  gateHandler := gateNode.Handler()
  // 断言 Master 服务器自身服务
  c.Assert(masterHandler.LocalService(), DeepEquals, []string{"MasterComponent"})
  // 断言 Master 服务器可进行远程调用的服务
  c.Assert(masterHandler.RemoteService(), DeepEquals, []string{"GateComponent"})
  // 断言 Gate 服务器自身服务
  c.Assert(gateHandler.LocalService(), DeepEquals, []string{"GateComponent"})
  // 断言 Gate 服务器可进行远程调用的服务
  c.Assert(gateHandler.RemoteService(), DeepEquals, []string{"MasterComponent"})
  // 注册 Game 服务器的 Components
  gameComps := &component.Components{}
  gameComps.Register(&GameComponent{})
  // 游戏服务器配置
  gameNode := &cluster.Node{
    // 提供给其它服务远程调用的地址
    ServiceAddr: "127.0.0.1:24451",
  }
  gameNode.Options = cluster.Options{
    // Master 服务器地址
    AdvertiseAddr: "127.0.0.1:4450",
    // 游戏服务器组件
    Components: gameComps,
  }
  // 启动游戏服务器
  err = gameNode.Startup()
  // 断言启动是否有错误
  c.Assert(err, IsNil)
  gameHandler := gameNode.Handler()
  // 断言 Master 服务器自身服务
  c.Assert(masterHandler.LocalService(), DeepEquals, []string{"MasterComponent"})
  // 断言 Master 服务器可进行远程调用的服务
  c.Assert(masterHandler.RemoteService(), DeepEquals, []string{"GameComponent", "GateComponent"})
  // 断言 Gate 服务器自身服务
  c.Assert(gateHandler.LocalService(), DeepEquals, []string{"GateComponent"})
  // 断言 Gate 服务器可进行远程调用的服务
  c.Assert(gateHandler.RemoteService(), DeepEquals, []string{"GameComponent", "MasterComponent"})
  // 断言 Game 服务器自身服务
  c.Assert(gameHandler.LocalService(), DeepEquals, []string{"GameComponent"})
  // 断言 Game 服务器可进行远程调用的服务
  c.Assert(gameHandler.RemoteService(), DeepEquals, []string{"GateComponent", "MasterComponent"})
  // 新建一个连接器
  connector := io.NewConnector()
  // 创建一个等待连接成功的 channel
  chWait := make(chan struct{})
  connector.OnConnected(func() {
    // 触发等待连接成功的 channel
    chWait <- struct{}{}
  })
  // 连接到网关服务器
  if err := connector.Start("127.0.0.1:14452"); err != nil {
    // 断言一下是否有连接错误
    c.Assert(err, IsNil)
  }
  // 订阅等待连接成功的 channel
  <-chWait
  // 创建一个订阅服务器消息的 channel
  onResult := make(chan string)
  // 订阅一下来自服务器的 Push
  connector.On("test", func(data interface{}) {
    onResult <- string(data.([]byte))
  })
  // 通知 Gate 服务器的 Test 服务
  err = connector.Notify("GateComponent.Test", &testdata.Ping{Content: "ping"})
  c.Assert(err, IsNil)
  // 断言服务器返回结果
  c.Assert(strings.Contains(<-onResult, "gate server pong"), IsTrue)
  // 通知 Game 服务器的 Test 服务
  err = connector.Notify("GameComponent.Test", &testdata.Ping{Content: "ping"})
  c.Assert(err, IsNil)
  c.Assert(strings.Contains(<-onResult, "game server pong"), IsTrue)
  // 请求 Gate 服务器的 Test2 服务
  err = connector.Request("GateComponent.Test2", &testdata.Ping{Content: "ping"}, func(data interface{}) {
    onResult <- string(data.([]byte))
  })
  c.Assert(err, IsNil)
  c.Assert(strings.Contains(<-onResult, "gate server pong2"), IsTrue)
  // 请求 Game 服务器的 Test2 服务
  err = connector.Request("GameComponent.Test2", &testdata.Ping{Content: "ping"}, func(data interface{}) {
    onResult <- string(data.([]byte))
  })
  c.Assert(err, IsNil)
  c.Assert(strings.Contains(<-onResult, "game server pong2"), IsTrue)
  // 通知 Master 服务器的 Test 服务
  err = connector.Notify("MasterComponent.Test", &testdata.Ping{Content: "ping"})
  c.Assert(err, IsNil)
  c.Assert(strings.Contains(<-onResult, "master server pong"), IsTrue)
}


小结


通过示例,我们知道服务器有三种:主服务器网关服务器游戏业务逻辑服务器

客户端一般连接到 网关服务器

启动服务器,一般会有如下操作:


  • 定义组件 struct Comp { component.Base }
  • 注册组件 components.Register(Comp)
  • 设置服务器启动选项 &cluster.Node{}
  • 启动服务器 node.Startup()
相关文章
|
2月前
|
运维 Prometheus 监控
如何在测试环境中保持操作系统、浏览器版本和服务器配置的稳定性和一致性?
如何在测试环境中保持操作系统、浏览器版本和服务器配置的稳定性和一致性?
|
9天前
|
存储 弹性计算 运维
端到端的ECS可观测性方案,助力云上业务安全稳定
本文介绍了云原生时代保障业务系统可靠性的方法和挑战,重点探讨了阿里云ECS在提升业务稳定性、性能监控及自动化恢复方面的能力。文章分为以下几个部分:首先,阐述了业务可靠性的三个阶段(事前预防、事中处理、事后跟进);其次,分析了云上业务系统面临的困难与挑战,并提出了通过更实时的监测和自动化工具有效规避风险;接着,详细描述了ECS实例稳定性和性能问题的解决方案;然后,介绍了即将发布的ECS Lens产品,它将全面提升云上业务的洞察能力和异常感知能力;最后,通过具体案例展示了如何利用OS自动重启和公网带宽自适应调节等功能确保业务连续性。总结部分强调了ECS致力于增强性能和稳定性的目标。
|
15天前
|
运维 数据挖掘 索引
服务器数据恢复—Lustre分布式文件系统服务器数据恢复案例
5台节点服务器,每台节点服务器上有一组RAID5阵列。每组RAID5阵列上有6块硬盘(其中1块硬盘设置为热备盘,其他5块硬盘为数据盘)。上层系统环境为Lustre分布式文件系统。 机房天花板漏水导致这5台节点服务器进水,每台服务器都有至少2块硬盘出现故障。每台服务器中的RAID5阵列短时间内同时掉线2块或以上数量的硬盘,导致RAID崩溃,服务器中数据无法正常读取。
|
1月前
|
消息中间件 架构师 数据库
本地消息表事务:10Wqps 高并发分布式事务的 终极方案,大厂架构师的 必备方案
45岁资深架构师尼恩分享了一篇关于分布式事务的文章,详细解析了如何在10Wqps高并发场景下实现分布式事务。文章从传统单体架构到微服务架构下分布式事务的需求背景出发,介绍了Seata这一开源分布式事务解决方案及其AT和TCC两种模式。随后,文章深入探讨了经典ebay本地消息表方案,以及如何使用RocketMQ消息队列替代数据库表来提高性能和可靠性。尼恩还分享了如何结合延迟消息进行事务数据的定时对账,确保最终一致性。最后,尼恩强调了高端面试中需要准备“高大上”的答案,并提供了多个技术领域的深度学习资料,帮助读者提升技术水平,顺利通过面试。
本地消息表事务:10Wqps 高并发分布式事务的 终极方案,大厂架构师的 必备方案
|
1月前
|
消息中间件 SQL 中间件
大厂都在用的分布式事务方案,Seata+RocketMQ带你打破10万QPS瓶颈
分布式事务涉及跨多个数据库或服务的操作,确保数据一致性。本地事务通过数据库直接支持ACID特性,而分布式事务则需解决跨服务协调难、高并发压力及性能与一致性权衡等问题。常见的解决方案包括两阶段提交(2PC)、Seata提供的AT和TCC模式、以及基于消息队列的最终一致性方案。这些方法各有优劣,适用于不同业务场景,选择合适的方案需综合考虑业务需求、系统规模和技术团队能力。
214 7
|
1月前
|
缓存 NoSQL Java
Spring Boot中的分布式缓存方案
Spring Boot提供了简便的方式来集成和使用分布式缓存。通过Redis和Memcached等缓存方案,可以显著提升应用的性能和扩展性。合理配置和优化缓存策略,可以有效避免常见的缓存问题,保证系统的稳定性和高效运行。
51 3
|
2月前
|
NoSQL 安全 PHP
hyperf-wise-locksmith,一个高效的PHP分布式锁方案
`hyperf-wise-locksmith` 是 Hyperf 框架下的互斥锁库,支持文件锁、分布式锁、红锁及协程锁,有效防止分布式环境下的竞争条件。本文介绍了其安装、特性和应用场景,如在线支付系统的余额扣减,确保操作的原子性。
33 4
|
2月前
|
NoSQL 算法 关系型数据库
分布式 ID 详解 ( 5大分布式 ID 生成方案 )
本文详解分布式全局唯一ID及其5种实现方案,关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
分布式 ID 详解 ( 5大分布式 ID 生成方案 )
|
2月前
|
缓存 Ubuntu Linux
Linux环境下测试服务器的DDR5内存性能
通过使用 `memtester`和 `sysbench`等工具,可以有效地测试Linux环境下服务器的DDR5内存性能。这些工具不仅可以评估内存的读写速度,还可以检测内存中的潜在问题,帮助确保系统的稳定性和性能。通过合理配置和使用这些工具,系统管理员可以深入了解服务器内存的性能状况,为系统优化提供数据支持。
50 4
|
2月前
|
NoSQL 容灾 MongoDB
MongoDB主备副本集方案:两台服务器使用非对称部署的方式实现高可用与容灾备份
在资源受限的情况下,为了实现MongoDB的高可用性,本文探讨了两种在两台服务器上部署MongoDB的方案。方案一是通过主备身份轮换,即一台服务器作为主节点,另一台同时部署备节点和仲裁节点;方案二是利用`priority`设置实现自动主备切换。两者相比,方案二自动化程度更高,适合追求快速故障恢复的场景,而方案一则提供了更多的手动控制选项。文章最后对比了这两种方案与标准三节点副本集的优缺点,指出三节点方案在高可用性和数据一致性方面表现更佳。
105 5
下一篇
开通oss服务