erlang 服务器的一些问题(找来的)

添置几个便宜的Linux系统到我的服务器组,OpenPoker可以要多大规模有多大规模。组合一打1U服务器系统可以轻松胜任五十万甚至一百万玩家同时在线。当然不仅仅是纸牌游戏,对于其他多人RPG网游(MMORPG)也是一样的。

我可以指派几个服务器做网关节点,另外几个做数据库节点访问存储介质上的数据,然后剩下的一些做游戏服务器。我还可以限制单台服务器最高接纳五千万家同时在线,所以任何一台当机,最多5千个玩家受影响。

另外要指出的是任何一台游戏服务器当机都不会有数据损毁因为所有Mnesia的数据访问操作都是由多个游戏,Mnesia节点实时备份的。

考虑到某些潜在错误,游戏客户端需要做一些辅助工作让玩家顺滑的重新连接到OpenPoker服务器集群。每当客户端发现网络错误,就会尝试连接网关节点,通过接力网络包得到一个新的游戏服务节点地址然后重新连接。这里需要点技巧因为不同的情况要不同对待:

OpenPoker划分如下需要重新连接的情况:

  1. 游戏服务器当机
  2. 客户端当机或者网络延迟超时
  3. 玩家换另外一个网络连接在线
  4. 玩家在游戏中切换另一个网络连接

最常见的就是客户端因为网络错误而断开连接。最不常见但是还是有可能的是同一个客户端在游戏中的时候从另一个电脑尝试连接。

每个OpenPoker游戏缓存发送给玩家的数据包,每次客户端重新连接都会收到自游戏开始的所有数据包然后再开始正常接受。OpenPoker使用TCP连接所以不用考虑数据包的发送顺序——所有数据包保证是按顺序收到的。

每个客户端连接由两个OpenPoker进程组成:套接字进程还有玩家进程。还有一个受限制的访客进程被使用直至玩家成功登陆,访客不能加入游戏。套接字进程虽网络中断而停止,但是玩家进程仍然保持活动。

玩家进程发送游戏数据包的时候可以侦测到已经中断的套接字进程,此时会进入自动运行状态或者暂停状态。登陆代码会在重新连接的时候同时参考套接字进程和玩家进程。用来侦测的代码如下:

login({atomic, [Player]}, [_Nick, Pass|_] = Args)      when is_record(Player, player) -> 	    Player1 = Player#player { 		          socket = fix_pid(Player#player.socket), 		          pid = fix_pid(Player#player.pid) 	                         }, 	      Condition = check_player(Player1, [Pass],  			                 [ 			                    fun is_account_disabled/2, 			                    fun is_bad_password/2, 			                    fun is_player_busy/2, 			                    fun is_player_online/2, 			                    fun is_client_down/2, 			                    fun is_offline/2 			                  ]), 	...	 

其中的各个条件是这么写的:

is_player_busy(Player, _) -> 	  {Online, _} = is_player_online(Player, []), 	  Playing = Player#player.game /= none, 	  {Online and Playing, player_busy}.

is_player_online(Player, _) -> 	  SocketAlive = Player#player.socket /= none, 	  PlayerAlive = Player#player.pid /= none, 	  {SocketAlive and PlayerAlive, player_online}.

is_client_down(Player, _) -> 	  SocketDown = Player#player.socket == none, 	  PlayerAlive = Player#player.pid /= none, 	  {SocketDown and PlayerAlive, client_down}.

is_offline(Player, _) -> 	  SocketDown = Player#player.socket == none, 	  PlayerDown = Player#player.pid == none, 	  {SocketDown and PlayerDown, player_offline}. 

要注意login函数首先要做的是修复已失败的进程ID。这样简化了处理过程,代码如下:

fix_pid(Pid)   when is_pid(Pid) -> 	  case util:is_process_alive(Pid) of 	      true -> 	            Pid; 	      _ -> 	            none 	  end;

fix_pid(Pid) -> 	  Pid.

和:

-module(util).-export([is_process_alive/1]).is_process_alive(Pid)      when is_pid(Pid) -> 	    rpc:call(node(Pid), erlang, is_process_alive, [Pid]).

