游戏服务器端架构升级之路
admin
2023-07-31 00:38:29
0

这几天的心情非常好,主要原因是我们把服务器端的架构升级到了 2.0,这样最大的一个好处就是:

Server重启完全不会影响外网服务

所以,也是想趁此机会,服务器端整个发展的历程,跟大家分享一下,干货比较多,框架代码也会全部开源:)

 

一. 农业时代

创业最重要的就是一个“快”字,所以最开始的时候,所有的架构都以快速出模型为前提。

而常看我博客的朋友应该知道我对python情有独钟,所以自然的,python成为了我开发服务端框架的语言。

python自带的多线程tcp服务器框架非常简单:ThreadingTCPServer,即每个链接一个线程的模式:

123456789101112131415161718192021 import SocketServer class RequestHandler(SocketServer.BaseRequestHandler):    def handle(self):        f = self.request.makefile(\’r\’)         while True:            message = f.readline()            if not message:                print \’client closed\’                break            print \”message, len: %s, content: %r\” % (len(message), message)            self.request.send(\’ok\\n\’) class MyServer(SocketServer.ThreadingTCPServer):    request_queue_size = 256    daemon_threads = True    allow_reuse_address = True server = MyServer((\’127.0.0.1\’, 7777), RequestHandler)server.serve_forever()

 

但是我又觉得多线程加锁使用实在太过麻烦,所以就引入了gevent:

1234567891011121314151617181920212223242526272829 import geventfrom gevent.server import StreamServer class RequestHandler(object):     closed = False     def __init__(self, sock, address):        self.sock = sock        self.address = address        self.f = self.sock.makefile(\’r\’)        self.handle()     def handle(self):        while not self.closed:            t = gevent.spawn(self.read_message)            t.join()     def read_message(self):        message = self.f.readline()        if not message:            self.closed = True            print \’client closed\’            return        print \”message, len: %s, content: %r\” % (len(message), message)        self.sock.send(\’ok\\n\’) server = StreamServer((\’127.0.0.1\’, 7777), RequestHandler)server.serve_forever()

 

而又因为之前在做个人开发者的时候,对flask的装饰器设计甚为喜欢,所以就参考flask设计了我自己的tcp server:

https://github.com/dantezhu/haven

使用方法也是非常简单(服务器端):

12345678910111213141516 import logging from haven import GHaven, THaven, loggerfrom netkit.box import Box app = GHaven(Box) @app.before_requestdef before_request(request):    logger.error(\’before_request\’) @app.route(1)def index(request):    request.write(dict(ret=100)) app.run(\’127.0.0.1\’, 7777, workers=2)

 

客户端:

123456789101112131415161718192021222324 from netkit.contrib.tcp_client import TcpClientfrom netkit.box import Box import time client = TcpClient(Box, \’127.0.0.1\’, 7777, timeout=5)client.connect() box = Box()box.cmd = 101box.body = \’我爱你\’ client.write(box) t1 = time.time() while True:    # 阻塞    box = client.read()    print \’time past: \’, time.time() t1    print box    if not box:        print \’server closed\’        break

 

这套架构在开发服务器端原型的时候非常有效,因为开发效率极高。

而我们开发的是棋牌游戏,当时为了图方便,几乎所有的数据都放在了进程内存里,所以操作起来也非常方便。

所以在整个研发过程中,服务器端的开发速度一直是客户端开发速度的数倍。

 

二. 工业时代

然而很快,我发现了现有框架的一些问题,而这些问题都极其致命。

1. 所有逻辑揉在一个进程中,性能太低,2000人同时打牌就会导致卡顿

2. 多线程的模型在大量用户在线时,性能极差

3. 逻辑server和存储server揉在一起,导致维护十分困难。重启逻辑服务器会影响业务,无法接受

因为如上的原因,我一直在考虑一套新的业务模型,除了要解决上面的问题之外,还要有如下的特性:

1. 尽量保留python开发业务逻辑,因为与c++相比,开发效率极高。

2. 可伸缩,分布式

3. 尽量少改动现有逻辑代码

4. 尽量让业务开发理解简单

 

最终,我实现了这套server框架,并将其开源在这里:

https://github.com/dantezhu/maple

maple的实现,受到了很多想有框架的启发,其中包括zmq,half-async half-sync,以及当时淘宝的一个业务模型分享。

我记得印象比较清楚的是,当时主讲是这么说的:

我们怎么判断该不该给某个server发送消息呢?

根据成功率?根据响应时间?

no,我们把push换成pull,让worker完成处理后,自己过来要数据。

要,我才给,不要我就不给

 

maple的整个模型即是如此:

  • gateway
  • worker
  • trigger

其中,gateway是用c++、epoll实现的一个高性能转发服务器,收到的客户端消息都会转发给对应的worker。

worker,即工作进程,他可以随时attach到某个gateway上来处理数据,也可以随时detach。并且worker使用python来实现的,兼顾了开发效率和运行效率。

trigger,触发器,即他可以触发事件来发给gateway,gateway会根据事件的不同,发给客户端或者worer。

 

详细的设计思路,可以参看maple的readme,里面有详细的设计思路。

一个简单的worker代码如下:

1234567891011121314151617181920212223242526272829303132 import logging LOG_FORMAT = \’\\n\’.join((    \’/\’ + \’-\’ * 80,    \'[%(levelname)s][%(asctime)s][%(process)d:%(thread)d][%(filename)s:%(lineno)d %(funcName)s]:\’,    \’%(message)s\’,    \’-\’ * 80 + \’/\’,)) logger = logging.getLogger(\’maple\’)handler = logging.StreamHandler()handler.setFormatter(logging.Formatter(LOG_FORMAT))logger.addHandler(handler)logger.setLevel(logging.DEBUG) from maple import Workerfrom netkit.box import Box app = Worker(Box) @app.close_clientdef close_client(request):    logger.error(\’close_client: %r\’, request) @app.route(1)def test(request):    request.write_to_client(dict(        ret=0,        body=\”test\”    )) app.run(\”192.168.1.67\”, 28000, workers=2, debug=True)

 

一个简单trigger代码如下:

123456789101112

相关内容

热门资讯

Mobi、epub格式电子书如... 在wps里全局设置里有一个文件关联,打开,勾选电子书文件选项就可以了。
500 行 Python 代码... 语法分析器描述了一个句子的语法结构,用来帮助其他的应用进行推理。自然语言引入了很多意外的歧义,以我们...
定时清理删除C:\Progra... C:\Program Files (x86)下面很多scoped_dir开头的文件夹 写个批处理 定...
scoped_dir32_70... 一台虚拟机C盘总是莫名奇妙的空间用完,导致很多软件没法再运行。经过仔细检查发现是C:\Program...
65536是2的几次方 计算2... 65536是2的16次方:65536=2⁶ 65536是256的2次方:65536=256 6553...
小程序支付时提示:appid和... [Q]小程序支付时提示:appid和mch_id不匹配 [A]小程序和微信支付没有进行关联,访问“小...
pycparser 是一个用... `pycparser` 是一个用 Python 编写的 C 语言解析器。它可以用来解析 C 代码并构...
微信小程序使用slider实现... 众所周知哈,微信小程序里面的音频播放是没有进度条的,但最近有个项目呢,客户要求音频要有进度条控制,所...
Apache Doris 2.... 亲爱的社区小伙伴们,我们很高兴地向大家宣布,Apache Doris 2.0.0 版本已于...
python清除字符串里非数字... 本文实例讲述了python清除字符串里非数字字符的方法。分享给大家供大家参考。具体如下: impor...