『码农虾扯蛋』之游戏后台杂谈(贰)
文接上回, 『码农虾扯蛋』之游戏后台杂谈(壹)。间隔的有点久,主要在于撸主忙于"啪、啪、啪"。咳咳,骚年,看到你淫荡的表情,就知道你想的有点多啊。
其实是在撸代码,另外还有个原因,就是懒……
四、 关于架构2--架构的拓扑
之前是大致介绍了下常见的游戏架构,咱们来个拔高,看能拔出点啥。给大家5分钟时间,诸位不妨把自己业务的外网运营部署拓扑图画出来。
诶,这位同学好迅猛,5秒就搞定啦!尼玛没上线交白卷的凑什么鸡脖热闹!Out!
一分钟了,哟,有位同学交卷了,恩,不错,分区小游戏的,简单,服务器不多,关系简单明了,PCU都上10w了,不错。
两分钟了,又有同学交卷了,恩,复杂的分区游戏,还有跨服的,关系得好好梳理梳理了,PCU都几十w啦,Nice!
三分钟过啊,这位同学交卷了是吧,全区全服游戏,节点不少,关系还挺复杂的,难为你了。
喂喂喂,那位在图上乱涂画的兄弟,你在搞什么鸡毛?啊,一个简单的全区游戏做成这个鸟样,到处都是连线,才几台服务器就这德行,你要和上面两位爷一样几百上千台服务器,想想都要脑裂的吧!
各位看官,如果您的业务线上有上千台机器,要没有个好的架构,怎么梳理这拓扑关系,说白了怎么去维护这套系统?恐怕是难上加难吧。oh,不对不好意思说错,要真没有个好的架构支持,只怕是做不到千台机器的量级就隔屁了吧,呵呵。
哎尼玛,为毛好好的说架构,又聊到拓扑上来了,不搭嘎啊。撸主自认为,好的架构也是个艺术品,看起来简洁、美观、有内涵还贼好用,不美的架构,多多少少会存在些问题。别问为什么,艺术家从来不说为什么。
话说到这,那么咱聊聊架构的拓扑都有哪些吧。游戏架构跳不出网络的框架,大家翻翻之前学网络课,就那Computer Networks的经典教材,上面是怎么说的,咱游戏的架构,也一样在这五指山里面好好呆着呢,撸主从网上copy了几个常见的请各位爷过目:
1. 总线型拓扑总线拓扑结构采用一个信道作为传输媒体,所有站点都通过相应的硬件接口直接连到这一公共传输媒体上,该公共传输媒体即称为总线。任何一个站发送的信号都沿着传输媒体传播,而且能被所有其它站所接收。 总线拓扑结构的优点: 1) 总线结构所需要的电缆数量少,线缆长度短,易于布线和维护。 2) 总线结构简单,又是元源工作,有较高的可靠性。传输速率高,可达1~100Mbps。 3) 易于扩充,增加或减少用户比较方便,结构简单,组网容易,网络扩展方便 4) 多个结点共用一条传输信道,信道利用率高。 总线拓扑的缺点: 1) 总线的传输距离有限,通信范围受到限制。 2) 故障诊断和隔离较困难。 3) 分布式协议不能保证信息的及时传送,不具有实时功能。站点必须是智能的,要有媒体访问控制功能,从而增加了站点的硬件和软件开销。 2. 星型拓扑星形拓扑是由中央节点和通过点到点通信链路接到中央节点的各个站点组成。中央节点执行集中式通信控制策略,因此中央节点相当复杂,而各个站点的通信处理负担都很小。 星型拓扑结构的优点: 1) 结构简单,连接方便,管理和维护都相对容易,而且扩展性强。 2) 网络延迟时间较小,传输误差低。 3) 在同一网段内支持多种传输介质,除非中心结点故障,否则网络不会轻易瘫痪。因此,星型网络拓扑结构是目前应用最广泛的一种网络拓扑结构。 星型拓扑结构的缺点: 1) 安装和维护的费用较高 2) 共享资源的能力较差 3) 通信线路利用率不高 4) 对中心结点要求相当高,一旦中心结点出现故障,则整个网络将瘫痪。 3. 环型拓扑环形拓扑网络由站点和连接站点的链路组成一个闭合环。每个站点能够接收从一条链路传来的数据,并以同样的速率串行地把该数据沿环送到另一端链路上。这种链路可以是单向的,也可以是双向的。 环形拓扑的优点: 1) 电缆长度短。环形拓扑网络所需的电缆长度和总线拓扑网络相似,但比星形拓扑网络要短得多。 2) 增加或减少工作站时,仅需简单的连接操作。 3) 可使用光纤。光纤的传输速率很高,十分适合于环形拓扑的单方向传输。 环形拓扑的缺点: 1) 节点的故障会引起全网故障。这是因为环上的数据传输要通过接在环上的每一个节点,一旦环中某一节点发生故障就会引起全网的故障。 2) 故障检测困难。这与总线拓扑相似,因为不是集中控制,故障检测需在网上各个节点进行,因此就不很容易。 3) 环形拓扑结构的媒体访问控制协议都采用令牌传递的方式,在负载很轻时,信道利用率相对来说就比较低。 4. 网状拓扑在网状拓扑结构中,节点之间的连接是任意的,每个节点都有多条线路与其他节点相连,这样使得节点之间存在多条路径可选 网状拓扑的优点: 1) 网络可靠性高,一般通信子网中任意两个节点交换机之间,存在着两条或两条以上的通信路径,这样,当一条路径发生故障时,还可以通过另一条路径把信息送至节点交换机。 2) 网络可组建成各种形状,采用多种通信信道,多种传输速率。 3) 网内节点共享资源容易。 4) 可改善线路的信息流量分配。 5) 可选择最佳路径,传输延迟小。 网状拓扑的缺点: 1) 控制复杂,软件复杂。 2) 线路费用高,不易扩充。 3) 在以太网中,如果设置不当,会造成广播风暴,严重时可以使网络完全瘫痪。 |
上面那些个优缺点,都是照本宣科,在游戏架构上,那是牛头不对马嘴,不能生搬硬套,不过也可以看出一些端倪,你要把物理网络里面传输的包,想象成高层点的数据包,也许就明白了,咱们拿比较常见的网状和星型拓扑来扯个蛋。
网状架构是最直观的,需要要就接上,直接通信,畅快!
但是,爽快的结果是爽起来就完全停不下来啊!
这里需要这里连,那里需要那里连,连来连去就完蛋了,稍微多点服务器,整个拓扑那是不忍直视啊。
PS:此处仅对身处新手村的架构师,在无招胜有招的搞基夹狗湿手里,能把网状拓扑玩出花来你信不信。
话掰回来,这时候网状架构的优缺点就很清晰了,规模不大的时候,清晰明了,消息路径段延时也比较小,任何单个节点挂掉,基本不会使得整个架构瘫痪;但在大规模架构里不适合直接使用,会导致整体控制极其复杂,不便于扩容缩容,网络教材里面的早期电话网就是最好的栗子。
至于星型架构,核心就是有个中心点,中心节点以外的服务器不直接连通,所有消息均通过中心节点中转,听起来耳熟是不是,没错,这个中心点就像是前篇文章里面提到的QQ游戏架构里面的转发层proxy,事实上也就是这么回事,咱就可以把QQ游戏的架构,粗暴的看成一个大大大大大的星型拓扑为主的混合型拓扑。
这么做的好处就是解耦以后,架构简单维护方便,扩容缩容,基本也都没有互相的影响。好,那么问题来了,缺点是啥,很明显,中心那个点就是整个星型架构的命门啊!
这命门要跪了,整个架构全完蛋,直接隔屁了有木有!
因此,针对这样显而易见的缺陷,也衍生出各种各样的解决方案,在这先卖个关子,这是以后要提到的单点问题。
五、关于架构3--些许细节
上面不是已经有架构1和2了,为啥还会有3!撸主窃以为,前面之提到一个简单的整体架构,并不能说明什么,毕竟在大家都套用各种组件的情况下,架构已经是越来越八股了,真正能影响结果的,是一些细节。
呃呃呃,之前不是说不要在意这些细节么
受篇幅和能力(这个是主要因素)的限制,撸主会用迅雷不及掩耳盗铃的速度,简单的聊下架构实作时需要考虑的几个细节问题。
1. 单点与容灾
单点和容灾是有着千丝万缕联系的两个难兄难弟。为什么这么说呢,要容灾,基本上是要避免单点的,如果某个模块是单点,就存在容灾的风险。那么,是不是意味着我们在设计架构的时候,所有节点都得规避单点呢?
从某种意义上说是的,但也不绝对。为啥,咱首先来说下要规避单点的,最简单的例子,如果数据层只有db没有dr,万一db挂了,直接呵呵了不是吗。OK,这个是数据层的,各种冷热备同步公司和业界都有比较成熟的方案,撸主就不班门弄斧了,聊点别的。
逻辑层,如果某种服务是无状态,部署时有多台机器服务,同时有机制能够探测出单点故障并将对应的服务流量切走,也能达到容灾的目的。但是,如果是有状态的,情况就复杂点了,当状态相对比较重要的时候,要做到热切换,那么就需要做到主备间的状态同步,否则切过去就得隔屁。举个极端的栗子,DB,呃呃呃,不是逻辑层的,但请不要在意这些细节,DB上的数据,就是一种状态,而且是强状态,所以,后面的你懂的,如果没有同步的DR,你切起来确定不会手抖么?
对于一些弱状态,根据需求,有的可以退化为无状态服务器处理,用户的损失,通常是可接受的,比如一些登录态和内存数据,即使是缓存的玩家数据,最大丢失时长也是可控的,咳咳,由后台合理的设计来控制。按照公司TDR3标准,容灾一节的要求是:
四、【容灾处理】 1. 不允许存在关键路径(游戏核心体验、玩法)上的单点(不可扩展的节点); 2. 不允许因Cache中的数据变更未及时入库,导致可能发生10分钟以上的回档。 |
BTW,个人意见,对于分区分服的游戏而言,由于天生就能算是一种容灾设计——恩,这个区玩不了你丫到别的区玩蛋去——可以在单区容灾设计上进行简化,保证核心线无单点,已经足够了,如果啥都做到单点,这设计和运维复杂度以及运营成本,估计够大家喝一壶咯。
好,前面扯的有点蛋裂,稍微总结下,如何规避单点问题,核心就是不能存在单点。
接着总第二点,不是单点的情况下,如果有服务器挂了,怎么切换?
1) 状态完全同步的,可以随意切换,例子就是前面说过无数遍的DB、DB、DB。
2) 不太care状态的主备模式,X1宕机,后续请求切换到X2去。
BTW,再扯远点,能不能正常的切换,还得注意切过去的备机能不能抗的住新来的请求压力。如果是冷热备模式,备机性能不如主机,或者是双机热备,切换前单个服务器压力已经超过50%,那么一旦其中一台服务器隔屁,能不能正常的切过去,还真是个问号,不,也许是个感叹号。万一弄不好,过载保护做的也不够,一层压一层,雪崩了,就不仅仅感叹号了。
OK,说完鹅厂的,聊聊某些小公司。通常来说,大家对数据层的容灾和单点处理,还是挺有共识,毕竟玩家的快乐就尼玛建立在那么几十几百k的数据上,要直接数据丢了,还tm能活下去么?因此,大家在数据层上,做的相对还是比较一致的。
但是,一般有但是就没好事,是的,在其他方面,那就真的是五花八门,所谓八仙过海各显神通鸟。部分BAT系的兄弟们,对这块还是有比较好认识的,知道哪些地方注意那些地方要规避容灾风险,不过迫于时间人力成本,恩,还有码农的惰性以及部分经验和认知的欠缺,蛮多小公司,对于其他层的容灾,基本是爱理不理的。部分走过TDR流程的代理游戏,也有类似的情况。某些小开发商还振振有词,这个模块/服务器从来没有出过问题,不要担心,撸主心想,不出问题,没啥用户量的时候,确实不要担心,就算挂了也不用太操心,担心的是万一人多了你再挂掉,能不能扛的住,就看您老人家的命咯。
But,撸主在这里多放一个屁,万事皆有轻重缓急,上面吐槽一些小开发商,人家好歹也是成了的,至少是小成吧,对于那些刚刚起步的兄弟们来说,这tm都不是事,先把要做的产品做出来再说,弄不好就死了呢,还没到要考虑什么单点容灾的份上嘞,下面几点,类同。等能养活自个,需要面对问题的时候,再来考虑吧,tony老大在海量之道里面不也说了么,先扛住,再优化。如果你有在外面晃荡的兄弟姐妹们,不如多让他们请吃个饭,鼓励鼓励,尽人事,听天命啦
2. 负载均衡
上面说单点容灾问题,主要提的是程序以及机器故障问题,其实还有个点,啥呢,性能单点,程序是没挂,部署上也不是单点,不过单个服务器性能到顶了,雪崩了,一样影响体验。这时候,就需要祭出负载均衡了。
负载均衡的策略有很多种,比较关键的有两个点:负载均衡的算法,以及对服务器负载的检查方法。先说说后面这个,服务器负载的高低,和服务器本身的特性有关系,比如,有的是CPU负载型,有的是IO负载型,还有的吃内存。因此对服务器负载的判断,也需要量体裁衣,您拿CPU消耗去衡量一个IO负载的服务器,必然是不靠谱的。通常来说,系统统计诸如CPU、内存、磁盘/网络IO以及进程数据诸如吞吐率、平均/最大响应时间、成功率等都可以作为衡量标准,具体哪个合适?
有了衡量的标准,下面简单聊聊部分简单常见的负载均衡的算法,撸主经验尚浅入道不深,只能点到为止,还请高人们补充指点。
随机式。Random一下,就扔出去,简单暴力,好么,前面扯了半天的负载能力衡量白瞎了,根本就不管啦,没所谓啊,做起来简单就是最大的优点啊,最后均不均衡就听天由命咯。
基于轮询式的(Round Robin),不管三七二十一,一个个轮着上, 还是不管后端负载,只要没死,轮呗。还是简单,不过相比随机,多个优点,在后端个服务器软硬件性能接近以及请求相对平均的情况下,负载相对均衡。
Hash式。根据请求者的key,hash到不同的服务器上。鹅厂内,拿UIN做key进行hash的,不少,如果用户号段分布均匀,负载均衡的还不错,实现也简单,不过一样没考虑机器负载,一旦宕机就影响一个号段的访问就屁了(不考虑备机的情况下)。
带权重的轮询/随机式(Weighted Round Robin/Random)。基本是随机式和轮询式的改良版本,合一块说了,不分开。带权重的方法,会综合考虑服务器的负载,动态调整服务器的权值,高权值的服务会分配到请求的概率会更高一些。当负载升高后,会定期调整权值,减少处理请求量。带权重的负载均衡方式,不一定会必然选择高权值的服务器,因为在调整权值的间隔期内,高权值服务器可能会被突发的大量请求压死。由于考虑到后端各个服务器的负载,这种负载均衡算法会相对均匀点,当然,实现上就会复杂了。
实际业务中,会采用具体哪种负载均衡的算法,影响因素有很多,比如,服务是否有状态?不同请求是否有时序关系?前后请求是否有逻辑依赖?不同的条件,导致最终的选择可能都是不一样的,不是看起来美好的东西都tm是最好用的,您要不是许仙,白娘子就不要去碰了,接口类型不对哎。
3. 数据一致性
严格意义上来说,数据一致性,并不能从架构设计直观的表现出来,但是,他的重要性,却是非常非常非常重要的,毕竟,玩家花大把银子,买的不过是你存在DB里面若干字节的数据而已。
数据一致性,又可以分为两个方面:一个是cache和DB的一致性,另一个是数据修改的一致性。首先聊聊cache和DB的一致性问题,数据层本身的cache和持久化存储咱就不谈了,只言片语根本就谈不下去,聊聊逻辑层的。为了减少DB压力,一般游戏逻辑服务器或多或少的会缓存一些玩家信息,回写呢,也不一定是即时写入的,那么在这么个空档期,万一服务器崩溃了,就会导致“最新”数据丢失。怎么规避,数据层面,其实很难了,你都没写到db里面去,规避个蛋蛋!再说了,也不太会有游戏业务做玩家数据多地换成确保数据完全不丢的,不是不可以做,关键是实现的成本相对收益,并不太值得。那么,万一隔屁了怎么找回呢?重要的数据,咱们都是有账单和日志的,通过账单查验,基本还是能够保证核心数据能够找回的,一般自研游戏都这么干。
再谈另外一个,如果逻辑设计是其他服务器直接从DB拉取玩家数据,那么在上面的条件下,就必然存在着读取到数据不是“最新”数据的可能。要是只是拿来做个展示,其实不一致也不是不可接受的,毕竟即便是你拉取到了最新的数据,如果展示过程中有更新,又没有推送的话,不也还是out了吗?但是,如果拉取到的数据牵扯到关键逻辑,或者后续会基于这些数据进行修改,那么不一致,问题就大发了。如何解决呢?说起来很简单,从保留最新数据的地方把数据拉过来,就可以了。至于怎么拉,不同业务不一样,在这不细说。
读的一致性搞定了,最关键的写的一致性来了,如何保证并发修改的正确性。正确性基于一个前提,啥?每次修改,他基于的基础数据,是最新的。怎么说,如果A数据初始状态是A0,Svr1和Svr2分别拿到A0的数据,一个想改成A1,另一个想改成A2,那么,无论最终写入的顺序和结果怎样,都可能是有问题的,因为如果Svr1先改成A1后,Svr2再拿到A1的数据,而不是A0,那么最终写回去的可能就不是A2而是A3值了。
屁话说了一大堆,得来点实际的了,怎么去保证写的一致性,常见的解决方法有很多,本质上,恩,本质上,就是化并发为串行。OK,咱们先来看看比较多见一些解决方法:
1) 数据加锁。无论是行锁还是字段锁,只要是可能修改到数据,那么在读取之前先把数据锁上,那么后续其他可能需要修改数据的操作,加锁都会失败,在先上锁操作完成之前,是不会有新的修改操作并发产生,好么,写串行了。读呢?纯粹的读取您随意,反正不影响最终一致性。但问题又来了,加锁,就意味着并发能力的下降,而且,如果是允许多点同时加锁写数据,那么,其他有缓存相同数据的服务器,在修改完之后还得收到相应的通知,否则他们缓存的数据,其实是失效的旧数据了,可能存在各种风险。
2) 基于数据版本,又称作乐观锁。要拿数据随意,拿过去的时候,顺道带上了当前的版本,如果要写,没问题,把之前的数据版本带回来就得嘞。DB会对版本做校验,和当前版本吻合才可以写入,同时版本号递增,否则直接扔一边去。显而易见,这样能够带来更高的并发性能,但是也依然有缺陷,如果同一个逻辑操作涉及多个数据修改,有的成功有的失败了,怎么处理?要不要做事务?放屁,你见过几个游戏做事务的啊?!要不要做重试?还是写日志?处理异常情况的时候,采用乐观锁方式就需要深思熟虑了。这点正好是锁数据可以良好规避的。
3) 单点修改。这只是个思想,具体实现有多种方式,比如,把同一个玩家的交互操作都限制在同一台服务器上,能够串行化操作;又如,不涉及实时的玩家数据交互,通过邮件或者离线操作队列的方式,保证同一个玩家的操作只在他自己登录服务器进行,也能够将操作串行化。
最后,撸主还想放个屁,我们是做软件的,别忘了看看科班的教材,啥呢?计算机体系结构,诺,就这本:
可以翻翻里面关于CPU修改缓存、内存等并保证数据一致性的方法,也许能再多点灵感。
好吧,今天到这,撸主接着去了。