靠谱 的软件外包伙伴

您的位置:首页 > 新闻动态 > 服务器软件架构:前端服务器与客户端随机负载平衡

服务器软件架构:前端服务器与客户端随机负载平衡

2016-01-23 09:37:30

Sergey Ignatchenko针对大规模多人网络游戏所撰写的开发与部署一书章章精彩,不过里面的内容远不仅适用于游戏。下面是这本书的最近一章:“关于服务器端架构、前端服务器与客户端随机负载平衡”问题

在前端服务器输入


[Enter Juliet] 
Hamlet: 
Thou art as sweet as the sum of the sum of Romeo and his horse and his black cat! Speak thy mind! 
[Exit Juliet] 


图片描述

这是用莎士比亚编程语言所编写的一段程序样例。

我们的经典部署架构(尤其在使用FSM时)并不差,运行良好,但对于大多游戏来说仍有很大的改进空间。更具体来讲,我们可以在游戏服务器前面再另加一行服务器,如下图: 
图片描述

如图所示,与经典部署架构相比,只是在游戏服务器前面增加了一行前端服务器。这些新增的前端服务器旨在处理所有来自客户端的通讯:所有“那个玩家有没有连接上”这种讨厌的问题,所有客户端到服务器的数据加密(如适用),所有这些密码等等,所有相对奇怪的reliable-UDP协议(如适用),当然还有客户端与不同游戏服务器之间的路径选择信息——这里处理着所有客户端的通讯。

此外,一般来说这些前端服务器在需要时会存储相关游戏世界的副本,并在游戏世界升级时扮演集中器(concentrator)的角色。也就是说:即便游戏服务器上有10万人同时观看某个比赛(比如某个比赛的决赛之类),只需要将更新发给几个前端服务器,而前端服务器会解决10万人的数据分配问题。在大型决赛中,当有成千上万人想要观看比赛时,这种能力非常方便。实际上无需将其制成直播视频——对现有的玩家来说并不方便,也贵得要命,但在客户端里是可以实现的。

我们稍候会对前端服务器的实现进行一些讨论,不过现在先讨论最重要的部分:

前端服务器必须能够在不对玩家造成重大不便的情况下就能简单更换。

也就是说,如果任何前端服务器因某种原因发生故障,玩家最多能看到几秒钟的断开。虽然仍会造成混乱,但比这样的场景——“整个游戏世界宕机,我们需要从备份恢复”好多了。换句话说,无论何时前端服务器因某种原因而崩溃(更糟的情况下甚至产生“黑洞”),所有连接的客户端都会检测到这一情况,并自动重连到其他前端服务器上;在这种情况下,所有玩家能看到的只是瞬间断开(也很讨厌,但比游戏挂掉好太多)。

前端服务器:好处

每当我们增加复杂层级的同时,总会产生这样一个问题“我们真的需要它吗”?从我看到的情况,将可轻松替换的前端服务器放置在游戏服务器的前面很有价值,有着诸多好处。具体来说:

  • 前端服务器从游戏服务器上分担了一些负载,并且很容易替换。

    • 也就是说游戏服务器的数量可以减少了。
    • 再加上前端服务器容易替换的特性,站点整体的可靠性得以增加;部分游戏世界服务器宕机的情况将越来越少。
  • 前端服务器可以使用更廉价的那些,严格说来甚至无需使用ECC和RAID,但游戏服务器当然得使用这种的。如上所述,前端服务器很容易替换,因此如果发生故障,其复杂可以自动重新分配到其他前端服务器上。如果想要部署在云端,你可能会想给前端服务器考虑更廉价的配置(即便是来自不同的CSP[1])。

  • 允许客户端有指向全站的唯一连接;这样做的好处包括:更好地控制与玩家终端的连接,从而可以控制来自不同数据流的优先级,消除那些难以分析的“部分连接”,并更好地隐藏站点的实现细节。

  • 允许简单的客户端负载平衡(无需硬件负载均衡器等等)。想要了解更多信息,参见下面的“客户端负载均衡与大数字法则”部分。

  • 在前端服务器上保存相关游戏世界的副本使得想要观看站点上某些比赛(比如大决赛之类的[2])的用户数量近乎无限;最重要的是,还不影响游戏服务器的性能! 此外通常无需为决赛组织任何工作,只要正确构建,系统便会按以下方式自主处理:

    • 每当有人来观看某场游戏,客户端会向前端服务器发出请求;
    • 如果前端服务器没有所请求游戏的副本,就会向相关的游戏服务器发出请求,同时更新到游戏世界状态;
    • 从这时起,前端服务器会保持游戏世界的副本“同步”,向所有请求的客户端发送该副本(与更新内容);
    • 也就是说从这时起,即便有10万个用户在观看该游戏服务器上的某场游戏,所有的额外负载都由前端服务器处理,不会影响到游戏服务器;
  • 前端服务器允许稍后增加安全性(基本能够担任DMZ的角色)。


