内容概要
如下图为JBoss集群中数据复制的简单示意图
如图中所示的两个节点的JBoss集群,一般来说一个集群中我们需要将http会话、EJB、JMS、Hibernate二级缓存等在所有节点上保持同步,也就是说,任何一个节点上的这些数据发生变化,都需要将变化的数据复制到其他节点上。JBoss集群使用JGroups完成这一操作,这里我们不深入探讨JGroups怎样完成状态复制,这里我们说基于jGroups 的坦克大战游戏。
坦克大战是经典的角色对战游戏,游戏中可以有多个坦克参与,但所有坦克分为两种角色,两种角色的坦克可以发射炮弹相互攻击对方,当坦克被击中后坦克的生命值会减少,坦克生命值为零时坦克爆炸,坦克可以通过添加燃料来回复生命值,不同角色的坦克碰撞后生命值也会减少。一般我们可以通过键盘控制坦克进行游戏:
- 通过键盘方向建(上下左右)控制坦克的移动和方向
- 通过空格建发(space)射子弹
- 通过F2建重新生成坦克(坦克被炸毁)
如上图坦克状态包括:
- 坦克方向
- 坦克生命值
- 坦克发射的子弹
- 燃料位置
坦克大战游戏
运行坦克大战游戏
使用示所示的方法,任意从SourceForge下载或编译生成DEMO_HOME,jGroups坦克大战 示例运行的启动脚本tankwar.sh/tankwar.bat位于DEMO_HOME/bin目录下,我们打开一个新的命令行窗口,通过启动脚本运行 jGroups坦克大战示例如下:
./tankwar.sh -n node1 isGood
- -n node1 指定坦克的名字为node1
- isGood 指定坦克的角色
./tankwar.sh -n node2
- -n node2 指定坦克的名字为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, ITankCommunication实现了ICommunication, IReplication, IJGroups, ITank接口。其中ICommunication定义了通信的方式,及异步通信和同步通信两种方式:
public interface ICommunication { public Session synchSend(Session session) throws TankWarCommunicationException; public void asychSend(Session session) throws TankWarCommunicationException;}坦克大战游戏采用异步实现的方式为主,即任何方法调运都为异步,该异步最终体现在jGroups提供的异步模式上。IReplication接口定义了要复制的视图:
public interface IReplication { public void replicateTank(TankView view); public void replicateBlood(BloodView view); public void replicateExplode(ExplodeView view); public void replicateMissile(MissileView view);}坦克大战游戏主要有四种实体,即坦克、血块、炸弹、子弹。其中坦克是坦克的抽象;血块代表燃料,用来恢复坦克生命值;炸弹为显示坦克爆炸的效果;子弹为坦克发射的子弹,用来攻击对方坦克。相对应的我们抽象出坦克视图、血块视图、炸弹视图、子弹视图, 分别代表四种实体。传输时只传输视图信息,这也是为心能考虑。IReplication有四个方法分别用来实现传输坦克视图、血块视图、炸弹视图、子弹视图。游戏界面层就是通过这些方法来完成相应状态同步。ITank为坦克大战游戏状态的抽象,如下:
public interface ITank { public MapgetTankMap(); public void put(String key, Tank value); public Map getMissileMap(); public void put(String key, Missile value); public List getExplodes(); public void add(Explode explode); public List 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);}createChannel(Stringid)方法可以根据id获取已经存在的通道;createChannel(Stringname, String cluster, ReceiverAdapterreciever)方法根据通道名字,集群的名字,以及消息接收适配器来初始化通道。
4. 协议栈
协议栈指jGroups协议栈,用来实现消息发送的接收。我们可以在DEMO_HOME/conf/tankwar-udp.xml中查看详细的配置
5. 传输层严格来讲,传输层也属于jGroups协议栈,我们单独抽象出此层是因为此层需要与物理网络交互,同时为了性能考虑我们对此层也做了特殊配置,稍候我们将进行详细说明。
坦克大战游戏中使用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-------------------------------------------------------------------这是协议栈成员关系维护协议打印输出的通道信息,即我们创建了三个通道TankWar-Tank-Cluster,TankWar-Missile-Cluster,TankWar-Other-Cluster分别用来传输坦克状态,子弹状态,其它(炸弹,燃料)的状态。 这样坦克大战游戏在协议栈初始化时共享传输层协议。
坦克大战游戏使用TCP代替UDP
jGroups为可靠多播通信工具包,其默认使用UDP,但有些网络环境UDP通信受限制,在这种情况下我们需要使用TCP代替UDP。我们只需在启动时使用-p参数知道TCP配置文件即可,具体:
./tankwar.sh -n node1 isGood -p tankwar-tcp.xml
- -n node1 指定坦克的名字为node1
- isGood 指定坦克的角色
- -p tankwar-tcp.xml 指定使用TCP作为底层传输协议
对坦克大战游戏进行监控
我们可以使用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);坦克大战游戏运行时我们可以启动jconsole如下图,我们可以监控各通道统计信息: