理解游戏服务器架构-部署架构

news/2024/4/19 19:51:19/

目录

前言

我所理解的服务器架构

什么是否部署架构

部署架构的职责

进程业务职责

网络链接及通讯方式

   与客户端的连接方式

   服务器之间连接关系

数据落地以及一致性 

数据库的选择

数据访问三级缓存

数据分片

 读写分离

分布式数据处理

负载均衡

热更新

配置更新

合服

合服的痛点

解决合服数据重叠问题

总结:


前言

       本人做游戏多年,当时从开发棋牌游戏开始阴差阳错进入游戏行业,服务器架构做过很多套,我曾经的小伙伴拿着我设计的架构带到新的公司,都有一个不错的表现。但是发现一个现象,后面设计的架构永远比前面设计的更为优秀或良好,也许随着时间的推移,个人技术以及新技术与理念的引入、新的业务场景等多种原因,让我们不得不对架构做一定的调整。

        我经常问我的求职者一个问题,你怎么理解服务器架构,架构两个词从字面上理解很好理解,如果对于一个系统工程了说,它可以理解为系统工程的顶层设计,如果是一个简单的单一功能,那它是单一个功能总体设计思路。   但是我们今天所讲的架构是一个承载百万人同时在线的架构设计,注定它不是一个简单问题。它包含着技术开发人员所需要掌握的技术需要的广阔性和深度,并拥有强力的设计理念。

我所理解的服务器架构

服务器架构我的理解为三个方面,它包含部署架构和逻辑底层架构、业务架构。

                

什么是否部署架构

部署架构是在考虑业务职责和各个场景的节点隶属关系、连接关系以及业务分割的服务器的概括性架构,它确定了服务器进程之间的部署关系。

部署架构的职责

     在这一层,我们应该抛开编程语言的限制,考虑进程业务职责,网络连接形式、数据落地以及一致性问题、扩展性问题、负载均衡问题、稳定性问题、热更新问题等

                        

进程业务职责

进程业务职责我们应该考虑,根据业务进行功能拆分,并确定进程应该做什么和它主要功能是什么,是我们赋予各个进程具体职责。

以游戏服务器为例,它应该需要为用户做登录认证的登录服;职责为统一网络连接和安全的网关服;为任务和养成系统等玩家具体玩法需要的游戏服;担负着全服排行榜和ID分配等全局功能功能的世界服;需要好友、聊天 等功能的社交服;需要保存数据的数据库服等

网络链接及通讯方式

   与客户端的连接方式

        当我们确定了业务职责时,那么用什么样的通讯方式就是我们具体要考虑的问题。首先我们

        需要思考一个问题,我们的业务那些进程需要与玩家进行连接呢?在游戏中一般,登陆服和

        网关服是需要与玩家进行连接。

        登陆服往往通过http的协议进行验证登陆,让然也可以选择TCP方式,以及后期发展的

        websocket的方式。websocket我们可以看着是http的变种,其实它最核心的还是http。

        当然根据实际业务场景有可能选择udp。帧同步的战斗服就是可确认的rudp协议方式。

             

   服务器之间连接关系

        在服务器部署架构中,我们要确认的一项重要内容之一就是多个服务器进程之间能够直接通

        讯。某个业务进程是被动连接还是主动连接?是一个连接还是多个连接?是一对多的关系还

        是一对一的关系?

      在连接关系中,我们同时要考虑服务器进程之间用何种方式进行通讯。我最近设计的一套服务 

      器架构,通过zookeeper进行服务器配置更新,每个服务器有自己的一个服务器节点ID,服务

      器节点ID通过zookeeper获得自己配置信息,同时将本节点注册到zookeeper上,其他节点监听

      到某个节点注册信息,zookeeper按照配置规则进行节点自动连接,一切做到自动化。

     在数据库的设计方式,我将数据功能设计成SDK的方式,数据库节点是作为server端,而使用

     数据库的为Client端,任何节点想要数据库的操作直接使用数据库SDK就可以,非常方便。

          

数据落地以及一致性 

        数据库的选择