Erlang里的进程ID包含运行进程的节点的Id. is_pid(Pid)返回参数是否为一个进程Id但是无法知道进程是否已中断。Erlang的内建函数erlang:is_process_alive(Pid)可以做到。is_process_alive也可以用来检查远程节点,用起来是没区别的。

更方便的是,我们可以用Erlang RPC功能,联合node(pid)来调用远程节点的is_process_alive()。用起来和访问本地节点一样,所以上面的代码实际上也是全局分布式进程检查。

最后剩的工作就是处理登陆的各种情况了。最直接的情况是玩家处于离线状态然后启动了一个玩家进程,连接玩家进程到套接字进程,然后更新玩家数据。

login(Player, player_offline, [Nick, _, Socket]) -> 	  {ok, Pid} = player:start(Nick), 	  OID = gen_server:call(Pid, ‘ID‘), 	  gen_server:cast(Pid, {‘SOCKET‘, Socket}), 	  Player1 = Player#player { 		                  oid = OID, 		                  pid = Pid, 		                  socket = Socket 	                       }, 	  {Player1, {ok, Pid}}. 如果登陆信息不正确就返回错误然后记录登陆尝试次数。如果尝试超过一定次数,可以用如下代码关闭账户:
login(Player, bad_password, _) -> 	  N = Player#player.login_errors + 1, 	  {atomic, MaxLoginErrors} =  	db:get(cluster_config, 0, max_login_errors), 	  if 	    N > MaxLoginErrors -> 	        Player1 = Player#player { 			                    disabled = true 		                         }, 	        {Player1, {error, ?ERR_ACCOUNT_DISABLED}};    true -> 	          Player1 = Player#player { 			                      login_errors = N 		                            }, 	          {Player1, {error, ?ERR_BAD_LOGIN}} 	  end;

login(Player, account_disabled, _) -> 	  {Player, {error, ?ERR_ACCOUNT_DISABLED}};

注销用户时,先用ObjectID找到玩家进程ID,然后停止玩家进程并更新数据库记录:

