ESPlatform 群集平台(02) -- 从外部访问群集
本文我们来解决一个常见的问题,那就是在ESPlatform群集之外如何访问和控制ESPlatform群集了?
在ESPlatform 群集平台(01) -- 迁移到群集平台一文中,我们提到ACMS提供了三个Remoting服务接口:IApplicationService、IClusterControlService、IPlatformCustomizeService。 IApplicationService我们已经介绍过了,而IClusterControlService 和 IPlatformCustomizeService 正是ESPlatform群集系统提供给外部系统(如BL)来访问或控制群集的。
通常我们会这样做,将ACMS暴露的IClusterControlService 和 IPlatformCustomizeService作为群集系统对外暴露的唯一通道,外部系统只能通过ACMS提供的这两个接口与整个群集系统通信。外部系统不要直接与某个AS通信,除非我们有充分的理由,否则,不要轻易破坏这一规则。这一规则的目的在于,尽可能使整个系统(可能是很庞大的、复杂的)保持简洁的、清晰的结构。
一.IClusterControlService接口
IClusterControlService接口用于访问群集系统中的AS服务器信息或控制群集中的服务器。IClusterControlService的定义如下:
public interface IClusterControlService { /// <summary> /// 群集中有新的服务器注册进来。 /// </summary> event CbGeneric<ClusterServerInfo> ServerRegistered; /// <summary> /// 群集中的某服务已被注销。 /// </summary> event CbGeneric<int> ServerUnregistered; /// <summary> /// 根据ServerAssignedPolicy,选择群集中合适的服务器。 /// </summary> ServerConfiguration GetServerToLogon(); /// <summary> /// 目标服务器是否正在线? /// </summary> /// <param name="serverID">目标服务器的ServerID</param> /// <returns>在线?</returns> bool IsServerOnline(int serverID); /// <summary> /// 冻结群集中的某服务器。如果群集中的某服务器处于冻结,表示该服务器不再接收新的用户(现已登陆的用户仍然正常)。 /// </summary> /// <param name="serverID">目标服务器的ID</param> void FreezeServer(int serverID); /// <summary> /// 解冻群集中的某服务器。 /// </summary> /// <param name="serverID">目标服务器的ID</param> void DefreezeServer(int serverID); /// <summary> /// 回调测试。如果回调成功,则正常返回,否则将抛出异常。 /// </summary> /// <param name="serverID">目标服务器的ID</param> void TestCallbackServer(int serverID); /// <summary> /// 从群集列表中移除(异常停止的)服务器。 /// </summary> /// <param name="serverID">将被移除的服务器的ID</param> void RemoveServer(int serverID); /// <summary> /// 获取目标服务器的信息。 /// </summary> ClusterServerInfo GetServer(int serverID); /// <summary> /// 获取群集中在线的所有服务器。 /// </summary> List<ClusterServerInfo> GetAllServers(); }
1.AS基础信息与事件
当我们动态地向群集中添加一台AS时,ACMS将会触发IClusterControlService接口的ServerRegistered事件;同样的,当动态地从群集中移除一台AS时,将触发IClusterControlService接口的ServerUnregistered事件。请注意,如果某台AS意外挂掉,则ACMS是不知情的,我们必需手动调用IClusterControlService接口的RemoveServer方法,此方法的执行仍然会触发IClusterControlService接口的ServerUnregistered事件。
GetServer用于获取群集中某台AS的详细信息,其返回的ClusterServerInfo类图如下:
通过返回的ClusterServerInfo,我们可以知道目标AS基本信息:启动时间、IP端口地址、在线用户数量、当前的CPU使用率、内存使用率、是否处于冻结状态。
2.群集分配策略
所谓群集分配策略,就是当一个客户端要连接到AS时,我们应该分配群集中的哪台AS给他?选择AS所采取的策略就是群集分配策略。
AS内置了三种常见的群集分配策略:轮询、人数最少、CPU利用率最小。该策略由ServerAssignedPolicy枚举定义:
public enum ServerAssignedPolicy { /// <summary> /// 轮询。 /// </summary> Poll = 0, /// <summary> /// 选择群集中在线人数最小的那台Server。 /// </summary> MinUserCount , /// <summary> /// 选择群集中Cpu利用率最小的那台Server。 /// </summary> MinCpuUsage }
在ACMS的配置文件ESPlatform.ACMServer.exe.config中,有key为ServerAssignedPolicy的配置项,我们可以在此指定所采用的AS分配策略。
IClusterControlService接口的GetServerToLogon方法将依据ServerAssignedPolicy的设置返回恰当的AS。
当我们想让某台AS不再接受新的客户端连接时,可以调用FreezeServer方法来冻结它。当AS被冻结后,GetServerToLogon方法将永远不会返回这台AS。
当然,我们也可以完全自己定义AS的分配策略,而忽略ACMS提供的内置的分配策略的存在。
假设我们已经采用了ACMS内置的分配策略或实现了自定义的分配策略,那么,客户端如何知道自己要登录到哪个AS了?需要有个第三方来提供这一查询服务。我们可以简单地做到这一点,比如,发布一个WebService,客户端先通过访问该WebService获取要登录的AS的地址,然后再去连接目标AS,等等。
二.IPlatformCustomizeService 接口
如果,希望从外部发送一个指令给群集中的某台AS或在线的某个客户端,该如何做了? 我们可以使用ACMS暴露的IPlatformCustomizeService接口。
public interface IPlatformCustomizeService { /// <summary> /// 向群集中的每台AS发送广播。将被AS的ICallbackHandler的HandleBroadcast处理。 /// </summary> /// <param name="informationType">广播信息类型</param> /// <param name="info">广播信息</param> void BroadcastInCluster(int informationType, byte[] info); /// <summary> /// 向应用群集中的某个AS发送信息。如果目标AS不在线,返回false。将被AS的ICallbackHandler的HandleInformation处理。 /// </summary> /// <param name="asID">接收信息的AS的ID。</param> /// <param name="informationType">自定义信息类型</param> /// <param name="info">信息</param> bool SendToServer(int asID, int informationType, byte[] info); /// <summary> /// 向应用群集中的某个AS查询信息。如果目标AS不在线,将抛出异常。将被AS的ICallbackHandler的HandleQuery处理。 /// </summary> /// <param name="asID">被查询的AS的ID。</param> /// <param name="informationType">自定义信息类型</param> /// <param name="info">信息</param> byte[] QueryServer(int asID, int informationType, byte[] info); /// <summary> /// 向平台上的用户发送自定义信息。如果目标用户不在线,返回false。 /// </summary> /// <param name="clientUserID">接收信息的目标用户ID。</param> /// <param name="informationType">自定义信息类型</param> /// <param name="info">信息</param> bool SendToClient(string clientUserID, int informationType, byte[] info); /// <summary> /// 向平台上的用户查询信息。如果目标用户不在线,或者超时没有回复,将抛出相应的异常。 /// </summary> /// <param name="clientUserID">接收信息的目标用户ID。</param> /// <param name="informationType">自定义信息类型</param> /// <param name="info">信息</param> byte[] QueryClient(string clientUserID, int informationType, byte[] info); /// <summary> /// 目标用户是否在线。 /// </summary> bool IsUserOnLine(string userID); /// <summary> /// 获取目标在线用户的基础信息。 /// </summary> /// <param name="userID">目标用户的ID</param> /// <returns>如果目标用户不在线,则返回null</returns> UserData GetUserData(string userID); /// <summary> /// 获取所有在线用户的人数。 /// </summary> int GetOnlineUserCount(); /// <summary> /// 将用户从某个AS上踢出。 /// </summary> void KickOut(string userID); }
就像ESPlus提供的自定义信息功能一样,我们可以从群集外部通过IPlatformCustomizeService接口发送自定义信息甚至同步调用给某个AS或某个在线客户端,当然,这些信息都是经过ACMS进行中转的。接口中每个方法的注释已经描述得很清楚了,这里就不再赘述了。
1.向AS发送自定义信息
IPlatformCustomizeService接口的BroadcastInCluster、SendToServer、QueryServer方法用于发送自定义信息或同步调用给AS,那么AS如何来处理这些信息了?
AS需要实现ESPlatform.Server.Application.ICallbackHandler接口来处理来自群集外部的信息。
public interface ICallbackHandler { /// <summary> /// 处理ASM发送过来的广播。 /// </summary> /// <param name="informationType">广播信息类型</param> /// <param name="info">广播信息</param> void HandleBroadcast(int informationType, byte[] info); /// <summary> /// 处理ASM发送过来的信息。 /// </summary> /// <param name="informationType">信息类型</param> /// <param name="info">信息</param> void HandleInformation(int informationType, byte[] info); /// <summary> /// 处理ASM发送过来的查询。 /// </summary> /// <param name="informationType">信息类型</param> /// <param name="info">信息</param> byte[] HandleQuery(int informationType, byte[] info); }
ICallbackHandler接口的三个方法刚好对应 IPlatformCustomizeService接口的三个方法。我们在实现了ICallbackHandler接口后,可以将其实例注入到ESPlatform.Rapid.RapidServerEngine的CallbackHandler属性上。如此,当AS接收到自定义信息时,便会回调ICallbackHandler对应的方法来处理了。
2.向客户端发送自定义信息
同样的问题,IPlatformCustomizeService接口的SendToClient、QueryClient方法用于发送自定义信息或同步调用给某个在线的客户端,那么客户端如何来处理这些信息了?实际上,客户端不需要实现新的接口,而是通过ICustomizeHander接口来统一处理。无论是来自AS的信息,还是来自群集外部的信息,都将有ICustomizeHander接口来处理。关于ICustomizeHander接口的详细介绍,可以参见ESFramework 开发手册(01) -- 发送和处理信息。
要注意的是,在分配信息类型informationType时,来自群集外的自定义信息不要与普通的自定义信息的类型重复就可以了。
三.例子
下面我们举个简单的例子,来说明外部系统与ESPlatform群集的交互。
我们假设有个简单的在线的网络游戏系统,由B/S和C/S两个子系统组成。B/S子系统用于实现像用户注册、资料修改、后台管理等业务功能;C/S子系统则用于实现所有的游戏逻辑,由于在线用户数量巨大,所以我们使用了ESPlatform群集平台。整个系统结构简化后如下图所示:
下面我们举两个常见的需求。
1.修改游戏配置参数
为了方便游戏管理员GM的操作,我们的B/S子系统提供了后台网站给管理员进行重要的游戏参数设置。比如,像游戏中的金融平衡系数等,这些参数的值必需提交给每个AS生效才可以。对于类似的需求,可用类似如下的流程实现:
(1)GM通过后台网站将修改参数的请求提交给Web。
(2)Web再向BL提交。
(3)BL修改DB(如果需要的话)成功后,发送自定义信息给ACMS。(通过IPlatformCustomizeService接口的BroadcastInCluster方法)
(4)ACMS在群集中广播自定义信息。
(5)每个AS都将回调上述ICallbackHandler接口的HandleBroadcast方法来使参数设定生效。
2.充值
充值功能一般在Web中完成,而在线的客户端却要实时显示最新的余额信息。类似这样的流程也很容易实现:
(1)玩家通过前台网站将充值请求提交给Web。
(2)支付成功后,Web向BL提交。
(3)BL修改DB成功后,发送自定义信息给ACMS。(通过IPlatformCustomizeService接口的SendToClient方法)
(4)ACMS根据玩家的ID找到其所在的AS,然后将信息转发给该AS。
(5)AS收到自定义信息后,在将其转发给目标玩家的客户端。
(6)客户端收到自定义信息后,更新内存中的最新余额信息,并更新UI显示。
为了方便讨论,我们这里对举例的场景做了很多简化,真正的系统通常要比这里描述的复杂很多。比如,ACMS可能需要访问数据库,而AS可能需要访问BL或者缓存服务器,等等。这些扩展设计该如何做,取决于我们实际的项目需求。后续的文章,我们将更深入地讨论ESPlatform群集系统与外部的交互,不仅仅外部主动调用群集系统,群集系统也可以主动与外部系统通信。
-----------------------------------------------------------------------------------------------------------------------------------------------
关于ESFramework的任何问题,欢迎联系我们:
电话:027-87638960
Q Q:372841921