对于业务,我们应该充分分析它的属性以及业务当量,根据业务属性和当量我们选择合适我们自己需要的数据库类型,通用的数据库有mysql、orcale、mongoDB

             

但们也有各自的缺点,MySQL、Oracle和MongoDB各自的缺点如下:

  1. MySQL:功能相对较少,并发控制能力有限,数据安全性较低。
  2. Oracle:劣势在于成本较高,不是开源数据库,需要购买许可证才能使用。
  3. MongoDB:不适合存储关系型数据,数据一致性相对较弱,且没有类似SQL的查询语言,查询功能相对较弱

对于游戏开发,游戏本身的数据库我可能选择mongodb,因为完美的将player玩家对象整个丢进我们的数据库中,简单并且高效实现。当然在window系统下,你也可以选择sqlServer,但是发它只能在window操作系统下运行。

数据访问三级缓存

在早期的数据访问和存储是进程直接操作数据库的。由于数据库数据往往保存在磁盘中,存储和访问效率就往往是非常低,尤其是访问量比较大时容易造成拥堵。

所以后来为了提高访问效率,在用户个人数据方面,为了提高访问数据的效率可能我们需要用到三级缓存机制,即:memory、redis/memcache、db数据落地三层设计。

                

对于热点数据,首先我们从memory中查找,没有在查找redis或memcache,最后才查找database。

数据分片

如果一个业务逻辑都在某个进程中,那么数据一致性相对就简单了许多。那么更多的考虑的是因为单点会不会出现执行效率和性能的问题以及如何提高承载上线,可是问题并没有那么简单。

例如:游戏服的社交往往处理好友、聊天、帮会等通用数据,如果在拥有天量用户的游戏中,我们不可能一个服务器节点保存所有的这些数据。可能进行数据的在进程的分片,数据如何分片就需要我们做一定的考量。同样我们的角色数据不可能只有一个数据库,可能需要架设数据库服务器集群来做处理,同样表格数据也需要分片等等,让访问数据更为高效。

分片算法:

            

 读写分离

当然我们不可能所有数据都进行分片,比如拍卖行数据以及处理。拍卖行数据这个系统的特性是用户基数达到一定量及的时候,读数据的压力比改数据的压力会大很多,在设计初期我们可能需要考虑读写分离。

读写分离适用于程序使用数据较多,而更新较少、查询较多的情况。此时,设计主从主从同步,可以减少数据库压力,提高性能。

拍卖行或者商行,更新相对较小,访问量较大,当我们在全局服更新数据时,同步更新到其他服,玩家访问拍卖行或者商行时,访问的是本服的即可,这样减少了全局服的压力。

数据库数据的IO(分库分表)

在服务器性能瓶颈中往往数据库的IO会拖累整个优秀的程序设计。

MySQL为例:数据库中的表超过1000000条记录时,效率会受到多种因素的影响。以下是一些可能影响数据库效率的关键因素:

硬件性能:

  • 磁盘I/O:SSD比传统的HDD更快。
  • 内存:足够的RAM可以确保更多的数据和索引被缓存在内存中。
  • CPU:强大的CPU可以更快地处理查询。

数据库设计:

  • 表结构:正确的数据类型、适当的索引和分区可以提高查询性能。
  • 索引:为经常用于查询条件的列创建索引。
  • 规范化:避免不必要的数据重复。

查询优化:

  • 查询语句:避免使用SELECT *,只选择需要的列。
  • 避免全表扫描:使用WHERE子句和索引来限制查询的数据量。
  • 连接优化:尽量减少表之间的连接,特别是当连接条件没有索引时。

数据库配置:

  • 缓冲池大小:例如,InnoDB的缓冲池大小应足够大,以容纳大部分的数据和索引。
  • 日志和二进制日志设置:这些设置可以影响写入的性能。

并发性:

  • 高并发读写可能会导致性能下降。
  • 使用连接池可以减少创建和关闭连接的开销。

数据分布:

  • 如果数据分布不均,某些查询可能会更慢。

网络延迟:

  • 如果应用程序和数据库不在同一台机器上,网络延迟也可能成为问题。