logout(OID) -> 	case db:find(player, OID) of 	  {atomic, [Player]} -> 	        player:stop(Player#player.pid), 	        {atomic, ok} = db:set(player, OID,  				      [{pid, none}, 				       {socket, none}]); 	  _ -> 	        oops 	  end.

如果注销不正常,可以分别针对各种重新连接条件处理。如果玩家在线却处于闲置状态,比如说停在大厅或者正旁观一个游戏(可能在喝着瓶百威,喂喂!,然后尝试从另一台电脑连接,那么程序先将其登出然后重新将其登入,就像从离线状态下登入一样:

login(Player, player_online, Args) -> 	  logout(Player#player.oid), 	  login(Player, player_offline, Args); 

如果玩家正在闲置而客户端断开连接了,那么只需要在记录里替换他的套接字进程地址然后通知玩家进程新的套接字:

login(Player, client_down, [_, _, Socket]) -> 	  gen_server:cast(Player#player.pid, {‘SOCKET‘, Socket}), 	P  layer1 = Player#player { 		                socket = Socket 	                       }, 	  {Player1, {ok, Player#player.pid}};

如果玩家在游戏中,那么除了运行上面那段以外,通知游戏重新发送过往事件。

login(Player, player_busy, Args) -> 	  Temp = login(Player, client_down, Args), 	  cardgame:cast(Player#player.game,  		    {‘RESEND UPDATES‘, Player#player.pid}), 	  Temp;

总而言之,包含着实时冗余数据库,智能重连的客户端,还有一些精巧的登陆代码的这一套组合方案可以提供高度的容错性,而且对于玩家来说,是透明的。

负载平衡

我可以用想多少就多少的服务器节点组建我的OpenPoker集群。也可以自由调配,比如说每个服务器节点5000个玩家,然后在整个集群中平摊工作负载。我可以在任何时候添加新的服务器节点,新节点自己会自动配置并开始接受新玩家。

网关节点控制着向OpenPoker集群里的所有活动节点平衡负载。网关节点的作用就是随机选择一个服务器节点,查询已连接玩家数,主机地址,端口等等。只要网关节点找到一个游戏服务器未达到负载最大值,它就把服务器的地址信息传递给客户端然后关闭连接。

很明显网关节点工作量不大,而且指向这个节点的连接都是瞬时的。你可以随便用个便宜机器做你的网关节点。

节点一般应该是一对一对的,这样如果一个失败,另一个可以马上替补。你可以采用Round-robin DNS来配置多个网关节点。

那么网关如何找到游戏服务器呢?

OpenPoker采用Erlang的分布式进程组(Distributed Named Process Groups来分组游戏服务器。所有节点都可以访问组列表,这一过程是自动的。新的游戏服务器只需加入服务器组。某个节点当机自动从组列表里剔除。

查找服务玩家最少的服务器的代码如下:

find_server(MaxPlayers) -> 	case pg2:get_closest_pid(?GAME_SERVERS) of  	  Pid when is_pid(Pid) -> 	        {Time, {Host, Port}} = timer:tc(gen_server, call, [Pid, ‘WHERE‘]), 	        Count = gen_server:call(Pid, ‘USER COUNT‘), 	        if 		      Count < MaxPlayers -> 		            io:format("~sw: ~w players~n", [Host, Port, Count]), 		            {Host, Port}; 		    true -> 		          io:format("~sw is full...~n", [Host, Port]), 		          find_server(MaxPlayers) 	        end; 	  Any -> 	        Any 	end.

pg2:get_closest_pid()返回一个随机的游戏服务器进程ID(网关节点上不运行任何游戏服务器)。然后向返回的服务器查询地址端口以及目前连接的玩家数。只要未足最大负载额就把地址返回给调用进程,否则继续查找。

多功能插座中间件

OpenPoker是一个开源软件,我最近也正在将其投向许多棋牌类运营商。所有商家都存在容错性和可伸缩性的问题,即使有些已经经过了长年的开发维护。有些已经重写了代码,而有些才刚刚起步。所有商家都在Java体系上大笔投入,所以他们不愿意换到Erlang也是可以理解的。

但是,对我来说这是一种商机。我越是深入研究,越发现Erlang更适合提供一个简单直接却又高效可靠的解决方案。我把这个解决方案看成一个多功能插座,就像你现在电源插头上连着的一样。

你的游戏服务器可以像简单的单一套接字服务器一样的写,只用一个数据库后台。实际上,可能比你现在的游戏服务器写得还要简单。你的游戏服务器就好比一个电源插头,多种电源插头接在我的插线板上,而玩家就从另一端流入。

你提供游戏服务,而我提供可伸缩性,负载平衡,还有容错性。我保持玩家连到插线板上并监视你的游戏服务器们,在需要的时候重启任何一个。我还可以在某个服务器当掉的情况下把玩家从一个服务器切换到另一个,而你可以随时插入新的服务器。

这么一个多功能插线板中间件就像一个黑匣子设置在玩家与服务器之间,而且你的游戏代码不需要做出任何修改。你可以享用这个方案带来的高伸缩性,负载平衡,可容错性等好处,与此同时节约投资并写仅仅修改一小部分体系结构。

你可以今天就开始写这个Erlang的中间件,在一个特别为TCP连接数做了优化的Linux机器上运行,把这台机器放到公众网上的同时保持你的游戏服务器群组在防火墙背后。就算你不打算用,我也建议你抽空看看Erlang考虑一下如何简化你的多人在线服务器架构。而且我随时愿意帮忙!

时间: 2024-11-02 23:00:47

erlang 服务器的一些问题(找来的)的相关文章

Erlang服务器内存吃紧的优化解决方法

问题提出:服务器100万人在线,16G内存快被吃光.玩家进程占用内存偏高 解决方法: 第一步: erlang:system_info(process_count). 查看进程数目是否正常,是否超过了erlang虚拟机的最大进程数. 第二步: 查看节点的内存瓶颈所在地方 > erlang:memory(). [{total,2099813400}, {processes,1985444264}, {processes_used,1985276128}, {system,114369136}, {a

服务器错误,404 - 找不到文件或目录。

访问服务器的某个文件路径报错 http://192.168.1.1:80/UploadFile/Video/20190702044918.mp4 在服务器的IIS管理器中找到 双击打开,查看类型是否存在 .mp4 的类型,如果没有.点击右上角添加 点确定.再试一下是否已解决问题. 附上一小部分常用的MIME类型 原文地址:https://www.cnblogs.com/zyadmin/p/11122259.html

打包后放在服务器上耳机目录找不到解决办法

vue-cli 项目,打包后js,css,img静态资源找不到 js,css资源都找不到 最近,我使用vue-cli脚手架搭建了一个vue项目框架,随意写了点页面,打包出来之后,放到tomcat运行, MOG,竟然所有的资源都找不到,全是404.通过查看api和别人的博客,修改了一下配置文件,config.js下的index.js image.png 默认的时候,是没有monitor的,记得assetsPublicPath前后都是有斜杠的,别弄错了哟, npm run build 之后,dist

1.一步一步学开发(游戏账服数据库的使用 Erlang 服务器)

mysql 与mongodb的特点与优劣 http://www.cnblogs.com/eternal1025/p/5419905.html 首先我们来分析下mysql 与mongodb的特点与优劣. 下面是我以前做的ppt的部分截图. 再来分析下应用场景, a.如果需要将mongodb作为后端db来代替mysql使用,即这里mysql与mongodb 属于平行级别,那么,这样的使用可能有以下几种情况的考量: (1)mongodb所负责部分以文档形式存储,能够有较好的代码亲和性,json格式的直

weblogic服务器上类或者方法找不到的解决办法

下面以eclipse-birt(报表)为例,介绍这种问题出现的原因以及解决之道: 分析比较好的见:http://developer.actuate.com/community/forum/index.php?/topic/9315-exception-javalangnosuchmethoderror/ 1>现象: I could run report as stand alone, but while I am trying to use report engine in weblogicI

一般服务器运行不正常有哪些因数引起

在使用服务器过程中,服务器不能正常运行这个问题相信很多企业都遇到过,那么是不是服务器出现问题就要找IDC运营商?一切都是IDC运营商的责任呢?下面就给大家简单分析服务器不正常运行的原因. 1.服务器所在的机房设备出现故障 服务器所在机房设备偶尔的故障问题,会造成服务器运行异常.成立多年的数据中心机房服务器硬件设备都是多次使用过的,所以租用服务器时,除了新机房或是新 换的机器外,一般来说,IDC运营商所给到用户的新机并不是说配件是全新的,不可避免会出现服务器硬件损坏致使服务器无法正常使用的情况,但

服务器概述与环境准备

在上两篇的文章我们了解了计算机与操作系统,今天我提到了服务器,对没有接触过的童鞋们,也许会感到很陌生,那么我们今天就来详细的聊聊服务器. 电脑对现在的童鞋们都不陌生了吧,那电脑与服务器有什么不同呢?答案是他比电脑的硬件更牛掰,其结构原理与电脑都一样,服务器提供服务,我们的电脑是获取服务的.那他是怎么提供服务呢?看看下面的图先给大家一个简单的认识: 上面是我们坐在家里通过电脑打开浏览器,然后就可以找到我们想要的资料,这个就是服务器给我们的信息.我们把我们的问题告诉服务器,服务器就会去找我们想要的.

web服务器底层-http请求与相应

http请求请求分为三个部分:请求行.请求头.请求数据请求行 请求方式(GET/POST等) 请求目录,分层(/books/1.html) HTTP协议版本(HTTP/1.1),请求方式有:POST GET HEAD OPTIONS DELETE TRACE PUT.一般用GET/POST,get方式是通过‘?参数’的形式在url里面显示,显示直白.post方式则没有参数显示,适合比较大的数据传送. 请求头(客户机环境) 一般的请求头有: Accept:用于告诉服务器客户机支持的数据类型 Acc

WinSCP登陆服务器提示收到了太大的SFTP包 支持的最大包大小1024000B

前情回顾: 每次用rm都心惊胆战,于是5月7号晚上,找资料把rm替换为mv命令,模拟成了WINDOWS下面的回收站. 组里面有好几个人一起使用服务器,回收站的文件夹就设置到了每个用户起始目录下. 把每个用户的.bashrc修改了一下,一时心好,想提示登录的用户现在可以找回文件: 在每个用户的~/.bashrc中增加了一行: ? 1 echo "using the order ur +filename  to recover your file" 5月8号早上来实验室,大师姐给我说,不能