如何搭建一套完整的视频直播系统

如何搭建一套完整的视频直播系统,第1张

您好,根据我们的开发经验,搭建一套完整的视频直播系统,可以有三种方式进行。

第一种方法是,自己召集人马,如果您资金雄厚,或您本身是一名技术,且有其他技术朋友的话,可以尝试和朋友一起自行搭建视频直播系统,人数的话大约需要五组人,包括产品组、IOS组、安卓组、后台组、测试组等等,人寿和资金都充足的话,大约八个月左右就能搞定。

第二种方法是,寻找外包,定制开发软件,时间上比自己开发要快一些,成本上差不多。

第三种办法是购买开源直播源码,直接搭建运营或者经过二次开发后搭建运营,这种方法是对时间成本和资金成本要求最低的。

以上,有问题可以继续追问我。

极光推送服务器端向ios端推送消息需要设置的参数有:

1、高送达率,时效保证:极光推送具有自主协议确认推送消息的送达。

2、服务集成SDK简单:只需要进行简单复制便可直接编译运行 。

3、推送内容多样性服务:除了通知,还有消息、多媒体等;还要其它的各种服务。

4、使应用程序保持高注意力:即使用户没有打开应用,能推送有用的消息。

深圳极光成立于2011年,是中国领先的开发者服务提供商,而且也帮助各行各业优化决策、提升效率。专注于为开发者提供稳定高效的消息推送、一键认证以及流量变现等服务,助力开发者的运营、增长与变现。

极光推送也是一个高度稳定、可扩展的云端架构,极大地帮助移动应用开发者节约开发和维护的成本,轻松实现毫秒级的精准推送。

端游、手游服务端常用的架构是什么样的?

类型1:卡牌、跑酷等弱交互服务端卡牌跑酷类

因为交互弱,玩家和玩家之间不需要实时面对面PK,打一下对方的离线数据,计算下排行榜,买卖下道具即可,所以实现往往使用简单的 HTTP服务器:

登录时可以使用非对称加密(RSA, DH),服务器根据客户端uid,当前时间戳还有服务端私钥,计算哈希得到的加密 key 并发送给客户端。之后双方都用 HTTP通信,并用那个key进行RC4加密。客户端收到key和时间戳后保存在内存,用于之后通信,服务端不需要保存 key,因为每次都可以根据客户端传上来的 uid 和 时间戳 以及服务端自己的私钥计算得到。用模仿 TLS的行为,来保证多次 HTTP请求间的客户端身份,并通过时间戳保证同一人两次登录密钥不同。

每局开始时,访问一下,请求一下关卡数据,玩完了又提交一下,验算一下是否合法,获得什么奖励,数据库用单台 MySQL或者 MongoDB即可,后端的 Redis做缓存(可选)。如果要实现通知,那么让客户端定时15秒轮询一下服务器,如果有消息就取下来,如果没消息可以逐步放长轮询时间,比如30秒;如果有消息,就缩短轮询时间到10秒,5秒,即便两人聊天,延迟也能自适应。

此类服务器用来实现一款三国类策略或者卡牌及酷跑的游戏已经绰绰有余,这类游戏因为逻辑简单,玩家之间交互不强,使用 HTTP来开发的话,开发速度快,调试只需要一个浏览器就可以把逻辑调试清楚了。

类型2:第一代游戏服务器 1978

1978年,英国著名的财经学校University of Essex的学生 Roy Trubshaw编写了世界上第一个MUD程序《MUD1》,在University of Essex于1980年接入 ARPANET之后加入了不少外部的玩家,甚至包括国外的玩家。《MUD1》程序的源代码在 ARPANET共享之后出现了众多的改编版本,至此MUD才在全世界广泛流行起来。不断完善的 MUD1的基础上产生了开源的 MudOS(1991),成为众多网游的鼻祖:

MUDOS采用 C语言开发,因为玩家和玩家之间有比较强的交互(聊天,交易,PK),MUDOS使用单线程无阻塞套接字来服务所有玩家,所有玩家的请求都发到同一个线程去处理,主线程每隔1秒钟更新一次所有对象(网络收发,更新对象状态机,处理超时,刷新地图,刷新NPC)。

游戏世界采用房间的形式组织起来,每个房间有东南西北四个方向可以移动到下一个房间,由于欧美最早的网游都是地牢迷宫形式的,因此场景的基本单位被成为 “房间”。MUDOS使用一门称为LPC的脚本语言来描述整个世界(包括房间拓扑,配置,NPC,以及各种剧情)。游戏里面的高级玩家(巫师),可以不断的通过修改脚本来为游戏添加房间以及增加剧情。早年 MUD1上线时只有17个房间,Roy Trubshaw毕业以后交给他的师弟 Richard Battle,在 Richard Battle手上,不断的添加各种玩法到一百多个房间,终于让 MUD发扬光大。

用户使用 Telnet之类的客户端用 Tcp协议连接到 MUDOS上,使用纯文字进行游戏,每条指令用回车进行分割。比如 1995年国内第一款 MUD游戏《侠客行》,你敲入:”go east”,游戏就会提示你:“后花园 - 这里是归云庄的后花园,种满了花草,几个庄丁正在浇花。此地乃是含羞草生长之地。这里唯一的出口是 north。这里有:花待 阿牧(A mu),还有二位庄丁(Zhuang Ding)”,然后你继续用文字操作,查看阿牧的信息:“look a mu”,系统提示:“花待 阿牧(A mu)他是陆乘风的弟子,受命在此看管含羞草。他看起来三十多岁,生得眉清目秀,端正大方,一表人才。他的武艺看上去不是很高,出手似乎极轻”。然后你可以选择击败他获得含羞草,但是你吃了含羞草却又可能会中毒死亡。在早期网上资源贫乏的时候,这样的游戏有很强的代入感。