了解了更多的数据库引起瓶颈问题,除了设计修改硬件和配置属性时,我们应该避免大的表格的出现;同时,尽可能避免只有一个全局数据库的可能性。

所以应当对于大表格数据进行分表,对于一个数据无法承载的应当通过集群和分库的方式解决数据IO的瓶颈。

分布式数据处理

服务器数据的落地不应该仅仅考虑当前问题,而要考虑后期扩容或随着时间的变化服务器减少问题。不应该只考虑本线程问题而考虑跨线程问题;只考虑本进程而不考虑跨进程问题。

在实际运用中,有可能有多个进程进行同样的逻辑处理,只是处理不同的数据而已。例如:社交服处理的都是社交相关的逻辑的数据,但是一个玩家的涉及需要修改的数据只能在单个社交服处理,不能两个社交服都可以修改一个玩家的同一份数据。

一句话总结,对于数据落地,我们尽可能让数据安全地保存,同一份数据的修改同时有且只有一个地方进行修改,并用一切手段提高访问和落地的效率。

负载均衡

负载均衡是决定了游戏稳定和承载上线的总要环节,作为服务器后台开发,那一定要知道我们的瓶颈在哪里,要知道单个进程的承载和单个服的承载,要对服务器前期要有个预设值,通过这个这个预设值来进行总体的实际。服务器的瓶颈通用的有网络IO,数据库IO、内存、CPU性能等问题  

         

我们不能实现一个服务器只有单一节点的运用。如果有,那么它的体量也大不到哪里去。所以我们要知道如何进行负载均衡,如何对服务器进行横向分布,如果在游戏上线前期没有做充分的准备,出现问题都有可能是对项目致命的问题。我们不仅仅考虑正确的情况下,同时我们要对外部危险攻击做好相应的准备。

在这里分享两个例子:

第一个案例:

有一天我曾经下面的一个小伙伴去其他公司负责整个地方棋牌项目。棋牌项目的特点就是被ddos攻击的重灾区,同样类型的游戏,市场就那么大,你抢别人的蛋糕,不有点硬技术可不行。当时小伙伴打电话给我,问怎么解决?最终我发现它的登录和网关是没有做负载均衡的。对方一直打他们的login服务器和网关,登录和网关它们没有做负载均衡,这种情况只能先上高防,后期把负载均衡加上,出现类似的情况,将新玩家导入到新的服务器,让ddos攻击有高防的机器,这样影响就小了很多。当然还有很多技术细节,这里就不一一列举了。由于小伙伴在这块考虑不足,负载均衡没有加上,最后这个项目刚开始一周就胎死腹中。

第二个案例:

我面试过应聘我们后端小主程的一个小朋友,他们公司做了一个大IP项目,腾讯发行他们家的游戏,社交、工会、跨服战等功能都在单个全局服,最后发现就是这个单个全局服成了整个游戏服务器的败笔。腾讯当天导量300多万,直接将服务器整崩溃了。腾讯推的量一般是短时间巨量,当导入巨量项目组接不住时,腾讯不会再给你导入多少量了,这个项目实在可惜。

以两个失败的案例告诉我们,一个差的架构可以间接杀死一个好的项目,服务器架构的最大体现往往在游戏上线的那刻尤为重要。负载均衡没有做好,可能造成项目不可挽回的损失,给我们巨大的流量和推广也无法接住,着实可惜。

为了实现负载均衡,我们可以通过nginx、动态服务器列表、设计上扩容组合实现。在登录时,一定要设计排队功能,当有大当量用户突然访问时,最起码有一个保底机制不至于出现登录挤兑情况影响。

     

Nginx(engine x)是基于 C 语言实现的一个高性能、轻量级的 HTTP 和反向代理 Web 服务器,同时也提供 IMAP/POP3/SMT服务。

Nginx 既可用作静态服务器,提供图片、视频服务,也可用作反向代理或负载均衡服务器。Nginx 作为反向代理,当代理后端应用集群时,需要进行负载均衡。

