如下图为JBoss集群中数据复制的简单示意图
如图中所示的两个节点的JBoss集群,一般来说一个集群中我们需要将http会话、EJB、JMS、Hibernate二级缓存等在所有节点上保持同步,也就是说,任何一个节点上的这些数据发生变化,都需要将变化的数据复制到其他节点上。JBoss集群使用JGroups完成这一操作,这里我们不深入探讨JGroups怎样完成状态复制,这里我们说基于jGroups 的坦克大战游戏。
坦克大战是经典的角色对战游戏,游戏中可以有多个坦克参与,但所有坦克分为两种角色,两种角色的坦克可以发射炮弹相互攻击对方,当坦克被击中后坦克的生命值会减少,坦克生命值为零时坦克爆炸,坦克可以通过添加燃料来回复生命值,不同角色的坦克碰撞后生命值也会减少。一般我们可以通过键盘控制坦克进行游戏:
如上图坦克状态包括:
使用示JBoss Cluster Framework Demo 介绍所示的方法,任意从SourceForge下载或编译生成DEMO_HOME,jGroups坦克大战 示例运行的启动脚本tankwar.sh/tankwar.bat位于DEMO_HOME/bin目录下,我们打开一个新的命令行窗口,通过启动脚本运行 jGroups坦克大战示例如下:
./tankwar.sh -n node1 isGood
./tankwar.sh -n node2
坦克大战有两种角色,使用isGood参数指定坦克为红方,如果不是用isGood,则坦克为蓝方。在实际运行中,我们可以启动多辆坦克,如下图为四辆坦克游戏效果截图:
如下图为蓝方坦克爆炸示意图:
如下图所示:
我们将jGroups 坦克大战游戏分为5个层:游戏界面、通信层、通道、协议栈、传输层,接下来我们依次对这5层进行简单介绍。
1. 游戏界面
该模块就一个类MainFrame,该类继承java.awt.Frame为坦克大战游戏提供了界面,我们可以通过键盘控制坦克在该界面移动,MainFrame构造方法需要提供通信层的接口Communication,如下
public class MainFrame extends Frame { public MainFrame(Communication comm, boolean isGood){ this.comm = comm ; } ... }
2. 通信层
该层是坦克大战游戏的核心,该层负责连接游戏界面和jGroups通道,该层的抽象接口为Communication如下:
public abstract class Communication implements ICommunication, IReplication, IJGroups, ITank
public interface ICommunication { public Session synchSend(Session session) throws TankWarCommunicationException; public void asychSend(Session session) throws TankWarCommunicationException; }
public interface IReplication { public void replicateTank(TankView view); public void replicateBlood(BloodView view); public void replicateExplode(ExplodeView view); public void replicateMissile(MissileView view); }
public interface ITank { public Map<String, Tank> getTankMap(); public void put(String key, Tank value); public Map<String, Missile> getMissileMap(); public void put(String key, Missile value); public List<Explode> getExplodes(); public void add(Explode explode); public List<Blood> getBloods(); public void add(Blood blood); public String getName(); public int getMemberSize(); }
坦克大战游戏有四种实体,由此坦克大战游戏的状态就指某一时刻游戏中所有的坦克、血块、炸弹、子弹。ITank就是坦克大战游戏的状态的抽象,例如使用getTankMap()方法可以获取当前状态下所有坦克的信息,游戏界面层通过此方法获取坦克将其显示在界面,put(Stringkey, Tank value)方法可以添加坦克,例如当新节点加入时该方法被调运。getName()方法获取当前坦克的名字。getMemberSize()获取坦克大战游戏中所有坦克数的总和。
3. 通道通道就指jGroups通道,jGroups通道负责通信层将坦克状态变化复制到其他坦克,我们定义了ChannelFactory接口,用来创建初始化jGroups通道。
public interface ChannelFactory { Channel createChannel(String id) throws Exception; ProtocolStackConfiguration getProtocolStackConfiguration(); public JChannel createChannel(String name, String cluster, ReceiverAdapter reciever); }
4. 协议栈
协议栈指jGroups协议栈,用来实现消息发送的接收。我们可以在DEMO_HOME/conf/tankwar-udp.xml中查看详细的配置
5. 传输层严格来讲,传输层也属于jGroups协议栈,我们单独抽象出此层是因为此层需要与物理网络交互,同时为了性能考虑我们对此层也做了特殊配置,稍候我们将进行详细说明。
坦克大战游戏中所有坦克之间的状态共享类似企业应用集群,比如JBoss集群中所有节点状态都必须保持一致,当集群中一个节点状态发生变化时它必须将自己的状态发送给集群中其他节点,其他节点接收到状态变化消息后更新自己的状态,使自己状态与其他节点同步。类似的JBoss集群, 坦克大战游戏中任意时刻当坦克移动、方向发生改变、发射子弹都需要将状态发送给其他坦克,比如游戏中有4辆坦克,每辆坦克都连续发射30发子弹,且坦克在移动,子弹也在移动,这样游戏需要同步维护4个坦克的状态,120发子弹的状态。 精确的说,坦克大战游戏中状态复制的密度比较大,这需要我们在使用jGroups时在性能方面做相关考虑,我们使用的方法是使用多通道代替单通道,且多个通道共享同一个传输层。
使用启动脚本开始游戏时终端会输出如下信息:------------------------------------------------------------------- GMS: address=node1-Tank, cluster=TankWar-Tank-Cluster, physical address=192.168.1.101:39891 ------------------------------------------------------------------- ------------------------------------------------------------------- GMS: address=node1-Missile, cluster=TankWar-Missile-Cluster, physical address=192.168.1.101:39891 ------------------------------------------------------------------- ------------------------------------------------------------------- GMS: address=node1-Other, cluster=TankWar-Other-Cluster, physical address=192.168.1.101:39891 -------------------------------------------------------------------
<UDP singleton_name="tankwar-tp"
jGroups为可靠多播通信工具包,其默认使用UDP,但有些网络环境UDP通信受限制,在这种情况下我们需要使用TCP代替UDP。我们只需在启动时使用-p参数知道TCP配置文件即可,具体:
./tankwar.sh -n node1 isGood -p tankwar-tcp.xml
我们可以使用jconsole对坦克大战游戏进行监控,jGroups util包提供接口获取MBeanServer,我们可以将通道注册到获取的MBeanServer,如下代码段:
MBeanServer server = Util.getMBeanServer(); if(server == null){ throw new Exception("No MBeanServers found;" + "\nTankWar needs to be run with an MBeanServer present"); } JmxConfigurator.registerChannel((JChannel)channel, server, "jgroups-tankwar", channel.getClusterName(), true);