ProjectDarkStar服务器端开发文档(五)Hello Persistence
Posted on 2009-11-24 16:56 Bill Yuan 阅读(523) 评论(0) 编辑 收藏 举报第四课:Hello Persistence!
Lesson 3 explained that tasks that are run on a delay or repeat don’t necessarily happen exactly at the time you asked for. They could happen a bit later if (for example) the system is very loaded, or a lot later if (for example) the entire data center has actually come down and had to be restarted.(8)
第三课解释了任务的延迟或周期性运行,并不需要严格按照你要求的时间点上发生。它们可以稍微迟一点发生,如果(比如)系统负载很高;也可以过很久再发生,如果(比如)全部数据都挂掉,必须重新启动。(8)
To track the last time the run task was called and calculate the true time-delta, we need a way of storing the past time value so that it will survive the system going down. This is called persistent storage, and in real games it is very important. Imagine how your users would react if your machine went down and they all lost their characters and everything on them!
为了追踪run最后一次被调用,并计算真正的运行时间,我们需要一种方法来保存经过的时间值,以至于它在系统挂掉时可以生存下来。这就是持久存储器,在真实游戏中,它是非常重要的。想象一下,如果你的机器挂了,你的玩家用户失去了所有的角色和装备,他们会是什么反应!
A Managed Object is an object for which the system tracks state and which the system makes persistent. We mentioned above that AppListener interface inherits the Managed Object interface and that your AppListener instance is automatically created by the system for you. The system also registers your AppListener as a Managed Object with the Data Manager. This means that its state will be preserved by the PDS for you.
一个管理对象(Managed Object)就是一个为系统追踪状态,被系统持久化的对象。我们上面提到的AppListener接口继承了管理对象接口,系统会自动为你创建AppListener实例。系统也会用数据管理器来将AppListener作为一个管理对象注册进来。这就是说,它的状态会由系统帮你维持。
1.编写HelloPersistence
Since our HelloTimer task is a Managed Object, all we need to do is add a field to track the last time run was called. Below is the code for HelloPersistence.
因为我们的HelloTimer任务是一个管理对象,我们需要做的就是添加一个域,来追踪run最后一次被调用。下面就是HelloPersistence的代码。
Run HelloPersistence as a PDS application. Stop the PDS, wait a minute, and then start it again. You will see that the elapsed time reported includes the down time. This is because currentTimeMillis is based on the system clock, and time kept moving forward even when the PDS wasn’t running.(9)
作为一个PDS应用运行HelloPersistence。停止PDS,等一分钟,再启动它。你会看到运行时间报告包含了停止的时间。这是因为currentTimeMillis方法是在系统时间的基础上的,就算PDS不运行了,时间还是会一直向前走。(9)
Persistence is that simple and automatic in the PDS. Any non-transient field on a registered Managed Object will be persisted.(10)
在PDS里,持久化就那么简单,并且是自动化的。在一个注册过的管理对象中,任何只要不是瞬态的域,都会被持久化。(10)
HelloPersistence
/*
* Copyright 2007-2009 Sun Microsystems, Inc.
*
* This file is part of Project Darkstar Server.
*
* Project Darkstar Server is free software: you can redistribute it
* and/or modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation and
* distributed hereunder to you.
*
* Project Darkstar Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
package com.sun.sgs.tutorial.server.lesson4;
import java.io.Serializable;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.sun.sgs.app.AppContext;
import com.sun.sgs.app.AppListener;
import com.sun.sgs.app.ClientSession;
import com.sun.sgs.app.ClientSessionListener;
import com.sun.sgs.app.ManagedObject;
import com.sun.sgs.app.Task;
import com.sun.sgs.app.TaskManager;
/**
* A simple persistence example for the Project Darkstar Server. As a
* {@link ManagedObject}, it is able to modify instance fields, demonstrated
* here by tracking the last timestamp at which a task was run and displaying
* the time delta.
*/
public class HelloPersistence implements AppListener, // to get called during
// application startup.
Serializable, // since all AppListeners are ManagedObjects.
Task // to schedule future calls to our run() method.
{
/** The version of the serialized form of this class. */
private static final long serialVersionUID = 1L;
/** The {@link Logger} for this class. */
private static final Logger logger = Logger
.getLogger(HelloPersistence.class.getName());
/** The delay before the first run of the task. */
public static final int DELAY_MS = 5000;
/** The time to wait before repeating the task. */
public static final int PERIOD_MS = 500;
/** The timestamp when this task was last run. */
private long lastTimestamp = System.currentTimeMillis();
// implement AppListener
/**
* {@inheritDoc}
*
*
* Schedules the {@code run()} method to be called periodically. Since SGS
* tasks are persistent, the scheduling only needs to be done the first time
* the application is started. When the server is killed and restarted, the
* scheduled timer task will continue ticking.
*
*
* Runs the task {@value #DELAY_MS} ms from now, repeating every
* {@value #PERIOD_MS} ms.
*/
public void initialize(Properties props) {
TaskManager taskManager = AppContext.getTaskManager();
taskManager.schedulePeriodicTask(this, DELAY_MS, PERIOD_MS);
}
/**
* {@inheritDoc}
*
*
* Prevents client logins by returning {@code null}.
*/
public ClientSessionListener loggedIn(ClientSession session) {
return null;
}
// implement Task
/**
* {@inheritDoc}
*
*
* Each time this {@code Task} is run, logs the current timestamp and the
* delta from the timestamp of the previous run.
*/
public void run() throws Exception {
long timestamp = System.currentTimeMillis();
long delta = timestamp - lastTimestamp;
// Update the field holding the most recent timestamp.
lastTimestamp = timestamp;
logger.log(Level.INFO,
"timestamp = {0,number,#}, delta = {1,number,#}", new Object[] {
timestamp, delta });
}
}
* Copyright 2007-2009 Sun Microsystems, Inc.
*
* This file is part of Project Darkstar Server.
*
* Project Darkstar Server is free software: you can redistribute it
* and/or modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation and
* distributed hereunder to you.
*
* Project Darkstar Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
package com.sun.sgs.tutorial.server.lesson4;
import java.io.Serializable;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.sun.sgs.app.AppContext;
import com.sun.sgs.app.AppListener;
import com.sun.sgs.app.ClientSession;
import com.sun.sgs.app.ClientSessionListener;
import com.sun.sgs.app.ManagedObject;
import com.sun.sgs.app.Task;
import com.sun.sgs.app.TaskManager;
/**
* A simple persistence example for the Project Darkstar Server. As a
* {@link ManagedObject}, it is able to modify instance fields, demonstrated
* here by tracking the last timestamp at which a task was run and displaying
* the time delta.
*/
public class HelloPersistence implements AppListener, // to get called during
// application startup.
Serializable, // since all AppListeners are ManagedObjects.
Task // to schedule future calls to our run() method.
{
/** The version of the serialized form of this class. */
private static final long serialVersionUID = 1L;
/** The {@link Logger} for this class. */
private static final Logger logger = Logger
.getLogger(HelloPersistence.class.getName());
/** The delay before the first run of the task. */
public static final int DELAY_MS = 5000;
/** The time to wait before repeating the task. */
public static final int PERIOD_MS = 500;
/** The timestamp when this task was last run. */
private long lastTimestamp = System.currentTimeMillis();
// implement AppListener
/**
* {@inheritDoc}
*
*
* Schedules the {@code run()} method to be called periodically. Since SGS
* tasks are persistent, the scheduling only needs to be done the first time
* the application is started. When the server is killed and restarted, the
* scheduled timer task will continue ticking.
*
*
* Runs the task {@value #DELAY_MS} ms from now, repeating every
* {@value #PERIOD_MS} ms.
*/
public void initialize(Properties props) {
TaskManager taskManager = AppContext.getTaskManager();
taskManager.schedulePeriodicTask(this, DELAY_MS, PERIOD_MS);
}
/**
* {@inheritDoc}
*
*
* Prevents client logins by returning {@code null}.
*/
public ClientSessionListener loggedIn(ClientSession session) {
return null;
}
// implement Task
/**
* {@inheritDoc}
*
*
* Each time this {@code Task} is run, logs the current timestamp and the
* delta from the timestamp of the previous run.
*/
public void run() throws Exception {
long timestamp = System.currentTimeMillis();
long delta = timestamp - lastTimestamp;
// Update the field holding the most recent timestamp.
lastTimestamp = timestamp;
logger.log(Level.INFO,
"timestamp = {0,number,#}, delta = {1,number,#}", new Object[] {
timestamp, delta });
}
}
2.编写HelloPersistence2
While we could put all the fields of our application on the AppListener, there are many good reasons not to do this. As any Managed Object grows larger, it takes more time for the system to store and retrieve it. Also, although PDS task code is written as if it were monothreaded, many tasks are actually executing in parallel at any given time. Should the tasks conflict in what data they have to modify, then one will either have to wait for the other to finish or, in a worst-case situation, actually abandon all the work it had done up to that point and try again later.
我们可以把应用中所有的域都放在AppListener中,然而,这里有很多好理由让我们不这么做。任何管理对象增大之时,它都会花费系统更多的时间来存储和恢复。虽然PDS的任务代码编写的好像它是单线程的,但是许多任务在给定时间内实际上是并行执行的。这些任务在必须修改数据时可能产生冲突,一个任务要么就是等另一个完成之后运行,要么,最坏的情况,在那一点停下所有的工作然后稍后重新尝试。
For these reasons, an application will want to create other Managed Objects of its own. Luckily, that’s easy to do!
因为这个原因,一个应用会想要为自己创建另一个管理对象。幸运的是,这很简单!
All Managed Objects must meet two criteria:
● They must be Serializable.
● They must implement the ManagedObject marker interface.
所有的管理对象都必须符合两个标准:
● 它们必须是序列化的。
● 它们必须实现ManagedObject接口。
One good way to break your application up into multiple Managed Objects is by the events they handle. A Managed Object can handle only one event at a time, so you want to separate all event handlers for events that might occur in parallel into separate Managed Objects. Below is the code to HelloPersistence2. It creates a separate TrivialTimedTask Managed Object from the AppListener to handle the timed task.
一个将你的应用分解成多个管理对象的好方式是利用它们处理的事件。一个管理对象在一刻只能处理一个事件,所以,你可以把那些处理并行发生的事件的处理器分解成独立的管理对象。下面是HelloPersistence2的代码。它从AppListener里分解并创建了一个独立的管理对象TrivialTimedTask,来处理定时任务。
HelloPersistence2
* Copyright 2007-2009 Sun Microsystems, Inc.
*
* This file is part of Project Darkstar Server.
*
* Project Darkstar Server is free software: you can redistribute it
* and/or modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation and
* distributed hereunder to you.
*
* Project Darkstar Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sun.sgs.tutorial.server.lesson4;
import java.io.Serializable;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.sun.sgs.app.AppContext;
import com.sun.sgs.app.AppListener;
import com.sun.sgs.app.ClientSession;
import com.sun.sgs.app.ClientSessionListener;
import com.sun.sgs.app.TaskManager;
/**
* A simple persistence example for the Project Darkstar Server.
*/
public class HelloPersistence2 implements AppListener, Serializable {
/** The version of the serialized form of this class. */
private static final long serialVersionUID = 1L;
/** The {@link Logger} for this class. */
private static final Logger logger = Logger
.getLogger(HelloPersistence2.class.getName());
/** The delay before the first run of the task. */
public static final int DELAY_MS = 5000;
/** The time to wait before repeating the task. */
public static final int PERIOD_MS = 500;
// implement AppListener
/**
* {@inheritDoc}
* <p>
* Creates a {@link TrivialTimedTask} and schedules its {@code run()} method
* to be called periodically.
* <p>
* Since SGS tasks are persistent, the scheduling only needs to be done the
* first time the application is started. When the server is killed and
* restarted, the scheduled timer task will continue ticking.
* <p>
* Runs the task {@value #DELAY_MS} ms from now, repeating every
* {@value #PERIOD_MS} ms.
*/
public void initialize(Properties props) {
TrivialTimedTask task = new TrivialTimedTask();
logger.log(Level.INFO, "Created task: {0}", task);
TaskManager taskManager = AppContext.getTaskManager();
taskManager.schedulePeriodicTask(task, DELAY_MS, PERIOD_MS);
}
/**
* {@inheritDoc}
* <p>
* Prevents client logins by returning {@code null}.
*/
public ClientSessionListener loggedIn(ClientSession session) {
return null;
}
}
TrivialTimedTask
This is the Managed Object we are going to have respond to the repeating task.
这就是我们将要响应重复任务的管理对象。
* Copyright 2007-2009 Sun Microsystems, Inc.
*
* This file is part of Project Darkstar Server.
*
* Project Darkstar Server is free software: you can redistribute it
* and/or modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation and
* distributed hereunder to you.
*
* Project Darkstar Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sun.sgs.tutorial.server.lesson4;
import com.sun.sgs.app.AppContext;
import java.io.Serializable;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.sun.sgs.app.ManagedObject;
import com.sun.sgs.app.Task;
/**
* A simple repeating Task that tracks and prints the time since it was last
* run.
*/
public class TrivialTimedTask implements Serializable, // for persistence, as
// required by //
// ManagedObject.
ManagedObject, // to let the SGS manage our persistence.
Task // to schedule future calls to our run() method.
{
/** The version of the serialized form of this class. */
private static final long serialVersionUID = 1L;
/** The {@link Logger} for this class. */
private static final Logger logger = Logger
.getLogger(TrivialTimedTask.class.getName());
/** The timestamp when this task was last run. */
private long lastTimestamp = System.currentTimeMillis();
// implement Task
/**
* {@inheritDoc}
* <p>
* Each time this {@code Task} is run, logs the current timestamp and the
* delta from the timestamp of the previous run.
*/
public void run() throws Exception {
// We will be modifying this object.
AppContext.getDataManager().markForUpdate(this);
long timestamp = System.currentTimeMillis();
long delta = timestamp - lastTimestamp;
// Update the field holding the most recent timestamp.
lastTimestamp = timestamp;
logger.log(Level.INFO,
"timestamp = {0,number,#}, delta = {1,number,#}", new Object[] {
timestamp, delta });
}
}
3.编写HelloPersistence3
A Managed Object does not actually become managed by the Data Manager, and thus persistent, until the Data Manager is made aware of it. The reason HelloPersistence2 works is because the Task Manager persisted the TrivialTimedTask object for us. In order to persist other Managed Objects, though, an application needs to take on the responsibility of informing the Data Manager itself. One way the Data Manager can become aware of a Managed Object is through a request for a Managed Reference.
一个管理对象只有等到数据管理器(Data Manager)觉察到它,它才能变为是被数据管理器管理的,持久化的。HelloPersistence2能工作的原因是因为任务管理器(Task Manager)为我们持久化了TrivialTimedTask对象。为了能将其他的对象持久化,一个应用有责任自己告知对象管理器。一个让数据管理器可以觉察到管理对象的方式是请求一个管理引用(Managed Reference)。
Managed Objects often need to refer to other Managed Objects. This is done with a Managed Reference. It is very important that the only fields on one Managed Object that reference another Managed Object be Managed References. This is how the Data Manager knows that it is a reference to a separate Managed Object. If you store a simple Java reference to the second Managed Object in a field on the first Managed Object, the second object will become part of the first object’s state when the first object is stored. The result will be that, the next time the first object tries to access the second, it will get its own local copy and not the real second Managed Object
管理对象经常需要与其他管理对象进行关联。管理引用来做这个事情。很重要的一点是,只有那些引用了其他管理对象的管理对象上的域才是管理引用。这就是数据管理器如何识别一个指向一个独立的管理对象的引用。如果你在第一个管理对象的域上保存一个指向第二个管理对象的简单的Java引用,当第一个对象被存储时,第二个对象将变成第一个对象状态的一部分。结果是,下次第一个对象尝试访问第二个对象时,它会得到自己本地的拷贝,而不是真正的第二个对象。
HelloPersistence3 below illustrates this by creating a second persistent object that is called from the TrivialTimedTask and that keeps the last-called time as part of its persistent state.
下面的HelloPersistence3用创建第二个持久化对象来说明,这个对象被TrivialTimedTask调用,并保持最后调用时间作为它的持久化状态的一部分。
HelloPersistence3
HelloPersistence3, below, is a task that delegates to a sub-task (a TrivialTimedTask that is not scheduled to run on its own). The sub-task is stored in a Managed Reference on HelloPersistence3.
下面的HelloPersistence3是将任务委托给一个分解任务(TrivialTimedTask,自身不能调用运行)。这个分解任务在HelloPersistence3里的一个管理引用中被存储。
* Copyright 2007-2009 Sun Microsystems, Inc.
*
* This file is part of Project Darkstar Server.
*
* Project Darkstar Server is free software: you can redistribute it
* and/or modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation and
* distributed hereunder to you.
*
* Project Darkstar Server is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.sun.sgs.tutorial.server.lesson4;
import java.io.Serializable;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;
import com.sun.sgs.app.AppContext;
import com.sun.sgs.app.AppListener;
import com.sun.sgs.app.ClientSession;
import com.sun.sgs.app.ClientSessionListener;
import com.sun.sgs.app.DataManager;
import com.sun.sgs.app.ManagedReference;
import com.sun.sgs.app.Task;
import com.sun.sgs.app.TaskManager;
/**
* A simple persistence example for the Project Darkstar Server.
*/
public class HelloPersistence3 implements AppListener, Serializable, Task {
/** The version of the serialized form of this class. */
private static final long serialVersionUID = 1L;
/** The {@link Logger} for this class. */
private static final Logger logger = Logger
.getLogger(HelloPersistence3.class.getName());
/** The delay before the first run of the task. */
public static final int DELAY_MS = 5000;
/** The time to wait before repeating the task. */
public static final int PERIOD_MS = 500;
/** A reference to our subtask, a {@link TrivialTimedTask}. */
private ManagedReference<TrivialTimedTask> subTaskRef = null;
/**
* Gets the subtask this task delegates to. Dereferences a
* {@link ManagedReference} in this object that holds the subtask.
* <p>
* This null-check idiom is common when getting a ManagedReference.
*
* @return the subtask this task delegates to, or null if none is set
*/
public TrivialTimedTask getSubTask() {
if (subTaskRef == null) {
return null;
}
return subTaskRef.get();
}
/**
* Sets the subtask this task delegates to. Stores the subtask as a
* {@link ManagedReference} in this object.
* <p>
* This null-check idiom is common when setting a ManagedReference, since
* {@link DataManager#createReference createReference} does not accept null
* parameters.
*
* @param subTask
* the subtask this task should delegate to, or null to clear the
* subtask
*/
public void setSubTask(TrivialTimedTask subTask) {
if (subTask == null) {
subTaskRef = null;
return;
}
DataManager dataManager = AppContext.getDataManager();
subTaskRef = dataManager.createReference(subTask);
}
// implement AppListener
/**
* {@inheritDoc}
* <p>
* Schedules the {@code run()} method of this object to be called
* periodically.
* <p>
* Since SGS tasks are persistent, the scheduling only needs to be done the
* first time the application is started. When the server is killed and
* restarted, the scheduled timer task will continue ticking.
* <p>
* Runs the task {@value #DELAY_MS} ms from now, repeating every
* {@value #PERIOD_MS} ms.
*/
public void initialize(Properties props) {
// Hold onto the task (as a managed reference)
setSubTask(new TrivialTimedTask());
TaskManager taskManager = AppContext.getTaskManager();
taskManager.schedulePeriodicTask(this, DELAY_MS, PERIOD_MS);
}
/**
* {@inheritDoc}
* <p>
* Prevents client logins by returning {@code null}.
*/
public ClientSessionListener loggedIn(ClientSession session) {
return null;
}
// implement Task
/**
* {@inheritDoc}
* <p>
* Calls the run() method of the subtask set on this object.
*/
public void run() throws Exception {
// Get the subTask (from the ManagedReference that holds it)
TrivialTimedTask subTask = getSubTask();
if (subTask == null) {
logger.log(Level.WARNING, "subTask is null");
return;
}
// Delegate to the subTask's run() method
subTask.run();
}
}
Another way to register a Managed Object to the Data Manager is with the setBinding call. This call does not return a Managed Reference, but instead binds the Managed Object to the string passed in with it to the call. Once a Managed Object has a name bound to it, the Managed Object may be retrieved by passing the same name to the getBinding call. Note that name bindings must be distinct. For each unique string used as a name binding by an application, there can be one and only one Managed Object bound.
另一个将管理对象注册到数据管理器的方法是调用setBinding方法。这个调用不会返回一个管理引用,取而代之的是将管理对象绑定到一个字符串上。一旦管理对象有一个绑定好的名字,这个管理对象可以通过相同的名字调用getBinding方法重新得到。注意,名称绑定必须是不重复的。一个应用里,每个唯一的字符串作为一个绑定名称,有且只有一个管理对象绑定。
Retrieving a Managed Object by its binding has some additional overhead, so it’s better to keep Managed References to Managed Objects in the other Managed Objects that need to call them. There are, however, some problems that are best solved with a name-binding convention; one common example is finding the player object for a particular player at the start of his or her session.
通过名称绑定来重新得到管理对象会有有一些额外的开销,所以,最好还是在那些需要调用管理对象的管理对像上保持指向管理对象的管理引用。然而,有一些问题最好还是用名称绑定解决,一个普通的例子,在会话开始的时候,为一个特定的玩家找到玩家对象。
There are a number of other interesting methods on the Data Manager. You might want to look at the Javadoc now, but discussion of them will be put off until required by the tutorial applications.
在数据管理器里还有很多有趣的方法。你现在可能想看看Javadoc,但是,关于它们的讨论将会拖延,直到指南中的应用需要提到它们。
注释:
(8) 一个完整的Project Darkstar Server的生产环境提供故障恢复机制,所以一台服务器的损失不会导致游戏的挂掉。在真正的灾难中,就像整个数据中心断电了,全部的后端程序可以脱机运行。
(9) 依赖于你的操作系统,在PDS运行时,你可能会看到HelloPersistence输出的运行时间会在500ms上下浮动。这是因为currentTimeMillis方法并不需要精确到1ms。尤其是,Windows系统的currentTimeMillis方法本来就比Java SE平台精确度低。
(10) 一个瞬态的域是被transient关键字标记的。一些值在超出使用它们的任务时,是无效的,它们就应该标记为transient,比如,一个域在当前的任务期间,缓存了一个管理器。