Nginx 提供了对上游服务器(真实业务逻辑访问的服务器)的负载均衡、故障转移、失败重试、容错、健康检查等功能,以一种廉价有效透明的方法扩展了网络设备和服务器的带宽、增加吞吐量、加强网络数据处理能力、提高网络的灵活性和可用性。
Nginx 具有高并发连接、低内存消耗、低成本、配置简单灵活、支持热部署、稳定性高、可扩展性好等优点,这些优点都得益于其优秀的架构设计(模块化、多进程和多路I/O复用模型)。

                                     

热更新

服务器热更新是指在不停机或者不关服的前提下,对服务器上的应用程序或系统进行更新。它的好处在于能够保证系统的高可用性,避免了系统在更新过程中的不可用现象,并且将系统升级的周期缩短到了最小

实践证明,每次重启服务器,都有可能一定概率造成玩家的流失,现如今获取一个付费用户是多么的不容易。更新可以分为,配置更新、代码更新、合服等

        

配置更新

配置更新是最频繁的更新,极限情况一天可能都要更新好几次,如果每次都要重新启服来进行更新,损失太大了。频繁的启服对用户的体验很不友好,同时让人不敢在游戏中消费。

所以配置的更新一定是热更新实现。当配置需要更新的时候,可以通过GM命令通知各个服务器,进行配置的重新装载。

代码热更新

对于代码,并不是所有代码都支持热更新的,对于Java它所支持的热更程度为代码的函数和成员变量都不能改变,但是函数内部的实现能够变化。如果是代码C++和Lua的结合,能够比较友好的实现代码热更新。所以代码的更新也取决与所选择的逻辑编程语言。

对于语言的限制技术上往往是有无力感的,因为对于技术的我们没有办法做什么。但是我有一种策略是可以将影响做到最小,当我们需要更新代码的时候,可以将现有玩家快速切换到更新好了的服务器,同时将当前服务器关闭进行更新,等当前服务器更新成功后再让新玩家进入当前服务器。当然这种情况玩家并不会无感,但起码影响小了很多。

合服

合服的痛点

肯定奇怪,合服为什么属于热更新范围内。其实合服也是更新的一部分。

合服通用做法是将需要合服的服务器关闭,同时进行数据库合并。但是并没有那么简单,因为1服和2服的数据往往不相通的,那么某些ID作为数据Index(索引)就有可能重叠问题。

比如:玩家数据库表格以playerId作为index(索引),采用自增的方式,1服如此,2服如此,自然就会有重叠的地方。而且之中情况是连锁反应的,因为玩家的道具、任务表格都是以playerId作为关键index(索引),这就一一去重,非常麻烦,即使我们客户通过工具来实现,实际起来非常的费劲,总有特例需要处理。

解决合服数据重叠问题

能够有一个长效机制让我们合服不怎么麻烦呢?答案肯定是有的,ID的唯一性其实给了启发。如果有一个机制能够让ID在不同进程之间产生不同无法重叠的ID,那么是否不是就可以解决ID重用问题?

是的,我们可以参考雪花算法。雪花算法生成的ID为64位整数,具体的格式如下:

0 | 0000000000 0000000000 0000000000 000000000 | 00000 | 00000 | 000000000000

其中,第1位为符号位,固定为0;接下来的41位为时间戳(毫秒级),记录了生成ID的时间;然后是10位的机器ID,5位数据中心ID和5位工作机器ID,用于标识不同的机器;最后是12位的序列号,用于表示在同一毫秒内生成的多个ID的顺序。

实际使用时,每台机器需要配置一个唯一的机器ID,以保证生成的ID不与其他机器生成的ID重复。同时,需要注意时钟回拨的问题,即当本地时钟发生回拨时,可能会导致生成的ID出现重复或者乱序的情况。

当然这只是抛砖引玉,还有其它类似算法,这里就不一一详举了。

我们在给角色命名或修改名字的时候,同样会出现类似的重叠问题。这以问题的解决就有待于读者去思考了。

总结:

在部署架构阶段,我们考虑的是我们架构设计的愿景,或者说是我们向往架构的设计的蓝图。告诉我们将要做什么,确定我们最重要的目标和方向,划分职责。犹如宪法在法典中的意义一样重要,它是指导思想。接下了的逻辑顶层架构设计以及业务架构设计都要依照部署架构的设计而进行落地。

我们应该抛开语言而考虑每个进程需要做什么,它们之间的连接关系,数据落地和分割,瓶颈问题的解决,这样从全局上考虑问题,思路就非常清晰了。


http://www.ppmy.cn/news/1405271.html

相关文章

hadoop 高可用(HA)、HDFS HA、Yarn HA

目录 hadoop 高可用(HA) HDFS高可用 HDFS高可用架构 QJM 主备切换: Yarn高可用 hadoop 高可用(HA) HDFS高可用 HDFS高可用架构 QJM 主备切换: Yarn高可用

1,static 关键字.Java

目录 1.概述 2.定义格式和使用 2.1 静态变量及其访问 2.2 实例变量及其访问 2.3 静态方法及其访问 2.4 实例方法及其访问 3.小结 1.概述 static表示静态,是Java中的一个修饰符,可以修饰成员方法,成员变量。被static修饰后的&#xff…

LandCover数据介绍与下载

一、LC介绍 土地覆盖(Land Cover,LC)是自然营造物和人工建筑物所覆盖的地表诸多要素的综合体。土地覆盖指地表所属的植被覆盖物(森林、草原、耕作植被等)或非植被覆盖物(冰雪、建筑物等)的具体类型,侧重描述地球表面的自然属性&a…

zabbix主动发现,注册及分布式监控

主动发现 结果 主动注册 结果 分布式监控 服务机:132 代理机:133 客户端:135 代理机 数据库赋权: 代理机配置 网页上配置代理 客户端配置 网页上配置主机 重启代理机服务 网页效果

【机器学习】数据探索(Data Exploration)---数据质量和数据特征分析

一、引言 在机器学习项目中,数据探索是至关重要的一步。它不仅是模型构建的基础,还是确保模型性能稳定、预测准确的关键。数据探索的过程中,数据质量和数据特征分析占据了核心地位。数据质量直接关系到模型能否从数据中提取有效信息&#xff…

C#多页面共用一个实例

C#多页面共用一个实例 案例&#xff1a; C#与硬件设备交互&#xff0c;交互类里面有打开设备、数据接发等1操作&#xff0c;在其他许多地方需要调用该设备兼顾各代码的耦合度 采用单例模式&#xff0c;eg.CAN设备&#xff1a; private CANClass(){}/// <summary>/// 获…

Apache ECharts-数据统计(详解、入门案例)

简介&#xff1a;Apache ECharts 是一款基于 Javascript 的数据可视化图表库&#xff0c;提供直观&#xff0c;生动&#xff0c;可交互&#xff0c;可个性化定制的数据可视化图表。 1、介绍 图 1.1 Apache ECharts 功能、运行环境 功能&#xff1a; ECharts&#xff…

基本电路理论-电流和电压的参考方向

&#x1f308;个人主页&#xff1a;会编程的果子君 &#x1f4ab;个人格言:“成为自己未来的主人~” 电流及参考方向 电流&#xff1a;带电粒子有规则的定向移动 电流强度&#xff1a;单位时间内通过导体横截面的电荷量&#xff0c;即&#xff1a;idq/dt 单位&#xff1a…

2013年认证杯SPSSPRO杯数学建模C题(第二阶段)公路运输业对于国内生产总值的影响分析全过程文档及程序

2013年认证杯SPSSPRO杯数学建模 C题 公路运输业对于国内生产总值的影响分析 原题再现&#xff1a; 交通运输作为国民经济的载体&#xff0c;沟通生产和消费&#xff0c;在经济发展中扮演着极其重要的角色。纵观几百年来交通运输与经济发展的相互关系&#xff0c;生产水平越高…

力扣贪心算法--第一天

前言 今天是贪心算法的第一天&#xff0c;算法之路重新开始&#xff01; 内容 之前没了解过贪心算法。 什么是贪心 贪心的本质是选择每一阶段的局部最优&#xff0c;从而达到全局最优。难点就是如何通过局部最优&#xff0c;推出整体最优。 一、455.分发饼干 假设你是一…

