easy解决服务端通信问题,同样使用了RPC技术。easy使用的ETCD+GRPC,直接将它们打包组合在了一起。随着服务发现的成熟,稳定,简单,若是不用,甚至你也并不需要RPC来分解你的架构。
GRPC 有默认resovler 解决服务发现的方案,只需要完成resolver,watch等,可以轻易实现,RPC的负载均衡。只不过这种只适合,对服务器ID信息等不敏感,比如说数据服务,和业务服务。不关心是哪台服务器完成的,只要数据处理完即可。
在游戏的应用场景中,登陆你可能使用的http,进入游戏用的游戏服务器,那么登陆服务需要知道用户在哪台游戏服务器中,使用token判定登陆,传输用户信息等走RPC通道,需要明确知道是哪台服务器。默认的服务发现就不满足我们的需求了。因此我们需要改造下,将游戏的这种具有耦合性质的负载也要支持才行。
go get github.com/slclub/easy/rpc
测试项目地址:github.com/slclub/easy/examples/rpc
我们直接使用grpc官网的helloworld。我们简单点直接上proto文件的描述语言。致于protoc等工具,以及go的依赖proto就不详细赘述了
syntax = "proto3"; option go_package = "google.golang.org/grpc/examples/helloworld/helloworld"; option java_multiple_files = true; option java_package = "io.grpc.examples.helloworld"; option java_outer_classname = "HelloWorldProto"; package helloworld; // The greeting service definition. service Greeter { // Sends a greeting rpc SayHello (HelloRequest) returns (HelloReply) {} } // The request message containing the user's name. message HelloRequest { string name = 1; } // The response message containing the greetings message HelloReply { string message = 1; }
// server is used to implement helloworld.GreeterServer. type hello struct { helloworld.UnimplementedGreeterServer } // SayHello implements helloworld.GreeterServer func (s *hello) SayHello(ctx context.Context, in *helloworld.HelloRequest) (*helloworld.HelloReply, error) { log.Info("Received: %v", in.GetName()) return &helloworld.HelloReply{Message: "Hello " + in.GetName()}, nil }
SayHello接口会自动依据protobuf 生成的helloworld_grpc.pb.go 文件代码去接收客户端来的请求。
etcd.NewWithOption(option.OptionWith(nil).Default( option.OptionFunc(func() (string, any) { return "Endpoints", strings.Split(etcdAddr, ";") })), )
etcdAddr 是 用分号链接起来的,etcd的监听地址字符串。ETCD 在整个服务发现体系仅仅需要这理论的一行代码足够了。演示项目我们没有配置账号和密码。实际项目需要主要服务安全。
// New 一个rpc 监听服务 server := cgrpc.NewServer(option.OptionWith(&cgrpc.Config{ PathName: "server1", Addr: serverAddr, Namespace: namespace, //TTL: 15, }).Default( option.DEFAULT_IGNORE_ZERO, option.OptionFunc(func() (string, any) { return "ID", "abluo" }), option.OptionFunc(func() (string, any) { return "AddrToClient", "127.0.0.1:18080" }), )) // 绑定业务接口到 rpc服务 // 可以被多次使用RegisterService,我们用的append server.RegisterService( func(server *grpc.Server) { helloworld.RegisterGreeterServer(server, &hello{}) }, ) // 监听;如果您有主监听,那么可以用go 并发运行 server.Serv()
其中AddrToClient 是针对游戏服务提供给游戏客户端的监听地址,对于grpc来说不是必须的。这里仅仅是从顺便使用grpc 的服务端注册到ETCD中,完成游戏端的服务发现。可以做到任何游戏服不停服的扩展性,动态添加或减少服务端的机器。
这里我们可以发现使用option.Assignment初始化的优势,演示代码的时候不需要写完整的配置参数,还能人为控制的保证参数的默认值。处理默认值都是统一的格式,流程化代码。
当然option.Assignment是有局限性的,仅仅适合服务类的对象,或者仅仅LoadOnce的逻辑部分使用。在业务代码不建议使用,因使用了reflect反射会性能低下。
// plan2 using the default value setting function. etcd.NewWithOption(option.OptionWith(nil).Default( option.OptionFunc(func() (string, any) { return "Endpoints", strings.Split(etcdAddr, ";") }), )) client := cgrpc.NewClient(option.OptionWith(&struct { PathName string Namespace string }{"server1", namespace})) client.Start() // do your things handle(client.ClientConn) // just for test client.Wait() // close client.Close()
为了启动测试命令随时可以手敲,笔者没有将ETCD,GRPC地址通过flag传递给应用工程。直接写到代码里了。运行前,请下修改代码中的监听地址变量的值。
$ cd 到 examples/rpc/server
$ go build && ./server
$ cd 到 examples/rpc/client
$ go build && ./client
首先双方通信正常OK。
启动服务后,笔者模拟,任意一端崩掉用(CTRL+C),再启动崩掉的端,整体服务立刻回复正常。
其他的测试情况就不一一再往这上面粘贴了。
服务端
断开gprc服务端再启动
客户端
构建一个完整架构的游戏服务器,仅仅靠服务发现,rpc等虽然还不足够。但是这么简单的实现服务端通信,还是让人很愉快。后续再有其他的服务组件,我们可以一一加入进去。同时给出测试项目。
go讲究毕竟是精简,笔者也不是弄个大杂烩,而是设计好package,需要什么我们就用什么。虽说做不到EASY在手天下我有,但是弄一个小体系,还是能节省我们很多项目时间。
最后欢迎大家互相交流学习,有好东西互相分享下。有兴趣的通许可以通过传送门去github,给EASY来个小小的star。因压力测试还不到位,不全,所以笔者一直没有发布一个Stable版本,希望大家谅解。
上一篇:软件设计师——信息安全(一)