用户数据保存在文件中,每个用户登录时,从文本文件里把用户的数据全部加载进来,操作全部在内存里面进行,无需马上刷回磁盘。用户退出了,或者每隔5分钟检查到数据改动了,都会保存会磁盘。这样的系统在当时每台服务器承载个4000人同时游戏,不是特别大的问题。从1991年的 MUDOS发布后,全球各地都在为他改进,扩充,退出新版本,随着 Windows图形机能的增强。1997游戏《UO》在 MUDOS的基础上为角色增加的x,y坐标,为每个房间增加了地图,并且为每个角色增加了动画,形成了第一代的图形网络游戏。

因为游戏内容基本可以通过 LPC脚本进行定制,所以MUDOS也成为名副其实的第一款服务端引擎,引擎一次性开发出来,然后制作不同游戏内容。后续国内的《万王之王》等游戏,很多都是跟《UO》一样,直接在 MUDOS上进行二次开发,加入房间的地图还有角色的坐标等要素,该架构一直为国内的第一代 MMORPG提供了稳固的支持,直到 2003年,还有游戏基于 MUDOS开发。虽然后面图形化增加了很多东西,但是这些MMORPG后端的本质还是 MUDOS。随着游戏内容的越来越复杂,架构变得越来越吃不消了,各种负载问题慢慢浮上水面,于是有了我们的第二代游戏服务器。

类型3:第二代游戏服务器 2003

2000年后,网游已经脱离最初的文字MUD,进入全面图形化年代。最先承受不住的其实是很多小文件,用户上下线,频繁的读取写入用户数据,导致负载越来越大。随着在线人数的增加和游戏数据的增加,服务器变得不抗重负。同时早期 EXT磁盘分区比较脆弱,稍微停电,容易发生大面积数据丢失。因此第一步就是拆分文件存储到数据库去。

此时游戏服务端已经脱离陈旧的 MUDOS体系,各个公司在参考 MUDOS结构的情况下,开始自己用 C在重新开发自己的游戏服务端。并且脚本也抛弃了 LPC,采用扩展性更好的 Python或者 Lua来代替。由于主逻辑使用单线程模型,随着游戏内容的增加,传统单服务器的结构进一步成为瓶颈。于是有人开始拆分游戏世界,变为下面的模型:

游戏服务器压力拆分后得意缓解,但是两台游戏服务器同时访问数据库,大量重复访问,大量数据交换,使得数据库成为下一个瓶颈。于是形成了数据库前端代理(DB Proxy),游戏服务器不直接访问数据库而是访问代理,再有代理访问数据库,同时提供内存级别的cache。早年 MySQL4之前没有提供存储过程,这个前端代理一般和 MySQL跑在同一台上,它转化游戏服务器发过来的高级数据操作指令,拆分成具体的数据库操作,一定程度上代替了存储过程:

但是这样的结构并没有持续太长时间,因为玩家切换场景经常要切换连接,中间的状态容易错乱。而且游戏服务器多了以后,相互之间数据交互又会变得比较麻烦,于是人们拆分了网络功能,独立出一个网关服务 Gate(有的地方叫 Session,有的地方叫 LinkSvr之类的,名字不同而已):

把网络功能单独提取出来,让用户统一去连接一个网关服务器,再有网关服务器转发数据到后端游戏服务器。而游戏服务器之间数据交换也统一连接到网管进行交换。这样类型的服务器基本能稳定的为玩家提供游戏服务,一台网关服务1-2万人,后面的游戏服务器每台服务5k-1w,依游戏类型和复杂度不同而已,图中隐藏了很多不重要的服务器,如登录和管理。这是目前应用最广的一个模型,到今天任然很多新项目会才用这样的结构来搭建。

人都是有惯性的,按照先前的经验,似乎把 MUDOS拆分的越开性能越好。于是大家继续想,网关可以拆分呀,基础服务如聊天交易,可以拆分呀,还可以提供web接口,数据库可以拆分呀,于是有了下面的模型:

这样的模型好用么?确实有成功游戏使用类似这样的架构,并且发挥了它的性能优势,比如一些大型 MMORPG。但是有两个挑战:每增加一级服务器,状态机复杂度可能会翻倍,导致研发和找bug的成本上升;并且对开发组挑战比较大,一旦项目时间吃紧,开发人员经验不足,很容易弄挂。

比如我见过某上海一线游戏公司的一个 RPG上来就要上这样的架构,我看了下他们团队成员的经验,问了下他们的上线日期,劝他们用前面稍微简单一点的模型。人家自信得很,认为有成功项目是这么做的,他们也要这么做,自己很想实现一套。于是他们义无反顾的开始编码,项目做了一年多,然后,就没有然后了。

现今在游戏成功率不高的情况下,一开始上一套比较复杂的架构需要考虑投资回报率,比如你的游戏上线半年内 PCU会去到多少?如果一个 APRG游戏,每组服务器5千人都到不了的话,那么选择一套更为贴近实际情况的结构更为经济。即使后面你的项目真的超过5千人朝着1万人目标奔的话,相信那个时候你的项目已经挣大钱了 ,你数着钱加着班去逐步迭代,一次次拆分它,相信心里也是乐开花的。

上面这些类型基本都是从拆分 MUDOS开始,将 MUDOS中的各个部件从单机一步步拆成分布式。虽然今天任然很多新项目在用上面某一种类似的结构,或者自己又做了其他热点模块的拆分。因为他们本质上都是对 MUDOS的分解,故将他们归纳为第二代游戏服务器。