注1:要记得你仍需保持一流的连通性; 
注2:由于大决赛是吸引注意力很好的方式,这种做法明显能够提供竞争力。

前端服务器:服务器延迟与玩家间延迟的差异

关于设置前端服务器的负面影响,我只能想到这两个。第一个是由前端服务器所带来的额外延迟。具体来讲,我们是在讨论从应用层面的客户端所发送的数据包,经前端服务器处理,进入前端服务器的TCP协议栈[3],再离开游戏服务器的TCP协议栈,到达游戏服务器应用层面所需的时间(再加上反向重复这一步骤所需的时间)。

让我们查看一下这个额外生成的延迟。从我的经验来讲,如果所使用的通讯层数据库相当优秀,应用层面的前端服务器的处理时间会在几微秒[4]。然后从前端服务器到游戏服务器会建立点对点TCP连接,这类连接的延迟(通过10G以太网)在8µs左右[Larsen2007]。将这两个延迟的时间加在一起,并乘以2得到往返延时(RTT),这个数字显示延时仍远远低于100µs。然而,还有进一步的原因(诸如延时开关,不同OS之间的差异,游戏之间的差异等)让人无法确认延时会低于100µs——也就是说,也许能达到,也许不能。另一方面,如果实现时足够仔细,前端服务器所带来的延迟会相应减少,在大多情况下可减少到1ms。

总结:

  • 如果可以忍受1毫秒左右的额外延迟,就别再担心额外延迟的问题了,启用前端服务器吧;包括所有类型的游戏,唯一可能的例外是大型多人在线射击游戏(MMOFPS)。
  • 如果可以忍受的延迟远低于1毫秒的话(难以想象,因为这个数字比1/60秒/帧的更新时间还要低一个数量级,不过在MMOFPS世界里什么都有可能)——就要多考虑一下了,尝试在实践中找出延迟种类;如果经过实验,确实无法接受所带来的延时[5],就可能需要放弃前端服务器了。
  • 具体情况可能有所不同,无法作出保证,不包括电池。

如果某些前端服务器过载,或运行的硬件差异很大,那么使用前端服务器的第二个(依我来看相当理论化,不过一般要看具体情况)潜在问题就会产生,那些连接到负载较低的前端服务器的玩家延迟也较低,从而在比赛中更有优势。

一方面来说,在真实世界的部署中,我并未发现有实际区别——也就是说,按我的经验如果某些前端服务器过载,这意味着大多数已经达到90%以上的负荷,这是应当避免的。另一方面,无论根据实际情况还是理论上,你都可能受到这种影响,尽管除了MMOFPS我尚未看到现实案例。

如果这类玩家之间的延迟差异成为案例(只有成为真正的问题时),对于与某些前端服务器相连接的某些游戏世界的玩家来说,可能需要实现某类关联(更多相关信息请参见“关联”部分)。然而,大规模关联可能会抵消前端服务器所带来的多数好处,因此如果需要在所有游戏中执行关联的话,不架设前端服务器也许更好一些;只在游戏的一小部分中实现关联(比如“高规格比赛”)带来的麻烦会更少一些,请查看后面的“关联”部分)。


注3:没错,在多数情况下我主张在服务器间通讯中使用TCP连接,参见上面的“服务器间通讯”章节。另一方面,UDP也是可以使用的。 
注4:注意这可能成为非普通情况,此外我已经实现了。 
注5:理论上你可能还想实验一下无线带宽技术什么的——完全适用于FSM架构,因为其中的通讯与其他部分的代码相分离,但一般来说不值得尝试。

客户端随机平衡与大数字法则

