线程池(高级扩展开发) SmartFoxServer 2X

高级扩展开发

在本文中,我们讨论扩展开发的高级方面:

服务器线程模型
自动负载平衡线程池
调优线程池
维护扩展中的状态
类加载架构
延迟和计划的任务

自2.9.0起
»服务器线程模型
注意:对于SFS2X 2.8.x及更早版本的所有用户,请参阅旧的线程模型文档。

SmartFoxServer 2X在多线程环境中运行所有Extensions。在扩展上基本上有两个独立的线程池:ExtensionController和SystemController。前一个实体负责处理客户端请求,后者调度系统事件,如LOGIN,USER_DISCONNECT,ROOM_VARIABLES_UPDATE等…

由于多个线程可以在Extension代码上同时运行,因此我们需要确保正确处理对共享状态的访问。标准的JDK的并发集合和锁定功能提供了强大的工具,以最小的工作量来处理最常见的并发问题。

我们强烈建议使用本机数组或旧式集合(如Vector和Hashtable)进行并发集合,从而可以限制并发并在代码中创建瓶颈。此外,当需要原子性时,我们建议将AtomicInteger,AtomicLong,AtomicReference作为有效的解决方案。最后,复杂操作需要以相互排斥的方式运行时,需要锁定类。

如果你想深入了解这个主题,我们还建议一些外部资源:

Java并发(多线程)
Java并发 没有痛苦

下图说明了我们所描述的内容。两个控制器运行单独的线程池,并在Extension类上同时调用请求和事件处理程序。
这里写图片描述
除此之外,扩展代码还可以安排任意数量的循环或延迟任务,这些任务又由不同的实体TaskScheduler处理,运行自己的线程池。

重要的是要注意,SFS2X API大部分时间都在处理并发:主SFSApi类提供的所有调用都是线程安全的,而且还有Game API和Buddy List API。但是有一些例外:例如,SFSObject和SFSArray不是线程安全的。由于这些对象主要用于数据传输,所以它们通常不被多个线程所抵触。在任何情况下,javadoc指定哪个对象需要特别注意线程安全性。

»自动负载平衡线程池

经常询问的问题之一是如何配置系统中运行的线程数量,以正确扩展应用程序。在SmartFoxServer 2.9.0中,我们通过删除手动配置的线程池,引入了可扩展性的显着改进。

我们提供的解决方案是一种自我监控,自动扩展的执行器,可以根据需求进行扩展,从而提供平衡的,可配置的延迟和资源使用率。

使用此功能,运行任何应用程序所需的手动配置和精细调整的量基本上减少到零。更重要的是,系统会自动监视消息队列的状态,并根据需要对线程缺少进行响应。这允许保护服务器不间断地创建和破坏针对小量流量的线程。同时,它允许对流量和/或缓慢的I / O工作中的重要increseas做出反应。一旦不再需要,线程将被释放。
BOTTOM LINE:自2.9.0版以来,您不需要手动配置系统/扩展线程的最大数量,也不需要在添加缓慢的I / O请求(如外部数据库和远程HTTP调用)时担心线程资源。

调整线程池

对于所有极客,我们在AdminTool>服务器配置器下提供一个高级面板,允许专家对默认配置进行微调。以下是每个Executor的可用参数列表:

核心线程:启动时使用的线程数。
备份线程:每次备份被触发时添加的线程数。
最大备份:最大量的线程备份操作。
队列大小触发备份:将添加新批备份线程的队列的大小。
备份触发时间:如果队列大小触发器仍然处于活动状态,则调用备份的秒数。
备份线程到期时间:删除备份线程的秒数,如果队列大小低于下一个值。
队列大小阻止备份到期:允许删除备份线程的队列大小。
日志活动:每次线程池调整大小时,在日志文件中添加消息。
完整队列警告间隔:有关消息队列已满的警告之间的秒数。
控制器请求队列大小:最大。控制器队列的大小。

注意:除非您有非常好的理由,否则我们建议您不要触摸默认配置值。默认设置已被微调,可在各种场景和工作负载中提供卓越的可扩展性。

»维护扩展中的状态

当我们使用SFSExtension类作为扩展的基类时,我们得到一个主要的扩展对象和一系列请求和事件处理程序。一个常见的问题是:在哪里保持应用程序共享状态? (分数,排行榜,游戏数据等)。

通常有两个逻辑答案:

在主要的扩展类暴露游戏模型通过getter / setter;
使用可以从代码中任何地方访问的Singleton。

我们强烈建议第二种方法是出于一系列原因:

在具有多个类加载器的环境中,单一密钥不容易实现(我们将在本文档的下一部分中解释所有细节);
由于它们的静态性质,单通道可以在多个分机重新启动中发挥讨厌的作用;
主扩展已经充当单例,并且通过getParentExtension()方法从任何请求或事件处理程序访问它非常简单;另外你不会得到我们刚才提到的Singleton的缺点。

»类加载架构

在关于扩展的介绍性文章中,我们提到每个Extension都加载在不同的ClassLoader中,以便在开发或甚至生产过程中允许热重新部署。我们还说明,为了在多个扩展中共享依赖关系,这些ClassLoaders遵循特定的层次结构:
这里写图片描述
他的图表显示,每个扩展“看到”所有部署的类在自己的ClassLoader中,它可以访问顶级的全局扩展类,由于它的父ClassLoader,最后它可以使用SFS2X框架中的任何类,由于这个层次结构中最顶层的元素。

当扩展名重新加载时,只有底层ClassLoader被销毁和重建。这将创建部署的jar文件中包含的类的新版本,而顶层的其余类别不受影响(这意味着它们无法重新加载)。

让我们来看一个实际的例子,看看这是如何有用的。假设扩展A是我们的主要区域扩展,而B和C是两个房间扩展,用于管理两个不同的游戏。我们希望扩展B和C与A通信,以访问游戏排行榜。

我们遇到的第一个问题是我们需要在所有三个扩展(A,B和C)中部署相同的模型类,以便正确运行该示例。这意味着每个ClassLoader都包含不同版本的同一个Class,这是Java中一个众所周知的问题。当您尝试从另一个上下文获取对象时,即使两个类是相同的字节码,Java运行时引发了臭名昭着的ClassCastException。从技术上讲,虽然它们是相同的字节码,但JVM将它们看作是不同的Class定义,即使在三个独立的上下文(ClassLoaders)中共享相同的完全限定名称。
注意
如果我们没有失去你,那么你可能会知道Java中ClassLoading的基础知识。如果这听起来很混乱,我们强烈建议您查看有关此主题的一些文章:

看看Java ClassLoader
Java类和类加载
Java类加载

幸运的是,解决方案非常简单:所有您需要做的是在扩展名/ _ lib _ /文件夹中部署模型类,您将能够在所有扩展中共享这些对象。
这里写图片描述
每个Extension ClassLoader现在都可以访问相同的模型类,因为它们在父Loader中可以访问。通过从主区域扩展提供访问游戏模型,我们创建了一个类似Singleton的解决方案,而不用担心静态数据。

如果您仍然需要创建一个或多个Singleton类,那么需要在多个扩展中共享,那么这种方法也会起作用。您将需要在lib /文件夹中的一个jar文件中部署Singleton。

^顶级菜单
»延迟和计划任务

通常,游戏逻辑需要使用定时器来重复的客户端更新(例如,结束时间的结束,特定事件的触​​发,NPC动作等)。

快速解决这个问题的方法是使用JDK中提供的ScheduledThreadPoolExecutor类,它提供了一个由一个线程池支持的方便的任务执行器。 SFS2X已经运行了这个Executor的自己的实例(包装在一个名为TaskScheduler的类中)。

以下Java代码片段显示了如何使用服务器自己的TaskScheduler运行循环任务。

public class SchedulerTest extends SFSExtension
{
    private class TaskRunner implements Runnable
    {
        private int runningCycles = 0;

        public void run()
        {
            runningCycles++;    
            trace("Inside the running task. Cycle:  " + runningCycles);

            if (runningCycles >= 10)
            {
                trace("Time to stop the task!");
                taskHandle.cancel();
            }
        }
    }

    // Keeps a reference to the task execution保持对任务执行的引用
    ScheduledFuture<?> taskHandle;

    @Override
    public void init()
    {
        SmartFoxServer sfs = SmartFoxServer.getInstance();

        // Schedule the task to run every second, with no initial delay安排任务每秒运行一次,没有初始延迟
        taskHandle = sfs.getTaskScheduler().scheduleAtFixedRate(new TaskRunner(), 0, 1, TimeUnit.SECONDS);
    }
}

scheduleAtFixedRate方法有四个参数:

一个可执行任务代码的Runnable对象
在执行开始之前的初始延迟
执行任务的时间间隔
用于表示时间值的时间单位

调度程序还会公布一个在指定时间后执行一次Runnable任务的调度方法。最后,调度程序的线程池可以在运行时通过resizeThreadPool()方法在动态调整大小。

注意
可以通过AdminTool中的“服务器配置器”模块调整系统TaskScheduler的线程池的初始大小。

建立多线程的两种方法及线程数据的共享和分离 http://www.cnblogs.com/gw811/archive/2012/10/15/2724882.html

翻译自:http://docs2x.smartfoxserver.com/AdvancedTopics/advanced-extensions

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值