类型4:第三代游戏服务器

2007从魔兽世界开始无缝世界地图已经深入人心,比较以往游戏玩家走个几步还需要切换场景,每次切换就要等待 LOADING个几十秒是一件十分破坏游戏体验的事情。于是对于 2005年以后的大型 MMORPG来说,无缝地图已成为一个标准配置。比较以往按照地图来切割游戏而言,无缝世界并不存在一块地图上面的人有且只由一台服务器处理了:

每台 Node服务器用来管理一块地图区域,由 NodeMaster(NM)来为他们提供总体管理。更高层次的 World则提供大陆级别的管理服务。这里省略若干细节服务器,比如传统数据库前端,登录服务器,日志和监控等,统统用 ADMIN概括。在这样的结构下,玩家从一块区域走向另外一块区域需要简单处理一下:

玩家1完全由节点A控制,玩家3完全由节点B控制。而处在两个节点边缘的2号玩家,则同时由A和B提供服务。玩家2从A移动到B的过程中,会同时向A请求左边的情况,并向B请求右边的情况。但是此时玩家2还是属于A管理。直到玩家2彻底离开AB边界很远,才彻底交由B管理。按照这样的逻辑将世界地图分割为一块一块的区域,交由不同的 Node去管理。

对于一个 Node所负责的区域,地理上没必要连接在一起,比如大陆的四周边缘部分和高山部分的区块人比较少,可以统一交给一个Node去管理,而这些区块在地理上并没有联系在一起的必要性。一个 Node到底管理哪些区块,可以根据游戏实时运行的负载情况,定时维护的时候进行更改 NodeMaster 上面的配置。于是碰到第一个问题是很多 Node服务器需要和玩家进行通信,需要问管理服务器特定UID为多少的玩家到底在哪台 Gate上,以前按场景切割的服务器这个问题不大,问了一次以后就可以缓存起来了,但是现在服务器种类增加不少,玩家又会飘来飘去,按UID查找玩家比较麻烦;另外一方面 GATE需要动态根据坐标计算和哪些 Node通信,导致逻辑越来越厚,于是把:“用户对象”从负责连接管理的 GATE中切割出来势在必行于是有了下面的模型:

网关服务器再次退回到精简的网络转发功能,而用户逻辑则由按照 UID划分的 OBJ服务器来承担,GATE是按照网络接入时的负载来分布,而 OBJ则是按照资源的编号(UID)来分布,这样和一个用户通信直接根据 UID计算出 OBJ服务器编号发送数据即可。而新独立出来的 OBJ则提供了更多高层次的服务:

对象移动:管理具体玩家在不同的 Node所管辖的区域之间的移动,并同需要的 Node进行沟通。

数据广播:Node可以给每个用户设置若干 TAG,然后通知 Object Master 按照TAG广播。

对象消息:通用消息推送,给某个用户发送数据,直接告诉 OBJ,不需要直接和 GATE打交道。

好友聊天:角色之间聊天直接走 OBJ/OBJ MASTER。整个服务器主体分为三层以后,NODE专注场景,OBJ专注玩家对象,

GATE专注网络。这样的模型在无缝场景服务器中得到广泛的应用。但是随着时间的推移,负载问题也越来越明显,做个活动,远来不活跃的区域变得十分活跃,靠每周维护来调整还是比较笨重的,于是有了动态负载均衡。动态负载均衡有两种方法,第一种是按照负载,由 Node Master 定时动态移动修改一下各个 Node的边界,而不同的玩家对象按照先前的方法从一台 Node上迁移到另外一台 Node上:

图11 动态负载均衡

Node Master定时查找地图上的热点区域,计算新的场景切割方式,然后告诉其他服务器开始调整,具体处理方式还是和上面对象跨越边界移动的方法一样。但是上面这种方式实现相对复杂一些,于是人们设计出了更为简单直接的一种新方法:

图12 基于网格的动态负载均衡

于网格的动态负载均衡还是将地图按照标准尺寸均匀切割成静态的网格,每个格子由一个具体的Node负责,但是根据负载情况,能够实时的迁移到其他 Node上。在迁移分为三个阶段:准备,切换,完成。三个状态由Node Master负责维护。准备阶段新的 Node开始同步老 Node上面该网格的数据,完成后告诉NM;NM确认OK后同时通知新旧 Node完成切换。完成切换后,如果 Obj服务器还在和老的 Node进行通信,老的 Node将会对它进行纠正,得到纠正的 OBJ将修正自己的状态,和新的 Node进行通信。

很多无缝动态负载均衡的服务端宣称自己支持无限的人数,但不意味着 MMORPG游戏的人数上限真的可以无限扩充,因为这样的体系会受制于网络带宽和客户端性能。带宽决定了同一个区域最大广播上限,而客户端性能决定了同一个屏幕到底可以绘制多少个角色。

从无缝地图引入了分布式对象模型开始,已经完全脱离 MUDOS体系,成为一种新的服务端模型。又由于动态负载均衡的引入,让无缝服务器如虎添翼,容纳着超过上一代游戏服务器数倍的人数上限,并提供了更好的游戏体验,我们称其为第三代游戏服务端架构。网游以大型多人角色扮演为开端,RPG网游在相当长的时间里一度占据90%以上,使得基于 MMORPG的服务端架构得到了蓬勃的发展,然而随着玩家对RPG的疲惫,各种非MMORPG游戏如雨后春笋般的出现在人们眼前,受到市场的欢迎。

类型5:战网游戏服务器

经典战网服务端和 RPG游戏有两个区别:RPG是分区分服的,北京区的用户和广州区的用户老死不相往来。而战网,虽然每局游戏一般都是 8人以内,但全国只有一套服务器,所有的玩家都可以在一起游戏,而玩家和玩家之使用 P2P的方式连接在一起,组成一局游戏:

玩家通过 Match Making 服务器使用:创建、加入、自动匹配、邀请 等方式组成一局游戏。服务器会选择一个人做 Host,其他人 P2P连接到做主的玩家上来。STUN是帮助玩家之间建立 P2P的牵引服务器,而由于 P2P联通情况大概只有 75%,实在联不通的玩家会通过 Forward进行转发。

大量的连接对战,体育竞技游戏采用类似的结构。P2P有网状模型(所有玩家互相连接),和星状模型(所有玩家连接一个主玩家)。复杂的游戏状态在网状模型下难以形成一致,因此星状P2P模型经受住了历史的考验。除去游戏数据,支持语音的战网系统也会将所有人的语音数据发送到做主的那个玩家机器上,通过混音去重再编码的方式返回给所有用户。

战网类游戏,以竞技、体育、动作等类型的游戏为主,较慢节奏的 RPG(包括ARPG)有本质上的区别,而激烈的游戏过程必然带来到较 RPG复杂的多的同步策略,这样的同步机制往往带来的是很多游戏结果由客户端直接计算得出,那在到处都是破解的今天,如何保证游戏结果的公正呢?

主要方法就是投票法,所有客户端都会独立计算,然后传递给服务器。如果结果相同就更新记录,如果结果不一致,会采取类似投票的方式确定最终结果。同时记录本剧游戏的所有输入,在可能的情况下,找另外闲散的游戏客户端验算整局游戏是否为该结果。并且记录经常有作弊嫌疑的用户,供运营人员封号时参考。

类型7:休闲游戏服务器

休闲游戏同战网服务器类似,都是全区架构,不同的是有房间服务器,还有具体的游戏服务器,游戏主体不再以玩家 P2P进行,而是连接到专门的游戏服务器处理:

和战网一样的全区架构,用户数据不能象分区的 RPG那样一次性load到内存,然后在内存里面直接修改。全区架构下,为了应对一个用户同时玩几个游戏,用户数据需要区分基本数据和不同的游戏数据,而游戏数据又需要区分积分数据、和文档数据。胜平负之类的积分可以直接提交增量修改,而更为普遍的文档类数据则需要提供读写令牌,写令牌只有一块,读令牌有很多块。同帐号同一个游戏同时在两台电脑上玩时,最先开始的那个游戏获得写令牌,可以操作任意的用户数据。而后开始的那个游戏除了可以提交胜平负积分的增量改变外,对用户数据采用只读的方式,保证游戏能运行下去,但是会提示用户,游戏数据锁定。

类型8:现代动作类网游

从早期的韩国动作游戏开始,传统的战网动作类游戏和 RPG游戏开始尝试融合。单纯的动作游戏玩家容易疲倦,留存也没有 RPG那么高;而单纯 RPG战斗却又慢节奏的乏味,无法满足很多玩家激烈对抗的期望,于是二者开始融合成为新一代的:动作 + 城镇 模式。玩家在城镇中聚集,然后以开副本的方式几个人出去以动作游戏的玩法来完成各种 RPG任务。本质就是一套 RPG服务端+副本服务端。由于每次副本时人物可以控制在8人以内,因此可以获得更为实时的游戏体验,让玩家玩的更加爽快。

说了那么多的游戏服务器类型,其实也差不多了,剩下的类型大家拼凑一下其实也就是这个样子而已。

Java web 服务端消息推送用极光推送就不错,效率很高。极光推送搭建起一个高度稳定、可扩展的云端架构,极大地帮助移动应用开发者节约开发和维护的成本,轻松实现毫秒级的精准推送。

极光推送服务是一款免费的第三方推送消息的平台。极光推送从整体来说还不错,具有以下优势:

1、开放注册,免费向所有的开发者开放使用。

2、JPush 是经过考验的大规模 App 推送平台,每天推送消息量级为数百亿条。 开发者集成 SDK 后,可以通过调用 API 推送消息。同时,JPush 提供可视化的 web 端控制台发送通知,统计分析推送效果。

3、开发者可以轻松地通过极光发送各个移动平台的系统通知,还可以在控制台编辑多种富文本展示模板; 极光还提供自定义消息的透传,客户端接到消息内容后根据自己的逻辑自由处理。

4、724小时专人专线服务:专属安全团队支持,24 小时随时响应,免除后顾之忧。

所以从以上来看,极光是一个不错的选择,深圳市和讯华谷信息技术有限公司(极光 Aurora Mobile,纳斯达克股票代码:JG)成立于2011年,是中国领先的开发者服务提供商,专注于为开发者提供稳定高效的消息推送、一键认证以及流量变现等服务,助力开发者的运营、增长与变现。

此文是根据周洋在高可用架构群中的分享内容整理而成,转发请注明出处。

周洋,360手机助手技术经理及架构师,负责360长连接消息系统,360手机助手架构的开发与维护。

不知道咱们群名什么时候改为“Python高可用架构群”了,所以不得不说,很荣幸能在接下来的一个小时里在Python群里讨论golang

360消息系统介绍

360消息系统更确切的说是长连接push系统,目前服务于360内部多个产品,开发平台数千款app,也支持部分聊天业务场景,单通道多app复用,支持上行数据,提供接入方不同粒度的上行数据和用户状态回调服务。

目前整个系统按不同业务分成9个功能完整的集群,部署在多个idc上(每个集群覆盖不同的idc),实时在线数亿量级。通常情况下,pc,手机,甚至是智能硬件上的360产品的push消息,基本上是从我们系统发出的。

关于push系统对比与性能指标的讨论