一旦设置了前端服务器,就会产生问题“如何确保所有前端服务器保持同等负载”,即典型的负载均衡问题。一般来讲,在至少近20年间负载均衡都是个很大的话题。从中引申出的三种常见技术分别是:DNS负载均衡、客户端随机平衡还有服务器端(一般是基于硬件的)负载均衡。而针对最后一种,随着制造硬件设备技术改进,毫无疑问至少在企业中普及度正在增长。我们来深入探究一下这些负载均衡的解决方案。

DNS负载均衡

DNS负载均衡是基于传统DNS请求的。每当客户端请求站点前端地址(以IP地址显示)时,就会向相应的DNS服务器发送请求。如果按DNS负载均衡来配置DNS服务器的话,就会向不同的DNS请求来源返回不同的IP地址,因按循环轮流(round-robin)的模式[6]而得名。

在不同的web服务器使用均衡浏览器时,DNS负载均衡有两个主要的缺点。首先将DNS服务器与请求路径缓存在一起会有问题(这是DNS处理的标准做法)。也就是说,即便服务器按均衡模式忠实返回所有的IP,其中一个返回的IP可能被Big Fat DNS服务器缓存(试想Comcast或AT&T),并分配给成千上万个客户端;在这种情况下,被缓存的“幸运”IP可能获得倾斜。对网络服务器使用DNS均衡负载的第二个问题在于,如果其中一个服务器宕机,普通的网络浏览器不会尝试列表中的其他服务器,因此一般来讲网络服务器范围中的均衡DNS无法提供服务器容错功能。

幸运地是,因为我们有客户端,可以同时轻松地解决这两个问题。此外这些技术也对你基于浏览器的游戏奏效(在JS加载完毕并开始运行后)。


注6:严格来讲要稍微复杂一些,因为DNS数据包中含有服务器列表,但由于近乎所有人都会忽略所返回的数据包中其他条目,只关注了第一条,因此与每条请求只返回一个IP相差无几。例外情况就是服务器可以自行作出选择,参见“客户端均衡”。

客户端随机平衡

为了改进DNS均衡,人们使用了一个简单的办法。我们不再从服务器端所有内容中循环返回,而是直接将同一张服务器列表返回给所有客户端。这张列表可能会硬编码到客户端中(我个人就用了这个办法,大获成功),或者通过DNS来分配这张列表,但要按照简单的IP列表匹配相应名称的形式,用getaddrinfo() 或类似命令来检索。用哪个办法无关紧要。

一旦客户端获得IP列表,一切都变得非常简单。客户端从IP列表中随机获取,尝试连接随机选中的IP。如果连接不成功或者丢失,客户端再尝试另一个随机IP,尝试重连。

失败案例包括: 