源浩流体设备与您相约2024年第13届生物发酵展

参展企业介绍 温州源浩流体设备科技有限公司是一家集设计、开发、制造、销售、服务于一体的高科技企业&#xff0c;公司主要生产各种不锈钢阀门、管件、卫生级流体设备(卫生级换向阀,卫生级减压阀,卫生级罐底阀)等。现为温州市泵阀协会会员&#xff0c;ISO9000 2008版质量质量…

单链表求集合的交集

#define _CRT_SECURE_NO_WARNINGS #include<stdio.h> #include<stdlib.h> typedef int ElemType; typedef struct LinkNode {ElemType data;LinkNode* next; }LinkNode, * LinkList; //尾插法建立单链表 void creatLinkList(LinkList& L) {L (LinkNode*)mallo…

QT - 日志:qDebug/qInfo/qWarning/qCritical

篇一、日志打印函数 头文件&#xff1a; #include <QDebug> 代码&#xff1a;qDebug()<<"hello world!"; 其他打印级别&#xff1a; qInfo(): 普通信息 qDebug(): 调试信息 qWarning(): 警告信息 qCritical(): 严重错误 qFatal(): 致命错误 1. qDebug…

Verilog语法回顾--用户定义原语

目录 用户定义原语 UDP定义 UDP状态表 状态表符号 组合UDP 电平敏感UDP 沿敏感时序UDP 参考《Verilog 编程艺术》魏家明著 用户定义原语 用户定义原语&#xff08;User-defined primitive&#xff0c;UDP&#xff09;是一种模拟硬件技术&#xff0c;可以通过设计新的原…

EFPN代码解读

论文 Extended Feature Pyramid Network for Small Object Detection python3 D:/Project/EFPN-detectron2-master/tools/train_net.py --config-file configs/InstanceSegmentation/pointrend_rcnn_R_50_FPN_1x_coco.yaml --num-gpus 1 训练脚本 cfg 中的配置 先获取配置…

Python(乱学)

字典在转化为其他类型时&#xff0c;会出现是否舍弃value的操作&#xff0c;只有在转化为字符串的时候才不会舍弃value 注释的快捷键是ctrl/ 字符串无法与整数&#xff0c;浮点数&#xff0c;等用加号完成拼接 5不入&#xff1f;&#xff1f;&#xff1f; 还有一种格式化的方法…

Java(内部类)

1.内部类 内的五大成员&#xff1a;属性、方法、构造方法、代码块、内部类 解释&#xff1a;在一个类的里面&#xff0c;再定义一个类。举例:在A类的内部定义B类&#xff0c;B类就被称为内部类注意&#xff1a;内部类表示的事物是外部类的一部分&#xff0c;内部类单独出现没…

pymc,一个灵活的的 Python 概率编程库!

目录 前言 安装与配置 概率模型 贝叶斯推断 概率分布 蒙特卡罗采样 贝叶斯网络 实例分析 PyMC库的应用场景 1. 概率建模 2. 时间序列分析 3. 模式识别 总结 前言 大家好&#xff0c;今天为大家分享一个超强的 Python 库 - pymc Github地址&#xff1a;https://gith…

JavaScript 对象管家 Proxy

JavaScript 在 ES6 中&#xff0c;引入了一个新的对象类型 Proxy&#xff0c;它可以用来代理另一个对象&#xff0c;并可以在代理过程中拦截、覆盖和定制对象的操作。Proxy 对象封装另一个对象并充当中间人&#xff0c;其提供了一个捕捉器函数&#xff0c;可以在代理对象上拦截…

Qt中实现域(Unix)套接字通信

Qt中实现域&#xff08;Unix&#xff09;套接字通信可以使用QLocalServer和QLocalSocket类。以下是一个简单的示例&#xff0c;演示了如何在两个Qt应用程序之间使用域套接字进行通信。 一、在服务器端&#xff1a; cpp Copy code #include <QtWidgets> #include <QL…