很多同行比较关心go语言在实现push系统上的性能问题,单机性能究竟如何,能否和其他语言实现的类似系统做对比么?甚至问如果是创业,第三方云推送平台,推荐哪个

其实各大厂都有类似的push系统,市场上也有类似功能的云服务。包括我们公司早期也有erlang,nodejs实现的类似系统,也一度被公司要求做类似的对比测试。我感觉在讨论对比数据的时候,很难保证大家环境和需求的统一,我只能说下我这里的体会,数据是有的,但这个数据前面估计会有很多定语~

第一个重要指标:单机的连接数指标

做过长连接的同行,应该有体会,如果在稳定连接情况下,连接数这个指标,在没有网络吞吐情况下对比,其实意义往往不大,维持连接消耗cpu资源很小,每条连接tcp协议栈会占约4k的内存开销,系统参数调整后,我们单机测试数据,最高也是可以达到单实例300w长连接。但做更高的测试,我个人感觉意义不大。

因为实际网络环境下,单实例300w长连接,从理论上算压力就很大:实际弱网络环境下,移动客户端的断线率很高,假设每秒有1000分之一的用户断线重连。300w长连接,每秒新建连接达到3w,这同时连入的3w用户,要进行注册,加载离线存储等对内rpc调用,另外300w长连接的用户心跳需要维持,假设心跳300s一次,心跳包每秒需要1w tps。单播和多播数据的转发,广播数据的转发,本身也要响应内部的rpc调用,300w长连接情况下,gc带来的压力,内部接口的响应延迟能否稳定保障。这些集中在一个实例中,可用性是一个挑战。所以线上单实例不会hold很高的长连接,实际情况也要根据接入客户端网络状况来决定。

第二个重要指标:消息系统的内存使用量指标

这一点上,使用go语言情况下,由于协程的原因,会有一部分额外开销。但是要做两个推送系统的对比,也有些需要确定问题。比如系统从设计上是否需要全双工(即读写是否需要同时进行)如果半双工,理论上对一个用户的连接只需要使用一个协程即可(这种情况下,对用户的断线检测可能会有延时),如果是全双工,那读/写各一个协程。两种场景内存开销是有区别的。

另外测试数据的大小往往决定我们对连接上设置的读写buffer是多大,是全局复用的,还是每个连接上独享的,还是动态申请的。另外是否全双工也决定buffer怎么开。不同的策略,可能在不同情况的测试中表现不一样。

第三个重要指标:每秒消息下发量

这一点上,也要看我们对消息到达的QoS级别(回复ack策略区别),另外看架构策略,每种策略有其更适用的场景,是纯粹推?还是推拉结合?甚至是否开启了消息日志?日志库的实现机制、以及缓冲开多大?flush策略……这些都影响整个系统的吞吐量。

另外为了HA,增加了内部通信成本,为了避免一些小概率事件,提供闪断补偿策略,这些都要考虑进去。如果所有的都去掉,那就是比较基础库的性能了。

所以我只能给出大概数据,24核,64G的服务器上,在QoS为message at least,纯粹推,消息体256B~1kB情况下,单个实例100w实际用户(200w+)协程,峰值可以达到2~5w的QPS内存可以稳定在25G左右,gc时间在200~800ms左右(还有优化空间)。

我们正常线上单实例用户控制在80w以内,单机最多两个实例。事实上,整个系统在推送的需求上,对高峰的输出不是提速,往往是进行限速,以防push系统瞬时的高吞吐量,转化成对接入方业务服务器的ddos攻击所以对于性能上,我感觉大家可以放心使用,至少在我们这个量级上,经受过考验,go15到来后,确实有之前投资又增值了的感觉。

消息系统架构介绍

下面是对消息系统的大概介绍,之前一些同学可能在gopher china上可以看到分享,这里简单讲解下架构和各个组件功能,额外补充一些当时遗漏的信息:

架构图如下,所有的service都 written by golang

几个大概重要组件介绍如下:

dispatcher service根据客户端请求信息,将应网络和区域的长连接服务器的,一组IP传送给客户端。客户端根据返回的IP,建立长连接,连接Room service

room Service,长连接网关,hold用户连接,并将用户注册进register service,本身也做一些接入安全策略、白名单、IP限制等。

register service是我们全局session存储组件,存储和索引用户的相关信息,以供获取和查询。

coordinator service用来转发用户的上行数据,包括接入方订阅的用户状态信息的回调,另外做需要协调各个组件的异步操作,比如kick用户操作,需要从register拿出其他用户做异步操作

saver service是存储访问层,承担了对redis和mysql的操作,另外也提供部分业务逻辑相关的内存缓存,比如广播信息的加载可以在saver中进行缓存。另外一些策略,比如客户端sdk由于被恶意或者意外修改,每次加载了消息,不回复ack,那服务端就不会删除消息,消息就会被反复加载,形成死循环,可以通过在saver中做策略和判断。(客户端总是不可信的)。

center service提供给接入方的内部api服务器,比如单播或者广播接口,状态查询接口等一系列api,包括运维和管理的api。

举两个常见例子,了解工作机制:比如发一条单播给一个用户,center先请求Register获取这个用户之前注册的连接通道标识、room实例地址,通过room service下发给长连接 Center Service比较重的工作如全网广播,需要把所有的任务分解成一系列的子任务,分发给所有center,然后在所有的子任务里,分别获取在线和离线的所有用户,再批量推到Room Service。通常整个集群在那一瞬间压力很大。

deployd/agent service用于部署管理各个进程,收集各组件的状态和信息,zookeeper和keeper用于整个系统的配置文件管理和简单调度

关于推送的服务端架构