int myrand() {//DON'T DO THIS! 
srand(time(0)); 
return rand(); 


在这种情况下,如果出现大量连接断开的情况,结果导致玩家尝试同时重连,IP分配很可能由于想要获得IP地址的各个客户端之间差异过小而被倾斜;如果所有客户端都在5秒内尝试重连,用myrand()函数最多只能获得5个不同的IP,甚至更少。除了这种极端情况,任何随机数发生器都是适用的。甚至简单的线性同余发生器,在项目启动时按time(0)(并非像上面列子中的那样在请求时)在实践中都是可用的。

大数字法则——根据这一法则,从大量实验中所获得的结果平均值应当很接近期望的数值,并随着实验数量增加而越来越接近。

客户端随机平衡:大数字法则,对比DNS负载均衡

与DNS负载均衡不同(这一方式在理论上提供了“理想的”平衡),客户端随机均衡依赖大数字统计法则来获得服务器间客户端的平衡分配。这一法则基本上就是在说,对于单个测试,实验数量越多,分配越平衡。TODO:增加二项分布的内容,还有一个案例。

事实上,尽管在理论上并不理想,客户端随机平衡比DNS负载均衡更为平衡。有双重原因。第一,一旦客户端数字很大(成百甚至更多),客户端随机平衡就会足够平衡——如果系统有成千上万名玩家,只有很少一些会出现问题;不会达到非常平衡,不过这种不平衡影响不大,而且随着人数增长,还会更加平衡。但是,从积极方面来讲,客户端随机平衡不会出现DNS缓存问题。即便使用DNS来分配IP列表,而且该列表被缓存,在使用客户端随机平衡时,在设计上系统中循环的所有IP都是一致的,因此与DNS负载均衡不同,缓存完全不会改变客户端分配。

总结:使用DNS负载均衡时要格外小心。换句话说,客户端随机平衡对于游戏效果更好,因为同时在线的玩家数量会从几百增长到成千上万;运行起来几乎没有问题,始终提供几乎完美的平衡体验。也就是说,如果整体的平衡负载是50%,事实上有些是48%,而有些是52%,但不会超过这个数字。[7]

至于刚刚在说DNS负载均衡运用到web浏览器时的第二个缺点(大多数的浏览器无法提供容错,来避免某个服务器崩溃)——一旦我们在客户端有整张列表时,这个问题就烟消云散了。一旦发现连接失败,可以另选一个IP地址。


注7:当然,服务器必须得运行足够的时间才能得出相同的结果;如果一个服务器刚刚加入服务,必须得过上一定的时间才能达到与其他服务器相同的负载级别。如果必要的话,也可以抑制这种影响。尽管抑制会造成不利影响,而且实践中从未见过此类需求。

服务器端负载均衡

这种做法与DNS负载均衡和客户端随机平衡都不相同,用到了服务器端负载均衡器。负载均衡器一般是额外的设备,设在服务器前面,完成负载均衡的工作。

额外的均衡能力一般对于游戏完全没有必要(大数字法则的趋势非常鲜明)。

在不同客户端造负载不同的情况下,服务器端负载均衡确实有更强大的平衡能力(因此服务器端均衡器可以在大数字法则无效的情况下继续使用)。但是一方面来说,这些额外的平衡能力对于游戏来说一般完全没有必要,大数字法则效果良好。另一方面,这种负载均衡器设备昂贵的要命(如果想要冗余的话价格还得翻倍,而冗余当然是必要的),还不包括数据中心间的平衡和容错(需要设计),并带来了额外的不可控延迟。[8]

此外,在谈到冗余和设备花费时,不少硬件生产商都会说:“来用我们的均衡设备吧,使用主动/主动配置,不会造成浪费。” 尽管的确是这样,可以使用很多主动/主动配置的服务器端负载均衡器,但还是得至少拥有一台冗余设备,在某台设备发生故障时处理相应负载。换句话说,如果你只有两台主动/主动配置设备,一台运行,每台设备的总负载必须低于50%,如果想要冗余这是难以避免的。

综上,对于游戏负载均衡来说,服务器端负载均衡设备实际用途不大。即便你使用了基于网络的部署架构(如上所述),也应当避免使用服务器端负载均衡器。


注8:大多数负载均衡器是为了平衡网站而设计,低于100毫秒几乎没什么关系,因此在购买设备前,至少要确保进行讨论,并测量延时情况(根据你的负载类型)。

均衡总结

根据我的经验,客户端随机平衡(针对前端服务器)运行良好,没必要改用其他的。DNS负载均衡几乎普遍不如客户端均衡,而基于硬件的服务器端均衡器太过复杂与昂贵,一般没必要在游戏环境中使用。如上所注,可能需要服务器端均衡器的例外情况在于使用基于Web的部署架构时。

作为CDN(内容分发网络)的前端服务器

使用前端服务器作为CDN是有可能的(甚至还能用来构建自己的CDN)。即便所有的游戏服务器都在单独的数据中心里运行,对于某些类型的游戏来说,使用不同数据中心的前端服务器可能也是不错的。如下图: 
图片描述

这点与经典CDN的原理十分相似:减少终端用户的延迟。另一方面,我们需要注意:与经典CDN不同,游戏类CDN的内容并不是静态的,因此通过更换更好的设备,而在延时方面获得改善也是有可能的,一般能提高几毫秒。

使用这样的配置架构还有其他原因:确保在主数据库停机时,仍能连接到网络(保持“某些连接”);在实践中,如果有像样的数据库,这种情况不应该发生。更确切来说,数据库会偶尔碰到1.5-2分钟的短暂故障(典型的边界网关协议收敛时间)。因此如果你在找借口,想要使用上图的优雅架构的话,而且你的客户端可以极快地探测到故障,并重定向到另一个数据中心,这种结构可能会对你的用户有一些影响。

想要让这种类似CDN的多数据中心前端服务器配置实现良好的话,需要考虑这么几点:

  • 数据中心之间必须连接良好(上图中的“某些连接”)。至少ISP间设备应当根据你的ISP来设置,以确保关键路径使用最佳数据流。

    • 严格来讲,“某些连接”不一定需要基于互联网;通过“专用”帧中继之类就可以降低数据中心间的延迟几毫秒,但花费可能会达到每月数万。
  • 由于使用前端服务器充当“集中器”,“某些连接”的流量可能比去往客户端的那些要低1到2个数量级。

  • 应当解决二级数据中心连通性下降的问题(特别是在数据中心间的连通性下降时)。最简单的处理办法就是让主数据中心拥有足够的处理能力(流量智能和CPU智能),以处理所有客户端,不过一般花费不斐。作为替换选项,根据游戏类型,在出现故障时关闭一些活动也是可以的。

CDN类布置的底线前端服务器的CDN类布置可能减少某些用户几毫秒的延时,前提是数据中心间的连接良好。从而改善一定的延时。根据我的经验,由于延迟方面的改善并不太大,来往于游戏服务器的数据包仍需走完全程,因此这种做法价值并不大。不过还是要记得有这种可能性。举个例子,在没有足够带宽直接提供给客户端的情况下,如果根据法律需要将游戏服务器保存在陌生位置时会很方便(例如赌牌游戏)。

前端服务器+游戏服务器=类CDN

换句话说,如果真的担心延时,将游戏世界服务器放置在离玩家近一些的地方会更好一些(留下数据库服务器),如下图: 
图片描述

这里我们将与时间最关键的东西(一般是游戏世界服务器)迁移到终端用户旁,降低相应数据中心附近用户的延迟情况。维护这样的基础设备确实令人头疼,不过这种做法是可取的。因此如果真的担心延迟情况,可以按照这种方式来部署。提醒一句:如果这样做,会出现“区域服务器”——同样也有自己的问题:你需要确保该区域的客户端只与相关的前端服务器连通,数据中心间连通的安全性也会成为很大的问题,等等;不过还是可行的,但这种做法仅限于真正有此需要的时候。

关于关联

在某些情况下,你可以决定需要一种“关联”,以便某类用户(一般是在特定游戏世界的用户)连到特定的前端服务器。

注意:在提到前端服务器的时候,“关联”的概念与在网络服务器的均衡器中所使用的经典概念(一般指的是“持久性”或“粘性”)并不相同。在网络世界中,持久性与粘性指的是从同一个服务器到同一个的客户端(以处理会话与各客户端缓存)。但对于前端服务器来说是不同的,一般指的是前端服务器到游戏世界的关联(对于玩家,或者对于玩家和观看者),而不是客户端到服务器的关联(参见“前端服务器:服务器间延迟与玩家间延迟的不同”部分)。

技术上来说,实现前端服务器到游戏世界的关联并不难,但真正的问题在于部署关联之后。简单来说,只要使用关联的游戏世界数量不大,就会很顺利。换句话说,一旦有大量玩家使用关联规则进行连接,不同的前端服务器间想要达到合理的均衡就变得十分困难。在没有关联时,由于大数字法则均衡实现近乎完美;在引入关联规则后,就会导致分配平衡倾斜,关联所影响的玩家越多,距离理想分配状态就偏离地越远,因此在达到负载均衡的同时管理这些规则可能成为很大的挑战。

底线:尽可能避免关联。

前端服务器:实现

现在我们来讨论一下如何实现前端服务器的部署。如上所述,前端服务器的关键属性是,在发生故障时易于替换。为了实现这一点,

必须确保在所有前端服务器上没有原始的游戏世界换句话说,前端服务器应当只有原始游戏世界状态的副本,而原始的游戏世界状态则保存在游戏服务器上。

如果使用普通的订阅/发布器(或状态复制器)之类的东西,也无需太过担心,但如果在前端服务器中引入了任何定制逻辑的话,要非常小心,因为易于替换属性可能会导致重要资料遗失。

前端服务器:QnFSM实现

前端服务器的一个实现是在纯粹的Queues-and-FSM架构下实现,如下图: 
图片描述

这里在游戏服务器方面,TCP-与UDP-相关的线程,与在“QnFSM下实现游戏服务器架构”部分中的描述非常类似;由一个或多个路径选择&数据线程(每个路径选择&数据 FSM中至少有一个线程)负责分发所有的数据包,并缓存数据(比如“游戏世界”数据)。我们来深入讨论一下这些路径相关的FSM。

路径选择&数据 FSM每个路径选择&数据 FSM都有自己要处理的数据(与更新)。例如,这样一个路径选择&数据 FSM可能包含一个游戏世界状态。另一个路径选择&数据 FSM可能处理点对点数据包的分发工作,而数据包是往返于玩家与游戏服务器之间的。路径选择&数据FSM所处理数据类型一般来说会有三种类型:

  • 普通连接处理器:处理点对点通讯,包括玩家输入与服务器之间的连接;
  • 普通发布/订阅处理器:缓存与处理普通但结构化的数据,比如可用游戏列表——如果允许玩家选择游戏的话;
  • 特殊游戏世界处理器:如果需求功能普通处理器无法解决的话,缓存与处理游戏世界数据。在很多情况下,可以不需要特殊游戏世界处理器;但如果想要执行某类服务器端过滤的话(比如服务器端的战争迷雾),使用它可以避免将数据发送给不应看到的用户,这样就无法从服务器端破解移除战争迷雾了——在这样的情况下,特殊游戏世界处理器非常必要。

在同一个路径选择&数据线程中有不止一个路径选择&数据FSM也是可能的(并且通常可行的),由于线程数量很大,这样能够减少不必要的负载(以及不必要的线程切换)。如何将这些路径选择&数据FSM结合到特定线程中——很大程度上取决于游戏,不过一般来说,普通连接处理器速度很快,可以放在一个线程中。对于普通发布/订阅器以及特殊游戏世界处理器来说,分发到不同的线程中应当考虑到一般负荷与允许的延迟。经验法则(一般来说):每个线程的FSM越多,延迟越高,线程相关的开销越小;不幸的是,其他依赖大多要根据游戏来定。

路径选择&数据Factory Thread

路径选择&数据Factory Thread的主要责任就是,根据来自TCP/UDP线程的请求,创建路径选择&数据线程(与相应FSM)。路径选择&数据FSM的典型生命周期如下:

  • 一个TCP/UDP FSM需要发送某些消息(或者提供某些状态的同步);如果路径选择&数据FSM中没有数据,需要在导入消息到自身缓存时发现这一情况。
  • TCP/UDP FSM向路径选择&数据Factory Thread发送请求;
  • Factory FSM(在适当的路径选择&数据FSM中)创建路径选择&数据线程;
  • 从适当的路径选择&数据FSM中发出消息,返回请求的TCP/UDP线程;
  • Factory FSM报告消息队列的ID:
    • TCP/UDP FSM向适当的队列发送消息(使用ID而非指针,允许确定“recording”或者“replay”)。
  • 每当路径选择&数据 FSM并非必须时,TCP/UDP会向Factory FSM报告:
    • 如果是最后一个需要该路径选择&数据 FSM的TCP/UDP FSM,那么Factory FSM可能引导适当的路径选择&数据线程撤销该路径选择&数据FSM。

在游戏服务器和客户端中的路径选择&数据FSM

我得承认,个人很喜欢这些路径选择&数据FSM。因为喜爱,我不止将它们用在前端服务器上,还用在游戏服务器和客户端上,尽管严格来说不应该放在那里;在简化工作上,它们确实帮了我大忙,让所有通讯都统一起来。不过是否在游戏服务器上和客户端时选用它们,就是你的选择了。

前端服务器总结

总结一下:

根据经验法则,前端服务器是个好东西。尤其是:

  • 减轻了游戏服务器的负载;
    • 让系统设备价格更低(因为前端服务器比较便宜);
    • 也提高了整体系统的可靠性(因为前端服务器很容易更换);
  • 促进了单一客户端的连接(一般来讲是好事);
  • 促进客户端负载均衡;
  • 出现大事件时,接待10万+的观看者变得很简单(事实上没有限制);
  • 缺点仅限于额外延迟,而这种延迟一般都在亚毫秒级别;

一般对于游戏来说,客户端负载均衡是最好的选择:

  • 唯一可能的例外是基于网络的部署架构——可能需要服务器端均衡器;
  • 需要避免大规模关联;

CDN类分布是有可能的,但有雷区; 
前端服务器可以通过QnFSM架构实现。

 

关于:中科研拓

深圳市中科研拓科技有限公司专注提供软件外包、app开发、智能硬件开发、O2O电商平台、手机应用程序、大数据系统、物联网项目等开发外包服务,十年研发经验,上百成功案例,中科院软件外包合作企业。通过IT技术实现创造客户和社会的价值,致力于为用户提供最佳的软件解决方案。联系电话400-0316-532,邮箱sales@zhongkerd.com,网址www.zhongkerd.com


  上一篇   [返回首页] [打印] [返回上页]   下一篇