常见的推送模型有长轮训拉取,服务端直接推送(360消息系统目前主要是这种),推拉结合(推送只发通知,推送后根据通知去拉取消息)

拉取的方式不说了,现在并不常用了,早期很多是nginx+lua+redis,长轮训,主要问题是开销比较大,时效性也不好,能做的优化策略不多。

直接推送的系统,目前就是360消息系统这种,消息类型是消耗型的,并且对于同一个用户并不允许重复消耗,如果需要多终端重复消耗,需要抽象成不同用户。

推的好处是实时性好,开销小,直接将消息下发给客户端,不需要客户端走从接入层到存储层主动拉取

但纯推送模型,有个很大问题,由于系统是异步的,他的时序性无法精确保证。这对于push需求来说是够用的,但如果复用推送系统做im类型通信,可能并不合适。

对于严格要求时序性,消息可以重复消耗的系统,目前也都是走推拉结合的模型,就是只使用我们的推送系统发通知,并附带id等给客户端做拉取的判断策略,客户端根据推送的key,主动从业务服务器拉取消息。并且当主从同步延迟的时候,跟进推送的key做延迟拉取策略。同时也可以通过消息本身的QoS,做纯粹的推送策略,比如一些“正在打字的”低优先级消息,不需要主动拉取了,通过推送直接消耗掉。

哪些因素决定推送系统的效果?

首先是sdk的完善程度,sdk策略和细节完善度,往往决定了弱网络环境下最终推送质量

SDK选路策略,最基本的一些策略如下:有些开源服务可能会针对用户hash一个该接入区域的固定ip,实际上在国内环境下不可行,最好分配器(dispatcher)是返回散列的一组,而且端口也要参开,必要时候,客户端告知是retry多组都连不上,返回不同idc的服务器。因为我们会经常检测到一些case,同一地区的不同用户,可能对同一idc内的不同ip连通性都不一样,也出现过同一ip不同端口连通性不同,所以用户的选路策略一定要灵活,策略要足够完善另外在选路过程中,客户端要对不同网络情况下的长连接ip做缓存,当网络环境切换时候(wifi、2G、3G),重新请求分配器,缓存不同网络环境的长连接ip。

客户端对于数据心跳和读写超时设置,完善断线检测重连机制

针对不同网络环境,或者客户端本身消息的活跃程度,心跳要自适应的进行调整并与服务端协商,来保证链路的连通性。并且在弱网络环境下,除了网络切换(wifi切3G)或者读写出错情况,什么时候重新建立链路也是一个问题。客户端发出的ping包,不同网络下,多久没有得到响应,认为网络出现问题,重新建立链路需要有个权衡。另外对于不同网络环境下,读取不同的消息长度,也要有不同的容忍时间,不能一刀切。好的心跳和读写超时设置,可以让客户端最快的检测到网络问题,重新建立链路,同时在网络抖动情况下也能完成大数据传输。

结合服务端做策略

另外系统可能结合服务端做一些特殊的策略,比如我们在选路时候,我们会将同一个用户尽量映射到同一个room service实例上。断线时,客户端尽量对上次连接成功的地址进行重试。主要是方便服务端做闪断情况下策略,会暂存用户闪断时实例上的信息,重新连入的 时候,做单实例内的迁移,减少延时与加载开销

客户端保活策略

很多创业公司愿意重新搭建一套push系统,确实不难实现,其实在协议完备情况下(最简单就是客户端不回ack不清数据),服务端会保证消息是不丢的。但问题是为什么在消息有效期内,到达率上不去?往往因为自己app的push service存活能力不高。选用云平台或者大厂的,往往sdk会做一些保活策略,比如和其他app共生,互相唤醒,这也是云平台的push service更有保障原因。我相信很多云平台旗下的sdk,多个使用同样sdk的app,为了实现服务存活,是可以互相唤醒和保证活跃的。另外现在push sdk本身是单连接,多app复用的,这为sdk实现,增加了新的挑战。

综上,对我来说,选择推送平台,优先会考虑客户端sdk的完善程度。对于服务端,选择条件稍微简单,要求部署接入点(IDC)越要多,配合精细的选路策略,效果越有保证,至于想知道哪些云服务有多少点,这个群里来自各地的小伙伴们,可以合伙测测。

go语言开发问题与解决方案

下面讲下,go开发过程中遇到挑战和优化策略,给大家看下当年的一张图,在第一版优化方案上线前一天截图~

可以看到,内存最高占用69G,GC时间单实例最高时候高达3~6s这种情况下,试想一次悲剧的请求,经过了几个正在执行gc的组件,后果必然是超时 gc照成的接入方重试,又加重了系统的负担。遇到这种情况当时整个系统最差情况每隔2,3天就需要重启一次~

当时出现问题,现在总结起来,大概以下几点

1散落在协程里的I/O,Buffer和对象不复用。

当时(12年)由于对go的gc效率理解有限,比较奔放,程序里大量short live的协程,对内通信的很多io操作,由于不想阻塞主循环逻辑或者需要及时响应的逻辑,通过单独go协程来实现异步。这回会gc带来很多负担。

针对这个问题,应尽量控制协程创建,对于长连接这种应用,本身已经有几百万并发协程情况下,很多情况没必要在各个并发协程内部做异步io,因为程序的并行度是有限,理论上做协程内做阻塞操作是没问题。

如果有些需要异步执行,比如如果不异步执行,影响对用户心跳或者等待response无法响应,最好通过一个任务池,和一组常驻协程,来消耗,处理结果,通过channel再传回调用方。使用任务池还有额外的好处,可以对请求进行打包处理,提高吞吐量,并且可以加入控量策略

2网络环境不好引起激增

go协程相比较以往高并发程序,如果做不好流控,会引起协程数量激增。早期的时候也会发现,时不时有部分主机内存会远远大于其他服务器,但发现时候,所有主要profiling参数都正常了。

后来发现,通信较多系统中,网络抖动阻塞是不可免的(即使是内网),对外不停accept接受新请求,但执行过程中,由于对内通信阻塞,大量协程被 创建,业务协程等待通信结果没有释放,往往瞬时会迎来协程暴涨。但这些内存在系统稳定后,virt和res都并没能彻底释放,下降后,维持高位。

处理这种情况,需要增加一些流控策略,流控策略可以选择在rpc库来做,或者上面说的任务池来做,其实我感觉放在任务池里做更合理些,毕竟rpc通信库可以做读写数据的限流,但它并不清楚具体的限流策略,到底是重试还是日志还是缓存到指定队列。任务池本身就是业务逻辑相关的,它清楚针对不同的接口需要的流控限制策略。

3低效和开销大的rpc框架

早期rpc通信框架比较简单,对内通信时候使用的也是短连接。这本来短连接开销和性能瓶颈超出我们预期,短连接io效率是低一些,但端口资源够,本身吞吐可以满足需要,用是没问题的,很多分层的系统,也有http短连接对内进行请求的

但早期go版本,这样写程序,在一定量级情况,是支撑不住的。短连接大量临时对象和临时buffer创建,在本已经百万协程的程序中,是无法承受的。所以后续我们对我们的rpc框架作了两次调整。

第二版的rpc框架,使用了连接池,通过长连接对内进行通信(复用的资源包括client和server的:编解码Buffer、Request/response),大大改善了性能。

但这种在一次request和response还是占用连接的,如果网络状况ok情况下,这不是问题,足够满足需要了,但试想一个room实例要与后面的数百个的register,coordinator,saver,center,keeper实例进行通信,需要建立大量的常驻连接,每个目标机几十个连接,也有数千个连接被占用。

非持续抖动时候(持续逗开多少无解),或者有延迟较高的请求时候,如果针对目标ip连接开少了,会有瞬时大量请求阻塞,连接无法得到充分利用。第三版增加了Pipeline操作,Pipeline会带来一些额外的开销,利用tcp的全双特性,以尽量少的连接完成对各个服务集群的rpc调用。

4Gc时间过长

Go的Gc仍旧在持续改善中,大量对象和buffer创建,仍旧会给gc带来很大负担,尤其一个占用了25G左右的程序。之前go team的大咖邮件也告知我们,未来会让使用协程的成本更低,理论上不需要在应用层做更多的策略来缓解gc

改善方式,一种是多实例的拆分,如果公司没有端口限制,可以很快部署大量实例,减少gc时长,最直接方法。不过对于360来说,外网通常只能使用80和433。因此常规上只能开启两个实例。当然很多人给我建议能否使用SO_REUSEPORT,不过我们内核版本确实比较低,并没有实践过。

另外能否模仿nginx,fork多个进程监控同样端口,至少我们目前没有这样做,主要对于我们目前进程管理上,还是独立的运行的,对外监听不同端口程序,还有配套的内部通信和管理端口,实例管理和升级上要做调整。

解决gc的另两个手段,是内存池和对象池,不过最好做仔细评估和测试,内存池、对象池使用,也需要对于代码可读性与整体效率进行权衡。

这种程序一定情况下会降低并行度,因为用池内资源一定要加互斥锁或者原子操作做CAS,通常原子操作实测要更快一些。CAS可以理解为可操作的更细行为粒度的锁(可以做更多CAS策略,放弃运行,防止忙等)。这种方式带来的问题是,程序的可读性会越来越像C语言,每次要malloc,各地方用完后要free,对于对象池free之前要reset,我曾经在应用层尝试做了一个分层次结构的“无锁队列”

上图左边的数组实际上是一个列表,这个列表按大小将内存分块,然后使用atomic操作进行CAS。但实际要看测试数据了,池技术可以明显减少临时对象和内存的申请和释放,gc时间会减少,但加锁带来的并行度的降低,是否能给一段时间内的整体吞吐量带来提升,要做测试和权衡…

在我们消息系统,实际上后续去除了部分这种黑科技,试想在百万个协程里面做自旋操作申请复用的buffer和对象,开销会很大,尤其在协程对线程多对多模型情况下,更依赖于golang本身调度策略,除非我对池增加更多的策略处理,减少忙等,感觉是在把runtime做的事情,在应用层非常不优雅的实现。普遍使用开销理论就大于收益。

但对于rpc库或者codec库,任务池内部,这些开定量协程,集中处理数据的区域,可以尝试改造~

对于有些固定对象复用,比如固定的心跳包什么的,可以考虑使用全局一些对象,进行复用,针对应用层数据,具体设计对象池,在部分环节去复用,可能比这种无差别的设计一个通用池更能进行效果评估

消息系统的运维及测试

下面介绍消息系统的架构迭代和一些迭代经验,由于之前在其他地方有过分享,后面的会给出相关链接,下面实际做个简单介绍,感兴趣可以去链接里面看

架构迭代~根据业务和集群的拆分,能解决部分灰度部署上线测试,减少点对点通信和广播通信不同产品的相互影响,针对特定的功能做独立的优化

消息系统架构和集群拆分,最基本的是拆分多实例,其次是按照业务类型对资源占用情况分类,按用户接入网络和对idc布点要求分类(目前没有条件,所有的产品都部署到全部idc)

系统的测试go语言在并发测试上有独特优势。

对于压力测试,目前主要针对指定的服务器,选定线上空闲的服务器做长连接压测。然后结合可视化,分析压测过程中的系统状态。但压测早期用的比较多,但实现的统计报表功能和我理想有一定差距。我觉得最近出的golang开源产品都符合这种场景,go写网络并发程序给大家带来的便利,让大家把以往为了降低复杂度,拆解或者分层协作的组件,又组合在了一起。

Q&A

Q1:协议栈大小,超时时间定制原则?

移动网络下超时时间按产品需求通常2g,3G情况下是5分钟,wifi情况下5~8分钟。但对于个别场景,要求响应非常迅速的场景,如果连接idle超过1分钟,都会有ping,pong,来校验是否断线检测,尽快做到重新连接。

Q2:消息是否持久化?

消息持久化,通常是先存后发,存储用的redis,但落地用的mysql。mysql只做故障恢复使用。

Q3:消息风暴怎么解决的?

如果是发送情况下,普通产品是不需要限速的,对于较大产品是有发送队列做控速度,按人数,按秒进行控速度发放,发送成功再发送下一条。

Q4:golang的工具链支持怎么样?我自己写过一些小程序千把行之内,确实很不错,但不知道代码量上去之后,配套的debug工具和profiling工具如何,我看上边有分享说golang自带的profiling工具还不错,那debug呢怎么样呢,官方一直没有出debug工具,gdb支持也不完善,不知你们用的什么?

是这样的,我们正常就是println,我感觉基本上可以定位我所有问题,但也不排除由于并行性通过println无法复现的问题,目前来看只能靠经验了。只要常见并发尝试,经过分析是可以找到的。go很快会推出调试工具的~

Q5:协议栈是基于tcp吗?

是否有协议拓展功能?协议栈是tcp,整个系统tcp长连接,没有考虑扩展其功能~如果有好的经验,可以分享~

Q6:问个问题,这个系统是接收上行数据的吧,系统接收上行数据后是转发给相应系统做处理么,是怎么转发呢,如果需要给客户端返回调用结果又是怎么处理呢?

系统上行数据是根据协议头进行转发,协议头里面标记了产品和转发类型,在coordinator里面跟进产品和转发类型,回调用户,如果用户需要阻塞等待回复才能后续操作,那通过再发送消息,路由回用户。因为整个系统是全异步的。

Q7:问个pushsdk的问题。pushsdk的单连接,多app复用方式,这样的情况下以下几个问题是如何解决的:1)系统流量统计会把所有流量都算到启动连接的应用吧?而启动应用的连接是不固定的吧?2)同一个pushsdk在不同的应用中的版本号可能不一样,这样暴露出来的接口可能有版本问题,如果用单连接模式怎么解决?

流量只能算在启动的app上了,但一般这种安装率很高的app承担可能性大,常用app本身被检测和杀死可能性较少,另外消息下发量是有严格控制 的。整体上用户还是省电和省流量的。我们pushsdk尽量向上兼容,出于这个目的,push sdk本身做的工作非常有限,抽象出来一些常见的功能,纯推的系统,客户端策略目前做的很少,也有这个原因。

Q8:生产系统的profiling是一直打开的么?

不是一直打开,每个集群都有采样,但需要开启哪个可以后台控制。这个profling是通过接口调用。

Q9:面前系统中的消息消费者可不可以分组?类似于Kafka。

客户端可以订阅不同产品的消息,接受不同的分组。接入的时候进行bind或者unbind操作

Q10:为什么放弃erlang,而选择go,有什么特别原因吗?我们现在用的erlang?

erlang没有问题,原因是我们上线后,其他团队才做出来,经过qa一个部门对比测试,在没有显著性能提升下,选择继续使用go版本的push,作为公司基础服务。

Q11:流控问题有排查过网卡配置导致的idle问题吗?

流控是业务级别的流控,我们上线前对于内网的极限通信量做了测试,后续将请求在rpc库内,控制在小于内部通信开销的上限以下在到达上限前作流控。

Q12:服务的协调调度为什么选择zk有考虑过raft实现吗?golang的raft实现很多啊,比如Consul和ectd之类的。

3年前,还没有后两者或者后两者没听过应该。zk当时公司内部成熟方案,不过目前来看,我们不准备用zk作结合系统的定制开发,准备用自己写的keeper代替zk,完成配置文件自动转数据结构,数据结构自动同步指定进程,同时里面可以完成很多自定义的发现和控制策略,客户端包含keeper的sdk就可以实现以上的所有监控数据,profling数据收集,配置文件更新,启动关闭等回调。完全抽象成语keeper通信sdk,keeper之间考虑用raft。

Q13:负载策略是否同时在服务侧与CLIENT侧同时做的 (DISPATCHER 会返回一组IP)?另外,ROOM SERVER/REGISTER SERVER连接状态的一致性|可用性如何保证 服务侧保活有无特别关注的地方 安全性方面是基于TLS再加上应用层加密

会在server端做,比如重启操作前,会下发指令类型消息,让客户端进行主动行为。部分消息使用了加密策略,自定义的rsa+des,另外满足我们安全公司的需要,也定制开发很多安全加密策略。一致性是通过冷备解决的,早期考虑双写,但实时状态双写同步代价太高而且容易有脏数据,比如register挂了,调用所有room,通过重新刷入指定register来解决。

Q14:这个keeper有开源打算吗?

还在写,如果没耦合我们系统太多功能,一定会开源的,主要这意味着,我们所有的bind在sdk的库也需要开源~

Q15:比较好奇lisence是哪个如果开源?

DABAN RP主题是一个优秀的主题,极致后台体验,无插件,集成会员系统
网站模板库 » 如何搭建一套完整的视频直播系统

0条评论

发表评论

提供最优质的资源集合

立即查看 了解详情